diff --git a/Content.Server/DeviceNetwork/Components/StationLimitedNetworkComponent.cs b/Content.Server/DeviceNetwork/Components/StationLimitedNetworkComponent.cs
new file mode 100644
index 0000000000..7d69c3c573
--- /dev/null
+++ b/Content.Server/DeviceNetwork/Components/StationLimitedNetworkComponent.cs
@@ -0,0 +1,22 @@
+using Robust.Shared.GameObjects;
+using Robust.Shared.Map;
+
+namespace Content.Server.DeviceNetwork.Components
+{
+ [RegisterComponent]
+ public sealed class StationLimitedNetworkComponent : Component
+ {
+ ///
+ /// The station id the device is limited to.
+ ///
+ [ViewVariables(VVAccess.ReadWrite)]
+ public EntityUid? StationId;
+
+ ///
+ /// Whether the entity is allowed to receive packets from entities that are not tied to any station
+ ///
+ [DataField("allowNonStationPackets")]
+ [ViewVariables(VVAccess.ReadWrite)]
+ public bool AllowNonStationPackets = false;
+ }
+}
diff --git a/Content.Server/DeviceNetwork/DeviceNet.cs b/Content.Server/DeviceNetwork/DeviceNet.cs
index 7d25985efe..d4f67d2f79 100644
--- a/Content.Server/DeviceNetwork/DeviceNet.cs
+++ b/Content.Server/DeviceNetwork/DeviceNet.cs
@@ -82,7 +82,7 @@ public sealed class DeviceNet
///
public bool Remove(DeviceNetworkComponent device)
{
- if (device.Address == null || Devices.Remove(device.Address))
+ if (device.Address == null || !Devices.Remove(device.Address))
return false;
if (device.ReceiveFrequency is not uint freq)
diff --git a/Content.Server/DeviceNetwork/Systems/DeviceNetworkSystem.cs b/Content.Server/DeviceNetwork/Systems/DeviceNetworkSystem.cs
index 4215485ff6..5a76c5ed66 100644
--- a/Content.Server/DeviceNetwork/Systems/DeviceNetworkSystem.cs
+++ b/Content.Server/DeviceNetwork/Systems/DeviceNetworkSystem.cs
@@ -48,18 +48,22 @@ namespace Content.Server.DeviceNetwork.Systems
/// The address of the entity that the packet gets sent to. If null, the message is broadcast to all devices on that frequency (except the sender)
/// The frequency to send on
/// The data to be sent
- public void QueuePacket(EntityUid uid, string? address, NetworkPayload data, uint? frequency = null, DeviceNetworkComponent? device = null)
+ /// Returns true when the packet was successfully enqueued.
+ public bool QueuePacket(EntityUid uid, string? address, NetworkPayload data, uint? frequency = null, DeviceNetworkComponent? device = null)
{
if (!Resolve(uid, ref device, false))
- return;
+ return false;
if (device.Address == string.Empty)
- return;
+ return false;
frequency ??= device.TransmitFrequency;
- if (frequency != null)
- _packets.Enqueue(new DeviceNetworkPacketEvent(device.DeviceNetId, address, frequency.Value, device.Address, uid, data));
+ if (frequency == null)
+ return false;
+
+ _packets.Enqueue(new DeviceNetworkPacketEvent(device.DeviceNetId, address, frequency.Value, device.Address, uid, data));
+ return true;
}
private void OnExamine(EntityUid uid, DeviceNetworkComponent device, ExaminedEvent args)
@@ -137,6 +141,32 @@ namespace Content.Server.DeviceNetwork.Systems
return GetNetwork(device.DeviceNetId).Remove(device);
}
+ ///
+ /// Checks if a device is already connected to its network
+ ///
+ /// True if the device was found in the network with its corresponding network id
+ public bool IsDeviceConnected(EntityUid uid, DeviceNetworkComponent? device)
+ {
+ if (!Resolve(uid, ref device, false))
+ return false;
+
+ if (!_networks.TryGetValue(device.DeviceNetId, out var deviceNet))
+ return false;
+
+ return deviceNet.Devices.ContainsValue(device);
+ }
+
+ ///
+ /// Checks if an address exists in the network with the given netId
+ ///
+ public bool IsAddressPresent(int netId, string? address)
+ {
+ if (address == null || !_networks.TryGetValue(netId, out var network))
+ return false;
+
+ return network.Devices.ContainsKey(address);
+ }
+
public void SetReceiveFrequency(EntityUid uid, uint? frequency, DeviceNetworkComponent? device = null)
{
if (!Resolve(uid, ref device, false))
diff --git a/Content.Server/DeviceNetwork/Systems/StationLimitedNetworkSystem.cs b/Content.Server/DeviceNetwork/Systems/StationLimitedNetworkSystem.cs
new file mode 100644
index 0000000000..cdcc37e569
--- /dev/null
+++ b/Content.Server/DeviceNetwork/Systems/StationLimitedNetworkSystem.cs
@@ -0,0 +1,68 @@
+using Content.Server.DeviceNetwork.Components;
+using Content.Server.Station.Systems;
+using JetBrains.Annotations;
+using Robust.Shared.Map;
+
+namespace Content.Server.DeviceNetwork.Systems
+{
+ ///
+ /// This system requires the StationLimitedNetworkComponent to be on the the sending entity as well as the receiving entity
+ ///
+ [UsedImplicitly]
+ public sealed class StationLimitedNetworkSystem : EntitySystem
+ {
+ [Dependency] private readonly StationSystem _stationSystem = default!;
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent(OnMapInit);
+ SubscribeLocalEvent(OnBeforePacketSent);
+ }
+
+ ///
+ /// Sets the station id the device is limited to.
+ ///
+ public void SetStation(EntityUid uid, EntityUid? stationId, StationLimitedNetworkComponent? component = null)
+ {
+ if (!Resolve(uid, ref component))
+ return;
+
+ component.StationId = stationId;
+ }
+
+ ///
+ /// Set the station id to the one the entity is on when the station limited component is added
+ ///
+ private void OnMapInit(EntityUid uid, StationLimitedNetworkComponent networkComponent, MapInitEvent args)
+ {
+ networkComponent.StationId = _stationSystem.GetOwningStation(uid);
+ }
+
+ ///
+ /// Checks if both devices are limited to the same station
+ ///
+ private void OnBeforePacketSent(EntityUid uid, StationLimitedNetworkComponent component, BeforePacketSentEvent args)
+ {
+ if (!CheckStationId(args.Sender, component.AllowNonStationPackets, component.StationId))
+ {
+ args.Cancel();
+ }
+ }
+
+ ///
+ /// Compares the station IDs of the sending and receiving network components.
+ /// Returns false if either of them doesn't have a station ID or if their station ID isn't equal.
+ /// Returns true even when the sending entity isn't tied to a station if `allowNonStationPackets` is set to true.
+ ///
+ private bool CheckStationId(EntityUid senderUid, bool allowNonStationPackets, EntityUid? receiverStationId, StationLimitedNetworkComponent? sender = null)
+ {
+ if (!receiverStationId.HasValue)
+ return false;
+
+ if (!Resolve(senderUid, ref sender, false))
+ return allowNonStationPackets;
+
+ return sender.StationId == receiverStationId;
+ }
+ }
+}
diff --git a/Content.Server/Medical/CrewMonitoring/CrewMonitoringConsoleSystem.cs b/Content.Server/Medical/CrewMonitoring/CrewMonitoringConsoleSystem.cs
index ea77cef6bd..98c9c2d8cf 100644
--- a/Content.Server/Medical/CrewMonitoring/CrewMonitoringConsoleSystem.cs
+++ b/Content.Server/Medical/CrewMonitoring/CrewMonitoringConsoleSystem.cs
@@ -1,9 +1,11 @@
using System.Linq;
+using Content.Server.DeviceNetwork;
using Content.Server.DeviceNetwork.Systems;
using Content.Server.Medical.SuitSensors;
using Content.Server.UserInterface;
using Content.Shared.Medical.CrewMonitoring;
using Robust.Shared.Map;
+using Content.Shared.Medical.SuitSensor;
using Robust.Shared.Timing;
namespace Content.Server.Medical.CrewMonitoring
@@ -13,33 +15,14 @@ namespace Content.Server.Medical.CrewMonitoring
[Dependency] private readonly SuitSensorSystem _sensors = default!;
[Dependency] private readonly SharedTransformSystem _xform = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
-
- private const float UpdateRate = 3f;
- private float _updateDif;
+ [Dependency] private readonly IMapManager _mapManager = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent(OnRemove);
SubscribeLocalEvent(OnPacketReceived);
- }
-
- public override void Update(float frameTime)
- {
- base.Update(frameTime);
-
- // check update rate
- _updateDif += frameTime;
- if (_updateDif < UpdateRate)
- return;
- _updateDif = 0f;
-
- var consoles = EntityManager.EntityQuery();
- foreach (var console in consoles)
- {
- UpdateTimeouts(console.Owner, console);
- UpdateUserInterface(console.Owner, console);
- }
+ SubscribeLocalEvent(OnUIOpened);
}
private void OnRemove(EntityUid uid, CrewMonitoringConsoleComponent component, ComponentRemove args)
@@ -49,12 +32,22 @@ namespace Content.Server.Medical.CrewMonitoring
private void OnPacketReceived(EntityUid uid, CrewMonitoringConsoleComponent component, DeviceNetworkPacketEvent args)
{
- var suitSensor = _sensors.PacketToSuitSensor(args.Data);
- if (suitSensor == null)
+ var payload = args.Data;
+ // check command
+ if (!payload.TryGetValue(DeviceNetworkConstants.Command, out string? command))
+ return;
+ if (command != DeviceNetworkConstants.CmdUpdatedState)
+ return;
+ if (!payload.TryGetValue(SuitSensorConstants.NET_STATUS_COLLECTION, out Dictionary? sensorStatus))
return;
- suitSensor.Timestamp = _gameTiming.CurTime;
- component.ConnectedSensors[args.SenderAddress] = suitSensor;
+ component.ConnectedSensors = sensorStatus;
+ UpdateUserInterface(uid, component);
+ }
+
+ private void OnUIOpened(EntityUid uid, CrewMonitoringConsoleComponent component, BoundUIOpenedEvent args)
+ {
+ UpdateUserInterface(uid, component);
}
private void UpdateUserInterface(EntityUid uid, CrewMonitoringConsoleComponent? component = null)
@@ -72,19 +65,5 @@ namespace Content.Server.Medical.CrewMonitoring
var uiState = new CrewMonitoringState(allSensors, xform.WorldPosition, component.Snap, component.Precision);
ui.SetState(uiState);
}
-
- private void UpdateTimeouts(EntityUid uid, CrewMonitoringConsoleComponent? component = null)
- {
- if (!Resolve(uid, ref component))
- return;
-
- foreach (var (address, sensor) in component.ConnectedSensors)
- {
- // if too many time passed - sensor just dropped connection
- var dif = _gameTiming.CurTime - sensor.Timestamp;
- if (dif.Seconds > component.SensorTimeout)
- component.ConnectedSensors.Remove(address);
- }
- }
}
}
diff --git a/Content.Server/Medical/CrewMonitoring/CrewMonitoringServerComponent.cs b/Content.Server/Medical/CrewMonitoring/CrewMonitoringServerComponent.cs
new file mode 100644
index 0000000000..3a73f4d0b4
--- /dev/null
+++ b/Content.Server/Medical/CrewMonitoring/CrewMonitoringServerComponent.cs
@@ -0,0 +1,34 @@
+using Content.Shared.Medical.SuitSensor;
+using Robust.Shared.Map;
+
+namespace Content.Server.Medical.CrewMonitoring;
+
+[RegisterComponent]
+[Access(typeof(CrewMonitoringServerSystem))]
+public sealed class CrewMonitoringServerComponent : Component
+{
+
+ ///
+ /// List of all currently connected sensors to this server.
+ ///
+ public readonly Dictionary SensorStatus = new();
+
+ ///
+ /// After what time sensor consider to be lost.
+ ///
+ [DataField("sensorTimeout"), ViewVariables(VVAccess.ReadWrite)]
+ public float SensorTimeout = 10f;
+
+ ///
+ /// Whether the server can become the currently active server. The server being unavailable usually means that it isn't powered
+ ///
+ [ViewVariables(VVAccess.ReadWrite)]
+ public bool Available = true;
+
+
+ ///
+ /// Whether the server is the currently active server for the station it's on
+ ///
+ [ViewVariables(VVAccess.ReadWrite)]
+ public bool Active = true;
+}
diff --git a/Content.Server/Medical/CrewMonitoring/CrewMonitoringServerSystem.cs b/Content.Server/Medical/CrewMonitoring/CrewMonitoringServerSystem.cs
new file mode 100644
index 0000000000..bb42542d78
--- /dev/null
+++ b/Content.Server/Medical/CrewMonitoring/CrewMonitoringServerSystem.cs
@@ -0,0 +1,198 @@
+using Content.Server.DeviceNetwork;
+using Content.Server.DeviceNetwork.Components;
+using Content.Server.DeviceNetwork.Systems;
+using Content.Server.Medical.SuitSensors;
+using Content.Server.Power.Components;
+using Content.Server.Station.Systems;
+using Content.Shared.Medical.SuitSensor;
+using Robust.Shared.Timing;
+
+namespace Content.Server.Medical.CrewMonitoring;
+
+public sealed class CrewMonitoringServerSystem : EntitySystem
+{
+ [Dependency] private readonly SuitSensorSystem _sensors = default!;
+ [Dependency] private readonly IGameTiming _gameTiming = default!;
+ [Dependency] private readonly DeviceNetworkSystem _deviceNetworkSystem = default!;
+ [Dependency] private readonly StationSystem _stationSystem = default!;
+
+ private const float UpdateRate = 3f;
+ private float _updateDiff;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent(OnRemove);
+ SubscribeLocalEvent(OnPacketReceived);
+ SubscribeLocalEvent(OnPowerChanged);
+ }
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ // check update rate
+ _updateDiff += frameTime;
+ if (_updateDiff < UpdateRate)
+ return;
+ _updateDiff -= UpdateRate;
+
+ var servers = EntityManager.EntityQuery();
+ List activeServers = new();
+
+ foreach (var server in servers)
+ {
+ //Make sure the server is disconnected when it becomes unavailable
+ if (!server.Available)
+ {
+ if (server.Active)
+ DisconnectServer(server.Owner, server);
+
+ continue;
+ }
+
+ if (!server.Active)
+ continue;
+
+ activeServers.Add(server.Owner);
+ }
+
+ foreach (var activeServer in activeServers)
+ {
+ UpdateTimeout(activeServer);
+ BroadcastSensorStatus(activeServer);
+ }
+ }
+
+ ///
+ /// Returns the address of the currently active server for the given station id if there is one
+ ///
+ public bool TryGetActiveServerAddress(EntityUid stationId, out string? address)
+ {
+ var servers = EntityManager.EntityQuery();
+ (CrewMonitoringServerComponent, DeviceNetworkComponent)? last = default;
+
+ foreach (var (server, device) in servers)
+ {
+ if (!_stationSystem.GetOwningStation(server.Owner)?.Equals(stationId) ?? false)
+ continue;
+
+ if (!server.Available)
+ {
+ DisconnectServer(server.Owner,server, device);
+ continue;
+ }
+
+ last = (server, device);
+
+ if (server.Active)
+ {
+ address = device.Address;
+ return true;
+ }
+ }
+
+ //If there was no active server for the station make the last available inactive one active
+ if (last.HasValue)
+ {
+ ConnectServer(last.Value.Item1.Owner, last.Value.Item1, last.Value.Item2);
+ address = last.Value.Item2.Address;
+ return true;
+ }
+
+ address = null;
+ return address != null;
+ }
+
+ ///
+ /// Adds or updates a sensor status entry if the received package is a sensor status update
+ ///
+ private void OnPacketReceived(EntityUid uid, CrewMonitoringServerComponent component, DeviceNetworkPacketEvent args)
+ {
+ var sensorStatus = _sensors.PacketToSuitSensor(args.Data);
+ if (sensorStatus == null)
+ return;
+
+ sensorStatus.Timestamp = _gameTiming.CurTime;
+ component.SensorStatus[args.SenderAddress] = sensorStatus;
+ }
+
+ ///
+ /// Clears the servers sensor status list
+ ///
+ private void OnRemove(EntityUid uid, CrewMonitoringServerComponent component, ComponentRemove args)
+ {
+ component.SensorStatus.Clear();
+ }
+
+ ///
+ /// Disconnects the server losing power
+ ///
+ private void OnPowerChanged(EntityUid uid, CrewMonitoringServerComponent component, ref PowerChangedEvent args)
+ {
+ component.Available = args.Powered;
+
+ if (!args.Powered)
+ DisconnectServer(uid, component);
+ }
+
+ ///
+ /// Drop the sensor status if it hasn't been updated for to long
+ ///
+ private void UpdateTimeout(EntityUid uid, CrewMonitoringServerComponent? component = null)
+ {
+ if (!Resolve(uid, ref component))
+ return;
+
+ foreach (var (address, sensor) in component.SensorStatus)
+ {
+ var dif = _gameTiming.CurTime - sensor.Timestamp;
+ if (dif.Seconds > component.SensorTimeout)
+ component.SensorStatus.Remove(address);
+ }
+ }
+
+ ///
+ /// Broadcasts the status of all connected sensors
+ ///
+ private void BroadcastSensorStatus(EntityUid uid, CrewMonitoringServerComponent? serverComponent = null, DeviceNetworkComponent? device = null)
+ {
+ if (!Resolve(uid, ref serverComponent, ref device))
+ return;
+
+ var payload = new NetworkPayload()
+ {
+ [DeviceNetworkConstants.Command] = DeviceNetworkConstants.CmdUpdatedState,
+ [SuitSensorConstants.NET_STATUS_COLLECTION] = serverComponent.SensorStatus
+ };
+
+ _deviceNetworkSystem.QueuePacket(uid, null, payload, device: device);
+ }
+
+ private void ConnectServer(EntityUid uid, CrewMonitoringServerComponent? server = null, DeviceNetworkComponent? device = null)
+ {
+ if (!Resolve(uid, ref server, ref device))
+ return;
+
+ server.Active = true;
+
+ if (_deviceNetworkSystem.IsDeviceConnected(uid, device))
+ return;
+
+ _deviceNetworkSystem.ConnectDevice(uid, device);
+ }
+
+ ///
+ /// Disconnects a server from the device network and clears the currently active server
+ ///
+ private void DisconnectServer(EntityUid uid, CrewMonitoringServerComponent? server = null, DeviceNetworkComponent? device = null)
+ {
+ if (!Resolve(uid, ref server, ref device))
+ return;
+
+ server.SensorStatus.Clear();
+ server.Active = false;
+
+ _deviceNetworkSystem.DisconnectDevice(uid, device, false);
+ }
+}
diff --git a/Content.Server/Medical/SuitSensors/SuitSensorComponent.cs b/Content.Server/Medical/SuitSensors/SuitSensorComponent.cs
index 96a85064d9..891a5a8a67 100644
--- a/Content.Server/Medical/SuitSensors/SuitSensorComponent.cs
+++ b/Content.Server/Medical/SuitSensors/SuitSensorComponent.cs
@@ -56,5 +56,19 @@ namespace Content.Server.Medical.SuitSensors
/// Last time when sensor updated owners status
///
public TimeSpan LastUpdate = TimeSpan.Zero;
+
+ ///
+ /// The station this suit sensor belongs to. If it's null the suit didn't spawn on a station and the sensor doesn't work.
+ ///
+ [DataField("station")]
+ public EntityUid? StationId = null;
+
+ ///
+ /// The server the suit sensor sends it state to.
+ /// The suit sensor will try connecting to a new server when no server is connected.
+ /// It does this by calling the servers entity system for performance reasons.
+ ///
+ [DataField("server")]
+ public string? ConnectedServer = null;
}
}
diff --git a/Content.Server/Medical/SuitSensors/SuitSensorSystem.cs b/Content.Server/Medical/SuitSensors/SuitSensorSystem.cs
index e512489dac..eecb894255 100644
--- a/Content.Server/Medical/SuitSensors/SuitSensorSystem.cs
+++ b/Content.Server/Medical/SuitSensors/SuitSensorSystem.cs
@@ -2,7 +2,9 @@ using Content.Server.Access.Systems;
using Content.Server.DeviceNetwork;
using Content.Server.DeviceNetwork.Components;
using Content.Server.DeviceNetwork.Systems;
+using Content.Server.Medical.CrewMonitoring;
using Content.Server.Popups;
+using Content.Server.Station.Systems;
using Content.Shared.Damage;
using Content.Shared.Examine;
using Content.Shared.Inventory.Events;
@@ -25,6 +27,8 @@ namespace Content.Server.Medical.SuitSensors
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
[Dependency] private readonly DeviceNetworkSystem _deviceNetworkSystem = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
+ [Dependency] private readonly CrewMonitoringServerSystem _monitoringServerSystem = default!;
+ [Dependency] private readonly StationSystem _stationSystem = default!;
[Dependency] private readonly SharedTransformSystem _xform = default!;
private const float UpdateRate = 1f;
@@ -57,27 +61,48 @@ namespace Content.Server.Medical.SuitSensors
var sensors = EntityManager.EntityQuery();
foreach (var (sensor, device) in sensors)
{
- if (device.TransmitFrequency is not uint frequency)
+ if (!device.TransmitFrequency.HasValue || !sensor.StationId.HasValue)
continue;
// check if sensor is ready to update
if (curTime - sensor.LastUpdate < sensor.UpdateRate)
continue;
- sensor.LastUpdate = curTime;
+
+ // Add a random offset to the next update time that isn't longer than the sensors update rate
+ sensor.LastUpdate = curTime.Add(TimeSpan.FromSeconds(_random.Next(0, sensor.UpdateRate.Seconds)));
// get sensor status
var status = GetSensorState(sensor.Owner, sensor);
if (status == null)
continue;
- // broadcast it to device network
+ //Retrieve active server address if the sensor isn't connected to a server
+ if (sensor.ConnectedServer == null)
+ {
+ if (!_monitoringServerSystem.TryGetActiveServerAddress(sensor.StationId.Value, out var address))
+ continue;
+
+ sensor.ConnectedServer = address;
+ }
+
+ // Send it to the connected server
var payload = SuitSensorToPacket(status);
- _deviceNetworkSystem.QueuePacket(sensor.Owner, null, payload, device: device);
+
+ // Clear the connected server if its address isn't on the network
+ if (!_deviceNetworkSystem.IsAddressPresent(device.DeviceNetId, sensor.ConnectedServer))
+ {
+ sensor.ConnectedServer = null;
+ continue;
+ }
+
+ _deviceNetworkSystem.QueuePacket(sensor.Owner, sensor.ConnectedServer, payload, device: device);
}
}
private void OnMapInit(EntityUid uid, SuitSensorComponent component, MapInitEvent args)
{
+ component.StationId = _stationSystem.GetOwningStation(uid);
+
// generate random mode
if (component.RandomMode)
{
@@ -277,7 +302,7 @@ namespace Content.Server.Medical.SuitSensors
}
///
- /// Serialize suit sensor status into device network package.
+ /// Serialize create a device network package from the suit sensors status.
///
public NetworkPayload SuitSensorToPacket(SuitSensorStatus status)
{
@@ -299,7 +324,7 @@ namespace Content.Server.Medical.SuitSensors
}
///
- /// Try to deserialize device network message into suit sensor status
+ /// Try to create the suit sensors status from the device network message
///
public SuitSensorStatus? PacketToSuitSensor(NetworkPayload payload)
{
diff --git a/Content.Shared/Medical/SuitSensor/SharedSuitSensor.cs b/Content.Shared/Medical/SuitSensor/SharedSuitSensor.cs
index d7aefaedc4..cdf3ee070c 100644
--- a/Content.Shared/Medical/SuitSensor/SharedSuitSensor.cs
+++ b/Content.Shared/Medical/SuitSensor/SharedSuitSensor.cs
@@ -51,5 +51,8 @@ namespace Content.Shared.Medical.SuitSensor
public const string NET_IS_ALIVE = "alive";
public const string NET_TOTAL_DAMAGE = "vitals";
public const string NET_CORDINATES = "cords";
+
+ ///Used by the CrewMonitoringServerSystem to send the status of all connected suit sensors to each crew monitor
+ public const string NET_STATUS_COLLECTION = "suit-status-collection";
}
}
diff --git a/Resources/Locale/en-US/devices/device-network.ftl b/Resources/Locale/en-US/devices/device-network.ftl
index e39a465d9a..fee17d9c87 100644
--- a/Resources/Locale/en-US/devices/device-network.ftl
+++ b/Resources/Locale/en-US/devices/device-network.ftl
@@ -1,6 +1,7 @@
# named frequencies
device-frequency-prototype-name-atmos = Atmospheric Devices
device-frequency-prototype-name-suit-sensors = Suit Sensors
+device-frequency-prototype-name-crew-monitor = Crew Monitor
device-frequency-prototype-name-lights = Smart Lights
device-frequency-prototype-name-mailing-units = Mailing Units
device-frequency-prototype-name-pdas = PDAs
diff --git a/Resources/Prototypes/Device/devicenet_frequencies.yml b/Resources/Prototypes/Device/devicenet_frequencies.yml
index f19598ac5c..3762129fdb 100644
--- a/Resources/Prototypes/Device/devicenet_frequencies.yml
+++ b/Resources/Prototypes/Device/devicenet_frequencies.yml
@@ -53,12 +53,19 @@
name: device-frequency-prototype-name-atmos
frequency: 1621
-# Only listen to this frequency if you are a health or GPS monitor. Otherwise you will just slow down the server by constantly receiving periodic broadcasts from every player-entity.
+# Only listen to this frequency if you are a crew monitor server. Otherwise you will just slow down the server by constantly receiving periodic broadcasts from every player-entity.
- type: deviceFrequency
id: SuitSensor
name: device-frequency-prototype-name-suit-sensors
frequency: 1262
+# Crew monitors listen to this for a list of suit sensor statuses
+- type: deviceFrequency
+ id: CrewMonitor
+ name: device-frequency-prototype-name-crew-monitor
+ frequency: 1261
+
+
# This frequency will likely have a LARGE number of listening entities. Please don't broadcast on this frequency.
- type: deviceFrequency
id: SmartLight #used by powered lights.
diff --git a/Resources/Prototypes/Entities/Clothing/Uniforms/base_clothinguniforms.yml b/Resources/Prototypes/Entities/Clothing/Uniforms/base_clothinguniforms.yml
index ac934f74c3..de52896e3b 100644
--- a/Resources/Prototypes/Entities/Clothing/Uniforms/base_clothinguniforms.yml
+++ b/Resources/Prototypes/Entities/Clothing/Uniforms/base_clothinguniforms.yml
@@ -35,7 +35,8 @@
deviceNetId: Wireless
transmitFrequencyId: SuitSensor
- type: WirelessNetworkConnection
- range: 500
+ range: 1200
+ - type: StationLimitedNetwork
- type: entity
abstract: true
diff --git a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml
index 6f3da2cce2..87d644fd6b 100644
--- a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml
+++ b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml
@@ -98,9 +98,10 @@
precision: 3
- type: DeviceNetwork
deviceNetId: Wireless
- receiveFrequencyId: SuitSensor
+ receiveFrequencyId: CrewMonitor
- type: WirelessNetworkConnection
range: 500
+ - type: StationLimitedNetwork
- type: Thieving
stripTimeReduction: 9999
stealthy: true
diff --git a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml
index 8e8a0800c1..42125ea3e4 100644
--- a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml
+++ b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml
@@ -176,7 +176,7 @@
ScanningModule: 1
Capacitor: 2
materialRequirements:
- Steel: 5
+ Steel: 5
Cable: 1
- type: entity
@@ -302,6 +302,21 @@
Glass: 5
Cable: 1
+- type: entity
+ id: CrewMonitoringServerMachineCircuitboard
+ parent: BaseMachineCircuitboard
+ name: crew monitoring server machine board
+ description: A machine printed circuit board for a crew monitoring server
+ components:
+ - type: MachineBoard
+ prototype: CrewMonitoringServer
+ requirements:
+ Capacitor: 1
+ ScanningModule: 2
+ materialRequirements:
+ Steel: 1
+ Cable: 2
+
- type: entity
id: CryoPodMachineCircuitboard
parent: BaseMachineCircuitboard
diff --git a/Resources/Prototypes/Entities/Objects/Specific/Medical/handheld_crew_monitor.yml b/Resources/Prototypes/Entities/Objects/Specific/Medical/handheld_crew_monitor.yml
index c5091fea7b..6e692044f6 100644
--- a/Resources/Prototypes/Entities/Objects/Specific/Medical/handheld_crew_monitor.yml
+++ b/Resources/Prototypes/Entities/Objects/Specific/Medical/handheld_crew_monitor.yml
@@ -17,8 +17,9 @@
- type: CrewMonitoringConsole
- type: DeviceNetwork
deviceNetId: Wireless
- receiveFrequencyId: SuitSensor
+ receiveFrequencyId: CrewMonitor
- type: WirelessNetworkConnection
range: 500
+ - type: StationLimitedNetwork
- type: StaticPrice
price: 500
diff --git a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml
index db8d0437e5..a3dfac668e 100644
--- a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml
+++ b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml
@@ -311,9 +311,9 @@
- type: CrewMonitoringConsole
- type: DeviceNetwork
deviceNetId: Wireless
- receiveFrequencyId: SuitSensor
+ receiveFrequencyId: CrewMonitor
- type: WirelessNetworkConnection
- range: 500
+ range: 1200
- type: entity
parent: BaseComputer
diff --git a/Resources/Prototypes/Entities/Structures/Machines/crew_monitor_server.yml b/Resources/Prototypes/Entities/Structures/Machines/crew_monitor_server.yml
new file mode 100644
index 0000000000..1e555edcf7
--- /dev/null
+++ b/Resources/Prototypes/Entities/Structures/Machines/crew_monitor_server.yml
@@ -0,0 +1,57 @@
+- type: entity
+ id: CrewMonitoringServer
+ parent: BaseMachinePowered
+ name: crew monitoring server
+ description: Receives and relays the status of all active suit sensors on the station.
+ components:
+ - type: Sprite
+ sprite: Structures/Machines/server.rsi
+ layers:
+ - state: server
+ - state: variant-crew
+ - type: Construction
+ graph: Machine
+ node: machine
+ containers:
+ - machine_board
+ - machine_parts
+ - type: Machine
+ board: CrewMonitoringServerMachineCircuitboard
+ - type: ContainerContainer
+ containers:
+ machine_board: !type:Container
+ machine_parts: !type:Container
+ - type: CrewMonitoringServer
+ - type: DeviceNetwork
+ deviceNetId: Wireless
+ transmitFrequencyId: CrewMonitor
+ receiveFrequencyId: SuitSensor
+ autoConnect: false
+ - type: WirelessNetworkConnection
+ range: 500
+ - type: StationLimitedNetwork
+ - type: ApcPowerReceiver
+ powerLoad: 200
+ priority: Low
+ - type: ExtensionCableReceiver
+ - type: Destructible
+ thresholds:
+ - trigger:
+ !type:DamageTrigger
+ damage: 300
+ behaviors:
+ - !type:DoActsBehavior
+ acts: ["Destruction"]
+ - !type:PlaySoundBehavior
+ sound:
+ path: /Audio/Effects/metalbreak.ogg
+ - !type:SpawnEntitiesBehavior
+ spawn:
+ SheetSteel1:
+ min: 1
+ max: 2
+ - type: AmbientSound
+ volume: -9
+ range: 5
+ sound:
+ path: /Audio/Ambience/Objects/server_fans.ogg
diff --git a/Resources/Textures/Structures/Machines/server.rsi/meta.json b/Resources/Textures/Structures/Machines/server.rsi/meta.json
index 1dc63798ab..25ff730243 100644
--- a/Resources/Textures/Structures/Machines/server.rsi/meta.json
+++ b/Resources/Textures/Structures/Machines/server.rsi/meta.json
@@ -26,6 +26,10 @@
{
"name": "server_o"
+ },
+ {
+ "name": "variant-crew"
+
}
]
}
diff --git a/Resources/Textures/Structures/Machines/server.rsi/variant-crew.png b/Resources/Textures/Structures/Machines/server.rsi/variant-crew.png
new file mode 100644
index 0000000000..e53a2ab166
Binary files /dev/null and b/Resources/Textures/Structures/Machines/server.rsi/variant-crew.png differ