mirror of
https://github.com/space-syndicate/space-station-14.git
synced 2026-10-09 10:21:18 +03:00
Make cameras react to the AI eye nearby (#43466)
* init TODO: Make this part of the surveillance camera system Make this be bearable when unpowered(Probably commented out until we make AI lose vision without power) Remove leftover shit Probably clean up the camera RSI * move to surv cameras, unpowered, remove leftovers * silly * review * review * I can see the light * reverse fixtures * review * dependency query * unused oops
This commit is contained in:
parent
9ad745045b
commit
4a88b375df
13 changed files with 207 additions and 161 deletions
|
|
@ -30,7 +30,7 @@ public sealed class SurveillanceCameraMicrophoneSystem : EntitySystem
|
|||
// This function ensures that chat popups appear on camera views that have connected microphones.
|
||||
foreach (var (_, __, camera, xform) in EntityQuery<SurveillanceCameraMicrophoneComponent, ActiveListenerComponent, SurveillanceCameraComponent, TransformComponent>())
|
||||
{
|
||||
if (camera.ActiveViewers.Count == 0)
|
||||
if (camera.ActivePvsViewers.Count == 0)
|
||||
continue;
|
||||
|
||||
// get range to camera. This way wispers will still appear as obfuscated if they are too far from the camera's microphone
|
||||
|
|
@ -41,7 +41,7 @@ public sealed class SurveillanceCameraMicrophoneSystem : EntitySystem
|
|||
if (range < 0 || range > ev.VoiceRange)
|
||||
continue;
|
||||
|
||||
foreach (var viewer in camera.ActiveViewers)
|
||||
foreach (var viewer in camera.ActivePvsViewers)
|
||||
{
|
||||
// if the player has not already received the chat message, send it to them but don't log it to the chat
|
||||
// window. This is simply so that it appears in camera.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,101 @@
|
|||
using Content.Shared.Power.EntitySystems;
|
||||
using Content.Shared.SurveillanceCamera;
|
||||
using Content.Shared.SurveillanceCamera.Components;
|
||||
using Robust.Shared.Physics.Events;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
|
||||
namespace Content.Server.SurveillanceCamera;
|
||||
|
||||
public partial class SurveillanceCameraSystem
|
||||
{
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
[Dependency] private readonly SharedPowerReceiverSystem _power = default!;
|
||||
[Dependency] private readonly EntityQuery<CameraActiveOnCollideComponent> _cameraQuery = default!;
|
||||
|
||||
public void InitializeCollide()
|
||||
{
|
||||
SubscribeLocalEvent<CameraActiveOnCollideColliderComponent, PreventCollideEvent>(OnPreventCollide);
|
||||
SubscribeLocalEvent<CameraActiveOnCollideColliderComponent, StartCollideEvent>(OnStart);
|
||||
SubscribeLocalEvent<CameraActiveOnCollideColliderComponent, EndCollideEvent>(OnEnd);
|
||||
|
||||
SubscribeLocalEvent<CameraActiveOnCollideColliderComponent, ComponentShutdown>(OnCollideShutdown);
|
||||
SubscribeLocalEvent<CameraActiveOnCollideComponent, SurveillanceCameraGetIsViewedExternallyEvent>(OnOverrideState);
|
||||
}
|
||||
|
||||
private void OnCollideShutdown(Entity<CameraActiveOnCollideColliderComponent> ent, ref ComponentShutdown args)
|
||||
{
|
||||
// TODO: Check this on the event.
|
||||
if (TerminatingOrDeleted(ent.Owner))
|
||||
return;
|
||||
|
||||
// Regenerate contacts for everything we were colliding with.
|
||||
var contacts = _physics.GetContacts(ent.Owner);
|
||||
|
||||
while (contacts.MoveNext(out var contact))
|
||||
{
|
||||
if (!contact.IsTouching)
|
||||
continue;
|
||||
|
||||
var other = contact.OtherEnt(ent.Owner);
|
||||
|
||||
if (_cameraQuery.HasComp(other))
|
||||
{
|
||||
_physics.RegenerateContacts(other);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// You may be wondering what de fok this is doing here.
|
||||
// At the moment there's no easy way to do collision whitelists based on components.
|
||||
private void OnPreventCollide(Entity<CameraActiveOnCollideColliderComponent> ent, ref PreventCollideEvent args)
|
||||
{
|
||||
if (!_cameraQuery.HasComp(args.OtherEntity))
|
||||
{
|
||||
args.Cancelled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEnd(Entity<CameraActiveOnCollideColliderComponent> ent, ref EndCollideEvent args)
|
||||
{
|
||||
if (args.OurFixtureId != ent.Comp.FixtureId)
|
||||
return;
|
||||
|
||||
if (!_cameraQuery.TryComp(args.OtherEntity, out var cameraCollider))
|
||||
return;
|
||||
|
||||
// TODO: Engine bug IsTouching box2d yay.
|
||||
var contacts = _physics.GetTouchingContacts(args.OtherEntity) - 1;
|
||||
|
||||
if (contacts > 0)
|
||||
return;
|
||||
|
||||
cameraCollider.Enabled = false;
|
||||
Dirty(args.OtherEntity, cameraCollider);
|
||||
UpdateVisuals(args.OtherEntity);
|
||||
}
|
||||
|
||||
private void OnStart(Entity<CameraActiveOnCollideColliderComponent> ent, ref StartCollideEvent args)
|
||||
{
|
||||
if (args.OurFixtureId != ent.Comp.FixtureId)
|
||||
return;
|
||||
|
||||
if (!_cameraQuery.TryComp(args.OtherEntity, out var cameraCollider))
|
||||
return;
|
||||
|
||||
cameraCollider.Enabled = true;
|
||||
Dirty(args.OtherEntity, cameraCollider);
|
||||
UpdateVisuals(args.OtherEntity);
|
||||
}
|
||||
|
||||
private void OnOverrideState(Entity<CameraActiveOnCollideComponent> ent, ref SurveillanceCameraGetIsViewedExternallyEvent args)
|
||||
{
|
||||
if (ent.Comp.RequiresPower && !_power.IsPowered(ent.Owner))
|
||||
return;
|
||||
|
||||
if (!ent.Comp.Enabled)
|
||||
return;
|
||||
|
||||
args.Viewed = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -13,15 +13,15 @@ using Content.Shared.DeviceNetwork.Components;
|
|||
|
||||
namespace Content.Server.SurveillanceCamera;
|
||||
|
||||
public sealed class SurveillanceCameraSystem : SharedSurveillanceCameraSystem
|
||||
public sealed partial class SurveillanceCameraSystem : SharedSurveillanceCameraSystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly ViewSubscriberSystem _viewSubscriberSystem = default!;
|
||||
[Dependency] private readonly DeviceNetworkSystem _deviceNetworkSystem = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _userInterface = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly SurveillanceCameraMapSystem _cameraMapSystem = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
|
||||
// Pings a surveillance camera subnet. All cameras will always respond
|
||||
// with a data message if they are on the same subnet.
|
||||
|
|
@ -61,6 +61,8 @@ public sealed class SurveillanceCameraSystem : SharedSurveillanceCameraSystem
|
|||
SubscribeLocalEvent<SurveillanceCameraComponent, DeviceNetworkPacketEvent>(OnPacketReceived);
|
||||
SubscribeLocalEvent<SurveillanceCameraComponent, SurveillanceCameraSetupSetName>(OnSetName);
|
||||
SubscribeLocalEvent<SurveillanceCameraComponent, SurveillanceCameraSetupSetNetwork>(OnSetNetwork);
|
||||
|
||||
InitializeCollide();
|
||||
}
|
||||
|
||||
private void OnPacketReceived(EntityUid uid, SurveillanceCameraComponent component, DeviceNetworkPacketEvent args)
|
||||
|
|
@ -232,7 +234,7 @@ public sealed class SurveillanceCameraSystem : SharedSurveillanceCameraSystem
|
|||
|
||||
var ev = new SurveillanceCameraDeactivateEvent(camera);
|
||||
|
||||
RemoveActiveViewers(camera, new(component.ActiveViewers), null, component);
|
||||
RemoveActiveViewers(camera, new(component.ActivePvsViewers), null, component);
|
||||
component.Active = false;
|
||||
|
||||
// Send a targetted event to all monitors.
|
||||
|
|
@ -249,6 +251,25 @@ public sealed class SurveillanceCameraSystem : SharedSurveillanceCameraSystem
|
|||
UpdateVisuals(camera, component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the camera is being viewed through by anyone at all.
|
||||
/// </summary>
|
||||
/// <param name="ent">The camera to check</param>
|
||||
/// <returns>True if the camera is looked through, otherwise False.</returns>
|
||||
public bool IsGettingViewed(Entity<SurveillanceCameraComponent?> ent)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp))
|
||||
return false;
|
||||
|
||||
if (ent.Comp.ActivePvsViewers.Count > 0 || ent.Comp.ActiveMonitors.Count > 0)
|
||||
return true;
|
||||
|
||||
var ev = new SurveillanceCameraGetIsViewedExternallyEvent();
|
||||
RaiseLocalEvent(ent, ref ev);
|
||||
|
||||
return ev.Viewed;
|
||||
}
|
||||
|
||||
public override void SetActive(EntityUid camera, bool setting, SurveillanceCameraComponent? component = null)
|
||||
{
|
||||
if (!Resolve(camera, ref component))
|
||||
|
|
@ -284,7 +305,8 @@ public sealed class SurveillanceCameraSystem : SharedSurveillanceCameraSystem
|
|||
}
|
||||
|
||||
_viewSubscriberSystem.AddViewSubscriber(camera, actor.PlayerSession);
|
||||
component.ActiveViewers.Add(player);
|
||||
|
||||
component.ActivePvsViewers.Add(player);
|
||||
|
||||
if (monitor != null)
|
||||
{
|
||||
|
|
@ -346,7 +368,7 @@ public sealed class SurveillanceCameraSystem : SharedSurveillanceCameraSystem
|
|||
if (Resolve(player, ref actor))
|
||||
_viewSubscriberSystem.RemoveViewSubscriber(camera, actor.PlayerSession);
|
||||
|
||||
component.ActiveViewers.Remove(player);
|
||||
component.ActivePvsViewers.Remove(player);
|
||||
|
||||
if (monitor != null)
|
||||
{
|
||||
|
|
@ -391,7 +413,7 @@ public sealed class SurveillanceCameraSystem : SharedSurveillanceCameraSystem
|
|||
key = SurveillanceCameraVisuals.Active;
|
||||
}
|
||||
|
||||
if (component.ActiveViewers.Count > 0 || component.ActiveMonitors.Count > 0)
|
||||
if (IsGettingViewed((uid, component)))
|
||||
{
|
||||
key = SurveillanceCameraVisuals.InUse;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +0,0 @@
|
|||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Light.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Can activate <see cref="LightOnCollideComponent"/> when collided with.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class LightOnCollideColliderComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public string FixtureId = "lightTrigger";
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Light.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Enables / disables pointlight whenever entities are contacting with it
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class LightOnCollideComponent : Component
|
||||
{
|
||||
}
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
using Content.Shared.Light.Components;
|
||||
using Robust.Shared.Physics.Events;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
|
||||
namespace Content.Shared.Light.EntitySystems;
|
||||
|
||||
public sealed class LightCollideSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
[Dependency] private readonly SlimPoweredLightSystem _lights = default!;
|
||||
|
||||
private EntityQuery<LightOnCollideComponent> _lightQuery;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_lightQuery = GetEntityQuery<LightOnCollideComponent>();
|
||||
|
||||
SubscribeLocalEvent<LightOnCollideColliderComponent, PreventCollideEvent>(OnPreventCollide);
|
||||
SubscribeLocalEvent<LightOnCollideColliderComponent, StartCollideEvent>(OnStart);
|
||||
SubscribeLocalEvent<LightOnCollideColliderComponent, EndCollideEvent>(OnEnd);
|
||||
|
||||
SubscribeLocalEvent<LightOnCollideColliderComponent, ComponentShutdown>(OnCollideShutdown);
|
||||
}
|
||||
|
||||
private void OnCollideShutdown(Entity<LightOnCollideColliderComponent> ent, ref ComponentShutdown args)
|
||||
{
|
||||
// TODO: Check this on the event.
|
||||
if (TerminatingOrDeleted(ent.Owner))
|
||||
return;
|
||||
|
||||
// Regenerate contacts for everything we were colliding with.
|
||||
var contacts = _physics.GetContacts(ent.Owner);
|
||||
|
||||
while (contacts.MoveNext(out var contact))
|
||||
{
|
||||
if (!contact.IsTouching)
|
||||
continue;
|
||||
|
||||
var other = contact.OtherEnt(ent.Owner);
|
||||
|
||||
if (_lightQuery.HasComp(other))
|
||||
{
|
||||
_physics.RegenerateContacts(other);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// You may be wondering what de fok this is doing here.
|
||||
// At the moment there's no easy way to do collision whitelists based on components.
|
||||
private void OnPreventCollide(Entity<LightOnCollideColliderComponent> ent, ref PreventCollideEvent args)
|
||||
{
|
||||
if (!_lightQuery.HasComp(args.OtherEntity))
|
||||
{
|
||||
args.Cancelled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEnd(Entity<LightOnCollideColliderComponent> ent, ref EndCollideEvent args)
|
||||
{
|
||||
if (args.OurFixtureId != ent.Comp.FixtureId)
|
||||
return;
|
||||
|
||||
if (!_lightQuery.HasComp(args.OtherEntity))
|
||||
return;
|
||||
|
||||
// TODO: Engine bug IsTouching box2d yay.
|
||||
var contacts = _physics.GetTouchingContacts(args.OtherEntity) - 1;
|
||||
|
||||
if (contacts > 0)
|
||||
return;
|
||||
|
||||
_lights.SetEnabled(args.OtherEntity, false);
|
||||
}
|
||||
|
||||
private void OnStart(Entity<LightOnCollideColliderComponent> ent, ref StartCollideEvent args)
|
||||
{
|
||||
if (args.OurFixtureId != ent.Comp.FixtureId)
|
||||
return;
|
||||
|
||||
if (!_lightQuery.HasComp(args.OtherEntity))
|
||||
return;
|
||||
|
||||
_lights.SetEnabled(args.OtherEntity, true);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.SurveillanceCamera.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Can activate <see cref="CameraActiveOnCollideComponent"/> when collided with.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class CameraActiveOnCollideColliderComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The fixture id used for detecting the collision.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string FixtureId = "lightTrigger";
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.SurveillanceCamera.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Marks an entity with <see cref="SurveillanceCameraComponent"/> whenever entities are contacting with it.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class CameraActiveOnCollideComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether this camera is currently being collided with.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool Enabled;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the entity must be powered for this component to work.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool RequiresPower = true;
|
||||
}
|
||||
|
|
@ -8,18 +8,23 @@ namespace Content.Shared.SurveillanceCamera.Components;
|
|||
[Access(typeof(SharedSurveillanceCameraSystem))]
|
||||
public sealed partial class SurveillanceCameraComponent : Component
|
||||
{
|
||||
// List of active viewers. This is for bookkeeping purposes,
|
||||
// so that when a camera shuts down, any entity viewing it
|
||||
// will immediately have their subscription revoked.
|
||||
/// <summary>
|
||||
/// List of active viewers who have a PVS view subscription on this camera.
|
||||
/// This is for bookkeeping purposes,
|
||||
/// so that when a camera shuts down, any entity viewing it
|
||||
/// will immediately have their subscription revoked.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public HashSet<EntityUid> ActiveViewers { get; } = new();
|
||||
public HashSet<EntityUid> ActivePvsViewers { get; } = new();
|
||||
|
||||
// Monitors != Viewers, as viewers are entities that are tied
|
||||
// to a player session that's viewing from this camera
|
||||
//
|
||||
// Monitors are grouped sets of viewers, and may be
|
||||
// completely different monitor types (e.g., monitor console,
|
||||
// AI, etc.)
|
||||
/// <summary>
|
||||
/// Monitors != Viewers, as viewers are entities that are tied
|
||||
/// to a player session that's viewing from this camera
|
||||
///
|
||||
/// Monitors are grouped sets of viewers, and may be
|
||||
/// completely different monitor types (e.g., monitor console,
|
||||
/// AI, etc.)
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public HashSet<EntityUid> ActiveMonitors { get; } = new();
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ using Robust.Shared.Serialization;
|
|||
|
||||
namespace Content.Shared.SurveillanceCamera;
|
||||
|
||||
public abstract class SharedSurveillanceCameraSystem : EntitySystem
|
||||
public abstract partial class SharedSurveillanceCameraSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
|
|
@ -68,3 +68,11 @@ public enum SurveillanceCameraVisuals : byte
|
|||
Xray,
|
||||
Emp
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised on a camera entity to find whether it is externally viewed by some entity.
|
||||
/// This does not use the actual viewers or monitors camera has and is simply used to see whether the camera is "technically"
|
||||
/// being looked through by somebody, such as the Station AI.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct SurveillanceCameraGetIsViewedExternallyEvent(bool Viewed = false);
|
||||
|
|
|
|||
|
|
@ -14,34 +14,6 @@
|
|||
- type: InstantAction
|
||||
event: !type:JumpToCoreEvent
|
||||
|
||||
- type: entity
|
||||
parent: BaseAction
|
||||
id: ActionSurvCameraLights
|
||||
name: Toggle camera lights
|
||||
description: Enable surveillance camera lights near wherever you're viewing.
|
||||
components:
|
||||
- type: Action
|
||||
priority: -5
|
||||
itemIconStyle: BigAction
|
||||
icon:
|
||||
sprite: Interface/Actions/actions_ai.rsi
|
||||
state: camera_light
|
||||
- type: InstantAction
|
||||
event: !type:RelayedActionComponentChangeEvent
|
||||
components:
|
||||
- type: LightOnCollideCollider
|
||||
- type: FixturesChange
|
||||
fixtures:
|
||||
lightTrigger:
|
||||
shape:
|
||||
!type:PhysShapeCircle
|
||||
radius: 0.35
|
||||
density: 80
|
||||
hard: false
|
||||
layer:
|
||||
- GhostImpassable
|
||||
|
||||
|
||||
- type: entity
|
||||
parent: BaseMentalAction
|
||||
id: ActionAIViewLaws
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@
|
|||
- type: ActionGrant
|
||||
actions:
|
||||
- ActionJumpToCore
|
||||
- ActionSurvCameraLights
|
||||
- ActionAIViewLaws
|
||||
- type: UserInterface
|
||||
interfaces:
|
||||
|
|
@ -487,6 +486,16 @@
|
|||
description: The AI's viewer.
|
||||
categories: [ HideSpawnMenu, DoNotMap ]
|
||||
components:
|
||||
- type: Fixtures
|
||||
fixtures:
|
||||
lightTrigger:
|
||||
shape:
|
||||
!type:PhysShapeCircle
|
||||
radius: 8
|
||||
density: 80
|
||||
hard: false
|
||||
layer:
|
||||
- GhostImpassable
|
||||
- type: NoFTL
|
||||
- type: Tag
|
||||
tags:
|
||||
|
|
@ -506,6 +515,7 @@
|
|||
- state: ai_camera
|
||||
shader: unshaded
|
||||
map: ["base"]
|
||||
- type: CameraActiveOnCollideCollider
|
||||
|
||||
# The holographic representation of the AI that is projected from a holopad.
|
||||
- type: entity
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
light:
|
||||
shape:
|
||||
!type:PhysShapeCircle
|
||||
radius: 5
|
||||
radius: 0.5
|
||||
hard: false
|
||||
mask:
|
||||
- GhostImpassable
|
||||
|
|
@ -24,7 +24,8 @@
|
|||
- None
|
||||
layer:
|
||||
- TabletopMachineLayer
|
||||
- type: LightOnCollide
|
||||
- type: CameraActiveOnCollide
|
||||
requiresPower: false # TODO: Change this when AI can no longer see via unpowered cameras
|
||||
- type: PointLight
|
||||
enabled: false
|
||||
radius: 5
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue