mirror of
https://github.com/space-syndicate/space-station-14.git
synced 2026-10-09 18:31:17 +03:00
459 lines
16 KiB
C#
459 lines
16 KiB
C#
using System.Linq;
|
|
using Content.Server.DeviceNetwork.Systems;
|
|
using Content.Shared.DeviceNetwork;
|
|
using Content.Shared.DeviceNetwork.Events;
|
|
using Content.Shared.DeviceNetwork.Systems;
|
|
using Content.Shared.Power;
|
|
using Content.Shared.SurveillanceCamera;
|
|
using Robust.Server.GameObjects;
|
|
using Robust.Shared.Prototypes;
|
|
|
|
namespace Content.Server.SurveillanceCamera;
|
|
|
|
public sealed partial class SurveillanceCameraMonitorSystem : EntitySystem
|
|
{
|
|
[Dependency] private SurveillanceCameraSystem _surveillanceCameras = default!;
|
|
[Dependency] private UserInterfaceSystem _userInterface = default!;
|
|
[Dependency] private DeviceNetworkSystem _deviceNetworkSystem = default!;
|
|
[Dependency] private DeviceNetworkRouterSystem _deviceNetworkRouter = default!;
|
|
|
|
public override void Initialize()
|
|
{
|
|
base.Initialize();
|
|
SubscribeLocalEvent<SurveillanceCameraMonitorComponent, SurveillanceCameraDeactivateEvent>(OnSurveillanceCameraDeactivate);
|
|
SubscribeLocalEvent<SurveillanceCameraMonitorComponent, PowerChangedEvent>(OnPowerChanged);
|
|
SubscribeLocalEvent<SurveillanceCameraMonitorComponent, ComponentShutdown>(OnShutdown);
|
|
SubscribeLocalEvent<SurveillanceCameraMonitorComponent, ComponentStartup>(OnComponentStartup);
|
|
Subs.BuiEvents<SurveillanceCameraMonitorComponent>(SurveillanceCameraMonitorUiKey.Key, subs =>
|
|
{
|
|
subs.Event<SurveillanceCameraRefreshCamerasMessage>(OnRefreshCamerasMessage);
|
|
subs.Event<SurveillanceCameraRefreshSubnetsMessage>(OnRefreshSubnetsMessage);
|
|
subs.Event<SurveillanceCameraDisconnectMessage>(OnDisconnectMessage);
|
|
subs.Event<SurveillanceCameraMonitorSubnetRequestMessage>(OnSubnetRequest);
|
|
subs.Event<SurveillanceCameraMonitorSwitchMessage>(OnSwitchMessage);
|
|
subs.Event<BoundUIClosedEvent>(OnBoundUiClose);
|
|
subs.Event<BoundUIOpenedEvent>(OnBoundUiOpen);
|
|
});
|
|
}
|
|
|
|
private const float MaxHeartbeatTime = 300f;
|
|
private const float HeartbeatDelay = 30f;
|
|
|
|
public override void Update(float frameTime)
|
|
{
|
|
var query = EntityQueryEnumerator<ActiveSurveillanceCameraMonitorComponent, SurveillanceCameraMonitorComponent>();
|
|
while (query.MoveNext(out var uid, out _, out var monitor))
|
|
{
|
|
monitor.LastHeartbeatSent += frameTime;
|
|
SendHeartbeat(uid, monitor);
|
|
monitor.LastHeartbeat += frameTime;
|
|
|
|
if (monitor.LastHeartbeat > MaxHeartbeatTime)
|
|
{
|
|
DisconnectCamera(uid, true, monitor);
|
|
RemComp<ActiveSurveillanceCameraMonitorComponent>(uid);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// ROUTING:
|
|
///
|
|
/// Monitor freq: General frequency for cameras, routers, and monitors to speak on.
|
|
///
|
|
/// Subnet freqs: Frequency for each specific subnet. Routers ping cameras here,
|
|
/// cameras ping back on monitor frequency. When a monitor
|
|
/// selects a subnet, it saves that subnet's frequency
|
|
/// so it can connect to the camera. All outbound cameras
|
|
/// always speak on the monitor frequency and will not
|
|
/// do broadcast pings - whatever talks to it, talks to it.
|
|
///
|
|
/// How a camera is discovered:
|
|
///
|
|
/// Subnet ping:
|
|
/// Surveillance camera monitor - [ monitor freq ] -> Router
|
|
/// Router -> camera discovery
|
|
/// Router - [ subnet freq ] -> Camera
|
|
/// Camera -> router ping
|
|
/// Camera - [ monitor freq ] -> Router
|
|
/// Router -> monitor data forward
|
|
/// Router - [ monitor freq ] -> Monitor
|
|
|
|
#region Event Handling
|
|
private void OnComponentStartup(EntityUid uid, SurveillanceCameraMonitorComponent component, ComponentStartup args)
|
|
{
|
|
RefreshSubnets(uid, component);
|
|
}
|
|
|
|
private void OnSubnetRequest(EntityUid uid, SurveillanceCameraMonitorComponent component,
|
|
SurveillanceCameraMonitorSubnetRequestMessage args)
|
|
{
|
|
if (args.Actor is { Valid: true } actor && !Deleted(actor))
|
|
{
|
|
SetActiveSubnet(uid, args.Subnet, component);
|
|
}
|
|
}
|
|
|
|
[SubscribeLocalEvent]
|
|
private void OnCameraConnect(Entity<SurveillanceCameraMonitorComponent> ent, ref DeviceNetworkPacketEvent<SurveillanceCameraConnectPayload> args)
|
|
{
|
|
var payload = args.Data;
|
|
if (ent.Comp.NextCameraAddress == payload.SenderAddress)
|
|
{
|
|
if (payload.SenderAddress != null)
|
|
ent.Comp.ActiveCameraAddress = payload.SenderAddress;
|
|
TrySwitchCameraByUid(ent, payload.Sender, ent.Comp);
|
|
}
|
|
|
|
ent.Comp.NextCameraAddress = null;
|
|
}
|
|
|
|
[SubscribeLocalEvent]
|
|
private void OnCameraHeartbeat(Entity<SurveillanceCameraMonitorComponent> ent, ref DeviceNetworkPacketEvent<SurveillanceCameraHeartbeatPayload> args)
|
|
{
|
|
if (args.Data.SenderAddress == ent.Comp.ActiveCameraAddress)
|
|
{
|
|
ent.Comp.LastHeartbeat = 0;
|
|
}
|
|
}
|
|
|
|
[SubscribeLocalEvent]
|
|
private void OnCameraData(Entity<SurveillanceCameraMonitorComponent> ent, ref DeviceNetworkPacketEvent<SurveillanceCameraDataPayload> args)
|
|
{
|
|
var payload = args.Data;
|
|
var subnetData = payload.Subnet;
|
|
|
|
if (ent.Comp.ActiveSubnet != subnetData)
|
|
{
|
|
DisconnectFromSubnet(ent, subnetData);
|
|
}
|
|
|
|
if (payload.SenderAddress != null)
|
|
ent.Comp.KnownCameras.TryAdd(payload.SenderAddress, payload.Name);
|
|
UpdateUserInterface(ent, ent.Comp);
|
|
}
|
|
|
|
[SubscribeLocalEvent]
|
|
private void OnSubnetData(Entity<SurveillanceCameraMonitorComponent> ent, ref DeviceNetworkPacketEvent<SurveillanceCameraSubnetDataPayload> args)
|
|
{
|
|
ent.Comp.KnownSubnets.TryAdd(args.Data.TransmitFrequency, args.SenderAddress);
|
|
UpdateUserInterface(ent, ent.Comp);
|
|
}
|
|
|
|
private void OnDisconnectMessage(EntityUid uid, SurveillanceCameraMonitorComponent component,
|
|
SurveillanceCameraDisconnectMessage message)
|
|
{
|
|
DisconnectCamera(uid, true, component);
|
|
}
|
|
|
|
private void OnRefreshCamerasMessage(EntityUid uid, SurveillanceCameraMonitorComponent component,
|
|
SurveillanceCameraRefreshCamerasMessage message)
|
|
{
|
|
component.KnownCameras.Clear();
|
|
PingCameraNetwork(uid, component);
|
|
}
|
|
|
|
private void OnRefreshSubnetsMessage(EntityUid uid, SurveillanceCameraMonitorComponent component,
|
|
SurveillanceCameraRefreshSubnetsMessage message)
|
|
{
|
|
RefreshSubnets(uid, component);
|
|
}
|
|
|
|
private void OnSwitchMessage(EntityUid uid, SurveillanceCameraMonitorComponent component, SurveillanceCameraMonitorSwitchMessage message)
|
|
{
|
|
// there would be a null check here, but honestly
|
|
// whichever one is the "latest" switch message gets to
|
|
// do the switch
|
|
TrySwitchCameraByAddress(uid, message.Address, message.CameraSubnet, component);
|
|
}
|
|
|
|
private void OnPowerChanged(EntityUid uid, SurveillanceCameraMonitorComponent component, ref PowerChangedEvent args)
|
|
{
|
|
if (!args.Powered)
|
|
{
|
|
RemoveActiveCamera(uid, component);
|
|
component.NextCameraAddress = null;
|
|
component.ActiveSubnet = string.Empty;
|
|
}
|
|
}
|
|
|
|
private void OnShutdown(EntityUid uid, SurveillanceCameraMonitorComponent component, ComponentShutdown args)
|
|
{
|
|
RemoveActiveCamera(uid, component);
|
|
}
|
|
|
|
// This is to ensure that there's no delay in ensuring that a camera is deactivated.
|
|
private void OnSurveillanceCameraDeactivate(EntityUid uid, SurveillanceCameraMonitorComponent monitor, SurveillanceCameraDeactivateEvent args)
|
|
{
|
|
DisconnectCamera(uid, false, monitor);
|
|
}
|
|
|
|
private void OnBoundUiOpen(EntityUid uid, SurveillanceCameraMonitorComponent component, BoundUIOpenedEvent args)
|
|
{
|
|
AddViewer(uid, args.Actor, component);
|
|
}
|
|
|
|
private void OnBoundUiClose(EntityUid uid, SurveillanceCameraMonitorComponent component, BoundUIClosedEvent args)
|
|
{
|
|
RemoveViewer(uid, args.Actor, component);
|
|
}
|
|
|
|
#endregion
|
|
|
|
private void SendHeartbeat(EntityUid uid, SurveillanceCameraMonitorComponent? monitor = null)
|
|
{
|
|
if (!Resolve(uid, ref monitor)
|
|
|| monitor.LastHeartbeatSent < HeartbeatDelay
|
|
|| monitor.ActiveSubnet is not { } activeSubnet
|
|
|| !monitor.KnownSubnets.TryGetValue(activeSubnet, out var subnetAddress))
|
|
{
|
|
return;
|
|
}
|
|
|
|
var payload = new SurveillanceCameraHeartbeatRequestPayload();
|
|
_deviceNetworkRouter.SendPacketRouted(uid, ref payload, subnetAddress, monitor.ActiveCameraAddress, ProtoMan.Index(activeSubnet).Frequency);
|
|
|
|
monitor.LastHeartbeatSent = 0;
|
|
}
|
|
|
|
private void DisconnectCamera(EntityUid uid, bool removeViewers, SurveillanceCameraMonitorComponent? monitor = null)
|
|
{
|
|
if (!Resolve(uid, ref monitor))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (removeViewers)
|
|
{
|
|
RemoveActiveCamera(uid, monitor);
|
|
}
|
|
|
|
monitor.ActiveCamera = null;
|
|
monitor.ActiveCameraAddress = string.Empty;
|
|
RemComp<ActiveSurveillanceCameraMonitorComponent>(uid);
|
|
UpdateUserInterface(uid, monitor);
|
|
}
|
|
|
|
private void RefreshSubnets(EntityUid uid, SurveillanceCameraMonitorComponent? monitor = null)
|
|
{
|
|
if (!Resolve(uid, ref monitor))
|
|
{
|
|
return;
|
|
}
|
|
|
|
monitor.KnownSubnets.Clear();
|
|
PingSubnets(uid, monitor);
|
|
}
|
|
|
|
private void PingCameraNetwork(EntityUid uid, SurveillanceCameraMonitorComponent? monitor = null)
|
|
{
|
|
if (!Resolve(uid, ref monitor)
|
|
|| monitor.ActiveSubnet == null
|
|
|| !monitor.KnownSubnets.TryGetValue(monitor.ActiveSubnet.Value, out var subnetData))
|
|
return;
|
|
|
|
var payload = new SurveillanceCameraPingPayload { Subnet = monitor.ActiveSubnet };
|
|
_deviceNetworkRouter.SendPacketRouted(uid, ref payload, null, null, ProtoMan.Index(monitor.ActiveSubnet.Value).Frequency);
|
|
}
|
|
|
|
private void SetActiveSubnet(EntityUid uid, ProtoId<DeviceFrequencyPrototype> subnet,
|
|
SurveillanceCameraMonitorComponent? monitor = null)
|
|
{
|
|
if (!Resolve(uid, ref monitor)
|
|
|| !monitor.KnownSubnets.ContainsKey(subnet))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (monitor.ActiveSubnet is { } previousSubnet)
|
|
DisconnectFromSubnet(uid, previousSubnet);
|
|
DisconnectCamera(uid, true, monitor);
|
|
monitor.ActiveSubnet = subnet;
|
|
monitor.KnownCameras.Clear();
|
|
UpdateUserInterface(uid, monitor);
|
|
|
|
ConnectToSubnet(uid, subnet);
|
|
}
|
|
|
|
private void PingSubnets(EntityUid uid, SurveillanceCameraMonitorComponent? monitor = null)
|
|
{
|
|
if (!Resolve(uid, ref monitor))
|
|
{
|
|
return;
|
|
}
|
|
|
|
var payload = new SurveillanceCameraPingSubnetPayload();
|
|
_deviceNetworkSystem.SendPacket(uid, null, ref payload);
|
|
}
|
|
|
|
private void ConnectToSubnet(EntityUid uid, string subnet, SurveillanceCameraMonitorComponent? monitor = null)
|
|
{
|
|
if (!Resolve(uid, ref monitor)
|
|
|| string.IsNullOrEmpty(subnet)
|
|
|| !monitor.KnownSubnets.TryGetValue(subnet, out var address))
|
|
{
|
|
return;
|
|
}
|
|
|
|
var payload = new SurveillanceCameraSubnetConnectPayload();
|
|
_deviceNetworkSystem.SendPacket(uid, address, ref payload);
|
|
|
|
PingSubnets(uid);
|
|
}
|
|
|
|
private void DisconnectFromSubnet(EntityUid uid, ProtoId<DeviceFrequencyPrototype> subnet, SurveillanceCameraMonitorComponent? monitor = null)
|
|
{
|
|
if (!Resolve(uid, ref monitor)
|
|
|| !monitor.KnownSubnets.TryGetValue(subnet, out var address))
|
|
{
|
|
return;
|
|
}
|
|
|
|
var payload = new SurveillanceCameraSubnetDisconnectPayload();
|
|
_deviceNetworkSystem.SendPacket(uid, address, ref payload);
|
|
}
|
|
|
|
// Adds a viewer to the camera and the monitor.
|
|
private void AddViewer(EntityUid uid, EntityUid player, SurveillanceCameraMonitorComponent? monitor = null)
|
|
{
|
|
if (!Resolve(uid, ref monitor))
|
|
{
|
|
return;
|
|
}
|
|
|
|
monitor.Viewers.Add(player);
|
|
|
|
if (monitor.ActiveCamera != null)
|
|
{
|
|
_surveillanceCameras.AddActiveViewer(monitor.ActiveCamera.Value, player, uid);
|
|
}
|
|
|
|
UpdateUserInterface(uid, monitor, player);
|
|
}
|
|
|
|
// Removes a viewer from the camera and the monitor.
|
|
private void RemoveViewer(EntityUid uid, EntityUid player, SurveillanceCameraMonitorComponent? monitor = null)
|
|
{
|
|
if (!Resolve(uid, ref monitor))
|
|
{
|
|
return;
|
|
}
|
|
|
|
monitor.Viewers.Remove(player);
|
|
|
|
if (monitor.ActiveCamera != null)
|
|
{
|
|
_surveillanceCameras.RemoveActiveViewer(monitor.ActiveCamera.Value, player);
|
|
}
|
|
}
|
|
|
|
// Sets the camera. If the camera is not null, this will return.
|
|
//
|
|
// The camera should always attempt to switch over, rather than
|
|
// directly setting it, so that the active viewer list and view
|
|
// subscriptions can be updated.
|
|
private void SetCamera(EntityUid uid, EntityUid camera, SurveillanceCameraMonitorComponent? monitor = null)
|
|
{
|
|
if (!Resolve(uid, ref monitor)
|
|
|| monitor.ActiveCamera != null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_surveillanceCameras.AddActiveViewers(camera, monitor.Viewers, uid);
|
|
|
|
monitor.ActiveCamera = camera;
|
|
|
|
// Reset the heartbeat timers for the new device.
|
|
monitor.LastHeartbeat = 0;
|
|
monitor.LastHeartbeatSent = 0;
|
|
|
|
AddComp<ActiveSurveillanceCameraMonitorComponent>(uid);
|
|
|
|
UpdateUserInterface(uid, monitor);
|
|
}
|
|
|
|
// Switches the camera's viewers over to this new given camera.
|
|
private void SwitchCamera(EntityUid uid, EntityUid camera, SurveillanceCameraMonitorComponent? monitor = null)
|
|
{
|
|
if (!Resolve(uid, ref monitor)
|
|
|| monitor.ActiveCamera == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_surveillanceCameras.SwitchActiveViewers(monitor.ActiveCamera.Value, camera, monitor.Viewers, uid);
|
|
|
|
monitor.ActiveCamera = camera;
|
|
|
|
// Reset the heartbeat timers for the new device.
|
|
monitor.LastHeartbeat = 0;
|
|
monitor.LastHeartbeatSent = 0;
|
|
|
|
UpdateUserInterface(uid, monitor);
|
|
}
|
|
|
|
private void TrySwitchCameraByAddress(EntityUid uid, string address, string? cameraSubnet = null, SurveillanceCameraMonitorComponent? monitor = null)
|
|
{
|
|
if (!Resolve(uid, ref monitor))
|
|
return;
|
|
|
|
if (cameraSubnet != null && cameraSubnet != monitor.ActiveSubnet)
|
|
SetActiveSubnet(uid, cameraSubnet, monitor);
|
|
|
|
if (monitor.ActiveSubnet is not { } activeSubnet
|
|
|| !monitor.KnownSubnets.TryGetValue(activeSubnet, out var subnetAddress))
|
|
return;
|
|
|
|
var payload = new SurveillanceCameraConnectRequestPayload();
|
|
monitor.NextCameraAddress = address;
|
|
_deviceNetworkRouter.SendPacketRouted(uid, ref payload, subnetAddress, address, ProtoMan.Index(activeSubnet).Frequency);
|
|
}
|
|
|
|
// Attempts to switch over the current viewed camera on this monitor
|
|
// to the new camera.
|
|
private void TrySwitchCameraByUid(EntityUid uid, EntityUid newCamera, SurveillanceCameraMonitorComponent? monitor = null)
|
|
{
|
|
if (!Resolve(uid, ref monitor))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (monitor.ActiveCamera == null)
|
|
{
|
|
SetCamera(uid, newCamera, monitor);
|
|
}
|
|
else
|
|
{
|
|
SwitchCamera(uid, newCamera, monitor);
|
|
}
|
|
}
|
|
|
|
private void RemoveActiveCamera(EntityUid uid, SurveillanceCameraMonitorComponent? monitor = null)
|
|
{
|
|
if (!Resolve(uid, ref monitor)
|
|
|| monitor.ActiveCamera == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_surveillanceCameras.RemoveActiveViewers(monitor.ActiveCamera.Value, monitor.Viewers, uid);
|
|
|
|
UpdateUserInterface(uid, monitor);
|
|
}
|
|
|
|
private void UpdateUserInterface(EntityUid uid, SurveillanceCameraMonitorComponent? monitor = null, EntityUid? player = null)
|
|
{
|
|
if (!Resolve(uid, ref monitor))
|
|
{
|
|
return;
|
|
}
|
|
|
|
var state = new SurveillanceCameraMonitorUiState(
|
|
GetNetEntity(monitor.ActiveCamera),
|
|
monitor.KnownSubnets.Keys.ToHashSet(),
|
|
monitor.ActiveCameraAddress,
|
|
monitor.ActiveSubnet,
|
|
monitor.KnownCameras);
|
|
_userInterface.SetUiState(uid, SurveillanceCameraMonitorUiKey.Key, state);
|
|
}
|
|
}
|