diff --git a/Content.Client/SurveillanceCamera/ActiveSurveillanceCameraMonitor.cs b/Content.Client/SurveillanceCamera/ActiveSurveillanceCameraMonitor.cs new file mode 100644 index 0000000000..e2c30db904 --- /dev/null +++ b/Content.Client/SurveillanceCamera/ActiveSurveillanceCameraMonitor.cs @@ -0,0 +1,9 @@ +namespace Content.Client.SurveillanceCamera; + +[RegisterComponent] +public sealed class ActiveSurveillanceCameraMonitorVisualsComponent : Component +{ + public float TimeLeft = 30f; + + public Action? OnFinish; +} diff --git a/Content.Client/SurveillanceCamera/SurveillanceCameraMonitorSystem.cs b/Content.Client/SurveillanceCamera/SurveillanceCameraMonitorSystem.cs new file mode 100644 index 0000000000..31fe357427 --- /dev/null +++ b/Content.Client/SurveillanceCamera/SurveillanceCameraMonitorSystem.cs @@ -0,0 +1,49 @@ +using Robust.Shared.Utility; + +namespace Content.Client.SurveillanceCamera; + +public sealed class SurveillanceCameraMonitorSystem : EntitySystem +{ + private readonly RemQueue _toRemove = new(); + + public override void Update(float frameTime) + { + foreach (var comp in EntityQuery()) + { + if (Paused(comp.Owner)) + { + continue; + } + + comp.TimeLeft -= frameTime; + + if (comp.TimeLeft <= 0 || Deleted(comp.Owner)) + { + if (comp.OnFinish != null) + { + comp.OnFinish(); + } + + _toRemove.Add(comp.Owner); + } + } + + foreach (var uid in _toRemove) + { + EntityManager.RemoveComponent(uid); + } + + _toRemove.List?.Clear(); + } + + public void AddTimer(EntityUid uid, Action onFinish) + { + var comp = EnsureComp(uid); + comp.OnFinish = onFinish; + } + + public void RemoveTimer(EntityUid uid) + { + EntityManager.RemoveComponent(uid); + } +} diff --git a/Content.Client/SurveillanceCamera/SurveillanceCameraVisualsComponent.cs b/Content.Client/SurveillanceCamera/SurveillanceCameraVisualsComponent.cs new file mode 100644 index 0000000000..3883b2e48f --- /dev/null +++ b/Content.Client/SurveillanceCamera/SurveillanceCameraVisualsComponent.cs @@ -0,0 +1,12 @@ +using Content.Shared.SurveillanceCamera; + +namespace Content.Client.SurveillanceCamera; + +// Dummy component so that targetted events work on client for +// appearance events. +[RegisterComponent] +public sealed class SurveillanceCameraVisualsComponent : Component +{ + [DataField("sprites")] + public readonly Dictionary CameraSprites = new(); +} diff --git a/Content.Client/SurveillanceCamera/SurveillanceCameraVisualsSystem.cs b/Content.Client/SurveillanceCamera/SurveillanceCameraVisualsSystem.cs new file mode 100644 index 0000000000..18e05f3223 --- /dev/null +++ b/Content.Client/SurveillanceCamera/SurveillanceCameraVisualsSystem.cs @@ -0,0 +1,29 @@ +using Content.Shared.SurveillanceCamera; +using Robust.Client.GameObjects; + +namespace Content.Client.SurveillanceCamera; + +public sealed class SurveillanceCameraVisualsSystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnAppearanceChange); + } + + private void OnAppearanceChange(EntityUid uid, SurveillanceCameraVisualsComponent component, + ref AppearanceChangeEvent args) + { + if (!args.AppearanceData.TryGetValue(SurveillanceCameraVisualsKey.Key, out var data) + || data is not SurveillanceCameraVisuals key + || args.Sprite == null + || !args.Sprite.LayerMapTryGet(SurveillanceCameraVisualsKey.Layer, out int layer) + || !component.CameraSprites.TryGetValue(key, out var state)) + { + return; + } + + args.Sprite.LayerSetState(layer, state); + } +} diff --git a/Content.Client/SurveillanceCamera/UI/SurveillanceCameraMonitorBoundUi.cs b/Content.Client/SurveillanceCamera/UI/SurveillanceCameraMonitorBoundUi.cs new file mode 100644 index 0000000000..0a33cbd0d5 --- /dev/null +++ b/Content.Client/SurveillanceCamera/UI/SurveillanceCameraMonitorBoundUi.cs @@ -0,0 +1,123 @@ +using Content.Client.Eye; +using Content.Shared.SurveillanceCamera; +using Robust.Client.GameObjects; + +namespace Content.Client.SurveillanceCamera.UI; + +public sealed class SurveillanceCameraMonitorBoundUserInterface : BoundUserInterface +{ + [Dependency] private readonly IEntityManager _entityManager = default!; + private readonly EyeLerpingSystem _eyeLerpingSystem; + private readonly SurveillanceCameraMonitorSystem _surveillanceCameraMonitorSystem; + + private SurveillanceCameraMonitorWindow? _window; + private EntityUid? _currentCamera; + + public SurveillanceCameraMonitorBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) + { + IoCManager.InjectDependencies(this); + _eyeLerpingSystem = _entityManager.EntitySysManager.GetEntitySystem(); + _surveillanceCameraMonitorSystem = _entityManager.EntitySysManager.GetEntitySystem(); + } + + protected override void Open() + { + base.Open(); + + _window = new SurveillanceCameraMonitorWindow(); + + if (State != null) + { + UpdateState(State); + } + + _window.OpenCentered(); + + _window.CameraSelected += OnCameraSelected; + _window.SubnetOpened += OnSubnetRequest; + _window.CameraRefresh += OnCameraRefresh; + _window.SubnetRefresh += OnSubnetRefresh; + _window.OnClose += Close; + _window.CameraSwitchTimer += OnCameraSwitchTimer; + _window.CameraDisconnect += OnCameraDisconnect; + } + + private void OnCameraSelected(string address) + { + SendMessage(new SurveillanceCameraMonitorSwitchMessage(address)); + } + + private void OnSubnetRequest(string subnet) + { + SendMessage(new SurveillanceCameraMonitorSubnetRequestMessage(subnet)); + } + + private void OnCameraSwitchTimer() + { + _surveillanceCameraMonitorSystem.AddTimer(Owner.Owner, _window!.OnSwitchTimerComplete); + } + + private void OnCameraRefresh() + { + SendMessage(new SurveillanceCameraRefreshCamerasMessage()); + } + + private void OnSubnetRefresh() + { + SendMessage(new SurveillanceCameraRefreshSubnetsMessage()); + } + + private void OnCameraDisconnect() + { + SendMessage(new SurveillanceCameraDisconnectMessage()); + } + + protected override void UpdateState(BoundUserInterfaceState state) + { + if (_window == null || state is not SurveillanceCameraMonitorUiState cast) + { + return; + } + + if (cast.ActiveCamera == null) + { + _window.UpdateState(null, cast.Subnets, cast.ActiveAddress, cast.ActiveSubnet, cast.Cameras); + + if (_currentCamera != null) + { + _surveillanceCameraMonitorSystem.RemoveTimer(Owner.Owner); + _eyeLerpingSystem.RemoveEye(_currentCamera.Value); + _currentCamera = null; + } + } + else + { + if (_currentCamera == null) + { + _eyeLerpingSystem.AddEye(cast.ActiveCamera.Value); + _currentCamera = cast.ActiveCamera; + } + else if (_currentCamera != cast.ActiveCamera) + { + _eyeLerpingSystem.RemoveEye(_currentCamera.Value); + _eyeLerpingSystem.AddEye(cast.ActiveCamera.Value); + _currentCamera = cast.ActiveCamera; + } + + if (_entityManager.TryGetComponent(cast.ActiveCamera, out EyeComponent eye)) + { + _window.UpdateState(eye.Eye, cast.Subnets, cast.ActiveAddress, cast.ActiveSubnet, cast.Cameras); + } + } + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + _window?.Dispose(); + } + } +} diff --git a/Content.Client/SurveillanceCamera/UI/SurveillanceCameraMonitorWindow.xaml b/Content.Client/SurveillanceCamera/UI/SurveillanceCameraMonitorWindow.xaml new file mode 100644 index 0000000000..8f996b8171 --- /dev/null +++ b/Content.Client/SurveillanceCamera/UI/SurveillanceCameraMonitorWindow.xaml @@ -0,0 +1,25 @@ + + + + + +