mirror of
https://github.com/space-syndicate/space-station-14.git
synced 2026-10-09 18:31:17 +03:00
* Refactor DeviceNetwork * Singleton * NetworkPayloads refactor * Reorganize DeviceNetworkSystem * Surveillance cameras * Fix code * Fix build * Fix test fail by rewriting entire singleton logic * Properly predict examine * Update DeviceNetworkSystem.API.cs * Update EntityTest.cs * Update DeviceNetworkSystem.cs * Alright I'll do this expensive check fine * Fix disposals debug assert * Implement device network static events support * Use frozen dictionaries for performance * Update SharedDeviceNetworkSystem.Payloads.cs * Parallel payload handling support * Fix some spagetti-code * Optimize NetworkPayload cancellation * Parallel payloads are now cancellable * Fix screens * Fix robotics console * Allow new methods for shared * Mail fix * Turret fix * Fix network system method usage + signal and borg fix * finally fix atmos devices OH MY GOOOD * Remove stupid parallelism from fax * Fix surveiollance cameras * Working on a single thing for 2 days straight award * Fix the test? * Update units.yml * Add IDevice interface because why not * Add more device network suffixes * Fix camera router failing test * yaml linter peak * Improve docs for DeviceNetworkHandlers * fix build * Update CartridgeLoaderSystem.Relay.cs * Remove parallel system and EntitySystem post init * Add more docs and also events * Remove unnecessary caching * Update DevicePayloadComponentSystem.cs * Back to entity events * Implement DeviceData * Remove serializable from payloads * Update DeviceData.cs * Add a prototype for a singleton * Fix tests * Fix air alarms * Update DeviceNetworkSystem.cs * Fix cameras * Make camera router event-less * Fix atmos sync payloads, add more docs * Remove singleton + instant packets * oops remove public access * Fix tests? * Why the hell overload effect makes puddle sparks * Apparently it was redundant * Update gates.yml * Finish documentation * Review * Mark relay properties as obsolete * Struct payloads support (#blazinglyfastmemorysafe) * Update DeviceNetworkTest.cs * Make device structs stuff better * Move address prefix to DeviceNetworkComponent * Locale fixes * Fix tests * Doesn't know boolean logic award * Update AtmosAlarmableSystem.cs * Fix address predicted examine * Partial fix to air alarmables * Fix deployable turrets UI * Fix firelocks, maybe * Apparently Activatable UI was enough lol * Fix surveillance cameras * Rename `QueuePacket` into `SendPacket` * Simplify DeviceData as much as possible * Update DeviceData.cs * Update FaxMachineComponent.cs * Remove DeviceData because its stupid * Update ScreenSystem.cs * Fix build (camera map broken still) * Fix map * fix usings * Review changes * Build fix
402 lines
13 KiB
C#
402 lines
13 KiB
C#
using Content.Server.Administration.Logs;
|
|
using Content.Server.DeviceNetwork.Systems;
|
|
using Content.Shared.Database;
|
|
using Content.Shared.DeviceNetwork.Components;
|
|
using Content.Shared.DeviceNetwork.Events;
|
|
using Content.Shared.DeviceNetwork.Systems;
|
|
using Content.Shared.Power;
|
|
using Content.Shared.SurveillanceCamera;
|
|
using Content.Shared.SurveillanceCamera.Components;
|
|
using Robust.Server.GameObjects;
|
|
using Robust.Shared.Player;
|
|
|
|
namespace Content.Server.SurveillanceCamera;
|
|
|
|
public sealed partial class SurveillanceCameraSystem : SharedSurveillanceCameraSystem
|
|
{
|
|
[Dependency] private ViewSubscriberSystem _viewSubscriberSystem = default!;
|
|
[Dependency] private DeviceNetworkSystem _deviceNetworkSystem = default!;
|
|
[Dependency] private DeviceNetworkRouterSystem _deviceNetworkRouter = default!;
|
|
[Dependency] private UserInterfaceSystem _userInterface = default!;
|
|
[Dependency] private IAdminLogManager _adminLogger = default!;
|
|
[Dependency] private SurveillanceCameraMapSystem _cameraMapSystem = default!;
|
|
[Dependency] private SharedAppearanceSystem _appearance = default!;
|
|
|
|
[Dependency] private EntityQuery<SurveillanceCameraRouterComponent> _routerQuery = default!;
|
|
|
|
public const int CameraNameLimit = 32;
|
|
|
|
public override void Initialize()
|
|
{
|
|
base.Initialize();
|
|
|
|
SubscribeLocalEvent<SurveillanceCameraComponent, ComponentShutdown>(OnShutdown);
|
|
SubscribeLocalEvent<SurveillanceCameraComponent, PowerChangedEvent>(OnPowerChanged);
|
|
SubscribeLocalEvent<SurveillanceCameraComponent, SurveillanceCameraSetupSetName>(OnSetName);
|
|
SubscribeLocalEvent<SurveillanceCameraComponent, SurveillanceCameraSetupSetNetwork>(OnSetNetwork);
|
|
|
|
InitializeCollide();
|
|
}
|
|
|
|
[SubscribeLocalEvent]
|
|
private void OnConnectRequest(Entity<SurveillanceCameraComponent> ent, ref DeviceNetworkPacketEvent<SurveillanceCameraConnectRequestPayload> args)
|
|
{
|
|
var payload = args.Data;
|
|
if (!ent.Comp.Active)
|
|
return;
|
|
|
|
var responsePayload = new SurveillanceCameraConnectPayload();
|
|
_deviceNetworkRouter.SendPacketRouted(ent.Owner, ref responsePayload, args.SenderAddress, payload.SenderAddress);
|
|
}
|
|
|
|
[SubscribeLocalEvent]
|
|
private void OnHeartbeatRequest(Entity<SurveillanceCameraComponent> ent, ref DeviceNetworkPacketEvent<SurveillanceCameraHeartbeatRequestPayload> args)
|
|
{
|
|
var payload = args.Data;
|
|
if (!ent.Comp.Active)
|
|
return;
|
|
|
|
var responsePayload = new SurveillanceCameraHeartbeatPayload();
|
|
_deviceNetworkRouter.SendPacketRouted(ent.Owner, ref responsePayload, args.SenderAddress, payload.SenderAddress);
|
|
}
|
|
|
|
[SubscribeLocalEvent]
|
|
private void OnPing(Entity<SurveillanceCameraComponent> ent, ref DeviceNetworkPacketEvent<SurveillanceCameraPingPayload> args)
|
|
{
|
|
var payload = args.Data;
|
|
if (!ent.Comp.Active)
|
|
return;
|
|
|
|
if (!_routerQuery.TryComp(args.Sender, out var routerComp))
|
|
return;
|
|
|
|
if (routerComp.SubnetName != payload.Subnet)
|
|
return;
|
|
|
|
var name = ent.Comp.UseEntityNameAsCameraId ? MetaData(ent).EntityName : ent.Comp.CameraId;
|
|
var responsePayload = new SurveillanceCameraDataPayload
|
|
{
|
|
Name = name,
|
|
};
|
|
_deviceNetworkRouter.SendPacketRouted(ent.Owner, ref responsePayload, args.SenderAddress, payload.SenderAddress);
|
|
}
|
|
|
|
private void OnPowerChanged(EntityUid camera, SurveillanceCameraComponent component, ref PowerChangedEvent args)
|
|
{
|
|
SetActive(camera, args.Powered, component);
|
|
}
|
|
|
|
private void OnShutdown(EntityUid camera, SurveillanceCameraComponent component, ComponentShutdown args)
|
|
{
|
|
Deactivate(camera, component);
|
|
}
|
|
|
|
private void OnSetName(EntityUid uid, SurveillanceCameraComponent component, SurveillanceCameraSetupSetName args)
|
|
{
|
|
if (args.UiKey is not SurveillanceCameraSetupUiKey key
|
|
|| key != SurveillanceCameraSetupUiKey.Camera
|
|
|| string.IsNullOrEmpty(args.Name)
|
|
|| args.Name.Length > CameraNameLimit)
|
|
{
|
|
return;
|
|
}
|
|
|
|
component.CameraId = args.Name;
|
|
component.NameSet = true;
|
|
Dirty(uid, component);
|
|
UpdateSetupInterface(uid, component);
|
|
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"{ToPrettyString(args.Actor)} set the name of {ToPrettyString(uid)} to \"{args.Name}.\"");
|
|
}
|
|
|
|
private void OnSetNetwork(Entity<SurveillanceCameraComponent> ent, ref SurveillanceCameraSetupSetNetwork args)
|
|
{
|
|
var (uid, component) = ent;
|
|
if (args.UiKey is not SurveillanceCameraSetupUiKey key
|
|
|| key != SurveillanceCameraSetupUiKey.Camera)
|
|
{
|
|
return;
|
|
}
|
|
if (args.Network < 0 || args.Network >= component.AvailableNetworks.Count)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!ProtoMan.Resolve(component.AvailableNetworks[args.Network], out var frequency))
|
|
{
|
|
return;
|
|
}
|
|
|
|
_deviceNetworkSystem.SetReceiveFrequency(uid, frequency.Frequency);
|
|
component.NetworkSet = true;
|
|
Dirty(uid, component);
|
|
UpdateSetupInterface(uid, component);
|
|
}
|
|
|
|
protected override void OpenSetupInterface(EntityUid uid, EntityUid player, SurveillanceCameraComponent? camera = null)
|
|
{
|
|
if (!Resolve(uid, ref camera))
|
|
return;
|
|
|
|
if (!_userInterface.TryOpenUi(uid, SurveillanceCameraSetupUiKey.Camera, player))
|
|
return;
|
|
|
|
UpdateSetupInterface(uid, camera);
|
|
}
|
|
|
|
private void UpdateSetupInterface(EntityUid uid, SurveillanceCameraComponent? camera = null, DeviceNetworkComponent? deviceNet = null)
|
|
{
|
|
if (!Resolve(uid, ref camera, ref deviceNet))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (camera.NameSet && camera.NetworkSet)
|
|
{
|
|
_userInterface.CloseUi(uid, SurveillanceCameraSetupUiKey.Camera);
|
|
return;
|
|
}
|
|
|
|
if (camera.AvailableNetworks.Count == 0)
|
|
{
|
|
if (deviceNet.ReceiveFrequencyId is { } recvFreq)
|
|
{
|
|
camera.AvailableNetworks.Add(recvFreq);
|
|
}
|
|
else if (!camera.NetworkSet)
|
|
{
|
|
_userInterface.CloseUi(uid, SurveillanceCameraSetupUiKey.Camera);
|
|
return;
|
|
}
|
|
}
|
|
|
|
var name = camera.UseEntityNameAsCameraId ? MetaData(uid).EntityName : camera.CameraId;
|
|
var state = new SurveillanceCameraSetupBoundUiState(name,
|
|
deviceNet.ReceiveFrequency ?? 0,
|
|
camera.AvailableNetworks,
|
|
camera.NameSet,
|
|
camera.NetworkSet);
|
|
_userInterface.SetUiState(uid, SurveillanceCameraSetupUiKey.Camera, state);
|
|
}
|
|
|
|
// If the camera deactivates for any reason, it must have all viewers removed,
|
|
// and the relevant event broadcast to all systems.
|
|
private void Deactivate(EntityUid camera, SurveillanceCameraComponent? component = null)
|
|
{
|
|
if (!Resolve(camera, ref component))
|
|
{
|
|
return;
|
|
}
|
|
|
|
var ev = new SurveillanceCameraDeactivateEvent(camera);
|
|
|
|
RemoveActiveViewers(camera, new(component.ActivePvsViewers), null, component);
|
|
component.Active = false;
|
|
|
|
// Send a targetted event to all monitors.
|
|
foreach (var monitor in component.ActiveMonitors)
|
|
{
|
|
RaiseLocalEvent(monitor, ev, true);
|
|
}
|
|
|
|
component.ActiveMonitors.Clear();
|
|
|
|
// Send a local event that's broadcasted everywhere afterwards.
|
|
RaiseLocalEvent(ev);
|
|
|
|
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))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (setting)
|
|
{
|
|
var attemptEv = new SurveillanceCameraSetActiveAttemptEvent();
|
|
RaiseLocalEvent(camera, ref attemptEv);
|
|
if (attemptEv.Cancelled)
|
|
return;
|
|
component.Active = setting;
|
|
}
|
|
else
|
|
{
|
|
Deactivate(camera, component);
|
|
}
|
|
|
|
UpdateVisuals(camera, component);
|
|
|
|
_cameraMapSystem.UpdateCameraMarker((camera, component));
|
|
}
|
|
|
|
public void AddActiveViewer(EntityUid camera, EntityUid player, EntityUid? monitor = null, SurveillanceCameraComponent? component = null, ActorComponent? actor = null)
|
|
{
|
|
if (!Resolve(camera, ref component)
|
|
|| !component.Active
|
|
|| !Resolve(player, ref actor))
|
|
{
|
|
return;
|
|
}
|
|
|
|
_viewSubscriberSystem.AddViewSubscriber(camera, actor.PlayerSession);
|
|
|
|
component.ActivePvsViewers.Add(player);
|
|
|
|
if (monitor != null)
|
|
{
|
|
component.ActiveMonitors.Add(monitor.Value);
|
|
}
|
|
|
|
UpdateVisuals(camera, component);
|
|
}
|
|
|
|
public void AddActiveViewers(EntityUid camera, HashSet<EntityUid> players, EntityUid? monitor = null, SurveillanceCameraComponent? component = null)
|
|
{
|
|
if (!Resolve(camera, ref component) || !component.Active)
|
|
{
|
|
return;
|
|
}
|
|
|
|
foreach (var player in players)
|
|
{
|
|
AddActiveViewer(camera, player, monitor, component);
|
|
}
|
|
|
|
// Add monitor without viewers
|
|
if (players.Count == 0 && monitor != null)
|
|
{
|
|
component.ActiveMonitors.Add(monitor.Value);
|
|
UpdateVisuals(camera, component);
|
|
}
|
|
}
|
|
|
|
// Switch the set of active viewers from one camera to another.
|
|
public void SwitchActiveViewers(EntityUid oldCamera, EntityUid newCamera, HashSet<EntityUid> players, EntityUid? monitor = null, SurveillanceCameraComponent? oldCameraComponent = null, SurveillanceCameraComponent? newCameraComponent = null)
|
|
{
|
|
if (!Resolve(oldCamera, ref oldCameraComponent)
|
|
|| !Resolve(newCamera, ref newCameraComponent)
|
|
|| !oldCameraComponent.Active
|
|
|| !newCameraComponent.Active)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (monitor != null)
|
|
{
|
|
oldCameraComponent.ActiveMonitors.Remove(monitor.Value);
|
|
newCameraComponent.ActiveMonitors.Add(monitor.Value);
|
|
}
|
|
|
|
foreach (var player in players)
|
|
{
|
|
RemoveActiveViewer(oldCamera, player, null, oldCameraComponent);
|
|
AddActiveViewer(newCamera, player, null, newCameraComponent);
|
|
}
|
|
}
|
|
|
|
public void RemoveActiveViewer(EntityUid camera, EntityUid player, EntityUid? monitor = null, SurveillanceCameraComponent? component = null, ActorComponent? actor = null)
|
|
{
|
|
if (!Resolve(camera, ref component))
|
|
return;
|
|
|
|
if (Resolve(player, ref actor))
|
|
_viewSubscriberSystem.RemoveViewSubscriber(camera, actor.PlayerSession);
|
|
|
|
component.ActivePvsViewers.Remove(player);
|
|
|
|
if (monitor != null)
|
|
{
|
|
component.ActiveMonitors.Remove(monitor.Value);
|
|
}
|
|
|
|
UpdateVisuals(camera, component);
|
|
}
|
|
|
|
public void RemoveActiveViewers(EntityUid camera, HashSet<EntityUid> players, EntityUid? monitor = null, SurveillanceCameraComponent? component = null)
|
|
{
|
|
if (!Resolve(camera, ref component))
|
|
{
|
|
return;
|
|
}
|
|
|
|
foreach (var player in players)
|
|
{
|
|
RemoveActiveViewer(camera, player, monitor, component);
|
|
}
|
|
|
|
// Even if not removing any viewers, remove the monitor
|
|
if (players.Count == 0 && monitor != null)
|
|
{
|
|
component.ActiveMonitors.Remove(monitor.Value);
|
|
UpdateVisuals(camera, component);
|
|
}
|
|
}
|
|
|
|
private void UpdateVisuals(EntityUid uid, SurveillanceCameraComponent? component = null, AppearanceComponent? appearance = null)
|
|
{
|
|
// Don't log missing, because otherwise tests fail.
|
|
if (!Resolve(uid, ref component, ref appearance, false))
|
|
{
|
|
return;
|
|
}
|
|
|
|
var key = SurveillanceCameraVisuals.Disabled;
|
|
|
|
if (component.Active)
|
|
{
|
|
key = SurveillanceCameraVisuals.Active;
|
|
}
|
|
|
|
if (IsGettingViewed((uid, component)))
|
|
{
|
|
key = SurveillanceCameraVisuals.InUse;
|
|
}
|
|
|
|
_appearance.SetData(uid, SurveillanceCameraVisualsKey.Key, key, appearance);
|
|
}
|
|
}
|
|
|
|
public sealed class OnSurveillanceCameraViewerAddEvent : EntityEventArgs
|
|
{
|
|
|
|
}
|
|
|
|
public sealed class OnSurveillanceCameraViewerRemoveEvent : EntityEventArgs
|
|
{
|
|
|
|
}
|
|
|
|
// What happens when a camera deactivates.
|
|
public sealed class SurveillanceCameraDeactivateEvent : EntityEventArgs
|
|
{
|
|
public EntityUid Camera { get; }
|
|
|
|
public SurveillanceCameraDeactivateEvent(EntityUid camera)
|
|
{
|
|
Camera = camera;
|
|
}
|
|
}
|
|
|
|
[ByRefEvent]
|
|
public record struct SurveillanceCameraSetActiveAttemptEvent(bool Cancelled);
|