diff --git a/Content.Server/Atmos/Monitor/Components/AtmosAlarmableComponent.cs b/Content.Server/Atmos/Monitor/Components/AtmosAlarmableComponent.cs
index c9435bdef6..c7fb878001 100644
--- a/Content.Server/Atmos/Monitor/Components/AtmosAlarmableComponent.cs
+++ b/Content.Server/Atmos/Monitor/Components/AtmosAlarmableComponent.cs
@@ -19,7 +19,10 @@ namespace Content.Server.Atmos.Monitor.Components
///
/// A component to add to device network devices if you want them to be alarmed
- /// by an atmospheric monitor.
+ /// by an atmospheric alarmer. This will store every single alert received, and
+ /// calculate the highest alert based on the alerts received. Equally, if you
+ /// link other alarmables to this, it will store the alerts from them to
+ /// calculate the highest network alert.
///
[RegisterComponent]
public sealed class AtmosAlarmableComponent : Component
@@ -27,6 +30,9 @@ namespace Content.Server.Atmos.Monitor.Components
[ViewVariables]
public List LinkedMonitors { get; set; } = new();
+ [ViewVariables]
+ public Dictionary NetworkAlarmStates = new();
+
[ViewVariables] public AtmosMonitorAlarmType LastAlarmState = AtmosMonitorAlarmType.Normal;
[ViewVariables] public AtmosMonitorAlarmType HighestNetworkState = AtmosMonitorAlarmType.Normal;
[ViewVariables] public bool IgnoreAlarms { get; set; } = false;
diff --git a/Content.Server/Atmos/Monitor/Components/AtmosAlarmingComponent.cs b/Content.Server/Atmos/Monitor/Components/AtmosAlarmingComponent.cs
new file mode 100644
index 0000000000..3ee10cf106
--- /dev/null
+++ b/Content.Server/Atmos/Monitor/Components/AtmosAlarmingComponent.cs
@@ -0,0 +1,14 @@
+namespace Content.Server.Atmos.Monitor.Components;
+
+[RegisterComponent]
+public sealed class AtmosAlarmingComponent : Component
+{
+ ///
+ /// All registered receivers in this alarmer.
+ ///
+ public HashSet RegisteredReceivers = new();
+
+ // Somebody should do this someday. I'll leave it here as a reminder,
+ // just in case.
+ // public string StationAlarmMonitorFrequencyId
+}
diff --git a/Content.Server/Atmos/Monitor/Systems/AtmosAlarmableSystem.cs b/Content.Server/Atmos/Monitor/Systems/AtmosAlarmableSystem.cs
index b38916567e..56ca5f134f 100644
--- a/Content.Server/Atmos/Monitor/Systems/AtmosAlarmableSystem.cs
+++ b/Content.Server/Atmos/Monitor/Systems/AtmosAlarmableSystem.cs
@@ -1,10 +1,13 @@
+using System.Diagnostics.CodeAnalysis;
using Content.Server.Atmos.Monitor.Components;
using Content.Server.DeviceNetwork;
using Content.Server.DeviceNetwork.Components;
using Content.Server.DeviceNetwork.Systems;
+using Content.Server.Power.Components;
using Content.Shared.Atmos.Monitor;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
+using Robust.Shared.Utility;
namespace Content.Server.Atmos.Monitor.Systems
{
@@ -12,9 +15,26 @@ namespace Content.Server.Atmos.Monitor.Systems
{
[Dependency] private readonly AppearanceSystem _appearance = default!;
[Dependency] private readonly AudioSystem _audioSystem = default!;
+
+ ///
+ /// Syncs alerts from this alarm receiver to other alarm receivers.
+ /// Creates a network effect as a result. Note: if the alert receiver
+ /// is not aware of the device beforehand, it will not sync.
+ ///
+ public const string SyncAlerts = "atmos_alarmable_sync_alerts";
+
public override void Initialize()
{
SubscribeLocalEvent(OnPacketRecv);
+ SubscribeLocalEvent(OnPowerChange);
+ }
+
+ private void OnPowerChange(EntityUid uid, AtmosAlarmableComponent component, PowerChangedEvent args)
+ {
+ if (!args.Powered)
+ {
+ Reset(uid, component);
+ }
}
private void OnPacketRecv(EntityUid uid, AtmosAlarmableComponent component, DeviceNetworkPacketEvent args)
@@ -24,25 +44,108 @@ namespace Content.Server.Atmos.Monitor.Systems
if (!EntityManager.TryGetComponent(uid, out DeviceNetworkComponent? netConn))
return;
- if (args.Data.TryGetValue(DeviceNetworkConstants.Command, out string? cmd)
- && cmd == AtmosMonitorSystem.AtmosMonitorAlarmCmd)
+ if (args.Data.TryGetValue(DeviceNetworkConstants.Command, out string? cmd))
{
- // does it have a state & network max state?
- // does it have a source?
- // and can this be alarmed by the source?
- // if so, raise an alarm
- if (args.Data.TryGetValue(DeviceNetworkConstants.CmdSetState, out AtmosMonitorAlarmType state)
- && args.Data.TryGetValue(AtmosMonitorSystem.AtmosMonitorAlarmNetMax, out AtmosMonitorAlarmType netMax)
- && args.Data.TryGetValue(AtmosMonitorSystem.AtmosMonitorAlarmSrc, out string? source)
- && component.AlarmedByPrototypes.Contains(source))
- {
- component.LastAlarmState = state;
- component.HighestNetworkState = netMax;
- UpdateAppearance(uid, netMax);
- PlayAlertSound(uid, netMax, component);
- RaiseLocalEvent(component.Owner, new AtmosMonitorAlarmEvent(state, netMax), true);
- }
+ return;
}
+
+ switch (cmd)
+ {
+ case AtmosMonitorSystem.AtmosMonitorAlarmCmd:
+ // Set the alert state, and then cache it so we can calculate
+ // the maximum alarm state at all times.
+ if (args.Data.TryGetValue(DeviceNetworkConstants.CmdSetState, out AtmosMonitorAlarmType state))
+ {
+ if (!component.NetworkAlarmStates.ContainsKey(args.SenderAddress))
+ {
+ component.NetworkAlarmStates.Add(args.SenderAddress, state);
+ }
+ else
+ {
+ component.NetworkAlarmStates[args.SenderAddress] = state;
+ }
+
+ if (!TryGetHighestAlert(uid, out var netMax, component))
+ {
+ netMax = AtmosMonitorAlarmType.Normal;
+ }
+
+ component.LastAlarmState = netMax.Value;
+
+ UpdateAppearance(uid, netMax.Value);
+ PlayAlertSound(uid, netMax.Value, component);
+ RaiseLocalEvent(component.Owner, new AtmosMonitorAlarmEvent(state, netMax.Value), true);
+ }
+ break;
+ case SyncAlerts:
+ // Synchronize alerts, but only if they're already known by this monitor.
+ // This should help eliminate the chain effect, especially with
+ if (!args.Data.TryGetValue(SyncAlerts,
+ out IReadOnlyDictionary? alarms))
+ {
+ break;
+ }
+
+ foreach (var (key, alarm) in alarms)
+ {
+ if (component.NetworkAlarmStates.ContainsKey(key))
+ {
+ component.NetworkAlarmStates[key] = alarm;
+ }
+ }
+
+ if (TryGetHighestAlert(uid, out var maxAlert, component)
+ && component.LastAlarmState < maxAlert)
+ {
+ component.LastAlarmState = maxAlert.Value;
+ RaiseLocalEvent(uid, new AtmosMonitorAlarmEvent(maxAlert.Value, maxAlert.Value));
+ }
+
+ break;
+ }
+ }
+
+ ///
+ /// Resets the state of this alarmable to normal.
+ ///
+ ///
+ ///
+ public void Reset(EntityUid uid, AtmosAlarmableComponent? alarmable = null)
+ {
+ if (!Resolve(uid, ref alarmable))
+ {
+ return;
+ }
+
+ alarmable.LastAlarmState = AtmosMonitorAlarmType.Normal;
+ alarmable.NetworkAlarmStates.Clear();
+
+ RaiseLocalEvent(uid, new AtmosMonitorAlarmEvent(AtmosMonitorAlarmType.Normal, AtmosMonitorAlarmType.Normal));
+ }
+
+ ///
+ /// Tries to get the highest possible alert stored in this alarm.
+ ///
+ ///
+ ///
+ ///
+ ///
+ private bool TryGetHighestAlert(EntityUid uid, [NotNullWhen(true)] out AtmosMonitorAlarmType? alarm,
+ AtmosAlarmableComponent? alarmable = null)
+ {
+ alarm = null;
+
+ if (!Resolve(uid, ref alarmable))
+ {
+ return false;
+ }
+
+ foreach (var alarmState in alarmable.NetworkAlarmStates.Values)
+ {
+ alarm = alarm < alarmState ? alarmState : alarm;
+ }
+
+ return alarm != null;
}
private void PlayAlertSound(EntityUid uid, AtmosMonitorAlarmType alarm, AtmosAlarmableComponent alarmable)
diff --git a/Content.Server/Atmos/Monitor/Systems/AtmosAlarmingSystem.cs b/Content.Server/Atmos/Monitor/Systems/AtmosAlarmingSystem.cs
new file mode 100644
index 0000000000..4addc104cb
--- /dev/null
+++ b/Content.Server/Atmos/Monitor/Systems/AtmosAlarmingSystem.cs
@@ -0,0 +1,31 @@
+using Content.Server.Atmos.Monitor.Components;
+
+namespace Content.Server.Atmos.Monitor.Systems;
+
+///
+/// System that alarms AtmosAlarmables via DeviceNetwork.
+/// This is one way, and is usually triggered by an event.
+///
+public sealed class AtmosAlarmingSystem : EntitySystem
+{
+ ///
+ /// The alarm command key.
+ ///
+ public const string AtmosAlarmCmd = "atmos_alarming_alarm_cmd";
+
+ ///
+ /// Register command. Registers this address so that the alarm can send
+ /// to the given device.
+ ///
+ public const string AtmosAlarmRegisterCmd = "atmos_alarming_register_cmd";
+
+ ///
+ /// Alarm data. Contains the alert passed into this alarmer.
+ ///
+ public const string AtmosAlarmData = "atmos_alarming_alarm_data";
+
+ private void OnAlert(EntityUid uid, AtmosAlarmingComponent component, AtmosMonitorAlarmEvent args)
+ {
+
+ }
+}
diff --git a/Content.Server/Atmos/Monitor/Systems/AtmosMonitoringSystem.cs b/Content.Server/Atmos/Monitor/Systems/AtmosMonitoringSystem.cs
index fffb45f0a5..493e4d301c 100644
--- a/Content.Server/Atmos/Monitor/Systems/AtmosMonitoringSystem.cs
+++ b/Content.Server/Atmos/Monitor/Systems/AtmosMonitoringSystem.cs
@@ -51,6 +51,8 @@ namespace Content.Server.Atmos.Monitor.Systems
public const string AtmosMonitorSetThresholdCmd = "atmos_monitor_set_threshold";
+ public const string AtmosMonitorGetDataCmd = "atmos_monitor_get_data";
+
// Packet data
///
/// Data response that contains the threshold types in an atmos monitor alarm.
@@ -82,7 +84,6 @@ namespace Content.Server.Atmos.Monitor.Systems
{
SubscribeLocalEvent(OnAtmosMonitorInit);
SubscribeLocalEvent(OnAtmosMonitorStartup);
- SubscribeLocalEvent(OnAtmosMonitorShutdown);
SubscribeLocalEvent(OnAtmosUpdate);
SubscribeLocalEvent(OnFireEvent);
SubscribeLocalEvent(OnPowerChangedEvent);
@@ -115,59 +116,6 @@ namespace Content.Server.Atmos.Monitor.Systems
_atmosDeviceSystem.LeaveAtmosphere(atmosDeviceComponent);
return;
}
-
- _checkPos.Add(uid);
- }
-
- private void OnAtmosMonitorShutdown(EntityUid uid, AtmosMonitorComponent component, ComponentShutdown args)
- {
- if (_checkPos.Contains(uid)) _checkPos.Remove(uid);
- }
-
- // hackiest shit ever but there's no PostStartup event
- private HashSet _checkPos = new();
-
- public override void Update(float frameTime)
- {
- /* NOPE
- foreach (var uid in _checkPos)
- OpenAirOrReposition(uid);
- */
- }
-
- private void OpenAirOrReposition(EntityUid uid, AtmosMonitorComponent? component = null, AppearanceComponent? appearance = null)
- {
- if (!Resolve(uid, ref component, ref appearance)) return;
-
- var transform = Transform(component.Owner);
-
- if (transform.GridUid == null)
- return;
-
- // atmos alarms will first attempt to get the air
- // directly underneath it - if not, then it will
- // instead place itself directly in front of the tile
- // it is facing, and then visually shift itself back
- // via sprite offsets (SS13 style but fuck it)
- var coords = transform.Coordinates;
- var pos = _transformSystem.GetGridOrMapTilePosition(uid, transform);
-
- if (_atmosphereSystem.IsTileAirBlocked(transform.GridUid.Value, pos))
- {
- var rotPos = transform.LocalRotation.RotateVec(new Vector2(0, -1));
- transform.Anchored = false;
- coords = coords.Offset(rotPos);
- transform.Coordinates = coords;
-
- appearance.SetData(AtmosMonitorVisuals.Offset, - new Vector2i(0, -1));
-
- transform.Anchored = true;
- }
-
- GasMixture? air = _atmosphereSystem.GetContainingMixture(uid, true);
- component.TileGas = air;
-
- _checkPos.Remove(uid);
}
private void BeforePacketRecv(EntityUid uid, AtmosMonitorComponent component, BeforePacketSentEvent args)
@@ -188,8 +136,11 @@ namespace Content.Server.Atmos.Monitor.Systems
// ignore packets from self, ignore from different frequency
if (netConn.Address == args.SenderAddress) return;
+ var responseKey = string.Empty;
+
switch (cmd)
{
+ /*
// sync on alarm or explicit sync
case AtmosMonitorAlarmCmd:
case AtmosMonitorAlarmSyncCmd:
@@ -199,10 +150,12 @@ namespace Content.Server.Atmos.Monitor.Systems
&& !component.NetworkAlarmStates.TryAdd(args.SenderAddress, state))
component.NetworkAlarmStates[args.SenderAddress] = state;
break;
+ */
case AtmosMonitorAlarmResetCmd:
Reset(uid);
// Don't clear alarm states here.
break;
+ /*
case AtmosMonitorAlarmResetAllCmd:
if (args.Data.TryGetValue(AtmosMonitorAlarmSrc, out string? resetSrc)
&& alarmable.AlarmedByPrototypes.Contains(resetSrc))
@@ -211,6 +164,7 @@ namespace Content.Server.Atmos.Monitor.Systems
component.NetworkAlarmStates.Clear();
}
break;
+ */
case AtmosMonitorSetThresholdCmd:
if (args.Data.TryGetValue(AtmosMonitorThresholdData, out AtmosAlarmThreshold? thresholdData)
&& args.Data.TryGetValue(AtmosMonitorThresholdDataType, out AtmosMonitorThresholdType? thresholdType))
@@ -220,9 +174,9 @@ namespace Content.Server.Atmos.Monitor.Systems
}
break;
- case AirAlarmSystem.AirAlarmSyncCmd:
+ case AtmosMonitorGetDataCmd:
var payload = new NetworkPayload();
- payload.Add(DeviceNetworkConstants.Command, AirAlarmSystem.AirAlarmSyncData);
+ payload.Add(DeviceNetworkConstants.Command, AtmosMonitorAtmosData);
if (component.TileGas != null)
{
var gases = new Dictionary();
@@ -231,7 +185,7 @@ namespace Content.Server.Atmos.Monitor.Systems
gases.Add(gas, component.TileGas.GetMoles(gas));
}
- payload.Add(AirAlarmSystem.AirAlarmSyncData, new AtmosSensorData(
+ payload.Add(AtmosMonitorAtmosData, new AtmosSensorData(
component.TileGas.Pressure,
component.TileGas.Temperature,
component.TileGas.TotalMoles,
@@ -259,10 +213,6 @@ namespace Content.Server.Atmos.Monitor.Systems
_atmosDeviceSystem.LeaveAtmosphere(atmosDeviceComponent);
component.TileGas = null;
}
-
- // clear memory when power cycled
- component.LastAlarmState = AtmosMonitorAlarmType.Normal;
- component.NetworkAlarmStates.Clear();
}
else if (args.Powered)
{