From a2d18e7a6d8a9012b1ed6e1a9135b35ff02665ae Mon Sep 17 00:00:00 2001 From: vulppine Date: Sat, 20 Aug 2022 07:36:53 -0700 Subject: [PATCH] starts moving alarm sync logic to alarmables --- .../Components/AtmosAlarmableComponent.cs | 8 +- .../Components/AtmosAlarmingComponent.cs | 14 ++ .../Monitor/Systems/AtmosAlarmableSystem.cs | 137 +++++++++++++++--- .../Monitor/Systems/AtmosAlarmingSystem.cs | 31 ++++ .../Monitor/Systems/AtmosMonitoringSystem.cs | 72 ++------- 5 files changed, 183 insertions(+), 79 deletions(-) create mode 100644 Content.Server/Atmos/Monitor/Components/AtmosAlarmingComponent.cs create mode 100644 Content.Server/Atmos/Monitor/Systems/AtmosAlarmingSystem.cs 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) {