Surveillance cameras (#8246)
* cameras but i didn't feel like git stashing them * Adds more functionality server-side for surveillance cameras * rider moment * Adds skeleton for SurveillanceCameraMonitorBoundUi on client * whoops * makes surveillance camera monitor UI more defined * removes tree from SurveillanceCameraMonitorWindow * surveillance camera active flag, other routing things * actually sets how SurveillanceCameraMonitorSystem sends camera info to clients * adds entity yaml, changes field * adds the camera/monitor entities, makes the UI open * SurveillanceCameraRouters (not implemented fully) * subnets for cameras, server-side * it works! * fixes rotation in cameras * whoops restores surveillance cameras to ignored components makes it so that switching cameras now lerps the other camera * makes the UI work * makes it so that cameras actually refresh now * cleanup * adds camera.rsi * cleans up prototypes a bit * adds camera subnet frequencies, cameras in subnets * adds surveillance camera router subnets * might fix testing errors * adds the circuit board to the surveillance camera monitor * fixes up the camera monitor (the detective will get his tv soon) * adds heartbeat, ensures subnet data is passed into cameras to send * fixes up a few things * whoops * changes to UI internals * fixes subnet selection issue * localized strings for UI * changes 'test' id to 'camera' for cameras * whoops * missing s * camera static! * adds a delay to camera switching * adjusts a few things in camera timing * adds setup for cameras/routers, localization for frequency names * adds setup ui for routers, makes subnet names in monitor window follow frequency name in prototype * localization, some cleanup * ui adjustments * adds surveillance camera visuals * fixes a bug when closing the UI for monitors * adds disconnect message to UI * adds construction graph to cameras * adds the camera to the construction menu * fixes network selection for setup, tweak to assembly * adds surveillance camera router construction, fixes up surveillance camera wire cutting * adds disconnect button to monitor UI * switches around the status text * tweaks monitor UI * makes the address actually show * might make tests pass * UI adjustments, hard name limit * ok, that didn't work * adds wireless cameras * makes the television work/look nicer * adds tripod cameras in addition to mobile cameras * makes wireless cameras constructable * fixes up those prototypes * reorganization in C#, small cleanup * ensures that power changes deactivate some devices * adds a component to the television, comments out a function * actually, never mind, i forgot that wireless cameras existed/are creatable for a second * tweaks to router construction, removes SubnetTest from prototypes * removes it from frequencies too * Apply suggestions from code review Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> * type serializers into components * setup window opens centered, enum is now byte * replaces active monitor list with ActiveSurveillanceCameraMonitorComponent * adds paused/deleted entity checks, changes how verbs are given * removes EntitySystem.Get<T>() references * fixes bug related to selecting network from setup, alphabet-orders network listing in setup * rider moment * adds minwidth to surveillance camera setup window * addresses reviews * should fix the issue with camera visuals not updating properly * addresses some reviews * addresses further review * addresses reviews related to RSIs * never needed a key there anyways * changes a few things with routers to ensure that they're active * whoops * ensurecomp over addcomp * whoops Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
@@ -0,0 +1,9 @@
|
|||||||
|
namespace Content.Client.SurveillanceCamera;
|
||||||
|
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class ActiveSurveillanceCameraMonitorVisualsComponent : Component
|
||||||
|
{
|
||||||
|
public float TimeLeft = 30f;
|
||||||
|
|
||||||
|
public Action? OnFinish;
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
|
namespace Content.Client.SurveillanceCamera;
|
||||||
|
|
||||||
|
public sealed class SurveillanceCameraMonitorSystem : EntitySystem
|
||||||
|
{
|
||||||
|
private readonly RemQueue<EntityUid> _toRemove = new();
|
||||||
|
|
||||||
|
public override void Update(float frameTime)
|
||||||
|
{
|
||||||
|
foreach (var comp in EntityQuery<ActiveSurveillanceCameraMonitorVisualsComponent>())
|
||||||
|
{
|
||||||
|
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<ActiveSurveillanceCameraMonitorVisualsComponent>(uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
_toRemove.List?.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddTimer(EntityUid uid, Action onFinish)
|
||||||
|
{
|
||||||
|
var comp = EnsureComp<ActiveSurveillanceCameraMonitorVisualsComponent>(uid);
|
||||||
|
comp.OnFinish = onFinish;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveTimer(EntityUid uid)
|
||||||
|
{
|
||||||
|
EntityManager.RemoveComponent<ActiveSurveillanceCameraMonitorVisualsComponent>(uid);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<SurveillanceCameraVisuals, string> CameraSprites = new();
|
||||||
|
}
|
||||||
@@ -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<SurveillanceCameraVisualsComponent, AppearanceChangeEvent>(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<EyeLerpingSystem>();
|
||||||
|
_surveillanceCameraMonitorSystem = _entityManager.EntitySysManager.GetEntitySystem<SurveillanceCameraMonitorSystem>();
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<DefaultWindow xmlns="https://spacestation14.io"
|
||||||
|
xmlns:viewport="clr-namespace:Content.Client.Viewport"
|
||||||
|
Title="{Loc 'surveillance-camera-monitor-ui-window'}">
|
||||||
|
<BoxContainer Orientation="Horizontal">
|
||||||
|
<BoxContainer Orientation="Vertical" MinWidth="350" VerticalExpand="True">
|
||||||
|
<!-- lazy -->
|
||||||
|
<OptionButton Name="SubnetSelector" />
|
||||||
|
<Button Name="SubnetRefreshButton" Text="{Loc 'surveillance-camera-monitor-ui-refresh-subnets'}" />
|
||||||
|
<ScrollContainer VerticalExpand="True">
|
||||||
|
<ItemList Name="SubnetList" />
|
||||||
|
</ScrollContainer>
|
||||||
|
<Button Name="CameraRefreshButton" Text="{Loc 'surveillance-camera-monitor-ui-refresh-cameras'}" />
|
||||||
|
<Button Name="CameraDisconnectButton" Text="{Loc 'surveillance-camera-monitor-ui-disconnect'}" />
|
||||||
|
<Label Name="CameraStatus" />
|
||||||
|
</BoxContainer>
|
||||||
|
<Control VerticalExpand="True" HorizontalExpand="True" Margin="5 5 5 5" Name="CameraViewBox">
|
||||||
|
<viewport:ScalingViewport Name="CameraView"
|
||||||
|
VerticalExpand="True"
|
||||||
|
HorizontalExpand="True"
|
||||||
|
MinSize="500 500"
|
||||||
|
MouseFilter="Ignore" />
|
||||||
|
<TextureRect VerticalExpand="True" HorizontalExpand="True" MinSize="500 500" Name="CameraViewBackground" />
|
||||||
|
</Control>
|
||||||
|
</BoxContainer>
|
||||||
|
</DefaultWindow>
|
||||||
@@ -0,0 +1,188 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using Content.Client.Resources;
|
||||||
|
using Content.Client.Viewport;
|
||||||
|
using Content.Shared.DeviceNetwork;
|
||||||
|
using Content.Shared.SurveillanceCamera;
|
||||||
|
using Robust.Client.AutoGenerated;
|
||||||
|
using Robust.Client.Graphics;
|
||||||
|
using Robust.Client.ResourceManagement;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Client.UserInterface.CustomControls;
|
||||||
|
using Robust.Client.UserInterface.XAML;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Client.SurveillanceCamera.UI;
|
||||||
|
|
||||||
|
[GenerateTypedNameReferences]
|
||||||
|
public sealed partial class SurveillanceCameraMonitorWindow : DefaultWindow
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
|
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||||
|
|
||||||
|
public event Action<string>? CameraSelected;
|
||||||
|
public event Action<string>? SubnetOpened;
|
||||||
|
public event Action? CameraRefresh;
|
||||||
|
public event Action? SubnetRefresh;
|
||||||
|
public event Action? CameraSwitchTimer;
|
||||||
|
public event Action? CameraDisconnect;
|
||||||
|
|
||||||
|
private string _currentAddress = string.Empty;
|
||||||
|
private readonly FixedEye _defaultEye = new();
|
||||||
|
private readonly Dictionary<string, int> _subnetMap = new();
|
||||||
|
|
||||||
|
private string? SelectedSubnet
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (SubnetSelector.ItemCount == 0
|
||||||
|
|| SubnetSelector.SelectedMetadata == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (string) SubnetSelector.SelectedMetadata;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public SurveillanceCameraMonitorWindow()
|
||||||
|
{
|
||||||
|
RobustXamlLoader.Load(this);
|
||||||
|
IoCManager.InjectDependencies(this);
|
||||||
|
|
||||||
|
// This could be done better. I don't want to deal with stylesheets at the moment.
|
||||||
|
var texture = _resourceCache.GetTexture("/Textures/Interface/Nano/square_black.png");
|
||||||
|
var shader = _prototypeManager.Index<ShaderPrototype>("CameraStatic").Instance().Duplicate();
|
||||||
|
|
||||||
|
CameraView.ViewportSize = new Vector2i(500, 500);
|
||||||
|
CameraView.Eye = _defaultEye; // sure
|
||||||
|
CameraViewBackground.Stretch = TextureRect.StretchMode.Scale;
|
||||||
|
CameraViewBackground.Texture = texture;
|
||||||
|
CameraViewBackground.ShaderOverride = shader;
|
||||||
|
|
||||||
|
SubnetList.OnItemSelected += OnSubnetListSelect;
|
||||||
|
|
||||||
|
SubnetSelector.OnItemSelected += args =>
|
||||||
|
{
|
||||||
|
// piss
|
||||||
|
SubnetOpened!((string) args.Button.GetItemMetadata(args.Id)!);
|
||||||
|
};
|
||||||
|
SubnetRefreshButton.OnPressed += _ => SubnetRefresh!();
|
||||||
|
CameraRefreshButton.OnPressed += _ => CameraRefresh!();
|
||||||
|
CameraDisconnectButton.OnPressed += _ => CameraDisconnect!();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The UI class should get the eye from the entity, and then
|
||||||
|
// pass it here so that the UI can change its view.
|
||||||
|
public void UpdateState(IEye? eye, HashSet<string> subnets, string activeAddress, string activeSubnet, Dictionary<string, string> cameras)
|
||||||
|
{
|
||||||
|
_currentAddress = activeAddress;
|
||||||
|
SetCameraView(eye);
|
||||||
|
|
||||||
|
if (subnets.Count == 0)
|
||||||
|
{
|
||||||
|
SubnetSelector.AddItem(Loc.GetString("surveillance-camera-monitor-ui-no-subnets"));
|
||||||
|
SubnetSelector.Disabled = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SubnetSelector.Disabled && subnets.Count != 0)
|
||||||
|
{
|
||||||
|
SubnetSelector.Clear();
|
||||||
|
SubnetSelector.Disabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// That way, we have *a* subnet selected if this is ever opened.
|
||||||
|
if (string.IsNullOrEmpty(activeSubnet))
|
||||||
|
{
|
||||||
|
SubnetOpened!(subnets.First());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the subnet count is unequal, that means
|
||||||
|
// we have to rebuild the subnet selector
|
||||||
|
if (SubnetSelector.ItemCount != subnets.Count)
|
||||||
|
{
|
||||||
|
SubnetSelector.Clear();
|
||||||
|
_subnetMap.Clear();
|
||||||
|
|
||||||
|
foreach (var subnet in subnets)
|
||||||
|
{
|
||||||
|
var id = AddSubnet(subnet);
|
||||||
|
_subnetMap.Add(subnet, id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_subnetMap.TryGetValue(activeSubnet, out var subnetId))
|
||||||
|
{
|
||||||
|
SubnetSelector.Select(subnetId);
|
||||||
|
}
|
||||||
|
|
||||||
|
PopulateCameraList(cameras);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PopulateCameraList(Dictionary<string, string> cameras)
|
||||||
|
{
|
||||||
|
SubnetList.Clear();
|
||||||
|
|
||||||
|
foreach (var (address, name) in cameras)
|
||||||
|
{
|
||||||
|
AddCameraToList(name, address);
|
||||||
|
}
|
||||||
|
|
||||||
|
SubnetList.SortItemsByText();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetCameraView(IEye? eye)
|
||||||
|
{
|
||||||
|
CameraView.Eye = eye ?? _defaultEye;
|
||||||
|
CameraView.Visible = eye != null;
|
||||||
|
CameraViewBackground.Visible = true;
|
||||||
|
CameraDisconnectButton.Disabled = eye == null;
|
||||||
|
|
||||||
|
if (eye != null)
|
||||||
|
{
|
||||||
|
CameraStatus.Text = Loc.GetString("surveillance-camera-monitor-ui-status",
|
||||||
|
("status", Loc.GetString("surveillance-camera-monitor-ui-status-connecting")),
|
||||||
|
("address", _currentAddress));
|
||||||
|
CameraSwitchTimer!();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CameraStatus.Text = Loc.GetString("surveillance-camera-monitor-ui-status-disconnected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnSwitchTimerComplete()
|
||||||
|
{
|
||||||
|
CameraViewBackground.Visible = false;
|
||||||
|
CameraStatus.Text = Loc.GetString("surveillance-camera-monitor-ui-status",
|
||||||
|
("status", Loc.GetString("surveillance-camera-monitor-ui-status-connected")),
|
||||||
|
("address", _currentAddress));
|
||||||
|
}
|
||||||
|
|
||||||
|
private int AddSubnet(string subnet)
|
||||||
|
{
|
||||||
|
var name = subnet;
|
||||||
|
if (_prototypeManager.TryIndex<DeviceFrequencyPrototype>(subnet, out var frequency))
|
||||||
|
{
|
||||||
|
name = Loc.GetString(frequency.Name ?? subnet);
|
||||||
|
}
|
||||||
|
|
||||||
|
SubnetSelector.AddItem(name);
|
||||||
|
SubnetSelector.SetItemMetadata(SubnetSelector.ItemCount - 1, subnet);
|
||||||
|
|
||||||
|
return SubnetSelector.ItemCount - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddCameraToList(string name, string address)
|
||||||
|
{
|
||||||
|
var item = SubnetList.AddItem($"{name}: {address}");
|
||||||
|
item.Metadata = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSubnetListSelect(ItemList.ItemListSelectedEventArgs args)
|
||||||
|
{
|
||||||
|
CameraSelected!((string) SubnetList[args.ItemIndex].Metadata!);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
using Content.Shared.SurveillanceCamera;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Client.SurveillanceCamera.UI;
|
||||||
|
|
||||||
|
public sealed class SurveillanceCameraSetupBoundUi : BoundUserInterface
|
||||||
|
{
|
||||||
|
private SurveillanceCameraSetupWindow? _window;
|
||||||
|
private SurveillanceCameraSetupUiKey _type;
|
||||||
|
|
||||||
|
public SurveillanceCameraSetupBoundUi(ClientUserInterfaceComponent component, object uiKey) : base(component, uiKey)
|
||||||
|
{
|
||||||
|
if (uiKey is not SurveillanceCameraSetupUiKey key)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_type = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Open()
|
||||||
|
{
|
||||||
|
_window = new();
|
||||||
|
|
||||||
|
if (_type == SurveillanceCameraSetupUiKey.Router)
|
||||||
|
{
|
||||||
|
_window.HideNameSelector();
|
||||||
|
}
|
||||||
|
|
||||||
|
_window.OpenCentered();
|
||||||
|
_window.OnNameConfirm += SendDeviceName;
|
||||||
|
_window.OnNetworkConfirm += SendSelectedNetwork;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SendSelectedNetwork(int idx)
|
||||||
|
{
|
||||||
|
SendMessage(new SurveillanceCameraSetupSetNetwork(idx));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SendDeviceName(string name)
|
||||||
|
{
|
||||||
|
SendMessage(new SurveillanceCameraSetupSetName(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateState(BoundUserInterfaceState state)
|
||||||
|
{
|
||||||
|
base.UpdateState(state);
|
||||||
|
|
||||||
|
if (_window == null || state is not SurveillanceCameraSetupBoundUiState cast)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_window.UpdateState(cast.Name, cast.NameDisabled, cast.NetworkDisabled);
|
||||||
|
_window.LoadAvailableNetworks(cast.Network, cast.Networks);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
base.Dispose(disposing);
|
||||||
|
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
_window!.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<DefaultWindow xmlns="https://spacestation14.io"
|
||||||
|
Title="{Loc 'surveillance-camera-setup'}"
|
||||||
|
MinWidth="400">
|
||||||
|
<BoxContainer Orientation="Vertical">
|
||||||
|
<BoxContainer Name="NamingSection">
|
||||||
|
<LineEdit HorizontalExpand="True" Name="DeviceName" />
|
||||||
|
<Button Name="NameConfirm" Text="{Loc 'surveillance-camera-setup-ui-set'}"/>
|
||||||
|
</BoxContainer>
|
||||||
|
<BoxContainer Name="NetworkSection">
|
||||||
|
<OptionButton HorizontalExpand="True" Name="NetworkSelector" />
|
||||||
|
<Button Name="NetworkConfirm" Text="{Loc 'surveillance-camera-setup-ui-set'}" />
|
||||||
|
</BoxContainer>
|
||||||
|
</BoxContainer>
|
||||||
|
</DefaultWindow>
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
using Content.Shared.DeviceNetwork;
|
||||||
|
using Robust.Client.AutoGenerated;
|
||||||
|
using Robust.Client.UserInterface.CustomControls;
|
||||||
|
using Robust.Client.UserInterface.XAML;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Client.SurveillanceCamera.UI;
|
||||||
|
|
||||||
|
[GenerateTypedNameReferences]
|
||||||
|
public sealed partial class SurveillanceCameraSetupWindow : DefaultWindow
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
|
|
||||||
|
public Action<string>? OnNameConfirm;
|
||||||
|
public Action<int>? OnNetworkConfirm;
|
||||||
|
|
||||||
|
public SurveillanceCameraSetupWindow()
|
||||||
|
{
|
||||||
|
RobustXamlLoader.Load(this);
|
||||||
|
|
||||||
|
IoCManager.InjectDependencies(this);
|
||||||
|
|
||||||
|
NetworkConfirm.OnPressed += _ => OnNetworkConfirm!(NetworkSelector.SelectedId);
|
||||||
|
NameConfirm.OnPressed += _ => OnNameConfirm!(DeviceName.Text);
|
||||||
|
NetworkSelector.OnItemSelected += args => NetworkSelector.SelectId(args.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void HideNameSelector() => NamingSection.Visible = false;
|
||||||
|
|
||||||
|
public void UpdateState(string name, bool disableNaming, bool disableNetworkSelector)
|
||||||
|
{
|
||||||
|
DeviceName.Text = name;
|
||||||
|
DeviceName.Editable = !disableNaming;
|
||||||
|
NameConfirm.Disabled = disableNaming;
|
||||||
|
|
||||||
|
NetworkSelector.Disabled = disableNetworkSelector;
|
||||||
|
NetworkConfirm.Disabled = disableNetworkSelector;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pass in a list of frequency prototype IDs.
|
||||||
|
public void LoadAvailableNetworks(uint currentNetwork, List<string> networks)
|
||||||
|
{
|
||||||
|
NetworkSelector.Clear();
|
||||||
|
|
||||||
|
if (networks.Count == 0)
|
||||||
|
{
|
||||||
|
NetworkSection.Visible = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var id = 0;
|
||||||
|
var idList = new List<(int id, string networkName)>();
|
||||||
|
foreach (var network in networks)
|
||||||
|
{
|
||||||
|
idList.Add((id, network));
|
||||||
|
id++;
|
||||||
|
}
|
||||||
|
|
||||||
|
idList.Sort((a, b) => a.networkName.CompareTo(b.networkName));
|
||||||
|
|
||||||
|
foreach (var (networkId, network) in idList)
|
||||||
|
{
|
||||||
|
if (!_prototypeManager.TryIndex(network, out DeviceFrequencyPrototype? frequency)
|
||||||
|
|| frequency.Name == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
NetworkSelector.AddItem(Loc.GetString(frequency.Name), networkId);
|
||||||
|
if (frequency.Frequency == currentNetwork)
|
||||||
|
{
|
||||||
|
NetworkSelector.SelectId(networkId);
|
||||||
|
}
|
||||||
|
|
||||||
|
id++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,7 +24,8 @@ namespace Content.Server.Entry
|
|||||||
"ToggleableLightVisuals",
|
"ToggleableLightVisuals",
|
||||||
"CableVisualizer",
|
"CableVisualizer",
|
||||||
"PotencyVisuals",
|
"PotencyVisuals",
|
||||||
"PaperVisuals"
|
"PaperVisuals",
|
||||||
|
"SurveillanceCameraVisuals"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Content.Server.SurveillanceCamera;
|
||||||
|
|
||||||
|
// Dummy component for active surveillance monitors.
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class ActiveSurveillanceCameraMonitorComponent : Component
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
using Content.Shared.DeviceNetwork;
|
||||||
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||||
|
|
||||||
|
namespace Content.Server.SurveillanceCamera;
|
||||||
|
|
||||||
|
[RegisterComponent]
|
||||||
|
[Friend(typeof(SurveillanceCameraSystem))]
|
||||||
|
public sealed 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.
|
||||||
|
public HashSet<EntityUid> ActiveViewers { 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.)
|
||||||
|
public HashSet<EntityUid> ActiveMonitors { get; } = new();
|
||||||
|
|
||||||
|
// If this camera is active or not. Deactivating a camera
|
||||||
|
// will not allow it to obtain any new viewers.
|
||||||
|
[ViewVariables]
|
||||||
|
public bool Active { get; set; } = true;
|
||||||
|
|
||||||
|
// This one isn't easy to deal with. Will require a UI
|
||||||
|
// to change/set this so mapping these in isn't
|
||||||
|
// the most terrible thing possible.
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
[DataField("id")]
|
||||||
|
public string CameraId { get; set; } = "camera";
|
||||||
|
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
[DataField("nameSet")]
|
||||||
|
public bool NameSet { get; set; }
|
||||||
|
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
[DataField("networkSet")]
|
||||||
|
public bool NetworkSet { get; set; }
|
||||||
|
|
||||||
|
// This has to be device network frequency prototypes.
|
||||||
|
[DataField("setupAvailableNetworks", customTypeSerializer:typeof(PrototypeIdListSerializer<DeviceFrequencyPrototype>))]
|
||||||
|
public List<string> AvailableNetworks { get; } = new();
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
namespace Content.Server.SurveillanceCamera;
|
||||||
|
|
||||||
|
[RegisterComponent]
|
||||||
|
[Friend(typeof(SurveillanceCameraMonitorSystem))]
|
||||||
|
public sealed class SurveillanceCameraMonitorComponent : Component
|
||||||
|
{
|
||||||
|
// Currently active camera viewed by this monitor.
|
||||||
|
public EntityUid? ActiveCamera { get; set; }
|
||||||
|
public string ActiveCameraAddress { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
// Last time this monitor was sent a heartbeat.
|
||||||
|
public float LastHeartbeat { get; set; }
|
||||||
|
// Last time this monitor sent a heartbeat.
|
||||||
|
public float LastHeartbeatSent { get; set; }
|
||||||
|
|
||||||
|
// Next camera this monitor is trying to connect to.
|
||||||
|
// If the monitor has connected to the camera, this
|
||||||
|
// should be set to null.
|
||||||
|
public string? NextCameraAddress { get; set; }
|
||||||
|
|
||||||
|
[ViewVariables]
|
||||||
|
// Set of viewers currently looking at this monitor.
|
||||||
|
public HashSet<EntityUid> Viewers { get; } = new();
|
||||||
|
|
||||||
|
// Current active subnet.
|
||||||
|
public string ActiveSubnet { get; set; } = default!;
|
||||||
|
|
||||||
|
// Known cameras in this subnet by address with name values.
|
||||||
|
// This is cleared when the subnet is changed.
|
||||||
|
public Dictionary<string, string> KnownCameras { get; } = new();
|
||||||
|
|
||||||
|
[ViewVariables]
|
||||||
|
// The subnets known by this camera monitor.
|
||||||
|
public Dictionary<string, string> KnownSubnets { get; } = new();
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
using Content.Shared.DeviceNetwork;
|
||||||
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||||
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||||
|
|
||||||
|
namespace Content.Server.SurveillanceCamera;
|
||||||
|
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class SurveillanceCameraRouterComponent : Component
|
||||||
|
{
|
||||||
|
[ViewVariables] public bool Active { get; set; }
|
||||||
|
|
||||||
|
// The name of the subnet connected to this router.
|
||||||
|
[DataField("subnetName")]
|
||||||
|
public string SubnetName { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[ViewVariables]
|
||||||
|
// The monitors to route to. This raises an issue related to
|
||||||
|
// camera monitors disappearing before sending a D/C packet,
|
||||||
|
// this could probably be refreshed every time a new monitor
|
||||||
|
// is added or removed from active routing.
|
||||||
|
public HashSet<string> MonitorRoutes { get; } = new();
|
||||||
|
|
||||||
|
[ViewVariables]
|
||||||
|
// The frequency that talks to this router's subnet.
|
||||||
|
public uint SubnetFrequency;
|
||||||
|
[DataField("subnetFrequency", customTypeSerializer:typeof(PrototypeIdSerializer<DeviceFrequencyPrototype>))]
|
||||||
|
public string? SubnetFrequencyId { get; set; }
|
||||||
|
|
||||||
|
[DataField("setupAvailableNetworks", customTypeSerializer:typeof(PrototypeIdListSerializer<DeviceFrequencyPrototype>))]
|
||||||
|
public List<string> AvailableNetworks { get; } = new();
|
||||||
|
}
|
||||||
@@ -0,0 +1,497 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using Content.Server.DeviceNetwork;
|
||||||
|
using Content.Server.DeviceNetwork.Systems;
|
||||||
|
using Content.Server.Power.Components;
|
||||||
|
using Content.Server.UserInterface;
|
||||||
|
using Content.Server.Wires;
|
||||||
|
using Content.Shared.Interaction;
|
||||||
|
using Content.Shared.SurveillanceCamera;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
|
using Robust.Server.Player;
|
||||||
|
|
||||||
|
namespace Content.Server.SurveillanceCamera;
|
||||||
|
|
||||||
|
public sealed class SurveillanceCameraMonitorSystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly SurveillanceCameraSystem _surveillanceCameras = default!;
|
||||||
|
[Dependency] private readonly UserInterfaceSystem _userInterface = default!;
|
||||||
|
[Dependency] private readonly DeviceNetworkSystem _deviceNetworkSystem = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
SubscribeLocalEvent<SurveillanceCameraMonitorComponent, SurveillanceCameraDeactivateEvent>(OnSurveillanceCameraDeactivate);
|
||||||
|
SubscribeLocalEvent<SurveillanceCameraMonitorComponent, BoundUIClosedEvent>(OnBoundUiClose);
|
||||||
|
SubscribeLocalEvent<SurveillanceCameraMonitorComponent, PowerChangedEvent>(OnPowerChanged);
|
||||||
|
SubscribeLocalEvent<SurveillanceCameraMonitorComponent, SurveillanceCameraMonitorSwitchMessage>(OnSwitchMessage);
|
||||||
|
SubscribeLocalEvent<SurveillanceCameraMonitorComponent, DeviceNetworkPacketEvent>(OnPacketReceived);
|
||||||
|
SubscribeLocalEvent<SurveillanceCameraMonitorComponent, SurveillanceCameraMonitorSubnetRequestMessage>(OnSubnetRequest);
|
||||||
|
SubscribeLocalEvent<SurveillanceCameraMonitorComponent, ComponentStartup>(OnComponentStartup);
|
||||||
|
SubscribeLocalEvent<SurveillanceCameraMonitorComponent, AfterActivatableUIOpenEvent>(OnToggleInterface);
|
||||||
|
SubscribeLocalEvent<SurveillanceCameraMonitorComponent, SurveillanceCameraRefreshCamerasMessage>(OnRefreshCamerasMessage);
|
||||||
|
SubscribeLocalEvent<SurveillanceCameraMonitorComponent, SurveillanceCameraRefreshSubnetsMessage>(OnRefreshSubnetsMessage);
|
||||||
|
SubscribeLocalEvent<SurveillanceCameraMonitorComponent, SurveillanceCameraDisconnectMessage>(OnDisconnectMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
private const float _maxHeartbeatTime = 300f;
|
||||||
|
private const float _heartbeatDelay = 30f;
|
||||||
|
|
||||||
|
public override void Update(float frameTime)
|
||||||
|
{
|
||||||
|
foreach (var (_, monitor) in EntityQuery<ActiveSurveillanceCameraMonitorComponent, SurveillanceCameraMonitorComponent>())
|
||||||
|
{
|
||||||
|
if (Paused(monitor.Owner))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
monitor.LastHeartbeatSent += frameTime;
|
||||||
|
SendHeartbeat(monitor.Owner, monitor);
|
||||||
|
monitor.LastHeartbeat += frameTime;
|
||||||
|
|
||||||
|
if (monitor.LastHeartbeat > _maxHeartbeatTime)
|
||||||
|
{
|
||||||
|
DisconnectCamera(monitor.Owner, true, monitor);
|
||||||
|
EntityManager.RemoveComponent<ActiveSurveillanceCameraMonitorComponent>(monitor.Owner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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.Session.AttachedEntity != null)
|
||||||
|
{
|
||||||
|
SetActiveSubnet(uid, args.Subnet, component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPacketReceived(EntityUid uid, SurveillanceCameraMonitorComponent component,
|
||||||
|
DeviceNetworkPacketEvent args)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(args.SenderAddress))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.Data.TryGetValue(DeviceNetworkConstants.Command, out string? command))
|
||||||
|
{
|
||||||
|
switch (command)
|
||||||
|
{
|
||||||
|
case SurveillanceCameraSystem.CameraConnectMessage:
|
||||||
|
if (component.NextCameraAddress == args.SenderAddress)
|
||||||
|
{
|
||||||
|
component.ActiveCameraAddress = args.SenderAddress;
|
||||||
|
TrySwitchCameraByUid(uid, args.Sender, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
component.NextCameraAddress = null;
|
||||||
|
break;
|
||||||
|
case SurveillanceCameraSystem.CameraHeartbeatMessage:
|
||||||
|
if (args.SenderAddress == component.ActiveCameraAddress)
|
||||||
|
{
|
||||||
|
component.LastHeartbeat = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case SurveillanceCameraSystem.CameraDataMessage:
|
||||||
|
if (!args.Data.TryGetValue(SurveillanceCameraSystem.CameraNameData, out string? name)
|
||||||
|
|| !args.Data.TryGetValue(SurveillanceCameraSystem.CameraSubnetData, out string? subnetData)
|
||||||
|
|| !args.Data.TryGetValue(SurveillanceCameraSystem.CameraAddressData, out string? address))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (component.ActiveSubnet != subnetData)
|
||||||
|
{
|
||||||
|
DisconnectFromSubnet(uid, subnetData);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!component.KnownCameras.ContainsKey(address))
|
||||||
|
{
|
||||||
|
component.KnownCameras.Add(address, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateUserInterface(uid, component);
|
||||||
|
break;
|
||||||
|
case SurveillanceCameraSystem.CameraSubnetData:
|
||||||
|
if (args.Data.TryGetValue(SurveillanceCameraSystem.CameraSubnetData, out string? subnet)
|
||||||
|
&& !component.KnownSubnets.ContainsKey(subnet))
|
||||||
|
{
|
||||||
|
component.KnownSubnets.Add(subnet, args.SenderAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateUserInterface(uid, component);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
RequestActiveSubnetInfo(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, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPowerChanged(EntityUid uid, SurveillanceCameraMonitorComponent component, PowerChangedEvent args)
|
||||||
|
{
|
||||||
|
if (!args.Powered)
|
||||||
|
{
|
||||||
|
RemoveActiveCamera(uid, component);
|
||||||
|
component.NextCameraAddress = null;
|
||||||
|
component.ActiveSubnet = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnToggleInterface(EntityUid uid, SurveillanceCameraMonitorComponent component,
|
||||||
|
AfterActivatableUIOpenEvent args)
|
||||||
|
{
|
||||||
|
AfterOpenUserInterface(uid, args.User, 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 OnBoundUiClose(EntityUid uid, SurveillanceCameraMonitorComponent component, BoundUIClosedEvent args)
|
||||||
|
{
|
||||||
|
if (args.Session.AttachedEntity == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoveViewer(uid, args.Session.AttachedEntity.Value, component);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private void SendHeartbeat(EntityUid uid, SurveillanceCameraMonitorComponent? monitor = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref monitor)
|
||||||
|
|| monitor.LastHeartbeatSent < _heartbeatDelay
|
||||||
|
|| !monitor.KnownSubnets.TryGetValue(monitor.ActiveSubnet, out var subnetAddress))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload = new NetworkPayload()
|
||||||
|
{
|
||||||
|
{ DeviceNetworkConstants.Command, SurveillanceCameraSystem.CameraHeartbeatMessage },
|
||||||
|
{ SurveillanceCameraSystem.CameraAddressData, monitor.ActiveCameraAddress }
|
||||||
|
};
|
||||||
|
|
||||||
|
_deviceNetworkSystem.QueuePacket(uid, subnetAddress, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
EntityManager.RemoveComponent<ActiveSurveillanceCameraMonitorComponent>(uid);
|
||||||
|
UpdateUserInterface(uid, monitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshSubnets(EntityUid uid, SurveillanceCameraMonitorComponent? monitor = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref monitor))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
monitor.KnownSubnets.Clear();
|
||||||
|
PingCameraNetwork(uid, monitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PingCameraNetwork(EntityUid uid, SurveillanceCameraMonitorComponent? monitor = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref monitor))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload = new NetworkPayload()
|
||||||
|
{
|
||||||
|
{ DeviceNetworkConstants.Command, SurveillanceCameraSystem.CameraPingMessage }
|
||||||
|
};
|
||||||
|
_deviceNetworkSystem.QueuePacket(uid, null, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetActiveSubnet(EntityUid uid, string subnet,
|
||||||
|
SurveillanceCameraMonitorComponent? monitor = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref monitor)
|
||||||
|
|| !monitor.KnownSubnets.ContainsKey(subnet))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DisconnectFromSubnet(uid, monitor.ActiveSubnet);
|
||||||
|
DisconnectCamera(uid, true, monitor);
|
||||||
|
monitor.ActiveSubnet = subnet;
|
||||||
|
monitor.KnownCameras.Clear();
|
||||||
|
UpdateUserInterface(uid, monitor);
|
||||||
|
|
||||||
|
ConnectToSubnet(uid, subnet);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RequestActiveSubnetInfo(EntityUid uid, SurveillanceCameraMonitorComponent? monitor = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref monitor)
|
||||||
|
|| !monitor.KnownSubnets.TryGetValue(monitor.ActiveSubnet, out var address))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload = new NetworkPayload()
|
||||||
|
{
|
||||||
|
{DeviceNetworkConstants.Command, SurveillanceCameraSystem.CameraPingSubnetMessage},
|
||||||
|
};
|
||||||
|
_deviceNetworkSystem.QueuePacket(uid, address, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ConnectToSubnet(EntityUid uid, string subnet, SurveillanceCameraMonitorComponent? monitor = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref monitor)
|
||||||
|
|| !monitor.KnownSubnets.TryGetValue(subnet, out var address))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload = new NetworkPayload()
|
||||||
|
{
|
||||||
|
{DeviceNetworkConstants.Command, SurveillanceCameraSystem.CameraSubnetConnectMessage},
|
||||||
|
};
|
||||||
|
_deviceNetworkSystem.QueuePacket(uid, address, payload);
|
||||||
|
|
||||||
|
RequestActiveSubnetInfo(uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DisconnectFromSubnet(EntityUid uid, string subnet, SurveillanceCameraMonitorComponent? monitor = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref monitor)
|
||||||
|
|| !monitor.KnownSubnets.TryGetValue(subnet, out var address))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload = new NetworkPayload()
|
||||||
|
{
|
||||||
|
{DeviceNetworkConstants.Command, SurveillanceCameraSystem.CameraSubnetDisconnectMessage},
|
||||||
|
};
|
||||||
|
_deviceNetworkSystem.QueuePacket(uid, address, 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)
|
||||||
|
{
|
||||||
|
EntityUid? monitorUid = monitor.Viewers.Count == 0 ? uid : null;
|
||||||
|
_surveillanceCameras.RemoveActiveViewer(monitor.ActiveCamera.Value, player, monitorUid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
UpdateUserInterface(uid, monitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TrySwitchCameraByAddress(EntityUid uid, string address,
|
||||||
|
SurveillanceCameraMonitorComponent? monitor = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref monitor)
|
||||||
|
|| !monitor.KnownSubnets.TryGetValue(monitor.ActiveSubnet, out var subnetAddress))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload = new NetworkPayload()
|
||||||
|
{
|
||||||
|
{DeviceNetworkConstants.Command, SurveillanceCameraSystem.CameraConnectMessage},
|
||||||
|
{SurveillanceCameraSystem.CameraAddressData, address}
|
||||||
|
};
|
||||||
|
|
||||||
|
monitor.NextCameraAddress = address;
|
||||||
|
_deviceNetworkSystem.QueuePacket(uid, subnetAddress, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is public primarily because it might be useful to have the ability to
|
||||||
|
// have this component added to any entity, and have them open the BUI (somehow).
|
||||||
|
public void AfterOpenUserInterface(EntityUid uid, EntityUid player, SurveillanceCameraMonitorComponent? monitor = null, ActorComponent? actor = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref monitor)
|
||||||
|
|| !Resolve(player, ref actor))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AddViewer(uid, player);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateUserInterface(EntityUid uid, SurveillanceCameraMonitorComponent? monitor = null, EntityUid? player = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref monitor))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
IPlayerSession? session = null;
|
||||||
|
if (player != null
|
||||||
|
&& TryComp(player, out ActorComponent? actor))
|
||||||
|
{
|
||||||
|
session = actor.PlayerSession;
|
||||||
|
}
|
||||||
|
|
||||||
|
var state = new SurveillanceCameraMonitorUiState(monitor.ActiveCamera, monitor.KnownSubnets.Keys.ToHashSet(), monitor.ActiveCameraAddress, monitor.ActiveSubnet, monitor.KnownCameras);
|
||||||
|
_userInterface.TrySetUiState(uid, SurveillanceCameraMonitorUiKey.Key, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,268 @@
|
|||||||
|
using Content.Server.Administration.Managers;
|
||||||
|
using Content.Server.DeviceNetwork;
|
||||||
|
using Content.Server.DeviceNetwork.Components;
|
||||||
|
using Content.Server.DeviceNetwork.Systems;
|
||||||
|
using Content.Server.Ghost.Components;
|
||||||
|
using Content.Server.Power.Components;
|
||||||
|
using Content.Shared.ActionBlocker;
|
||||||
|
using Content.Shared.DeviceNetwork;
|
||||||
|
using Content.Shared.SurveillanceCamera;
|
||||||
|
using Content.Shared.Verbs;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Server.SurveillanceCamera;
|
||||||
|
|
||||||
|
public sealed class SurveillanceCameraRouterSystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly DeviceNetworkSystem _deviceNetworkSystem = default!;
|
||||||
|
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
|
||||||
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
|
[Dependency] private readonly UserInterfaceSystem _userInterface = default!;
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
SubscribeLocalEvent<SurveillanceCameraRouterComponent, ComponentInit>(OnInitialize);
|
||||||
|
SubscribeLocalEvent<SurveillanceCameraRouterComponent, DeviceNetworkPacketEvent>(OnPacketReceive);
|
||||||
|
SubscribeLocalEvent<SurveillanceCameraRouterComponent, SurveillanceCameraSetupSetNetwork>(OnSetNetwork);
|
||||||
|
SubscribeLocalEvent<SurveillanceCameraRouterComponent, GetVerbsEvent<AlternativeVerb>>(AddVerbs);
|
||||||
|
SubscribeLocalEvent<SurveillanceCameraRouterComponent, PowerChangedEvent>(OnPowerChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnInitialize(EntityUid uid, SurveillanceCameraRouterComponent router, ComponentInit args)
|
||||||
|
{
|
||||||
|
if (router.SubnetFrequencyId == null ||
|
||||||
|
!_prototypeManager.TryIndex(router.SubnetFrequencyId, out DeviceFrequencyPrototype? subnetFrequency))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
router.SubnetFrequency = subnetFrequency.Frequency;
|
||||||
|
router.Active = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPacketReceive(EntityUid uid, SurveillanceCameraRouterComponent router, DeviceNetworkPacketEvent args)
|
||||||
|
{
|
||||||
|
if (!router.Active
|
||||||
|
|| string.IsNullOrEmpty(args.SenderAddress)
|
||||||
|
|| !args.Data.TryGetValue(DeviceNetworkConstants.Command, out string? command))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (command)
|
||||||
|
{
|
||||||
|
case SurveillanceCameraSystem.CameraConnectMessage:
|
||||||
|
if (!args.Data.TryGetValue(SurveillanceCameraSystem.CameraAddressData, out string? address))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConnectCamera(uid, args.SenderAddress, address, router);
|
||||||
|
break;
|
||||||
|
case SurveillanceCameraSystem.CameraHeartbeatMessage:
|
||||||
|
if (!args.Data.TryGetValue(SurveillanceCameraSystem.CameraAddressData, out string? camera))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SendHeartbeat(uid, args.SenderAddress, camera, router);
|
||||||
|
break;
|
||||||
|
case SurveillanceCameraSystem.CameraSubnetConnectMessage:
|
||||||
|
AddMonitorToRoute(uid, args.SenderAddress, router);
|
||||||
|
PingSubnet(uid, router);
|
||||||
|
break;
|
||||||
|
case SurveillanceCameraSystem.CameraSubnetDisconnectMessage:
|
||||||
|
RemoveMonitorFromRoute(uid, args.SenderAddress, router);
|
||||||
|
break;
|
||||||
|
case SurveillanceCameraSystem.CameraPingSubnetMessage:
|
||||||
|
PingSubnet(uid, router);
|
||||||
|
break;
|
||||||
|
case SurveillanceCameraSystem.CameraPingMessage:
|
||||||
|
SubnetPingResponse(uid, args.SenderAddress, router);
|
||||||
|
break;
|
||||||
|
case SurveillanceCameraSystem.CameraDataMessage:
|
||||||
|
SendCameraInfo(uid, args.Data, router);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPowerChanged(EntityUid uid, SurveillanceCameraRouterComponent component, PowerChangedEvent args)
|
||||||
|
{
|
||||||
|
component.MonitorRoutes.Clear();
|
||||||
|
component.Active = args.Powered;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddVerbs(EntityUid uid, SurveillanceCameraRouterComponent component, GetVerbsEvent<AlternativeVerb> verbs)
|
||||||
|
{
|
||||||
|
if (!_actionBlocker.CanInteract(verbs.User, uid))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (component.SubnetFrequencyId != null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AlternativeVerb verb = new();
|
||||||
|
verb.Text = Loc.GetString("surveillance-camera-setup");
|
||||||
|
verb.Act = () => OpenSetupInterface(uid, verbs.User, component);
|
||||||
|
verbs.Verbs.Add(verb);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSetNetwork(EntityUid uid, SurveillanceCameraRouterComponent component,
|
||||||
|
SurveillanceCameraSetupSetNetwork args)
|
||||||
|
{
|
||||||
|
if (args.UiKey is not SurveillanceCameraSetupUiKey key
|
||||||
|
|| key != SurveillanceCameraSetupUiKey.Router)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (args.Network < 0 || args.Network >= component.AvailableNetworks.Count)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_prototypeManager.TryIndex<DeviceFrequencyPrototype>(component.AvailableNetworks[args.Network],
|
||||||
|
out var frequency))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
component.SubnetFrequencyId = component.AvailableNetworks[args.Network];
|
||||||
|
component.SubnetFrequency = frequency.Frequency;
|
||||||
|
component.Active = true;
|
||||||
|
UpdateSetupInterface(uid, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpenSetupInterface(EntityUid uid, EntityUid player, SurveillanceCameraRouterComponent? camera = null, ActorComponent? actor = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref camera)
|
||||||
|
|| !Resolve(player, ref actor))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_userInterface.GetUiOrNull(uid, SurveillanceCameraSetupUiKey.Router)!.Open(actor.PlayerSession);
|
||||||
|
UpdateSetupInterface(uid, camera);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateSetupInterface(EntityUid uid, SurveillanceCameraRouterComponent? router = null, DeviceNetworkComponent? deviceNet = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref router, ref deviceNet))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (router.AvailableNetworks.Count == 0 || router.SubnetFrequencyId != null)
|
||||||
|
{
|
||||||
|
_userInterface.TryCloseAll(uid, SurveillanceCameraSetupUiKey.Router);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var state = new SurveillanceCameraSetupBoundUiState(router.SubnetName, deviceNet.ReceiveFrequency ?? 0,
|
||||||
|
router.AvailableNetworks, true, router.SubnetFrequencyId != null);
|
||||||
|
_userInterface.TrySetUiState(uid, SurveillanceCameraSetupUiKey.Router, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SendHeartbeat(EntityUid uid, string origin, string destination,
|
||||||
|
SurveillanceCameraRouterComponent? router = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref router))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload = new NetworkPayload()
|
||||||
|
{
|
||||||
|
{ DeviceNetworkConstants.Command, SurveillanceCameraSystem.CameraHeartbeatMessage },
|
||||||
|
{ SurveillanceCameraSystem.CameraAddressData, origin }
|
||||||
|
};
|
||||||
|
|
||||||
|
_deviceNetworkSystem.QueuePacket(uid, destination, payload, router.SubnetFrequency);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SubnetPingResponse(EntityUid uid, string origin, SurveillanceCameraRouterComponent? router = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref router) || router.SubnetFrequencyId == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload = new NetworkPayload()
|
||||||
|
{
|
||||||
|
{ DeviceNetworkConstants.Command, SurveillanceCameraSystem.CameraSubnetData },
|
||||||
|
{ SurveillanceCameraSystem.CameraSubnetData, router.SubnetFrequencyId }
|
||||||
|
};
|
||||||
|
|
||||||
|
_deviceNetworkSystem.QueuePacket(uid, origin, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ConnectCamera(EntityUid uid, string origin, string address, SurveillanceCameraRouterComponent? router = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref router))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload = new NetworkPayload()
|
||||||
|
{
|
||||||
|
{ DeviceNetworkConstants.Command, SurveillanceCameraSystem.CameraConnectMessage },
|
||||||
|
{ SurveillanceCameraSystem.CameraAddressData, origin }
|
||||||
|
};
|
||||||
|
|
||||||
|
_deviceNetworkSystem.QueuePacket(uid, address, payload, router.SubnetFrequency);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds a monitor to the set of routes.
|
||||||
|
private void AddMonitorToRoute(EntityUid uid, string address, SurveillanceCameraRouterComponent? router = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref router))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
router.MonitorRoutes.Add(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveMonitorFromRoute(EntityUid uid, string address, SurveillanceCameraRouterComponent? router = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref router))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
router.MonitorRoutes.Remove(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pings a subnet to get all camera information.
|
||||||
|
private void PingSubnet(EntityUid uid, SurveillanceCameraRouterComponent? router = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref router))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload = new NetworkPayload()
|
||||||
|
{
|
||||||
|
{ DeviceNetworkConstants.Command, SurveillanceCameraSystem.CameraPingMessage },
|
||||||
|
{ SurveillanceCameraSystem.CameraSubnetData, router.SubnetName }
|
||||||
|
};
|
||||||
|
|
||||||
|
_deviceNetworkSystem.QueuePacket(uid, null, payload, router.SubnetFrequency);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sends camera information to all monitors currently interested.
|
||||||
|
private void SendCameraInfo(EntityUid uid, NetworkPayload payload, SurveillanceCameraRouterComponent? router = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref router))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var address in router.MonitorRoutes)
|
||||||
|
{
|
||||||
|
_deviceNetworkSystem.QueuePacket(uid, address, payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,417 @@
|
|||||||
|
using Content.Server.Administration.Managers;
|
||||||
|
using Content.Server.DeviceNetwork;
|
||||||
|
using Content.Server.DeviceNetwork.Components;
|
||||||
|
using Content.Server.DeviceNetwork.Systems;
|
||||||
|
using Content.Server.Ghost.Components;
|
||||||
|
using Content.Server.Power.Components;
|
||||||
|
using Content.Shared.ActionBlocker;
|
||||||
|
using Content.Shared.DeviceNetwork;
|
||||||
|
using Content.Shared.SurveillanceCamera;
|
||||||
|
using Content.Shared.Verbs;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Server.SurveillanceCamera;
|
||||||
|
|
||||||
|
public sealed class SurveillanceCameraSystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
|
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
|
||||||
|
[Dependency] private readonly ViewSubscriberSystem _viewSubscriberSystem = default!;
|
||||||
|
[Dependency] private readonly DeviceNetworkSystem _deviceNetworkSystem = default!;
|
||||||
|
[Dependency] private readonly UserInterfaceSystem _userInterface = default!;
|
||||||
|
|
||||||
|
// Pings a surveillance camera subnet. All cameras will always respond
|
||||||
|
// with a data message if they are on the same subnet.
|
||||||
|
public const string CameraPingSubnetMessage = "surveillance_camera_ping_subnet";
|
||||||
|
|
||||||
|
// Pings a surveillance camera. Useful to ensure that the camera is still on
|
||||||
|
// before connecting fully.
|
||||||
|
public const string CameraPingMessage = "surveillance_camera_ping";
|
||||||
|
|
||||||
|
// Camera heartbeat. Monitors ping this to ensure that a camera is still able to
|
||||||
|
// be contacted. If this doesn't get sent after some time, the monitor will
|
||||||
|
// automatically disconnect.
|
||||||
|
public const string CameraHeartbeatMessage = "surveillance_camera_heartbeat";
|
||||||
|
|
||||||
|
// Surveillance camera data. This generally should contain nothing
|
||||||
|
// except for the subnet that this camera is on -
|
||||||
|
// this is because of the fact that the PacketEvent already
|
||||||
|
// contains the sender UID, and that this will always be targeted
|
||||||
|
// towards the sender that pinged the camera.
|
||||||
|
public const string CameraDataMessage = "surveillance_camera_data";
|
||||||
|
public const string CameraConnectMessage = "surveillance_camera_connect";
|
||||||
|
public const string CameraSubnetConnectMessage = "surveillance_camera_subnet_connect";
|
||||||
|
public const string CameraSubnetDisconnectMessage = "surveillance_camera_subnet_disconnect";
|
||||||
|
|
||||||
|
public const string CameraAddressData = "surveillance_camera_data_origin";
|
||||||
|
public const string CameraNameData = "surveillance_camera_data_name";
|
||||||
|
public const string CameraSubnetData = "surveillance_camera_data_subnet";
|
||||||
|
|
||||||
|
public const int CameraNameLimit = 32;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
SubscribeLocalEvent<SurveillanceCameraComponent, ComponentShutdown>(OnShutdown);
|
||||||
|
SubscribeLocalEvent<SurveillanceCameraComponent, PowerChangedEvent>(OnPowerChanged);
|
||||||
|
SubscribeLocalEvent<SurveillanceCameraComponent, DeviceNetworkPacketEvent>(OnPacketReceived);
|
||||||
|
SubscribeLocalEvent<SurveillanceCameraComponent, SurveillanceCameraSetupSetName>(OnSetName);
|
||||||
|
SubscribeLocalEvent<SurveillanceCameraComponent, SurveillanceCameraSetupSetNetwork>(OnSetNetwork);
|
||||||
|
SubscribeLocalEvent<SurveillanceCameraComponent, GetVerbsEvent<AlternativeVerb>>(AddVerbs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPacketReceived(EntityUid uid, SurveillanceCameraComponent component, DeviceNetworkPacketEvent args)
|
||||||
|
{
|
||||||
|
if (!component.Active)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TryComp(uid, out DeviceNetworkComponent? deviceNet))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.Data.TryGetValue(DeviceNetworkConstants.Command, out string? command))
|
||||||
|
{
|
||||||
|
var payload = new NetworkPayload()
|
||||||
|
{
|
||||||
|
{ DeviceNetworkConstants.Command, string.Empty },
|
||||||
|
{ CameraAddressData, deviceNet.Address },
|
||||||
|
{ CameraNameData, component.CameraId },
|
||||||
|
{ CameraSubnetData, string.Empty }
|
||||||
|
};
|
||||||
|
|
||||||
|
var dest = string.Empty;
|
||||||
|
|
||||||
|
switch (command)
|
||||||
|
{
|
||||||
|
case CameraConnectMessage:
|
||||||
|
if (!args.Data.TryGetValue(CameraAddressData, out dest)
|
||||||
|
|| string.IsNullOrEmpty(args.Address))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
payload[DeviceNetworkConstants.Command] = CameraConnectMessage;
|
||||||
|
break;
|
||||||
|
case CameraHeartbeatMessage:
|
||||||
|
if (!args.Data.TryGetValue(CameraAddressData, out dest)
|
||||||
|
|| string.IsNullOrEmpty(args.Address))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
payload[DeviceNetworkConstants.Command] = CameraHeartbeatMessage;
|
||||||
|
break;
|
||||||
|
case CameraPingMessage:
|
||||||
|
if (!args.Data.TryGetValue(CameraSubnetData, out string? subnet))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dest = args.SenderAddress;
|
||||||
|
payload[CameraSubnetData] = subnet;
|
||||||
|
payload[DeviceNetworkConstants.Command] = CameraDataMessage;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
_deviceNetworkSystem.QueuePacket(
|
||||||
|
uid,
|
||||||
|
dest,
|
||||||
|
payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddVerbs(EntityUid uid, SurveillanceCameraComponent component, GetVerbsEvent<AlternativeVerb> verbs)
|
||||||
|
{
|
||||||
|
if (!_actionBlocker.CanInteract(verbs.User, uid))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (component.NameSet && component.NetworkSet)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AlternativeVerb verb = new();
|
||||||
|
verb.Text = Loc.GetString("surveillance-camera-setup");
|
||||||
|
verb.Act = () => OpenSetupInterface(uid, verbs.User, component);
|
||||||
|
verbs.Verbs.Add(verb);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private void OnPowerChanged(EntityUid camera, SurveillanceCameraComponent component, 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;
|
||||||
|
UpdateSetupInterface(uid, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSetNetwork(EntityUid uid, SurveillanceCameraComponent component,
|
||||||
|
SurveillanceCameraSetupSetNetwork args)
|
||||||
|
{
|
||||||
|
if (args.UiKey is not SurveillanceCameraSetupUiKey key
|
||||||
|
|| key != SurveillanceCameraSetupUiKey.Camera)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (args.Network < 0 || args.Network >= component.AvailableNetworks.Count)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_prototypeManager.TryIndex<DeviceFrequencyPrototype>(component.AvailableNetworks[args.Network],
|
||||||
|
out var frequency))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_deviceNetworkSystem.SetReceiveFrequency(uid, frequency.Frequency);
|
||||||
|
component.NetworkSet = true;
|
||||||
|
UpdateSetupInterface(uid, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpenSetupInterface(EntityUid uid, EntityUid player, SurveillanceCameraComponent? camera = null, ActorComponent? actor = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref camera)
|
||||||
|
|| !Resolve(player, ref actor))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_userInterface.GetUiOrNull(uid, SurveillanceCameraSetupUiKey.Camera)!.Open(actor.PlayerSession);
|
||||||
|
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.TryCloseAll(uid, SurveillanceCameraSetupUiKey.Camera);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (camera.AvailableNetworks.Count == 0)
|
||||||
|
{
|
||||||
|
if (deviceNet.ReceiveFrequencyId != null)
|
||||||
|
{
|
||||||
|
camera.AvailableNetworks.Add(deviceNet.ReceiveFrequencyId);
|
||||||
|
}
|
||||||
|
else if (!camera.NetworkSet)
|
||||||
|
{
|
||||||
|
_userInterface.TryCloseAll(uid, SurveillanceCameraSetupUiKey.Camera);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var state = new SurveillanceCameraSetupBoundUiState(camera.CameraId, deviceNet.ReceiveFrequency ?? 0,
|
||||||
|
camera.AvailableNetworks, camera.NameSet, camera.NetworkSet);
|
||||||
|
_userInterface.TrySetUiState(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.ActiveViewers), null, component);
|
||||||
|
component.Active = false;
|
||||||
|
|
||||||
|
// Send a targetted event to all monitors.
|
||||||
|
foreach (var monitor in component.ActiveMonitors)
|
||||||
|
{
|
||||||
|
RaiseLocalEvent(monitor, ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
component.ActiveMonitors.Clear();
|
||||||
|
|
||||||
|
// Send a local event that's broadcasted everywhere afterwards.
|
||||||
|
RaiseLocalEvent(ev);
|
||||||
|
|
||||||
|
UpdateVisuals(camera, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetActive(EntityUid camera, bool setting, SurveillanceCameraComponent? component = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(camera, ref component))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setting)
|
||||||
|
{
|
||||||
|
component.Active = setting;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Deactivate(camera, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateVisuals(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.ActiveViewers.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
|| !Resolve(player, ref actor))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_viewSubscriberSystem.RemoveViewSubscriber(camera, actor.PlayerSession);
|
||||||
|
component.ActiveViewers.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateVisuals(EntityUid camera, SurveillanceCameraComponent? component = null, AppearanceComponent? appearance = null)
|
||||||
|
{
|
||||||
|
// Don't log missing, because otherwise tests fail.
|
||||||
|
if (!Resolve(camera, ref component, ref appearance, false))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var key = SurveillanceCameraVisuals.Disabled;
|
||||||
|
|
||||||
|
if (component.Active)
|
||||||
|
{
|
||||||
|
key = SurveillanceCameraVisuals.Active;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (component.ActiveViewers.Count > 0 || component.ActiveMonitors.Count > 0)
|
||||||
|
{
|
||||||
|
key = SurveillanceCameraVisuals.InUse;
|
||||||
|
}
|
||||||
|
|
||||||
|
appearance.SetData(SurveillanceCameraVisualsKey.Key, key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Shared.SurveillanceCamera;
|
||||||
|
|
||||||
|
// Camera monitor state. If the camera is null, there should be a blank
|
||||||
|
// space where the camera is.
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed class SurveillanceCameraMonitorUiState : BoundUserInterfaceState
|
||||||
|
{
|
||||||
|
// The active camera on the monitor. If this is null, the part of the UI
|
||||||
|
// that contains the monitor should clear.
|
||||||
|
public EntityUid? ActiveCamera { get; }
|
||||||
|
|
||||||
|
// Currently available subnets. Does not send the entirety of the possible
|
||||||
|
// cameras to view because that could be really, really large
|
||||||
|
public HashSet<string> Subnets { get; }
|
||||||
|
|
||||||
|
public string ActiveAddress;
|
||||||
|
|
||||||
|
// Currently active subnet.
|
||||||
|
public string ActiveSubnet { get; }
|
||||||
|
|
||||||
|
// Known cameras, by address and name.
|
||||||
|
public Dictionary<string, string> Cameras { get; }
|
||||||
|
|
||||||
|
public SurveillanceCameraMonitorUiState(EntityUid? activeCamera, HashSet<string> subnets, string activeAddress, string activeSubnet, Dictionary<string, string> cameras)
|
||||||
|
{
|
||||||
|
ActiveCamera = activeCamera;
|
||||||
|
Subnets = subnets;
|
||||||
|
ActiveAddress = activeAddress;
|
||||||
|
ActiveSubnet = activeSubnet;
|
||||||
|
Cameras = cameras;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed class SurveillanceCameraMonitorSwitchMessage : BoundUserInterfaceMessage
|
||||||
|
{
|
||||||
|
public string Address { get; }
|
||||||
|
|
||||||
|
public SurveillanceCameraMonitorSwitchMessage(string address)
|
||||||
|
{
|
||||||
|
Address = address;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed class SurveillanceCameraMonitorSubnetRequestMessage : BoundUserInterfaceMessage
|
||||||
|
{
|
||||||
|
public string Subnet { get; }
|
||||||
|
|
||||||
|
public SurveillanceCameraMonitorSubnetRequestMessage(string subnet)
|
||||||
|
{
|
||||||
|
Subnet = subnet;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sent when the user requests that the cameras on the current subnet be refreshed.
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed class SurveillanceCameraRefreshCamerasMessage : BoundUserInterfaceMessage
|
||||||
|
{}
|
||||||
|
|
||||||
|
// Sent when the user requests that the subnets known by the monitor be refreshed.
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed class SurveillanceCameraRefreshSubnetsMessage : BoundUserInterfaceMessage
|
||||||
|
{}
|
||||||
|
|
||||||
|
// Sent when the user wants to disconnect the monitor from the camera.
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed class SurveillanceCameraDisconnectMessage : BoundUserInterfaceMessage
|
||||||
|
{}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public enum SurveillanceCameraMonitorUiKey : byte
|
||||||
|
{
|
||||||
|
Key
|
||||||
|
}
|
||||||
|
|
||||||
|
// SETUP
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed class SurveillanceCameraSetupBoundUiState : BoundUserInterfaceState
|
||||||
|
{
|
||||||
|
public string Name { get; }
|
||||||
|
public uint Network { get; }
|
||||||
|
public List<string> Networks { get; }
|
||||||
|
public bool NameDisabled { get; }
|
||||||
|
public bool NetworkDisabled { get; }
|
||||||
|
|
||||||
|
public SurveillanceCameraSetupBoundUiState(string name, uint network, List<string> networks, bool nameDisabled, bool networkDisabled)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
Network = network;
|
||||||
|
Networks = networks;
|
||||||
|
NameDisabled = nameDisabled;
|
||||||
|
NetworkDisabled = networkDisabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed class SurveillanceCameraSetupSetName : BoundUserInterfaceMessage
|
||||||
|
{
|
||||||
|
public string Name { get; }
|
||||||
|
|
||||||
|
public SurveillanceCameraSetupSetName(string name)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed class SurveillanceCameraSetupSetNetwork : BoundUserInterfaceMessage
|
||||||
|
{
|
||||||
|
public int Network { get; }
|
||||||
|
|
||||||
|
public SurveillanceCameraSetupSetNetwork(int network)
|
||||||
|
{
|
||||||
|
Network = network;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public enum SurveillanceCameraSetupUiKey : byte
|
||||||
|
{
|
||||||
|
Camera,
|
||||||
|
Router
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
using Robust.Shared.GameStates;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Shared.SurveillanceCamera;
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public enum SurveillanceCameraVisualsKey : byte
|
||||||
|
{
|
||||||
|
Key,
|
||||||
|
Layer
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public enum SurveillanceCameraVisuals : byte
|
||||||
|
{
|
||||||
|
Active,
|
||||||
|
InUse,
|
||||||
|
Disabled,
|
||||||
|
// Reserved for future use
|
||||||
|
Xray,
|
||||||
|
Emp
|
||||||
|
}
|
||||||
@@ -3,6 +3,18 @@ device-frequency-prototype-name-atmos = Atmospheric Devices
|
|||||||
device-frequency-prototype-name-suit-sensors = Suit Sensors
|
device-frequency-prototype-name-suit-sensors = Suit Sensors
|
||||||
device-frequency-prototype-name-lights = Smart Lights
|
device-frequency-prototype-name-lights = Smart Lights
|
||||||
|
|
||||||
|
## camera frequencies
|
||||||
|
device-frequency-prototype-name-surveillance-camera-test = Subnet Test
|
||||||
|
device-frequency-prototype-name-surveillance-camera-engineering = Engineering Cameras
|
||||||
|
device-frequency-prototype-name-surveillance-camera-security = Security Cameras
|
||||||
|
device-frequency-prototype-name-surveillance-camera-science = Science Cameras
|
||||||
|
device-frequency-prototype-name-surveillance-camera-supply = Supply Cameras
|
||||||
|
device-frequency-prototype-name-surveillance-camera-command = Command Cameras
|
||||||
|
device-frequency-prototype-name-surveillance-camera-service = Service Cameras
|
||||||
|
device-frequency-prototype-name-surveillance-camera-medical = Medical Cameras
|
||||||
|
device-frequency-prototype-name-surveillance-camera-general = General Cameras
|
||||||
|
device-frequency-prototype-name-surveillance-camera-entertainment = Entertainment Cameras
|
||||||
|
|
||||||
# prefixes for randomly generated device addresses
|
# prefixes for randomly generated device addresses
|
||||||
device-address-prefix-vent = Vnt-
|
device-address-prefix-vent = Vnt-
|
||||||
device-address-prefix-scrubber = Scr-
|
device-address-prefix-scrubber = Scr-
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
surveillance-camera-monitor-ui-window = Camera monitor
|
||||||
|
surveillance-camera-monitor-ui-refresh-cameras = Refresh cameras
|
||||||
|
surveillance-camera-monitor-ui-refresh-subnets = Refresh subnets
|
||||||
|
surveillance-camera-monitor-ui-disconnect = Disconnect
|
||||||
|
surveillance-camera-monitor-ui-status = {$status} {$address}
|
||||||
|
surveillance-camera-monitor-ui-status-connecting = Connecting:
|
||||||
|
surveillance-camera-monitor-ui-status-connected = Connected:
|
||||||
|
surveillance-camera-monitor-ui-status-disconnected = Disconnected
|
||||||
|
surveillance-camera-monitor-ui-no-subnets = No Subnets
|
||||||
|
|
||||||
|
surveillance-camera-setup = Setup
|
||||||
|
surveillance-camera-setup-ui-set = Set
|
||||||
@@ -277,6 +277,10 @@
|
|||||||
- OreProcessorMachineCircuitboard
|
- OreProcessorMachineCircuitboard
|
||||||
- CircuitImprinterMachineCircuitboard
|
- CircuitImprinterMachineCircuitboard
|
||||||
- UniformPrinterMachineCircuitboard
|
- UniformPrinterMachineCircuitboard
|
||||||
|
- SurveillanceCameraRouterCircuitboard
|
||||||
|
- SurveillanceCameraWirelessRouterCircuitboard
|
||||||
|
- SurveillanceWirelessCameraMovableCircuitboard
|
||||||
|
- SurveillanceWirelessCameraAnchoredCircuitboard
|
||||||
- AirAlarmElectronics
|
- AirAlarmElectronics
|
||||||
- FireAlarmElectronics
|
- FireAlarmElectronics
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,53 @@
|
|||||||
|
- type: deviceFrequency
|
||||||
|
id: SurveillanceCamera
|
||||||
|
name: device-frequency-prototype-name-surveillance-camera
|
||||||
|
frequency: 1926 # i only have so many of these, y'know?
|
||||||
|
|
||||||
|
- type: deviceFrequency
|
||||||
|
id: SurveillanceCameraEngineering
|
||||||
|
name: device-frequency-prototype-name-surveillance-camera-engineering
|
||||||
|
frequency: 1931
|
||||||
|
|
||||||
|
- type: deviceFrequency
|
||||||
|
id: SurveillanceCameraSecurity
|
||||||
|
name: device-frequency-prototype-name-surveillance-camera-security
|
||||||
|
frequency: 1932
|
||||||
|
|
||||||
|
- type: deviceFrequency
|
||||||
|
id: SurveillanceCameraScience
|
||||||
|
name: device-frequency-prototype-name-surveillance-camera-science
|
||||||
|
frequency: 1933
|
||||||
|
|
||||||
|
- type: deviceFrequency
|
||||||
|
id: SurveillanceCameraSupply
|
||||||
|
name: device-frequency-prototype-name-surveillance-camera-supply
|
||||||
|
frequency: 1934
|
||||||
|
|
||||||
|
- type: deviceFrequency
|
||||||
|
id: SurveillanceCameraCommand
|
||||||
|
name: device-frequency-prototype-name-surveillance-camera-command
|
||||||
|
frequency: 1935
|
||||||
|
|
||||||
|
- type: deviceFrequency
|
||||||
|
id: SurveillanceCameraService
|
||||||
|
name: device-frequency-prototype-name-surveillance-camera-service
|
||||||
|
frequency: 1936
|
||||||
|
|
||||||
|
- type: deviceFrequency
|
||||||
|
id: SurveillanceCameraMedical
|
||||||
|
name: device-frequency-prototype-name-surveillance-camera-medical
|
||||||
|
frequency: 1937
|
||||||
|
|
||||||
|
- type: deviceFrequency
|
||||||
|
id: SurveillanceCameraGeneral
|
||||||
|
name: device-frequency-prototype-name-surveillance-camera-general
|
||||||
|
frequency: 1938
|
||||||
|
|
||||||
|
- type: deviceFrequency
|
||||||
|
id: SurveillanceCameraEntertainment
|
||||||
|
name: device-frequency-prototype-name-surveillance-camera-entertainment
|
||||||
|
frequency: 1939
|
||||||
|
|
||||||
- type: deviceFrequency
|
- type: deviceFrequency
|
||||||
id: AtmosMonitor
|
id: AtmosMonitor
|
||||||
name: device-frequency-prototype-name-atmos
|
name: device-frequency-prototype-name-atmos
|
||||||
|
|||||||
@@ -396,3 +396,56 @@
|
|||||||
materialRequirements:
|
materialRequirements:
|
||||||
Glass: 2
|
Glass: 2
|
||||||
Cable: 2
|
Cable: 2
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
id: SurveillanceCameraRouterCircuitboard
|
||||||
|
parent: BaseMachineCircuitboard
|
||||||
|
name: Surveillance camera router board
|
||||||
|
description: A machine printed circuit board for a surveillance camera router
|
||||||
|
components:
|
||||||
|
- type: MachineBoard
|
||||||
|
prototype: SurveillanceCameraRouterConstructed
|
||||||
|
requirements:
|
||||||
|
Capacitor: 4
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
id: SurveillanceCameraWirelessRouterCircuitboard
|
||||||
|
parent: BaseMachineCircuitboard
|
||||||
|
name: Surveillance camera wireless router board
|
||||||
|
description: A machine printed circuit board for a surveillance camera wireless router
|
||||||
|
components:
|
||||||
|
- type: MachineBoard
|
||||||
|
prototype: SurveillanceCameraWirelessRouterConstructed
|
||||||
|
requirements:
|
||||||
|
Laser: 1
|
||||||
|
Capacitor: 4
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
id: SurveillanceWirelessCameraMovableCircuitboard
|
||||||
|
parent: BaseMachineCircuitboard
|
||||||
|
name: movable wireless camera board
|
||||||
|
description: A machine printed circuit board for a movable wireless camera
|
||||||
|
components:
|
||||||
|
- type: MachineBoard
|
||||||
|
prototype: SurveillanceWirelessCameraMovableConstructed
|
||||||
|
requirements:
|
||||||
|
Laser: 1
|
||||||
|
Capacitor: 4
|
||||||
|
materialRequirements:
|
||||||
|
Glass: 2
|
||||||
|
Cable: 2
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
id: SurveillanceWirelessCameraAnchoredCircuitboard
|
||||||
|
parent: BaseMachineCircuitboard
|
||||||
|
name: wireless camera board
|
||||||
|
description: A machine printed circuit board for a wireless camera
|
||||||
|
components:
|
||||||
|
- type: MachineBoard
|
||||||
|
prototype: SurveillanceWirelessCameraAnchoredConstructed
|
||||||
|
requirements:
|
||||||
|
Laser: 1
|
||||||
|
Capacitor: 4
|
||||||
|
materialRequirements:
|
||||||
|
Glass: 2
|
||||||
|
Cable: 2
|
||||||
|
|||||||
@@ -66,6 +66,24 @@
|
|||||||
- type: ComputerBoard
|
- type: ComputerBoard
|
||||||
prototype: ComputerSupplyRequest
|
prototype: ComputerSupplyRequest
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: BaseComputerCircuitboard
|
||||||
|
id: SurveillanceCameraMonitorCircuitboard
|
||||||
|
name: surveillance camera monitor board
|
||||||
|
description: A computer printed circuit board for a surveillance camera monitor
|
||||||
|
components:
|
||||||
|
- type: ComputerBoard
|
||||||
|
prototype: ComputerSurveillanceCameraMonitor
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: BaseComputerCircuitboard
|
||||||
|
id: SurveillanceWirelessCameraMonitorCircuitboard
|
||||||
|
name: surveillance wireless camera monitor board
|
||||||
|
description: A computer printed circuit board for a surveillance wireless camera monitor
|
||||||
|
components:
|
||||||
|
- type: ComputerBoard
|
||||||
|
prototype: ComputerSurveillanceWirelessCameraMonitor
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
parent: BaseComputerCircuitboard
|
parent: BaseComputerCircuitboard
|
||||||
id: PowerMonitoringComputerCircuitboard
|
id: PowerMonitoringComputerCircuitboard
|
||||||
|
|||||||
@@ -399,30 +399,99 @@
|
|||||||
color: "#b89f25"
|
color: "#b89f25"
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
parent: BaseStructure
|
parent: ComputerBase
|
||||||
|
id: ComputerSurveillanceCameraMonitor
|
||||||
|
name: camera monitor
|
||||||
|
description: A surveillance camera monitor. You're watching them. Maybe.
|
||||||
|
components:
|
||||||
|
- type: Appearance
|
||||||
|
visuals:
|
||||||
|
- type: ComputerVisualizer
|
||||||
|
key: tech_key
|
||||||
|
screen: cameras
|
||||||
|
- type: Computer
|
||||||
|
board: SurveillanceCameraMonitorCircuitboard
|
||||||
|
- type: DeviceNetwork
|
||||||
|
deviceNetId: Wired
|
||||||
|
receiveFrequencyId: SurveillanceCamera
|
||||||
|
transmitFrequencyId: SurveillanceCamera
|
||||||
|
- type: WiredNetworkConnection
|
||||||
|
- type: SurveillanceCameraMonitor
|
||||||
|
- type: ActivatableUI
|
||||||
|
key: enum.SurveillanceCameraMonitorUiKey.Key
|
||||||
|
- type: ActivatableUIRequiresPower
|
||||||
|
- type: Transform
|
||||||
|
anchored: true
|
||||||
|
- type: UserInterface
|
||||||
|
interfaces:
|
||||||
|
- key: enum.SurveillanceCameraMonitorUiKey.Key
|
||||||
|
type: SurveillanceCameraMonitorBoundUserInterface
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: ComputerBase
|
||||||
|
id: ComputerSurveillanceWirelessCameraMonitor
|
||||||
|
name: wireless camera monitor
|
||||||
|
description: A wireless surveillance camera monitor. You're watching them. Maybe.
|
||||||
|
components:
|
||||||
|
- type: Appearance
|
||||||
|
visuals:
|
||||||
|
- type: ComputerVisualizer
|
||||||
|
key: tech_key
|
||||||
|
screen: cameras
|
||||||
|
- type: Computer
|
||||||
|
board: SurveillanceWirelessCameraMonitorCircuitboard
|
||||||
|
- type: DeviceNetwork
|
||||||
|
deviceNetId: Wireless
|
||||||
|
receiveFrequencyId: SurveillanceCamera
|
||||||
|
transmitFrequencyId: SurveillanceCamera
|
||||||
|
- type: WirelessNetworkConnection
|
||||||
|
range: 200
|
||||||
|
- type: SurveillanceCameraMonitor
|
||||||
|
- type: ActivatableUI
|
||||||
|
key: enum.SurveillanceCameraMonitorUiKey.Key
|
||||||
|
- type: ActivatableUIRequiresPower
|
||||||
|
- type: Transform
|
||||||
|
anchored: true
|
||||||
|
- type: UserInterface
|
||||||
|
interfaces:
|
||||||
|
- key: enum.SurveillanceCameraMonitorUiKey.Key
|
||||||
|
type: SurveillanceCameraMonitorBoundUserInterface
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: ComputerSurveillanceWirelessCameraMonitor
|
||||||
id: ComputerTelevision
|
id: ComputerTelevision
|
||||||
name: wooden television
|
name: wooden television
|
||||||
description: It's an old television displaying the station's cameras, if they worked.
|
description: Finally, some decent reception around here...
|
||||||
components:
|
components:
|
||||||
- type: InteractionOutline
|
- type: Appearance
|
||||||
- type: Sprite
|
visuals:
|
||||||
sprite: Structures/Machines/computers.rsi
|
- type: ComputerVisualizer
|
||||||
layers:
|
body: television
|
||||||
- state: television
|
bodyBroken: television_broken
|
||||||
- state: detective_television
|
screen: detective_television
|
||||||
shader: unshaded
|
- type: Sprite
|
||||||
visuals:
|
sprite: Structures/Machines/computers.rsi
|
||||||
- type: PointLight
|
layers:
|
||||||
radius: 1.5
|
- map: [ "enum.ComputerVisualizer+Layers.KeyboardOn" ]
|
||||||
energy: 1.6
|
visible: false
|
||||||
color: "#b89f25"
|
- map: [ "enum.ComputerVisualizer+Layers.Keyboard" ]
|
||||||
- type: Fixtures
|
visible: false
|
||||||
fixtures:
|
- map: [ "enum.ComputerVisualizer+Layers.Body"]
|
||||||
- shape:
|
state: television
|
||||||
!type:PhysShapeAabb
|
- map: [ "enum.ComputerVisualizer+Layers.Screen" ]
|
||||||
bounds: "-0.25,-0.25,0.25,0.25"
|
state: detective_television
|
||||||
mass: 50
|
shader: unshaded
|
||||||
mask:
|
- type: PointLight
|
||||||
- TabletopMachineMask
|
radius: 1.5
|
||||||
layer:
|
energy: 1.6
|
||||||
- TabletopMachineLayer
|
color: "#b89f25"
|
||||||
|
- type: Fixtures
|
||||||
|
fixtures:
|
||||||
|
- shape:
|
||||||
|
!type:PhysShapeAabb
|
||||||
|
bounds: "-0.25,-0.25,0.25,0.25"
|
||||||
|
mass: 50
|
||||||
|
mask:
|
||||||
|
- TabletopMachineMask
|
||||||
|
layer:
|
||||||
|
- TabletopMachineLayer
|
||||||
|
|||||||
@@ -250,6 +250,7 @@
|
|||||||
- DiagnoserMachineCircuitboard
|
- DiagnoserMachineCircuitboard
|
||||||
- ChemMasterMachineCircuitboard
|
- ChemMasterMachineCircuitboard
|
||||||
- ChemDispenserMachineCircuitboard
|
- ChemDispenserMachineCircuitboard
|
||||||
|
- SurveillanceCameraRouterCircuitboard
|
||||||
- HydroponicsTrayMachineCircuitboard
|
- HydroponicsTrayMachineCircuitboard
|
||||||
- SolarControlComputerCircuitboard
|
- SolarControlComputerCircuitboard
|
||||||
- AutolatheMachineCircuitboard
|
- AutolatheMachineCircuitboard
|
||||||
|
|||||||
@@ -0,0 +1,148 @@
|
|||||||
|
- type: entity
|
||||||
|
abstract: true
|
||||||
|
parent: BaseMachinePowered
|
||||||
|
id: SurveillanceCameraRouterBase
|
||||||
|
name: camera router
|
||||||
|
description: A surveillance camera router. It routes. Perhaps.
|
||||||
|
components:
|
||||||
|
- type: DeviceNetwork
|
||||||
|
deviceNetId: Wired
|
||||||
|
receiveFrequencyId: SurveillanceCamera
|
||||||
|
transmitFrequencyId: SurveillanceCamera
|
||||||
|
- type: WiredNetworkConnection
|
||||||
|
- type: UserInterface
|
||||||
|
interfaces:
|
||||||
|
- key: enum.SurveillanceCameraSetupUiKey.Router
|
||||||
|
type: SurveillanceCameraSetupBoundUi
|
||||||
|
- type: Construction
|
||||||
|
graph: Machine
|
||||||
|
node: machine
|
||||||
|
- type: Machine
|
||||||
|
board: SurveillanceCameraRouterCircuitboard
|
||||||
|
- type: Sprite
|
||||||
|
sprite: Structures/Machines/server.rsi
|
||||||
|
layers:
|
||||||
|
- state: server
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: SurveillanceCameraRouterBase
|
||||||
|
id: SurveillanceCameraRouterConstructed
|
||||||
|
suffix: Constructed
|
||||||
|
components:
|
||||||
|
- type: SurveillanceCameraRouter
|
||||||
|
setupAvailableNetworks:
|
||||||
|
- SurveillanceCameraEngineering
|
||||||
|
- SurveillanceCameraSecurity
|
||||||
|
- SurveillanceCameraService
|
||||||
|
- SurveillanceCameraSupply
|
||||||
|
- SurveillanceCameraScience
|
||||||
|
- SurveillanceCameraGeneral
|
||||||
|
- SurveillanceCameraMedical
|
||||||
|
- SurveillanceCameraCommand
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: SurveillanceCameraRouterBase
|
||||||
|
id: SurveillanceCameraRouterEngineering
|
||||||
|
suffix: Engineering
|
||||||
|
components:
|
||||||
|
- type: SurveillanceCameraRouter
|
||||||
|
subnetFrequency: SurveillanceCameraEngineering
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: SurveillanceCameraRouterBase
|
||||||
|
id: SurveillanceCameraRouterSecurity
|
||||||
|
suffix: Security
|
||||||
|
components:
|
||||||
|
- type: SurveillanceCameraRouter
|
||||||
|
subnetFrequency: SurveillanceCameraSecurity
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: SurveillanceCameraRouterBase
|
||||||
|
id: SurveillanceCameraRouterScience
|
||||||
|
suffix: Science
|
||||||
|
components:
|
||||||
|
- type: SurveillanceCameraRouter
|
||||||
|
subnetFrequency: SurveillanceCameraScience
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: SurveillanceCameraRouterBase
|
||||||
|
id: SurveillanceCameraRouterSupply
|
||||||
|
suffix: Supply
|
||||||
|
components:
|
||||||
|
- type: SurveillanceCameraRouter
|
||||||
|
subnetFrequency: SurveillanceCameraSupply
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: SurveillanceCameraRouterBase
|
||||||
|
id: SurveillanceCameraRouterCommand
|
||||||
|
suffix: Command
|
||||||
|
components:
|
||||||
|
- type: SurveillanceCameraRouter
|
||||||
|
subnetFrequency: SurveillanceCameraCommand
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: SurveillanceCameraRouterBase
|
||||||
|
id: SurveillanceCameraRouterService
|
||||||
|
suffix: Service
|
||||||
|
components:
|
||||||
|
- type: SurveillanceCameraRouter
|
||||||
|
subnetFrequency: SurveillanceCameraService
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: SurveillanceCameraRouterBase
|
||||||
|
id: SurveillanceCameraRouterMedical
|
||||||
|
suffix: Medical
|
||||||
|
components:
|
||||||
|
- type: SurveillanceCameraRouter
|
||||||
|
subnetFrequency: SurveillanceCameraMedical
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: SurveillanceCameraRouterBase
|
||||||
|
id: SurveillanceCameraRouterGeneral
|
||||||
|
suffix: General
|
||||||
|
components:
|
||||||
|
- type: SurveillanceCameraRouter
|
||||||
|
subnetFrequency: SurveillanceCameraGeneral
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: BaseMachinePowered
|
||||||
|
id: SurveillanceCameraWirelessRouterBase
|
||||||
|
name: wireless camera router
|
||||||
|
description: A wireless surveillance camera router. It routes. Perhaps.
|
||||||
|
components:
|
||||||
|
- type: DeviceNetwork
|
||||||
|
deviceNetId: Wireless
|
||||||
|
receiveFrequencyId: SurveillanceCamera
|
||||||
|
transmitFrequencyId: SurveillanceCamera
|
||||||
|
- type: WirelessNetworkConnection
|
||||||
|
range: 200
|
||||||
|
- type: UserInterface
|
||||||
|
interfaces:
|
||||||
|
- key: enum.SurveillanceCameraSetupUiKey.Router
|
||||||
|
type: SurveillanceCameraSetupBoundUi
|
||||||
|
- type: Construction
|
||||||
|
graph: Machine
|
||||||
|
node: machine
|
||||||
|
- type: Machine
|
||||||
|
board: SurveillanceCameraWirelessRouterCircuitboard
|
||||||
|
- type: Sprite
|
||||||
|
sprite: Structures/Machines/server.rsi
|
||||||
|
layers:
|
||||||
|
- state: server
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: SurveillanceCameraWirelessRouterBase
|
||||||
|
id: SurveillanceCameraWirelessRouterConstructed
|
||||||
|
suffix: Constructed
|
||||||
|
components:
|
||||||
|
- type: SurveillanceCameraRouter
|
||||||
|
setupAvailableNetworks:
|
||||||
|
- SurveillanceCameraEntertainment
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: SurveillanceCameraWirelessRouterBase
|
||||||
|
id: SurveillanceCameraWirelessRouterEntertainment
|
||||||
|
suffix: Entertainment
|
||||||
|
components:
|
||||||
|
- type: SurveillanceCameraRouter
|
||||||
|
subnetFrequency: SurveillanceCameraEntertainment
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
- type: entity
|
||||||
|
abstract: true
|
||||||
|
parent: BaseStructureDynamic
|
||||||
|
id: SurveillanceWirelessCameraBase
|
||||||
|
name: wireless camera
|
||||||
|
description: A camera. It's watching you. Kinda.
|
||||||
|
components:
|
||||||
|
- type: Construction
|
||||||
|
graph: Machine
|
||||||
|
node: machine
|
||||||
|
- type: InteractionOutline
|
||||||
|
- type: Eye
|
||||||
|
- type: WirelessNetworkConnection
|
||||||
|
range: 200
|
||||||
|
- type: Damageable
|
||||||
|
damageContainer: Inorganic
|
||||||
|
damageModifierSet: Metallic
|
||||||
|
- type: Rotatable
|
||||||
|
rotateWhileAnchored: true
|
||||||
|
- type: Fixtures
|
||||||
|
fixtures:
|
||||||
|
- shape:
|
||||||
|
!type:PhysShapeCircle
|
||||||
|
radius: 0.45
|
||||||
|
mass: 50
|
||||||
|
mask:
|
||||||
|
- MachineMask
|
||||||
|
- type: UserInterface
|
||||||
|
interfaces:
|
||||||
|
- key: enum.SurveillanceCameraSetupUiKey.Camera
|
||||||
|
type: SurveillanceCameraSetupBoundUi
|
||||||
|
placement:
|
||||||
|
mode: SnapgridCenter
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
abstract: true
|
||||||
|
id: SurveillanceWirelessCameraAnchoredBase
|
||||||
|
parent: SurveillanceWirelessCameraBase
|
||||||
|
suffix: Anchored
|
||||||
|
components:
|
||||||
|
- type: Machine
|
||||||
|
board: SurveillanceWirelessCameraAnchoredCircuitboard
|
||||||
|
- type: Anchorable
|
||||||
|
- type: Transform
|
||||||
|
anchored: true
|
||||||
|
- type: Sprite
|
||||||
|
noRot: true
|
||||||
|
sprite: Structures/monitors.rsi
|
||||||
|
layers:
|
||||||
|
- map: [ "enum.SurveillanceCameraVisualsKey.Key" ]
|
||||||
|
state: television
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
abstract: true
|
||||||
|
id: SurveillanceWirelessCameraMovableBase
|
||||||
|
parent: SurveillanceWirelessCameraBase
|
||||||
|
suffix: Movable
|
||||||
|
components:
|
||||||
|
- type: Machine
|
||||||
|
board: SurveillanceWirelessCameraMovableCircuitboard
|
||||||
|
- type: Transform
|
||||||
|
- type: Sprite
|
||||||
|
noRot: true
|
||||||
|
sprite: Structures/monitors.rsi
|
||||||
|
layers:
|
||||||
|
- map: [ "enum.SurveillanceCameraVisualsKey.Key" ]
|
||||||
|
state: mobilevision
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: SurveillanceWirelessCameraAnchoredBase
|
||||||
|
suffix: Constructed, Anchored
|
||||||
|
id: SurveillanceWirelessCameraAnchoredConstructed
|
||||||
|
components:
|
||||||
|
- type: DeviceNetwork
|
||||||
|
deviceNetId: Wireless
|
||||||
|
receiveFrequencyId: SurveillanceCamera
|
||||||
|
transmitFrequencyId: SurveillanceCamera
|
||||||
|
- type: SurveillanceCamera
|
||||||
|
setupAvailableNetworks:
|
||||||
|
- SurveillanceCameraEntertainment
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: SurveillanceWirelessCameraMovableBase
|
||||||
|
suffix: Constructed, Movable
|
||||||
|
id: SurveillanceWirelessCameraMovableConstructed
|
||||||
|
components:
|
||||||
|
- type: DeviceNetwork
|
||||||
|
deviceNetId: Wireless
|
||||||
|
receiveFrequencyId: SurveillanceCameraEntertainment
|
||||||
|
transmitFrequencyId: SurveillanceCamera
|
||||||
|
- type: SurveillanceCamera
|
||||||
|
setupAvailableNetworks:
|
||||||
|
- SurveillanceCameraEntertainment
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: SurveillanceWirelessCameraAnchoredBase
|
||||||
|
suffix: Entertainment, Anchored
|
||||||
|
id: SurveillanceWirelessCameraAnchoredEntertainment
|
||||||
|
components:
|
||||||
|
- type: DeviceNetwork
|
||||||
|
deviceNetId: Wireless
|
||||||
|
receiveFrequencyId: SurveillanceCameraEntertainment
|
||||||
|
transmitFrequencyId: SurveillanceCamera
|
||||||
|
- type: SurveillanceCamera
|
||||||
|
networkSet: true
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: SurveillanceWirelessCameraMovableBase
|
||||||
|
suffix: Entertainment, Movable
|
||||||
|
id: SurveillanceWirelessCameraMovableEntertainment
|
||||||
|
components:
|
||||||
|
- type: DeviceNetwork
|
||||||
|
deviceNetId: Wireless
|
||||||
|
receiveFrequencyId: SurveillanceCameraEntertainment
|
||||||
|
transmitFrequencyId: SurveillanceCamera
|
||||||
|
- type: SurveillanceCamera
|
||||||
|
networkSet: true
|
||||||
@@ -0,0 +1,212 @@
|
|||||||
|
- type: entity
|
||||||
|
abstract: true
|
||||||
|
id: SurveillanceCameraBase
|
||||||
|
name: camera
|
||||||
|
description: A surveillance camera. It's watching you. Kinda.
|
||||||
|
components:
|
||||||
|
- type: Clickable
|
||||||
|
- type: InteractionOutline
|
||||||
|
- type: Construction
|
||||||
|
graph: SurveillanceCamera
|
||||||
|
node: camera
|
||||||
|
- type: Electrified
|
||||||
|
enabled: false
|
||||||
|
usesApcPower: true
|
||||||
|
- type: WallMount
|
||||||
|
- type: ApcPowerReceiver
|
||||||
|
- type: ExtensionCableReceiver
|
||||||
|
- type: Eye
|
||||||
|
- type: WiredNetworkConnection
|
||||||
|
- type: Transform
|
||||||
|
anchored: true
|
||||||
|
- type: Wires
|
||||||
|
alwaysRandomize: true
|
||||||
|
LayoutId: SurveillanceCamera
|
||||||
|
- type: Damageable
|
||||||
|
damageContainer: Inorganic
|
||||||
|
damageModifierSet: Metallic
|
||||||
|
- type: Sprite
|
||||||
|
drawdepth: WallMountedItems
|
||||||
|
sprite: Structures/Wallmounts/camera.rsi
|
||||||
|
layers:
|
||||||
|
- map: [ "enum.SurveillanceCameraVisualsKey.Layer" ]
|
||||||
|
state: camera
|
||||||
|
- type: Appearance
|
||||||
|
- type: SurveillanceCameraVisuals
|
||||||
|
sprites:
|
||||||
|
Active: camera
|
||||||
|
Disabled: camera_off
|
||||||
|
InUse: camera_in_use
|
||||||
|
- type: UserInterface
|
||||||
|
interfaces:
|
||||||
|
- key: enum.SurveillanceCameraSetupUiKey.Camera
|
||||||
|
type: SurveillanceCameraSetupBoundUi
|
||||||
|
- key: enum.WiresUiKey.Key
|
||||||
|
type: WiresBoundUserInterface
|
||||||
|
placement:
|
||||||
|
mode: SnapgridCenter
|
||||||
|
snap:
|
||||||
|
- Wallmount
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: SurveillanceCameraBase
|
||||||
|
id: SurveillanceCameraConstructed
|
||||||
|
name: camera
|
||||||
|
suffix: Constructed
|
||||||
|
components:
|
||||||
|
- type: DeviceNetwork
|
||||||
|
deviceNetId: Wired
|
||||||
|
transmitFrequencyId: SurveillanceCamera
|
||||||
|
- type: SurveillanceCamera
|
||||||
|
setupAvailableNetworks:
|
||||||
|
- SurveillanceCameraEngineering
|
||||||
|
- SurveillanceCameraSecurity
|
||||||
|
- SurveillanceCameraService
|
||||||
|
- SurveillanceCameraSupply
|
||||||
|
- SurveillanceCameraScience
|
||||||
|
- SurveillanceCameraGeneral
|
||||||
|
- SurveillanceCameraMedical
|
||||||
|
- SurveillanceCameraCommand
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: SurveillanceCameraBase
|
||||||
|
id: SurveillanceCameraEngineering
|
||||||
|
name: camera
|
||||||
|
suffix: Engineering
|
||||||
|
components:
|
||||||
|
- type: DeviceNetwork
|
||||||
|
deviceNetId: Wired
|
||||||
|
receiveFrequencyId: SurveillanceCameraEngineering
|
||||||
|
transmitFrequencyId: SurveillanceCamera
|
||||||
|
- type: SurveillanceCamera
|
||||||
|
networkSet: true
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: SurveillanceCameraBase
|
||||||
|
id: SurveillanceCameraSecurity
|
||||||
|
name: camera
|
||||||
|
suffix: Security
|
||||||
|
components:
|
||||||
|
- type: DeviceNetwork
|
||||||
|
deviceNetId: Wired
|
||||||
|
receiveFrequencyId: SurveillanceCameraSecurity
|
||||||
|
transmitFrequencyId: SurveillanceCamera
|
||||||
|
- type: SurveillanceCamera
|
||||||
|
networkSet: true
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: SurveillanceCameraBase
|
||||||
|
id: SurveillanceCameraScience
|
||||||
|
name: camera
|
||||||
|
suffix: Science
|
||||||
|
components:
|
||||||
|
- type: DeviceNetwork
|
||||||
|
deviceNetId: Wired
|
||||||
|
receiveFrequencyId: SurveillanceCameraScience
|
||||||
|
transmitFrequencyId: SurveillanceCamera
|
||||||
|
- type: SurveillanceCamera
|
||||||
|
networkSet: true
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: SurveillanceCameraBase
|
||||||
|
id: SurveillanceCameraSupply
|
||||||
|
name: camera
|
||||||
|
suffix: Supply
|
||||||
|
components:
|
||||||
|
- type: DeviceNetwork
|
||||||
|
deviceNetId: Wired
|
||||||
|
receiveFrequencyId: SurveillanceCameraSupply
|
||||||
|
transmitFrequencyId: SurveillanceCamera
|
||||||
|
- type: SurveillanceCamera
|
||||||
|
networkSet: true
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: SurveillanceCameraBase
|
||||||
|
id: SurveillanceCameraScience
|
||||||
|
name: camera
|
||||||
|
suffix: Science
|
||||||
|
components:
|
||||||
|
- type: DeviceNetwork
|
||||||
|
deviceNetId: Wired
|
||||||
|
receiveFrequencyId: SurveillanceCameraScience
|
||||||
|
transmitFrequencyId: SurveillanceCamera
|
||||||
|
- type: SurveillanceCamera
|
||||||
|
networkSet: true
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: SurveillanceCameraBase
|
||||||
|
id: SurveillanceCameraCommand
|
||||||
|
name: camera
|
||||||
|
suffix: Command
|
||||||
|
components:
|
||||||
|
- type: DeviceNetwork
|
||||||
|
deviceNetId: Wired
|
||||||
|
receiveFrequencyId: SurveillanceCameraCommand
|
||||||
|
transmitFrequencyId: SurveillanceCamera
|
||||||
|
- type: SurveillanceCamera
|
||||||
|
networkSet: true
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: SurveillanceCameraBase
|
||||||
|
id: SurveillanceCameraService
|
||||||
|
name: camera
|
||||||
|
suffix: Service
|
||||||
|
components:
|
||||||
|
- type: DeviceNetwork
|
||||||
|
deviceNetId: Wired
|
||||||
|
receiveFrequencyId: SurveillanceCameraService
|
||||||
|
transmitFrequencyId: SurveillanceCamera
|
||||||
|
- type: SurveillanceCamera
|
||||||
|
networkSet: true
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: SurveillanceCameraBase
|
||||||
|
id: SurveillanceCameraMedical
|
||||||
|
name: camera
|
||||||
|
suffix: Medical
|
||||||
|
components:
|
||||||
|
- type: DeviceNetwork
|
||||||
|
deviceNetId: Wired
|
||||||
|
receiveFrequencyId: SurveillanceCameraMedical
|
||||||
|
transmitFrequencyId: SurveillanceCamera
|
||||||
|
- type: SurveillanceCamera
|
||||||
|
networkSet: true
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: SurveillanceCameraBase
|
||||||
|
id: SurveillanceCameraGeneral
|
||||||
|
name: camera
|
||||||
|
suffix: General
|
||||||
|
components:
|
||||||
|
- type: DeviceNetwork
|
||||||
|
deviceNetId: Wired
|
||||||
|
receiveFrequencyId: SurveillanceCameraGeneral
|
||||||
|
transmitFrequencyId: SurveillanceCamera
|
||||||
|
- type: SurveillanceCamera
|
||||||
|
networkSet: true
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
id: SurveillanceCameraAssembly
|
||||||
|
name: camera
|
||||||
|
description: A surveillance camera. Doesn't seem to be watching anybody any time soon. Probably.
|
||||||
|
components:
|
||||||
|
- type: Clickable
|
||||||
|
- type: InteractionOutline
|
||||||
|
- type: Construction
|
||||||
|
graph: SurveillanceCamera
|
||||||
|
node: assembly
|
||||||
|
- type: WallMount
|
||||||
|
- type: Transform
|
||||||
|
anchored: true
|
||||||
|
- type: Damageable
|
||||||
|
damageContainer: Inorganic
|
||||||
|
damageModifierSet: Metallic
|
||||||
|
- type: Sprite
|
||||||
|
drawdepth: WallMountedItems
|
||||||
|
sprite: Structures/Wallmounts/camera.rsi
|
||||||
|
layers:
|
||||||
|
- state: camera_off
|
||||||
|
placement:
|
||||||
|
mode: SnapgridCenter
|
||||||
|
snap:
|
||||||
|
- Wallmount
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
- type: constructionGraph
|
||||||
|
id: SurveillanceCamera
|
||||||
|
start: start
|
||||||
|
graph:
|
||||||
|
- node: start
|
||||||
|
edges:
|
||||||
|
- to: assembly
|
||||||
|
steps:
|
||||||
|
- material: Steel
|
||||||
|
amount: 2
|
||||||
|
doAfter: 2.0
|
||||||
|
|
||||||
|
- node: assembly
|
||||||
|
entity: SurveillanceCameraAssembly
|
||||||
|
edges:
|
||||||
|
- to: wired
|
||||||
|
steps:
|
||||||
|
- material: Cable
|
||||||
|
amount: 1
|
||||||
|
doAfter: 1
|
||||||
|
- to: start
|
||||||
|
completed:
|
||||||
|
- !type:SpawnPrototype
|
||||||
|
prototype: SheetSteel1
|
||||||
|
amount: 2
|
||||||
|
- !type:DeleteEntity {}
|
||||||
|
steps:
|
||||||
|
- tool: Welding
|
||||||
|
doAfter: 2
|
||||||
|
|
||||||
|
- node: wired
|
||||||
|
entity: SurveillanceCameraAssembly
|
||||||
|
edges:
|
||||||
|
- to: camera
|
||||||
|
steps:
|
||||||
|
- tool: Screwing
|
||||||
|
doAfter: 2
|
||||||
|
|
||||||
|
# once a camera is constructed, it will
|
||||||
|
# instead just require wire cutting + welding
|
||||||
|
# to immediately deconstruct
|
||||||
|
- node: camera
|
||||||
|
entity: SurveillanceCameraConstructed
|
||||||
|
edges:
|
||||||
|
- to: start
|
||||||
|
conditions:
|
||||||
|
- !type:AllWiresCut {}
|
||||||
|
- !type:WirePanel {}
|
||||||
|
completed:
|
||||||
|
- !type:SpawnPrototype
|
||||||
|
prototype: SheetSteel1
|
||||||
|
amount: 2
|
||||||
|
- !type:DeleteEntity {}
|
||||||
|
steps:
|
||||||
|
- tool: Welding
|
||||||
|
doAfter: 2
|
||||||
|
|
||||||
@@ -1,3 +1,17 @@
|
|||||||
|
- type: construction
|
||||||
|
name: camera
|
||||||
|
id: camera
|
||||||
|
graph: SurveillanceCamera
|
||||||
|
startNode: start
|
||||||
|
targetNode: camera
|
||||||
|
category: Utilities
|
||||||
|
description: "Surveillance camera. It's watching. Soon."
|
||||||
|
icon:
|
||||||
|
sprite: Structures/Wallmounts/camera.rsi
|
||||||
|
state: camera
|
||||||
|
objectType: Structure
|
||||||
|
placementMode: SnapgridCenter
|
||||||
|
|
||||||
# POWER
|
# POWER
|
||||||
- type: construction
|
- type: construction
|
||||||
name: APC
|
name: APC
|
||||||
@@ -457,4 +471,4 @@
|
|||||||
icon:
|
icon:
|
||||||
sprite: Structures/Piping/Atmospherics/gasmixer.rsi
|
sprite: Structures/Piping/Atmospherics/gasmixer.rsi
|
||||||
state: gasMixer
|
state: gasMixer
|
||||||
|
|
||||||
|
|||||||
@@ -294,3 +294,39 @@
|
|||||||
materials:
|
materials:
|
||||||
Steel: 100
|
Steel: 100
|
||||||
Glass: 900
|
Glass: 900
|
||||||
|
|
||||||
|
- type: latheRecipe
|
||||||
|
id: SurveillanceCameraRouterCircuitboard
|
||||||
|
icon: Objects/Misc/module.rsi/id_mod.png
|
||||||
|
result: SurveillanceCameraRouterCircuitboard
|
||||||
|
completetime: 4
|
||||||
|
materials:
|
||||||
|
Steel: 100
|
||||||
|
Glass: 900
|
||||||
|
|
||||||
|
- type: latheRecipe
|
||||||
|
id: SurveillanceCameraWirelessRouterCircuitboard
|
||||||
|
icon: Objects/Misc/module.rsi/id_mod.png
|
||||||
|
result: SurveillanceCameraWirelessRouterCircuitboard
|
||||||
|
completetime: 4
|
||||||
|
materials:
|
||||||
|
Steel: 100
|
||||||
|
Glass: 900
|
||||||
|
|
||||||
|
- type: latheRecipe
|
||||||
|
id: SurveillanceWirelessCameraAnchoredCircuitboard
|
||||||
|
icon: Objects/Misc/module.rsi/id_mod.png
|
||||||
|
result: SurveillanceWirelessCameraAnchoredCircuitboard
|
||||||
|
completetime: 4
|
||||||
|
materials:
|
||||||
|
Steel: 100
|
||||||
|
Glass: 900
|
||||||
|
|
||||||
|
- type: latheRecipe
|
||||||
|
id: SurveillanceWirelessCameraMovableCircuitboard
|
||||||
|
icon: Objects/Misc/module.rsi/id_mod.png
|
||||||
|
result: SurveillanceWirelessCameraMovableCircuitboard
|
||||||
|
completetime: 4
|
||||||
|
materials:
|
||||||
|
Steel: 100
|
||||||
|
Glass: 900
|
||||||
|
|||||||
@@ -31,6 +31,11 @@
|
|||||||
positionInput: 0,0
|
positionInput: 0,0
|
||||||
life: 0
|
life: 0
|
||||||
|
|
||||||
|
- type: shader
|
||||||
|
id: CameraStatic
|
||||||
|
kind: source
|
||||||
|
path: "/Textures/Shaders/camera_static.swsl"
|
||||||
|
|
||||||
- type: shader
|
- type: shader
|
||||||
id: Texture
|
id: Texture
|
||||||
kind: source
|
kind: source
|
||||||
|
|||||||
@@ -66,3 +66,9 @@
|
|||||||
- !type:ArcadeOverflowWireAction
|
- !type:ArcadeOverflowWireAction
|
||||||
- !type:ArcadePlayerInvincibleWireAction
|
- !type:ArcadePlayerInvincibleWireAction
|
||||||
- !type:ArcadeEnemyInvincibleWireAction
|
- !type:ArcadeEnemyInvincibleWireAction
|
||||||
|
|
||||||
|
- type: wireLayout
|
||||||
|
id: SurveillanceCamera
|
||||||
|
dummyWires: 4
|
||||||
|
wires:
|
||||||
|
- !type:PowerWireAction
|
||||||
|
|||||||
BIN
Resources/Textures/Interface/Nano/square_black.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
40
Resources/Textures/Shaders/camera_static.swsl
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
// Credited to PelicanPolice on Shadertoy
|
||||||
|
// Free for any purpose, commercial or otherwise.
|
||||||
|
// But do post here so I can see where it got used!
|
||||||
|
// If using on shadertoy, do link to this project on yours :)
|
||||||
|
|
||||||
|
highp float noise(highp vec2 pos, highp float evolve) {
|
||||||
|
|
||||||
|
// Loop the evolution (over a very long period of time).
|
||||||
|
highp float e = fract((evolve*0.01));
|
||||||
|
|
||||||
|
// Coordinates
|
||||||
|
highp float cx = pos.x*e;
|
||||||
|
highp float cy = sin(pos.y*e * 376.0); // is this close enough to a 60hz interference? :smol:
|
||||||
|
|
||||||
|
highp float v2_2 = cx*2.4/cy*23.0+pow(abs(cy/22.4),3.3);
|
||||||
|
highp float v2_1 = fract(fract(v2_2));
|
||||||
|
highp float v2 = fract(2.0/v2_1);
|
||||||
|
highp float v3 = fract(cx*evolve/pow(abs(cy),0.050));
|
||||||
|
|
||||||
|
// Generate a "random" black or white value
|
||||||
|
return fract(0.1*v2*v3);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void fragment() {
|
||||||
|
highp vec2 coords = FRAGCOORD.xy;
|
||||||
|
|
||||||
|
// Increase this number to test performance
|
||||||
|
int intensity = 1;
|
||||||
|
|
||||||
|
highp vec3 colour;
|
||||||
|
for (int i = 0; i < intensity; i++)
|
||||||
|
{
|
||||||
|
// Generate a black to white pixel
|
||||||
|
colour = vec3(noise(coords,TIME));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output to screen
|
||||||
|
COLOR = vec4(colour,1.0);
|
||||||
|
}
|
||||||
BIN
Resources/Textures/Structures/Wallmounts/camera.rsi/camera.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 894 B |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 873 B |
|
After Width: | Height: | Size: 283 B |
@@ -0,0 +1 @@
|
|||||||
|
{"version": 1, "size": {"x": 32, "y": 32}, "license": "CC-BY-SA-3.0", "copyright": "Taken from /tg/station at https://github.com/tgstation/tgstation/commit/6bfe3b2e4fcbcdac9159dc4f0327a82ddf05ba7bi", "states": [{"name": "camera", "directions": 8, "delays": [[0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3], [0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3], [0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3], [0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3], [0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3], [0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3], [0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3], [0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3]]}, {"name": "camera_assembly", "directions": 8, "delays": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]]}, {"name": "camera_emp", "directions": 8, "delays": [[0.2, 0.2, 0.1, 0.1], [0.2, 0.2, 0.1, 0.1], [0.2, 0.2, 0.1, 0.1], [0.2, 0.2, 0.1, 0.1], [0.2, 0.2, 0.1, 0.1], [0.2, 0.2, 0.1, 0.1], [0.2, 0.2, 0.1, 0.1], [0.2, 0.2, 0.1, 0.1]]}, {"name": "camera_in_use", "directions": 8, "delays": [[0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3], [0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3], [0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3], [0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3], [0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3], [0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3], [0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3], [0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3]]}, {"name": "camera_off", "directions": 8, "delays": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]]}, {"name": "cameracase", "directions": 1, "delays": [[1.0]]}, {"name": "xraycamera", "directions": 8, "delays": [[0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3], [0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3], [0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3], [0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3], [0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3], [0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3], [0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3], [0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3]]}, {"name": "xraycamera_assembly", "directions": 8, "delays": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]]}, {"name": "xraycamera_emp", "directions": 8, "delays": [[0.2, 0.2, 0.1, 0.1], [0.2, 0.2, 0.1, 0.1], [0.2, 0.2, 0.1, 0.1], [0.2, 0.2, 0.1, 0.1], [0.2, 0.2, 0.1, 0.1], [0.2, 0.2, 0.1, 0.1], [0.2, 0.2, 0.1, 0.1], [0.2, 0.2, 0.1, 0.1]]}, {"name": "xraycamera_in_use", "directions": 8, "delays": [[0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3], [0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3], [0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3], [0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3], [0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3], [0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3], [0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3], [0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3]]}, {"name": "xraycamera_off", "directions": 8, "delays": [[1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0], [1.0]]}]}
|
||||||
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 1023 B |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
16
Resources/Textures/Structures/monitors.rsi/meta.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"size": {"x": 32, "y": 32},
|
||||||
|
"license": "From Goonstation at https://github.com/goonstation/goonstation/commit/ed3b3c552e384bb3eb7056468a125f6a8c661261",
|
||||||
|
"copyright": "CC-BY-NC-SA-3.0",
|
||||||
|
"states": [
|
||||||
|
{"name": "mobilevision", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]},
|
||||||
|
{"name": "party", "directions": 1, "delays": [[1.0]]},
|
||||||
|
{"name": "rad0", "directions": 1, "delays": [[1.0]]},
|
||||||
|
{"name": "rad1", "directions": 1, "delays": [[0.1, 0.1]]},
|
||||||
|
{"name": "shipalert0", "directions": 1, "delays": [[1.0]]},
|
||||||
|
{"name": "shipalert1", "directions": 1, "delays": [[1.0]]},
|
||||||
|
{"name": "shipalert2", "directions": 1, "delays": [[1.0]]},
|
||||||
|
{"name": "television", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
Resources/Textures/Structures/monitors.rsi/mobilevision.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
Resources/Textures/Structures/monitors.rsi/party.png
Normal file
|
After Width: | Height: | Size: 315 B |
BIN
Resources/Textures/Structures/monitors.rsi/rad0.png
Normal file
|
After Width: | Height: | Size: 283 B |
BIN
Resources/Textures/Structures/monitors.rsi/rad1.png
Normal file
|
After Width: | Height: | Size: 378 B |
BIN
Resources/Textures/Structures/monitors.rsi/shipalert0.png
Normal file
|
After Width: | Height: | Size: 434 B |
BIN
Resources/Textures/Structures/monitors.rsi/shipalert1.png
Normal file
|
After Width: | Height: | Size: 408 B |
BIN
Resources/Textures/Structures/monitors.rsi/shipalert2.png
Normal file
|
After Width: | Height: | Size: 475 B |
BIN
Resources/Textures/Structures/monitors.rsi/television.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |