file-scoped namespaces
This commit is contained in:
@@ -3,25 +3,24 @@ using Content.Shared.Atmos.Monitor.Components;
|
||||
using Content.Shared.Atmos.Piping.Unary.Components;
|
||||
using Robust.Shared.Network;
|
||||
|
||||
namespace Content.Server.Atmos.Monitor.Components
|
||||
namespace Content.Server.Atmos.Monitor.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed class AirAlarmComponent : Component
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed class AirAlarmComponent : Component
|
||||
{
|
||||
[ViewVariables] public AirAlarmMode CurrentMode { get; set; } = AirAlarmMode.Filtering;
|
||||
[ViewVariables] public AirAlarmMode CurrentMode { get; set; } = AirAlarmMode.Filtering;
|
||||
|
||||
// Remember to null this afterwards.
|
||||
[ViewVariables] public IAirAlarmModeUpdate? CurrentModeUpdater { get; set; }
|
||||
// Remember to null this afterwards.
|
||||
[ViewVariables] public IAirAlarmModeUpdate? CurrentModeUpdater { get; set; }
|
||||
|
||||
[ViewVariables] public AirAlarmTab CurrentTab { get; set; }
|
||||
[ViewVariables] public AirAlarmTab CurrentTab { get; set; }
|
||||
|
||||
public Dictionary<string, IAtmosDeviceData> DeviceData = new();
|
||||
public Dictionary<string, GasVentPumpData> VentData = new();
|
||||
public Dictionary<string, GasVentScrubberData> ScrubberData = new();
|
||||
public Dictionary<string, AtmosSensorData> SensorData = new();
|
||||
public Dictionary<string, IAtmosDeviceData> DeviceData = new();
|
||||
public Dictionary<string, GasVentPumpData> VentData = new();
|
||||
public Dictionary<string, GasVentScrubberData> ScrubberData = new();
|
||||
public Dictionary<string, AtmosSensorData> SensorData = new();
|
||||
|
||||
public HashSet<NetUserId> ActivePlayers = new();
|
||||
public HashSet<NetUserId> ActivePlayers = new();
|
||||
|
||||
public bool CanSync = true;
|
||||
}
|
||||
public bool CanSync = true;
|
||||
}
|
||||
|
||||
@@ -3,60 +3,58 @@ using Content.Shared.Tag;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set;
|
||||
|
||||
namespace Content.Server.Atmos.Monitor.Components
|
||||
namespace Content.Server.Atmos.Monitor.Components;
|
||||
// AtmosAlarmables are entities that can be alarmed
|
||||
// by a linked AtmosMonitor (alarmer?) if a threshold
|
||||
// is passed in some way. The intended use is to
|
||||
// do something in case something dangerous happens,
|
||||
// e.g., activate firelocks in case a temperature
|
||||
// threshold is reached
|
||||
//
|
||||
// It goes:
|
||||
//
|
||||
// AtmosMonitor -> AtmosDeviceUpdateEvent
|
||||
// -> Threshold calculation
|
||||
// -> AtmosAlarmEvent
|
||||
// -> Everything linked to that monitor (targetted)
|
||||
|
||||
/// <summary>
|
||||
/// A component to add to device network devices if you want them to be alarmed
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class AtmosAlarmableComponent : Component
|
||||
{
|
||||
// AtmosAlarmables are entities that can be alarmed
|
||||
// by a linked AtmosMonitor (alarmer?) if a threshold
|
||||
// is passed in some way. The intended use is to
|
||||
// do something in case something dangerous happens,
|
||||
// e.g., activate firelocks in case a temperature
|
||||
// threshold is reached
|
||||
//
|
||||
// It goes:
|
||||
//
|
||||
// AtmosMonitor -> AtmosDeviceUpdateEvent
|
||||
// -> Threshold calculation
|
||||
// -> AtmosAlarmEvent
|
||||
// -> Everything linked to that monitor (targetted)
|
||||
[ViewVariables]
|
||||
public readonly Dictionary<string, AtmosMonitorAlarmType> NetworkAlarmStates = new();
|
||||
|
||||
[ViewVariables] public AtmosMonitorAlarmType LastAlarmState = AtmosMonitorAlarmType.Normal;
|
||||
|
||||
[ViewVariables] public bool IgnoreAlarms { get; set; } = false;
|
||||
|
||||
[DataField("alarmSound")]
|
||||
public SoundSpecifier AlarmSound { get; set; } = new SoundPathSpecifier("/Audio/Machines/alarm.ogg");
|
||||
|
||||
[DataField("alarmVolume")]
|
||||
public float AlarmVolume { get; set; } = -10;
|
||||
|
||||
/// <summary>
|
||||
/// A component to add to device network devices if you want them to be alarmed
|
||||
/// 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.
|
||||
/// List of tags to check for when synchronizing alarms.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class AtmosAlarmableComponent : Component
|
||||
{
|
||||
[ViewVariables]
|
||||
public readonly Dictionary<string, AtmosMonitorAlarmType> NetworkAlarmStates = new();
|
||||
[DataField("syncWith", customTypeSerializer: typeof(PrototypeIdHashSetSerializer<TagPrototype>))]
|
||||
public HashSet<string> SyncWithTags { get; } = new();
|
||||
|
||||
[ViewVariables] public AtmosMonitorAlarmType LastAlarmState = AtmosMonitorAlarmType.Normal;
|
||||
[DataField("monitorAlertTypes")]
|
||||
public HashSet<AtmosMonitorThresholdType>? MonitorAlertTypes { get; }
|
||||
|
||||
[ViewVariables] public bool IgnoreAlarms { get; set; } = false;
|
||||
|
||||
[DataField("alarmSound")]
|
||||
public SoundSpecifier AlarmSound { get; set; } = new SoundPathSpecifier("/Audio/Machines/alarm.ogg");
|
||||
|
||||
[DataField("alarmVolume")]
|
||||
public float AlarmVolume { get; set; } = -10;
|
||||
|
||||
/// <summary>
|
||||
/// List of tags to check for when synchronizing alarms.
|
||||
/// </summary>
|
||||
[DataField("syncWith", customTypeSerializer: typeof(PrototypeIdHashSetSerializer<TagPrototype>))]
|
||||
public HashSet<string> SyncWithTags { get; } = new();
|
||||
|
||||
[DataField("monitorAlertTypes")]
|
||||
public HashSet<AtmosMonitorThresholdType>? MonitorAlertTypes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// If this device should receive only. If it can only
|
||||
/// receive, that means that attempting to sync outwards
|
||||
/// will result in nothing happening.
|
||||
/// </summary>
|
||||
[DataField("receiveOnly")]
|
||||
public bool ReceiveOnly { get; }
|
||||
}
|
||||
/// <summary>
|
||||
/// If this device should receive only. If it can only
|
||||
/// receive, that means that attempting to sync outwards
|
||||
/// will result in nothing happening.
|
||||
/// </summary>
|
||||
[DataField("receiveOnly")]
|
||||
public bool ReceiveOnly { get; }
|
||||
}
|
||||
|
||||
@@ -3,68 +3,67 @@ using Content.Shared.Atmos.Monitor;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.Atmos.Monitor.Components
|
||||
namespace Content.Server.Atmos.Monitor.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed class AtmosMonitorComponent : Component
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed class AtmosMonitorComponent : Component
|
||||
{
|
||||
// Whether this monitor can send alarms,
|
||||
// or recieve atmos command events.
|
||||
//
|
||||
// Useful for wires; i.e., pulsing a monitor wire
|
||||
// will make it send an alert, and cutting
|
||||
// it will make it so that alerts are no longer
|
||||
// sent/receieved.
|
||||
//
|
||||
// Note that this cancels every single network
|
||||
// event, including ones that may not be
|
||||
// related to atmos monitor events.
|
||||
[ViewVariables]
|
||||
public bool NetEnabled = true;
|
||||
// Whether this monitor can send alarms,
|
||||
// or recieve atmos command events.
|
||||
//
|
||||
// Useful for wires; i.e., pulsing a monitor wire
|
||||
// will make it send an alert, and cutting
|
||||
// it will make it so that alerts are no longer
|
||||
// sent/receieved.
|
||||
//
|
||||
// Note that this cancels every single network
|
||||
// event, including ones that may not be
|
||||
// related to atmos monitor events.
|
||||
[ViewVariables]
|
||||
public bool NetEnabled = true;
|
||||
|
||||
[DataField("temperatureThreshold", customTypeSerializer: (typeof(PrototypeIdSerializer<AtmosAlarmThreshold>)))]
|
||||
public readonly string? TemperatureThresholdId;
|
||||
[DataField("temperatureThreshold", customTypeSerializer: (typeof(PrototypeIdSerializer<AtmosAlarmThreshold>)))]
|
||||
public readonly string? TemperatureThresholdId;
|
||||
|
||||
[ViewVariables]
|
||||
public AtmosAlarmThreshold? TemperatureThreshold;
|
||||
[ViewVariables]
|
||||
public AtmosAlarmThreshold? TemperatureThreshold;
|
||||
|
||||
[DataField("pressureThreshold", customTypeSerializer: (typeof(PrototypeIdSerializer<AtmosAlarmThreshold>)))]
|
||||
public readonly string? PressureThresholdId;
|
||||
[DataField("pressureThreshold", customTypeSerializer: (typeof(PrototypeIdSerializer<AtmosAlarmThreshold>)))]
|
||||
public readonly string? PressureThresholdId;
|
||||
|
||||
[ViewVariables]
|
||||
public AtmosAlarmThreshold? PressureThreshold;
|
||||
[ViewVariables]
|
||||
public AtmosAlarmThreshold? PressureThreshold;
|
||||
|
||||
// monitor fire - much different from temperature
|
||||
// since there's events for fire, setting this to true
|
||||
// will make the atmos monitor act like a smoke detector,
|
||||
// immediately signalling danger if there's a fire
|
||||
[DataField("monitorFire")]
|
||||
public bool MonitorFire = false;
|
||||
// monitor fire - much different from temperature
|
||||
// since there's events for fire, setting this to true
|
||||
// will make the atmos monitor act like a smoke detector,
|
||||
// immediately signalling danger if there's a fire
|
||||
[DataField("monitorFire")]
|
||||
public bool MonitorFire = false;
|
||||
|
||||
// really messy but this is parsed at runtime after
|
||||
// prototypes are initialized, there's no
|
||||
// way without implementing a new
|
||||
// type serializer
|
||||
[DataField("gasThresholds")]
|
||||
public Dictionary<Gas, string>? GasThresholdIds;
|
||||
// really messy but this is parsed at runtime after
|
||||
// prototypes are initialized, there's no
|
||||
// way without implementing a new
|
||||
// type serializer
|
||||
[DataField("gasThresholds")]
|
||||
public Dictionary<Gas, string>? GasThresholdIds;
|
||||
|
||||
[ViewVariables]
|
||||
public Dictionary<Gas, AtmosAlarmThreshold>? GasThresholds;
|
||||
[ViewVariables]
|
||||
public Dictionary<Gas, AtmosAlarmThreshold>? GasThresholds;
|
||||
|
||||
// Stores a reference to the gas on the tile this is on.
|
||||
[ViewVariables]
|
||||
public GasMixture? TileGas;
|
||||
// Stores a reference to the gas on the tile this is on.
|
||||
[ViewVariables]
|
||||
public GasMixture? TileGas;
|
||||
|
||||
// Stores the last alarm state of this alarm.
|
||||
[ViewVariables]
|
||||
public AtmosMonitorAlarmType LastAlarmState = AtmosMonitorAlarmType.Normal;
|
||||
// Stores the last alarm state of this alarm.
|
||||
[ViewVariables]
|
||||
public AtmosMonitorAlarmType LastAlarmState = AtmosMonitorAlarmType.Normal;
|
||||
|
||||
[ViewVariables] public HashSet<AtmosMonitorThresholdType> TrippedThresholds = new();
|
||||
[ViewVariables] public HashSet<AtmosMonitorThresholdType> TrippedThresholds = new();
|
||||
|
||||
/// <summary>
|
||||
/// Registered devices in this atmos monitor. Alerts will be sent directly
|
||||
/// to these devices.
|
||||
/// </summary>
|
||||
[ViewVariables] public HashSet<string> RegisteredDevices = new();
|
||||
}
|
||||
/// <summary>
|
||||
/// Registered devices in this atmos monitor. Alerts will be sent directly
|
||||
/// to these devices.
|
||||
/// </summary>
|
||||
[ViewVariables] public HashSet<string> RegisteredDevices = new();
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
namespace Content.Server.Atmos.Monitor.Components
|
||||
namespace Content.Server.Atmos.Monitor.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed class FireAlarmComponent : Component
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed class FireAlarmComponent : Component
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,210 +5,209 @@ using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Monitor.Components;
|
||||
using Content.Shared.Atmos.Piping.Unary.Components;
|
||||
|
||||
namespace Content.Server.Atmos.Monitor
|
||||
namespace Content.Server.Atmos.Monitor;
|
||||
|
||||
/// <summary>
|
||||
/// This is an interface that air alarm modes use
|
||||
/// in order to execute the defined modes.
|
||||
/// </summary>
|
||||
public interface IAirAlarmMode
|
||||
{
|
||||
// This is executed the moment the mode
|
||||
// is set. This is to ensure that 'dumb'
|
||||
// modes such as Filter/Panic are immediately
|
||||
// set.
|
||||
/// <summary>
|
||||
/// Executed the mode is set on an air alarm.
|
||||
/// This is to ensure that modes like Filter/Panic
|
||||
/// are immediately set.
|
||||
/// </summary>
|
||||
public void Execute(EntityUid uid);
|
||||
}
|
||||
|
||||
// IAirAlarmModeUpdate
|
||||
//
|
||||
// This is an interface that AirAlarmSystem uses
|
||||
// in order to 'update' air alarm modes so that
|
||||
// modes like Replace can be implemented.
|
||||
/// <summary>
|
||||
/// An interface that AirAlarmSystem uses
|
||||
/// in order to update air alarm modes that
|
||||
/// need updating (e.g., Replace)
|
||||
/// </summary>
|
||||
public interface IAirAlarmModeUpdate
|
||||
{
|
||||
/// <summary>
|
||||
/// This is an interface that air alarm modes use
|
||||
/// in order to execute the defined modes.
|
||||
/// This is checked by AirAlarmSystem when
|
||||
/// a mode is updated. This should be set
|
||||
/// to a DeviceNetwork address, or some
|
||||
/// unique identifier that ID's the
|
||||
/// owner of the mode's executor.
|
||||
/// </summary>
|
||||
public interface IAirAlarmMode
|
||||
{
|
||||
// This is executed the moment the mode
|
||||
// is set. This is to ensure that 'dumb'
|
||||
// modes such as Filter/Panic are immediately
|
||||
// set.
|
||||
/// <summary>
|
||||
/// Executed the mode is set on an air alarm.
|
||||
/// This is to ensure that modes like Filter/Panic
|
||||
/// are immediately set.
|
||||
/// </summary>
|
||||
public void Execute(EntityUid uid);
|
||||
}
|
||||
|
||||
// IAirAlarmModeUpdate
|
||||
//
|
||||
// This is an interface that AirAlarmSystem uses
|
||||
// in order to 'update' air alarm modes so that
|
||||
// modes like Replace can be implemented.
|
||||
public string NetOwner { get; set; }
|
||||
/// <summary>
|
||||
/// An interface that AirAlarmSystem uses
|
||||
/// in order to update air alarm modes that
|
||||
/// need updating (e.g., Replace)
|
||||
/// This is executed every time the air alarm
|
||||
/// update loop is fully executed. This should
|
||||
/// be where all the logic goes.
|
||||
/// </summary>
|
||||
public interface IAirAlarmModeUpdate
|
||||
public void Update(EntityUid uid);
|
||||
}
|
||||
|
||||
public sealed class AirAlarmModeFactory
|
||||
{
|
||||
private static IAirAlarmMode _filterMode = new AirAlarmFilterMode();
|
||||
private static IAirAlarmMode _fillMode = new AirAlarmFillMode();
|
||||
private static IAirAlarmMode _panicMode = new AirAlarmPanicMode();
|
||||
private static IAirAlarmMode _noneMode = new AirAlarmNoneMode();
|
||||
|
||||
// still not a fan since ReplaceMode must have an allocation
|
||||
// but it's whatever
|
||||
public static IAirAlarmMode? ModeToExecutor(AirAlarmMode mode) => mode switch
|
||||
{
|
||||
/// <summary>
|
||||
/// This is checked by AirAlarmSystem when
|
||||
/// a mode is updated. This should be set
|
||||
/// to a DeviceNetwork address, or some
|
||||
/// unique identifier that ID's the
|
||||
/// owner of the mode's executor.
|
||||
/// </summary>
|
||||
public string NetOwner { get; set; }
|
||||
/// <summary>
|
||||
/// This is executed every time the air alarm
|
||||
/// update loop is fully executed. This should
|
||||
/// be where all the logic goes.
|
||||
/// </summary>
|
||||
public void Update(EntityUid uid);
|
||||
AirAlarmMode.Filtering => _filterMode,
|
||||
AirAlarmMode.Fill => _fillMode,
|
||||
AirAlarmMode.Panic => _panicMode,
|
||||
AirAlarmMode.None => _noneMode,
|
||||
AirAlarmMode.Replace => new AirAlarmReplaceMode(),
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
// like a tiny little EntitySystem
|
||||
public abstract class AirAlarmModeExecutor : IAirAlarmMode
|
||||
{
|
||||
[Dependency] public readonly IEntityManager EntityManager = default!;
|
||||
public readonly DeviceNetworkSystem DeviceNetworkSystem;
|
||||
public readonly AirAlarmSystem AirAlarmSystem;
|
||||
|
||||
public abstract void Execute(EntityUid uid);
|
||||
|
||||
public AirAlarmModeExecutor()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
DeviceNetworkSystem = EntitySystem.Get<DeviceNetworkSystem>();
|
||||
AirAlarmSystem = EntitySystem.Get<AirAlarmSystem>();
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class AirAlarmModeFactory
|
||||
public sealed class AirAlarmNoneMode : AirAlarmModeExecutor
|
||||
{
|
||||
public override void Execute(EntityUid uid)
|
||||
{
|
||||
private static IAirAlarmMode _filterMode = new AirAlarmFilterMode();
|
||||
private static IAirAlarmMode _fillMode = new AirAlarmFillMode();
|
||||
private static IAirAlarmMode _panicMode = new AirAlarmPanicMode();
|
||||
private static IAirAlarmMode _noneMode = new AirAlarmNoneMode();
|
||||
if (!EntityManager.TryGetComponent(uid, out AirAlarmComponent? alarm))
|
||||
return;
|
||||
|
||||
// still not a fan since ReplaceMode must have an allocation
|
||||
// but it's whatever
|
||||
public static IAirAlarmMode? ModeToExecutor(AirAlarmMode mode) => mode switch
|
||||
foreach (var (addr, device) in alarm.VentData)
|
||||
{
|
||||
AirAlarmMode.Filtering => _filterMode,
|
||||
AirAlarmMode.Fill => _fillMode,
|
||||
AirAlarmMode.Panic => _panicMode,
|
||||
AirAlarmMode.None => _noneMode,
|
||||
AirAlarmMode.Replace => new AirAlarmReplaceMode(),
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
// like a tiny little EntitySystem
|
||||
public abstract class AirAlarmModeExecutor : IAirAlarmMode
|
||||
{
|
||||
[Dependency] public readonly IEntityManager EntityManager = default!;
|
||||
public readonly DeviceNetworkSystem DeviceNetworkSystem;
|
||||
public readonly AirAlarmSystem AirAlarmSystem;
|
||||
|
||||
public abstract void Execute(EntityUid uid);
|
||||
|
||||
public AirAlarmModeExecutor()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
DeviceNetworkSystem = EntitySystem.Get<DeviceNetworkSystem>();
|
||||
AirAlarmSystem = EntitySystem.Get<AirAlarmSystem>();
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class AirAlarmNoneMode : AirAlarmModeExecutor
|
||||
{
|
||||
public override void Execute(EntityUid uid)
|
||||
{
|
||||
if (!EntityManager.TryGetComponent(uid, out AirAlarmComponent? alarm))
|
||||
return;
|
||||
|
||||
foreach (var (addr, device) in alarm.VentData)
|
||||
{
|
||||
device.Enabled = false;
|
||||
AirAlarmSystem.SetData(uid, addr, device);
|
||||
}
|
||||
|
||||
foreach (var (addr, device) in alarm.ScrubberData)
|
||||
{
|
||||
device.Enabled = false;
|
||||
AirAlarmSystem.SetData(uid, addr, device);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class AirAlarmFilterMode : AirAlarmModeExecutor
|
||||
{
|
||||
public override void Execute(EntityUid uid)
|
||||
{
|
||||
if (!EntityManager.TryGetComponent(uid, out AirAlarmComponent? alarm))
|
||||
return;
|
||||
|
||||
foreach (var (addr, device) in alarm.VentData)
|
||||
{
|
||||
AirAlarmSystem.SetData(uid, addr, GasVentPumpData.FilterModePreset);
|
||||
}
|
||||
|
||||
foreach (var (addr, device) in alarm.ScrubberData)
|
||||
{
|
||||
AirAlarmSystem.SetData(uid, addr, GasVentScrubberData.FilterModePreset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class AirAlarmPanicMode : AirAlarmModeExecutor
|
||||
{
|
||||
public override void Execute(EntityUid uid)
|
||||
{
|
||||
if (!EntityManager.TryGetComponent(uid, out AirAlarmComponent? alarm))
|
||||
return;
|
||||
|
||||
foreach (var (addr, device) in alarm.VentData)
|
||||
{
|
||||
AirAlarmSystem.SetData(uid, addr, GasVentPumpData.PanicModePreset);
|
||||
}
|
||||
|
||||
foreach (var (addr, device) in alarm.ScrubberData)
|
||||
{
|
||||
AirAlarmSystem.SetData(uid, addr, GasVentScrubberData.PanicModePreset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class AirAlarmFillMode : AirAlarmModeExecutor
|
||||
{
|
||||
public override void Execute(EntityUid uid)
|
||||
{
|
||||
if (!EntityManager.TryGetComponent(uid, out AirAlarmComponent? alarm))
|
||||
return;
|
||||
|
||||
foreach (var (addr, device) in alarm.VentData)
|
||||
{
|
||||
AirAlarmSystem.SetData(uid, addr, GasVentPumpData.FillModePreset);
|
||||
}
|
||||
|
||||
foreach (var (addr, device) in alarm.ScrubberData)
|
||||
{
|
||||
AirAlarmSystem.SetData(uid, addr, GasVentScrubberData.FillModePreset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class AirAlarmReplaceMode : AirAlarmModeExecutor, IAirAlarmModeUpdate
|
||||
{
|
||||
private AirAlarmComponent? _alarm;
|
||||
private float _lastPressure = Atmospherics.OneAtmosphere;
|
||||
|
||||
public string NetOwner { get; set; } = string.Empty;
|
||||
|
||||
public override void Execute(EntityUid uid)
|
||||
{
|
||||
if (!EntityManager.TryGetComponent(uid, out _alarm))
|
||||
return;
|
||||
|
||||
SetSiphon(uid);
|
||||
device.Enabled = false;
|
||||
AirAlarmSystem.SetData(uid, addr, device);
|
||||
}
|
||||
|
||||
public void Update(EntityUid uid)
|
||||
foreach (var (addr, device) in alarm.ScrubberData)
|
||||
{
|
||||
if (_alarm == null)
|
||||
return;
|
||||
|
||||
// just a little pointer
|
||||
|
||||
_lastPressure = AirAlarmSystem.CalculatePressureAverage(_alarm);
|
||||
if (_lastPressure <= 0.2f) // anything below and it might get stuck
|
||||
{
|
||||
AirAlarmSystem.SetMode(uid, NetOwner!, AirAlarmMode.Filtering, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetSiphon(EntityUid uid)
|
||||
{
|
||||
foreach (var (addr, device) in _alarm!.VentData)
|
||||
{
|
||||
AirAlarmSystem.SetData(uid, addr, GasVentPumpData.ReplaceModePreset);
|
||||
}
|
||||
|
||||
foreach (var (addr, device) in _alarm!.ScrubberData)
|
||||
{
|
||||
AirAlarmSystem.SetData(uid, addr, GasVentScrubberData.ReplaceModePreset);
|
||||
}
|
||||
device.Enabled = false;
|
||||
AirAlarmSystem.SetData(uid, addr, device);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class AirAlarmFilterMode : AirAlarmModeExecutor
|
||||
{
|
||||
public override void Execute(EntityUid uid)
|
||||
{
|
||||
if (!EntityManager.TryGetComponent(uid, out AirAlarmComponent? alarm))
|
||||
return;
|
||||
|
||||
foreach (var (addr, device) in alarm.VentData)
|
||||
{
|
||||
AirAlarmSystem.SetData(uid, addr, GasVentPumpData.FilterModePreset);
|
||||
}
|
||||
|
||||
foreach (var (addr, device) in alarm.ScrubberData)
|
||||
{
|
||||
AirAlarmSystem.SetData(uid, addr, GasVentScrubberData.FilterModePreset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class AirAlarmPanicMode : AirAlarmModeExecutor
|
||||
{
|
||||
public override void Execute(EntityUid uid)
|
||||
{
|
||||
if (!EntityManager.TryGetComponent(uid, out AirAlarmComponent? alarm))
|
||||
return;
|
||||
|
||||
foreach (var (addr, device) in alarm.VentData)
|
||||
{
|
||||
AirAlarmSystem.SetData(uid, addr, GasVentPumpData.PanicModePreset);
|
||||
}
|
||||
|
||||
foreach (var (addr, device) in alarm.ScrubberData)
|
||||
{
|
||||
AirAlarmSystem.SetData(uid, addr, GasVentScrubberData.PanicModePreset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class AirAlarmFillMode : AirAlarmModeExecutor
|
||||
{
|
||||
public override void Execute(EntityUid uid)
|
||||
{
|
||||
if (!EntityManager.TryGetComponent(uid, out AirAlarmComponent? alarm))
|
||||
return;
|
||||
|
||||
foreach (var (addr, device) in alarm.VentData)
|
||||
{
|
||||
AirAlarmSystem.SetData(uid, addr, GasVentPumpData.FillModePreset);
|
||||
}
|
||||
|
||||
foreach (var (addr, device) in alarm.ScrubberData)
|
||||
{
|
||||
AirAlarmSystem.SetData(uid, addr, GasVentScrubberData.FillModePreset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class AirAlarmReplaceMode : AirAlarmModeExecutor, IAirAlarmModeUpdate
|
||||
{
|
||||
private AirAlarmComponent? _alarm;
|
||||
private float _lastPressure = Atmospherics.OneAtmosphere;
|
||||
|
||||
public string NetOwner { get; set; } = string.Empty;
|
||||
|
||||
public override void Execute(EntityUid uid)
|
||||
{
|
||||
if (!EntityManager.TryGetComponent(uid, out _alarm))
|
||||
return;
|
||||
|
||||
SetSiphon(uid);
|
||||
}
|
||||
|
||||
public void Update(EntityUid uid)
|
||||
{
|
||||
if (_alarm == null)
|
||||
return;
|
||||
|
||||
// just a little pointer
|
||||
|
||||
_lastPressure = AirAlarmSystem.CalculatePressureAverage(_alarm);
|
||||
if (_lastPressure <= 0.2f) // anything below and it might get stuck
|
||||
{
|
||||
AirAlarmSystem.SetMode(uid, NetOwner!, AirAlarmMode.Filtering, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetSiphon(EntityUid uid)
|
||||
{
|
||||
foreach (var (addr, device) in _alarm!.VentData)
|
||||
{
|
||||
AirAlarmSystem.SetData(uid, addr, GasVentPumpData.ReplaceModePreset);
|
||||
}
|
||||
|
||||
foreach (var (addr, device) in _alarm!.ScrubberData)
|
||||
{
|
||||
AirAlarmSystem.SetData(uid, addr, GasVentScrubberData.ReplaceModePreset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -11,297 +11,296 @@ using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Atmos.Monitor.Systems
|
||||
namespace Content.Server.Atmos.Monitor.Systems;
|
||||
|
||||
public sealed class AtmosAlarmableSystem : EntitySystem
|
||||
{
|
||||
public sealed class AtmosAlarmableSystem : EntitySystem
|
||||
[Dependency] private readonly AppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly AudioSystem _audioSystem = default!;
|
||||
[Dependency] private readonly DeviceNetworkSystem _deviceNet = default!;
|
||||
|
||||
/// <summary>
|
||||
/// An alarm. Has three valid states: Normal, Warning, Danger.
|
||||
/// Will attempt to fetch the tags from the alarming entity
|
||||
/// to send over.
|
||||
/// </summary>
|
||||
public const string AlertCmd = "atmos_alarm";
|
||||
|
||||
public const string AlertSource = "atmos_alarm_source";
|
||||
|
||||
public const string AlertTypes = "atmos_alarm_types";
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public const string SyncAlerts = "atmos_alarmable_sync_alerts";
|
||||
|
||||
public const string ResetAll = "atmos_alarmable_reset_all";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
[Dependency] private readonly AppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly AudioSystem _audioSystem = default!;
|
||||
[Dependency] private readonly DeviceNetworkSystem _deviceNet = default!;
|
||||
SubscribeLocalEvent<AtmosAlarmableComponent, DeviceNetworkPacketEvent>(OnPacketRecv);
|
||||
SubscribeLocalEvent<AtmosAlarmableComponent, PowerChangedEvent>(OnPowerChange);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An alarm. Has three valid states: Normal, Warning, Danger.
|
||||
/// Will attempt to fetch the tags from the alarming entity
|
||||
/// to send over.
|
||||
/// </summary>
|
||||
public const string AlertCmd = "atmos_alarm";
|
||||
|
||||
public const string AlertSource = "atmos_alarm_source";
|
||||
|
||||
public const string AlertTypes = "atmos_alarm_types";
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public const string SyncAlerts = "atmos_alarmable_sync_alerts";
|
||||
|
||||
public const string ResetAll = "atmos_alarmable_reset_all";
|
||||
|
||||
public override void Initialize()
|
||||
private void OnPowerChange(EntityUid uid, AtmosAlarmableComponent component, PowerChangedEvent args)
|
||||
{
|
||||
if (!args.Powered)
|
||||
{
|
||||
SubscribeLocalEvent<AtmosAlarmableComponent, DeviceNetworkPacketEvent>(OnPacketRecv);
|
||||
SubscribeLocalEvent<AtmosAlarmableComponent, PowerChangedEvent>(OnPowerChange);
|
||||
Reset(uid, component);
|
||||
}
|
||||
|
||||
private void OnPowerChange(EntityUid uid, AtmosAlarmableComponent component, PowerChangedEvent args)
|
||||
else
|
||||
{
|
||||
if (!args.Powered)
|
||||
{
|
||||
Reset(uid, component);
|
||||
}
|
||||
else
|
||||
{
|
||||
TryUpdateAlert(
|
||||
uid,
|
||||
TryGetHighestAlert(uid, out var alarm) ? alarm.Value : AtmosMonitorAlarmType.Normal,
|
||||
component,
|
||||
TryUpdateAlert(
|
||||
uid,
|
||||
TryGetHighestAlert(uid, out var alarm) ? alarm.Value : AtmosMonitorAlarmType.Normal,
|
||||
component,
|
||||
false);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPacketRecv(EntityUid uid, AtmosAlarmableComponent component, DeviceNetworkPacketEvent args)
|
||||
{
|
||||
if (component.IgnoreAlarms) return;
|
||||
|
||||
if (!EntityManager.TryGetComponent(uid, out DeviceNetworkComponent? netConn))
|
||||
return;
|
||||
|
||||
if (!args.Data.TryGetValue(DeviceNetworkConstants.Command, out string? cmd)
|
||||
|| !args.Data.TryGetValue(AlertSource, out HashSet<string>? sourceTags))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var isValid = sourceTags.Any(source => component.SyncWithTags.Contains(source));
|
||||
|
||||
if (!isValid)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (cmd)
|
||||
{
|
||||
case AlertCmd:
|
||||
// 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))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (args.Data.TryGetValue(AlertTypes, out HashSet<AtmosMonitorThresholdType>? types) && component.MonitorAlertTypes != null)
|
||||
{
|
||||
isValid = types.Any(type => component.MonitorAlertTypes.Contains(type));
|
||||
}
|
||||
|
||||
if (!component.NetworkAlarmStates.ContainsKey(args.SenderAddress))
|
||||
{
|
||||
if (!isValid)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
component.NetworkAlarmStates.Add(args.SenderAddress, state);
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is because if the alert is no longer valid,
|
||||
// it may mean that the threshold we need to look at has
|
||||
// been removed from the threshold types passed:
|
||||
// basically, we need to reset this state to normal here.
|
||||
component.NetworkAlarmStates[args.SenderAddress] = isValid ? state : AtmosMonitorAlarmType.Normal;
|
||||
}
|
||||
|
||||
if (!TryGetHighestAlert(uid, out var netMax, component))
|
||||
{
|
||||
netMax = AtmosMonitorAlarmType.Normal;
|
||||
}
|
||||
|
||||
TryUpdateAlert(uid, netMax.Value, component);
|
||||
|
||||
break;
|
||||
case ResetAll:
|
||||
Reset(uid, component);
|
||||
break;
|
||||
case SyncAlerts:
|
||||
if (!args.Data.TryGetValue(SyncAlerts,
|
||||
out IReadOnlyDictionary<string, AtmosMonitorAlarmType>? alarms))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
foreach (var (key, alarm) in alarms)
|
||||
{
|
||||
if (!component.NetworkAlarmStates.TryAdd(key, alarm))
|
||||
{
|
||||
component.NetworkAlarmStates[key] = alarm;
|
||||
}
|
||||
}
|
||||
|
||||
if (TryGetHighestAlert(uid, out var maxAlert, component))
|
||||
{
|
||||
TryUpdateAlert(uid, maxAlert.Value, component);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void TryUpdateAlert(EntityUid uid, AtmosMonitorAlarmType type, AtmosAlarmableComponent alarmable, bool sync = true)
|
||||
{
|
||||
if (alarmable.LastAlarmState == type)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (sync)
|
||||
{
|
||||
SyncAlertsToNetwork(uid, null, alarmable);
|
||||
}
|
||||
|
||||
alarmable.LastAlarmState = type;
|
||||
UpdateAppearance(uid, type);
|
||||
PlayAlertSound(uid, type, alarmable);
|
||||
RaiseLocalEvent(uid, new AtmosAlarmEvent(type), true);
|
||||
}
|
||||
|
||||
public void SyncAlertsToNetwork(EntityUid uid, string? address = null, AtmosAlarmableComponent? alarmable = null, TagComponent? tags = null)
|
||||
{
|
||||
if (!Resolve(uid, ref alarmable, ref tags) || alarmable.ReceiveOnly)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var payload = new NetworkPayload
|
||||
{
|
||||
[DeviceNetworkConstants.Command] = SyncAlerts,
|
||||
[SyncAlerts] = alarmable.NetworkAlarmStates,
|
||||
[AlertSource] = tags.Tags
|
||||
};
|
||||
|
||||
_deviceNet.QueuePacket(uid, address, payload);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Forces this alarmable to have a specific alert. This will not be reset until the alarmable
|
||||
/// is manually reset. This will store the alarmable as a device in its network states.
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="alarmType"></param>
|
||||
/// <param name="alarmable"></param>
|
||||
public void ForceAlert(EntityUid uid, AtmosMonitorAlarmType alarmType,
|
||||
AtmosAlarmableComponent? alarmable = null, DeviceNetworkComponent? devNet = null, TagComponent? tags = null)
|
||||
{
|
||||
if (!Resolve(uid, ref alarmable, ref devNet, ref tags))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TryUpdateAlert(uid, alarmType, alarmable, false);
|
||||
|
||||
if (alarmable.ReceiveOnly)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!alarmable.NetworkAlarmStates.TryAdd(devNet.Address, alarmType))
|
||||
{
|
||||
alarmable.NetworkAlarmStates[devNet.Address] = alarmType;
|
||||
}
|
||||
|
||||
var payload = new NetworkPayload
|
||||
{
|
||||
[DeviceNetworkConstants.Command] = AlertCmd,
|
||||
[DeviceNetworkConstants.CmdSetState] = alarmType,
|
||||
[AlertSource] = tags.Tags
|
||||
};
|
||||
|
||||
_deviceNet.QueuePacket(uid, null, payload);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the state of this alarmable to normal.
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="alarmable"></param>
|
||||
public void Reset(EntityUid uid, AtmosAlarmableComponent? alarmable = null)
|
||||
{
|
||||
if (!Resolve(uid, ref alarmable))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TryUpdateAlert(uid, AtmosMonitorAlarmType.Normal, alarmable, false);
|
||||
|
||||
alarmable.NetworkAlarmStates.Clear();
|
||||
}
|
||||
|
||||
public void ResetAllOnNetwork(EntityUid uid, AtmosAlarmableComponent? alarmable = null, TagComponent? tags = null)
|
||||
{
|
||||
if (!Resolve(uid, ref alarmable, ref tags) || alarmable.ReceiveOnly)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Reset(uid, alarmable);
|
||||
|
||||
var payload = new NetworkPayload
|
||||
{
|
||||
[DeviceNetworkConstants.Command] = ResetAll,
|
||||
[AlertSource] = tags.Tags
|
||||
};
|
||||
|
||||
_deviceNet.QueuePacket(uid, null, payload);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get the highest possible alert stored in this alarm.
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="alarm"></param>
|
||||
/// <param name="alarmable"></param>
|
||||
/// <returns></returns>
|
||||
public 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 == null || alarm < alarmState ? alarmState : alarm;
|
||||
}
|
||||
|
||||
return alarm != null;
|
||||
}
|
||||
|
||||
private void PlayAlertSound(EntityUid uid, AtmosMonitorAlarmType alarm, AtmosAlarmableComponent alarmable)
|
||||
{
|
||||
if (alarm == AtmosMonitorAlarmType.Danger)
|
||||
{
|
||||
_audioSystem.PlayPvs(alarmable.AlarmSound, uid, AudioParams.Default.WithVolume(alarmable.AlarmVolume));
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateAppearance(EntityUid uid, AtmosMonitorAlarmType alarm)
|
||||
{
|
||||
_appearance.SetData(uid, AtmosMonitorVisuals.AlarmType, alarm);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class AtmosAlarmEvent : EntityEventArgs
|
||||
private void OnPacketRecv(EntityUid uid, AtmosAlarmableComponent component, DeviceNetworkPacketEvent args)
|
||||
{
|
||||
public AtmosMonitorAlarmType AlarmType { get; }
|
||||
if (component.IgnoreAlarms) return;
|
||||
|
||||
public AtmosAlarmEvent(AtmosMonitorAlarmType netMax)
|
||||
if (!EntityManager.TryGetComponent(uid, out DeviceNetworkComponent? netConn))
|
||||
return;
|
||||
|
||||
if (!args.Data.TryGetValue(DeviceNetworkConstants.Command, out string? cmd)
|
||||
|| !args.Data.TryGetValue(AlertSource, out HashSet<string>? sourceTags))
|
||||
{
|
||||
AlarmType = netMax;
|
||||
return;
|
||||
}
|
||||
|
||||
var isValid = sourceTags.Any(source => component.SyncWithTags.Contains(source));
|
||||
|
||||
if (!isValid)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (cmd)
|
||||
{
|
||||
case AlertCmd:
|
||||
// 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))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (args.Data.TryGetValue(AlertTypes, out HashSet<AtmosMonitorThresholdType>? types) && component.MonitorAlertTypes != null)
|
||||
{
|
||||
isValid = types.Any(type => component.MonitorAlertTypes.Contains(type));
|
||||
}
|
||||
|
||||
if (!component.NetworkAlarmStates.ContainsKey(args.SenderAddress))
|
||||
{
|
||||
if (!isValid)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
component.NetworkAlarmStates.Add(args.SenderAddress, state);
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is because if the alert is no longer valid,
|
||||
// it may mean that the threshold we need to look at has
|
||||
// been removed from the threshold types passed:
|
||||
// basically, we need to reset this state to normal here.
|
||||
component.NetworkAlarmStates[args.SenderAddress] = isValid ? state : AtmosMonitorAlarmType.Normal;
|
||||
}
|
||||
|
||||
if (!TryGetHighestAlert(uid, out var netMax, component))
|
||||
{
|
||||
netMax = AtmosMonitorAlarmType.Normal;
|
||||
}
|
||||
|
||||
TryUpdateAlert(uid, netMax.Value, component);
|
||||
|
||||
break;
|
||||
case ResetAll:
|
||||
Reset(uid, component);
|
||||
break;
|
||||
case SyncAlerts:
|
||||
if (!args.Data.TryGetValue(SyncAlerts,
|
||||
out IReadOnlyDictionary<string, AtmosMonitorAlarmType>? alarms))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
foreach (var (key, alarm) in alarms)
|
||||
{
|
||||
if (!component.NetworkAlarmStates.TryAdd(key, alarm))
|
||||
{
|
||||
component.NetworkAlarmStates[key] = alarm;
|
||||
}
|
||||
}
|
||||
|
||||
if (TryGetHighestAlert(uid, out var maxAlert, component))
|
||||
{
|
||||
TryUpdateAlert(uid, maxAlert.Value, component);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void TryUpdateAlert(EntityUid uid, AtmosMonitorAlarmType type, AtmosAlarmableComponent alarmable, bool sync = true)
|
||||
{
|
||||
if (alarmable.LastAlarmState == type)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (sync)
|
||||
{
|
||||
SyncAlertsToNetwork(uid, null, alarmable);
|
||||
}
|
||||
|
||||
alarmable.LastAlarmState = type;
|
||||
UpdateAppearance(uid, type);
|
||||
PlayAlertSound(uid, type, alarmable);
|
||||
RaiseLocalEvent(uid, new AtmosAlarmEvent(type), true);
|
||||
}
|
||||
|
||||
public void SyncAlertsToNetwork(EntityUid uid, string? address = null, AtmosAlarmableComponent? alarmable = null, TagComponent? tags = null)
|
||||
{
|
||||
if (!Resolve(uid, ref alarmable, ref tags) || alarmable.ReceiveOnly)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var payload = new NetworkPayload
|
||||
{
|
||||
[DeviceNetworkConstants.Command] = SyncAlerts,
|
||||
[SyncAlerts] = alarmable.NetworkAlarmStates,
|
||||
[AlertSource] = tags.Tags
|
||||
};
|
||||
|
||||
_deviceNet.QueuePacket(uid, address, payload);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Forces this alarmable to have a specific alert. This will not be reset until the alarmable
|
||||
/// is manually reset. This will store the alarmable as a device in its network states.
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="alarmType"></param>
|
||||
/// <param name="alarmable"></param>
|
||||
public void ForceAlert(EntityUid uid, AtmosMonitorAlarmType alarmType,
|
||||
AtmosAlarmableComponent? alarmable = null, DeviceNetworkComponent? devNet = null, TagComponent? tags = null)
|
||||
{
|
||||
if (!Resolve(uid, ref alarmable, ref devNet, ref tags))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TryUpdateAlert(uid, alarmType, alarmable, false);
|
||||
|
||||
if (alarmable.ReceiveOnly)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!alarmable.NetworkAlarmStates.TryAdd(devNet.Address, alarmType))
|
||||
{
|
||||
alarmable.NetworkAlarmStates[devNet.Address] = alarmType;
|
||||
}
|
||||
|
||||
var payload = new NetworkPayload
|
||||
{
|
||||
[DeviceNetworkConstants.Command] = AlertCmd,
|
||||
[DeviceNetworkConstants.CmdSetState] = alarmType,
|
||||
[AlertSource] = tags.Tags
|
||||
};
|
||||
|
||||
_deviceNet.QueuePacket(uid, null, payload);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the state of this alarmable to normal.
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="alarmable"></param>
|
||||
public void Reset(EntityUid uid, AtmosAlarmableComponent? alarmable = null)
|
||||
{
|
||||
if (!Resolve(uid, ref alarmable))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TryUpdateAlert(uid, AtmosMonitorAlarmType.Normal, alarmable, false);
|
||||
|
||||
alarmable.NetworkAlarmStates.Clear();
|
||||
}
|
||||
|
||||
public void ResetAllOnNetwork(EntityUid uid, AtmosAlarmableComponent? alarmable = null, TagComponent? tags = null)
|
||||
{
|
||||
if (!Resolve(uid, ref alarmable, ref tags) || alarmable.ReceiveOnly)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Reset(uid, alarmable);
|
||||
|
||||
var payload = new NetworkPayload
|
||||
{
|
||||
[DeviceNetworkConstants.Command] = ResetAll,
|
||||
[AlertSource] = tags.Tags
|
||||
};
|
||||
|
||||
_deviceNet.QueuePacket(uid, null, payload);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get the highest possible alert stored in this alarm.
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="alarm"></param>
|
||||
/// <param name="alarmable"></param>
|
||||
/// <returns></returns>
|
||||
public 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 == null || alarm < alarmState ? alarmState : alarm;
|
||||
}
|
||||
|
||||
return alarm != null;
|
||||
}
|
||||
|
||||
private void PlayAlertSound(EntityUid uid, AtmosMonitorAlarmType alarm, AtmosAlarmableComponent alarmable)
|
||||
{
|
||||
if (alarm == AtmosMonitorAlarmType.Danger)
|
||||
{
|
||||
_audioSystem.PlayPvs(alarmable.AlarmSound, uid, AudioParams.Default.WithVolume(alarmable.AlarmVolume));
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateAppearance(EntityUid uid, AtmosMonitorAlarmType alarm)
|
||||
{
|
||||
_appearance.SetData(uid, AtmosMonitorVisuals.AlarmType, alarm);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class AtmosAlarmEvent : EntityEventArgs
|
||||
{
|
||||
public AtmosMonitorAlarmType AlarmType { get; }
|
||||
|
||||
public AtmosAlarmEvent(AtmosMonitorAlarmType netMax)
|
||||
{
|
||||
AlarmType = netMax;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,368 +16,365 @@ using Robust.Shared.Audio;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Atmos.Monitor.Systems
|
||||
namespace Content.Server.Atmos.Monitor.Systems;
|
||||
|
||||
// AtmosMonitorSystem. Grabs all the AtmosAlarmables connected
|
||||
// to it via local APC net, and starts sending updates of the
|
||||
// current atmosphere. Monitors fire (which always triggers as
|
||||
// a danger), and atmos (which triggers based on set thresholds).
|
||||
public sealed class AtmosMonitorSystem : EntitySystem
|
||||
{
|
||||
// AtmosMonitorSystem. Grabs all the AtmosAlarmables connected
|
||||
// to it via local APC net, and starts sending updates of the
|
||||
// current atmosphere. Monitors fire (which always triggers as
|
||||
// a danger), and atmos (which triggers based on set thresholds).
|
||||
public sealed class AtmosMonitorSystem : EntitySystem
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
|
||||
[Dependency] private readonly AtmosDeviceSystem _atmosDeviceSystem = default!;
|
||||
[Dependency] private readonly DeviceNetworkSystem _deviceNetSystem = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
// Commands
|
||||
public const string AtmosMonitorSetThresholdCmd = "atmos_monitor_set_threshold";
|
||||
|
||||
// Packet data
|
||||
public const string AtmosMonitorThresholdData = "atmos_monitor_threshold_data";
|
||||
|
||||
public const string AtmosMonitorThresholdDataType = "atmos_monitor_threshold_type";
|
||||
|
||||
public const string AtmosMonitorThresholdGasType = "atmos_monitor_threshold_gas";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
|
||||
[Dependency] private readonly AtmosDeviceSystem _atmosDeviceSystem = default!;
|
||||
[Dependency] private readonly DeviceNetworkSystem _deviceNetSystem = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
SubscribeLocalEvent<AtmosMonitorComponent, ComponentInit>(OnAtmosMonitorInit);
|
||||
SubscribeLocalEvent<AtmosMonitorComponent, ComponentStartup>(OnAtmosMonitorStartup);
|
||||
SubscribeLocalEvent<AtmosMonitorComponent, AtmosDeviceUpdateEvent>(OnAtmosUpdate);
|
||||
SubscribeLocalEvent<AtmosMonitorComponent, TileFireEvent>(OnFireEvent);
|
||||
SubscribeLocalEvent<AtmosMonitorComponent, PowerChangedEvent>(OnPowerChangedEvent);
|
||||
SubscribeLocalEvent<AtmosMonitorComponent, BeforePacketSentEvent>(BeforePacketRecv);
|
||||
SubscribeLocalEvent<AtmosMonitorComponent, DeviceNetworkPacketEvent>(OnPacketRecv);
|
||||
}
|
||||
|
||||
// Commands
|
||||
public const string AtmosMonitorSetThresholdCmd = "atmos_monitor_set_threshold";
|
||||
private void OnAtmosMonitorInit(EntityUid uid, AtmosMonitorComponent component, ComponentInit args)
|
||||
{
|
||||
if (component.TemperatureThresholdId != null)
|
||||
component.TemperatureThreshold = new(_prototypeManager.Index<AtmosAlarmThreshold>(component.TemperatureThresholdId));
|
||||
|
||||
// Packet data
|
||||
public const string AlertTypes = "atmos_monitor_alert_types";
|
||||
if (component.PressureThresholdId != null)
|
||||
component.PressureThreshold = new(_prototypeManager.Index<AtmosAlarmThreshold>(component.PressureThresholdId));
|
||||
|
||||
public const string AtmosMonitorThresholdData = "atmos_monitor_threshold_data";
|
||||
|
||||
public const string AtmosMonitorThresholdDataType = "atmos_monitor_threshold_type";
|
||||
|
||||
public const string AtmosMonitorThresholdGasType = "atmos_monitor_threshold_gas";
|
||||
|
||||
public override void Initialize()
|
||||
if (component.GasThresholdIds != null)
|
||||
{
|
||||
SubscribeLocalEvent<AtmosMonitorComponent, ComponentInit>(OnAtmosMonitorInit);
|
||||
SubscribeLocalEvent<AtmosMonitorComponent, ComponentStartup>(OnAtmosMonitorStartup);
|
||||
SubscribeLocalEvent<AtmosMonitorComponent, AtmosDeviceUpdateEvent>(OnAtmosUpdate);
|
||||
SubscribeLocalEvent<AtmosMonitorComponent, TileFireEvent>(OnFireEvent);
|
||||
SubscribeLocalEvent<AtmosMonitorComponent, PowerChangedEvent>(OnPowerChangedEvent);
|
||||
SubscribeLocalEvent<AtmosMonitorComponent, BeforePacketSentEvent>(BeforePacketRecv);
|
||||
SubscribeLocalEvent<AtmosMonitorComponent, DeviceNetworkPacketEvent>(OnPacketRecv);
|
||||
}
|
||||
|
||||
private void OnAtmosMonitorInit(EntityUid uid, AtmosMonitorComponent component, ComponentInit args)
|
||||
{
|
||||
if (component.TemperatureThresholdId != null)
|
||||
component.TemperatureThreshold = new(_prototypeManager.Index<AtmosAlarmThreshold>(component.TemperatureThresholdId));
|
||||
|
||||
if (component.PressureThresholdId != null)
|
||||
component.PressureThreshold = new(_prototypeManager.Index<AtmosAlarmThreshold>(component.PressureThresholdId));
|
||||
|
||||
if (component.GasThresholdIds != null)
|
||||
{
|
||||
component.GasThresholds = new();
|
||||
foreach (var (gas, id) in component.GasThresholdIds)
|
||||
if (_prototypeManager.TryIndex<AtmosAlarmThreshold>(id, out var gasThreshold))
|
||||
component.GasThresholds.Add(gas, new(gasThreshold));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAtmosMonitorStartup(EntityUid uid, AtmosMonitorComponent component, ComponentStartup args)
|
||||
{
|
||||
if (!HasComp<ApcPowerReceiverComponent>(uid)
|
||||
&& TryComp<AtmosDeviceComponent>(uid, out var atmosDeviceComponent))
|
||||
{
|
||||
_atmosDeviceSystem.LeaveAtmosphere(atmosDeviceComponent);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void BeforePacketRecv(EntityUid uid, AtmosMonitorComponent component, BeforePacketSentEvent args)
|
||||
{
|
||||
if (!component.NetEnabled) args.Cancel();
|
||||
}
|
||||
|
||||
private void OnPacketRecv(EntityUid uid, AtmosMonitorComponent component, DeviceNetworkPacketEvent args)
|
||||
{
|
||||
// sync the internal 'last alarm state' from
|
||||
// the other alarms, so that we can calculate
|
||||
// the highest network alarm state at any time
|
||||
if (!args.Data.TryGetValue(DeviceNetworkConstants.Command, out string? cmd))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (cmd)
|
||||
{
|
||||
case AtmosDeviceNetworkSystem.RegisterDevice:
|
||||
component.RegisteredDevices.Add(args.SenderAddress);
|
||||
break;
|
||||
case AtmosAlarmableSystem.ResetAll:
|
||||
Reset(uid);
|
||||
// Don't clear alarm states here.
|
||||
break;
|
||||
case AtmosMonitorSetThresholdCmd:
|
||||
if (args.Data.TryGetValue(AtmosMonitorThresholdData, out AtmosAlarmThreshold? thresholdData)
|
||||
&& args.Data.TryGetValue(AtmosMonitorThresholdDataType, out AtmosMonitorThresholdType? thresholdType))
|
||||
{
|
||||
args.Data.TryGetValue(AtmosMonitorThresholdGasType, out Gas? gas);
|
||||
SetThreshold(uid, thresholdType.Value, thresholdData, gas);
|
||||
}
|
||||
|
||||
break;
|
||||
case AtmosDeviceNetworkSystem.SyncData:
|
||||
var payload = new NetworkPayload();
|
||||
payload.Add(DeviceNetworkConstants.Command, AtmosDeviceNetworkSystem.SyncData);
|
||||
if (component.TileGas != null)
|
||||
{
|
||||
var gases = new Dictionary<Gas, float>();
|
||||
foreach (var gas in Enum.GetValues<Gas>())
|
||||
{
|
||||
gases.Add(gas, component.TileGas.GetMoles(gas));
|
||||
}
|
||||
|
||||
payload.Add(AtmosDeviceNetworkSystem.SyncData, new AtmosSensorData(
|
||||
component.TileGas.Pressure,
|
||||
component.TileGas.Temperature,
|
||||
component.TileGas.TotalMoles,
|
||||
component.LastAlarmState,
|
||||
gases,
|
||||
component.PressureThreshold ?? new(),
|
||||
component.TemperatureThreshold ?? new(),
|
||||
component.GasThresholds ?? new()
|
||||
));
|
||||
}
|
||||
|
||||
_deviceNetSystem.QueuePacket(uid, args.SenderAddress, payload);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPowerChangedEvent(EntityUid uid, AtmosMonitorComponent component, PowerChangedEvent args)
|
||||
{
|
||||
if (TryComp<AtmosDeviceComponent>(uid, out var atmosDeviceComponent))
|
||||
{
|
||||
if (!args.Powered)
|
||||
{
|
||||
if (atmosDeviceComponent.JoinedGrid != null)
|
||||
{
|
||||
_atmosDeviceSystem.LeaveAtmosphere(atmosDeviceComponent);
|
||||
component.TileGas = null;
|
||||
}
|
||||
}
|
||||
else if (args.Powered)
|
||||
{
|
||||
if (atmosDeviceComponent.JoinedGrid == null)
|
||||
{
|
||||
_atmosDeviceSystem.JoinAtmosphere(atmosDeviceComponent);
|
||||
var air = _atmosphereSystem.GetContainingMixture(uid, true);
|
||||
component.TileGas = air;
|
||||
}
|
||||
|
||||
Alert(uid, component.LastAlarmState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnFireEvent(EntityUid uid, AtmosMonitorComponent component, ref TileFireEvent args)
|
||||
{
|
||||
if (!this.IsPowered(uid, EntityManager))
|
||||
return;
|
||||
|
||||
// if we're monitoring for atmos fire, then we make it similar to a smoke detector
|
||||
// and just outright trigger a danger event
|
||||
//
|
||||
// somebody else can reset it :sunglasses:
|
||||
if (component.MonitorFire
|
||||
&& component.LastAlarmState != AtmosMonitorAlarmType.Danger)
|
||||
{
|
||||
component.TrippedThresholds.Add(AtmosMonitorThresholdType.Temperature);
|
||||
Alert(uid, AtmosMonitorAlarmType.Danger, null, component); // technically???
|
||||
}
|
||||
|
||||
// only monitor state elevation so that stuff gets alarmed quicker during a fire,
|
||||
// let the atmos update loop handle when temperature starts to reach different
|
||||
// thresholds and different states than normal -> warning -> danger
|
||||
if (component.TemperatureThreshold != null
|
||||
&& component.TemperatureThreshold.CheckThreshold(args.Temperature, out var temperatureState)
|
||||
&& temperatureState > component.LastAlarmState)
|
||||
{
|
||||
component.TrippedThresholds.Add(AtmosMonitorThresholdType.Temperature);
|
||||
Alert(uid, AtmosMonitorAlarmType.Danger, null, component);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAtmosUpdate(EntityUid uid, AtmosMonitorComponent component, AtmosDeviceUpdateEvent args)
|
||||
{
|
||||
if (!this.IsPowered(uid, EntityManager))
|
||||
return;
|
||||
|
||||
// can't hurt
|
||||
// (in case something is making AtmosDeviceUpdateEvents
|
||||
// outside the typical device loop)
|
||||
if (!TryComp<AtmosDeviceComponent>(uid, out var atmosDeviceComponent)
|
||||
|| atmosDeviceComponent.JoinedGrid == null)
|
||||
return;
|
||||
|
||||
// if we're not monitoring atmos, don't bother
|
||||
if (component.TemperatureThreshold == null
|
||||
&& component.PressureThreshold == null
|
||||
&& component.GasThresholds == null)
|
||||
return;
|
||||
|
||||
UpdateState(uid, component.TileGas, component);
|
||||
}
|
||||
|
||||
// Update checks the current air if it exceeds thresholds of
|
||||
// any kind.
|
||||
//
|
||||
// If any threshold exceeds the other, that threshold
|
||||
// immediately replaces the current recorded state.
|
||||
//
|
||||
// If the threshold does not match the current state
|
||||
// of the monitor, it is set in the Alert call.
|
||||
private void UpdateState(EntityUid uid, GasMixture? air, AtmosMonitorComponent? monitor = null)
|
||||
{
|
||||
if (air == null) return;
|
||||
|
||||
if (!Resolve(uid, ref monitor)) return;
|
||||
|
||||
AtmosMonitorAlarmType state = AtmosMonitorAlarmType.Normal;
|
||||
HashSet<AtmosMonitorThresholdType> alarmTypes = new(monitor.TrippedThresholds);
|
||||
|
||||
if (monitor.TemperatureThreshold != null
|
||||
&& monitor.TemperatureThreshold.CheckThreshold(air.Temperature, out var temperatureState))
|
||||
{
|
||||
if (temperatureState > state)
|
||||
{
|
||||
state = temperatureState;
|
||||
alarmTypes.Add(AtmosMonitorThresholdType.Temperature);
|
||||
}
|
||||
else if (temperatureState == AtmosMonitorAlarmType.Normal)
|
||||
{
|
||||
alarmTypes.Remove(AtmosMonitorThresholdType.Temperature);
|
||||
}
|
||||
}
|
||||
|
||||
if (monitor.PressureThreshold != null
|
||||
&& monitor.PressureThreshold.CheckThreshold(air.Pressure, out var pressureState)
|
||||
)
|
||||
{
|
||||
if (pressureState > state)
|
||||
{
|
||||
state = pressureState;
|
||||
alarmTypes.Add(AtmosMonitorThresholdType.Pressure);
|
||||
}
|
||||
else if (pressureState == AtmosMonitorAlarmType.Normal)
|
||||
{
|
||||
alarmTypes.Remove(AtmosMonitorThresholdType.Pressure);
|
||||
}
|
||||
}
|
||||
|
||||
if (monitor.GasThresholds != null)
|
||||
{
|
||||
var tripped = false;
|
||||
foreach (var (gas, threshold) in monitor.GasThresholds)
|
||||
{
|
||||
var gasRatio = air.GetMoles(gas) / air.TotalMoles;
|
||||
if (threshold.CheckThreshold(gasRatio, out var gasState)
|
||||
&& gasState > state)
|
||||
{
|
||||
state = gasState;
|
||||
tripped = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (tripped)
|
||||
{
|
||||
alarmTypes.Add(AtmosMonitorThresholdType.Gas);
|
||||
}
|
||||
else
|
||||
{
|
||||
alarmTypes.Remove(AtmosMonitorThresholdType.Gas);
|
||||
}
|
||||
}
|
||||
|
||||
// if the state of the current air doesn't match the last alarm state,
|
||||
// we update the state
|
||||
if (state != monitor.LastAlarmState || !alarmTypes.SetEquals(monitor.TrippedThresholds))
|
||||
{
|
||||
Alert(uid, state, alarmTypes, monitor);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Alerts the network that the state of a monitor has changed.
|
||||
/// </summary>
|
||||
/// <param name="state">The alarm state to set this monitor to.</param>
|
||||
/// <param name="alarms">The alarms that caused this alarm state.</param>
|
||||
public void Alert(EntityUid uid, AtmosMonitorAlarmType state, HashSet<AtmosMonitorThresholdType>? alarms = null, AtmosMonitorComponent? monitor = null)
|
||||
{
|
||||
if (!Resolve(uid, ref monitor)) return;
|
||||
|
||||
monitor.LastAlarmState = state;
|
||||
monitor.TrippedThresholds = alarms ?? monitor.TrippedThresholds;
|
||||
|
||||
BroadcastAlertPacket(monitor);
|
||||
|
||||
// TODO: Central system that grabs *all* alarms from wired network
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets a single monitor's alarm.
|
||||
/// </summary>
|
||||
private void Reset(EntityUid uid)
|
||||
{
|
||||
Alert(uid, AtmosMonitorAlarmType.Normal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Broadcasts an alert packet to all devices on the network,
|
||||
/// which consists of the current alarm types,
|
||||
/// the highest alarm currently cached by this monitor,
|
||||
/// and the current alarm state of the monitor (so other
|
||||
/// alarms can sync to it).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Alarmables use the highest alarm to ensure that a monitor's
|
||||
/// state doesn't override if the alarm is lower. The state
|
||||
/// is synced between monitors the moment a monitor sends out an alarm,
|
||||
/// or if it is explicitly synced (see ResetAll/Sync).
|
||||
/// </remarks>
|
||||
private void BroadcastAlertPacket(AtmosMonitorComponent monitor, TagComponent? tags = null)
|
||||
{
|
||||
if (!monitor.NetEnabled) return;
|
||||
|
||||
if (!Resolve(monitor.Owner, ref tags))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var payload = new NetworkPayload
|
||||
{
|
||||
[DeviceNetworkConstants.Command] = AtmosAlarmableSystem.AlertCmd,
|
||||
[DeviceNetworkConstants.CmdSetState] = monitor.LastAlarmState,
|
||||
[AtmosAlarmableSystem.AlertSource] = tags.Tags,
|
||||
[AtmosAlarmableSystem.AlertTypes] = monitor.TrippedThresholds
|
||||
};
|
||||
|
||||
foreach (var addr in monitor.RegisteredDevices)
|
||||
{
|
||||
_deviceNetSystem.QueuePacket(monitor.Owner, addr, payload);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a monitor's threshold.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of threshold to change.</param>
|
||||
/// <param name="threshold">Threshold data.</param>
|
||||
/// <param name="gas">Gas, if applicable.</param>
|
||||
public void SetThreshold(EntityUid uid, AtmosMonitorThresholdType type, AtmosAlarmThreshold threshold, Gas? gas = null, AtmosMonitorComponent? monitor = null)
|
||||
{
|
||||
if (!Resolve(uid, ref monitor)) return;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case AtmosMonitorThresholdType.Pressure:
|
||||
monitor.PressureThreshold = threshold;
|
||||
break;
|
||||
case AtmosMonitorThresholdType.Temperature:
|
||||
monitor.TemperatureThreshold = threshold;
|
||||
break;
|
||||
case AtmosMonitorThresholdType.Gas:
|
||||
if (gas == null || monitor.GasThresholds == null) return;
|
||||
monitor.GasThresholds[(Gas) gas] = threshold;
|
||||
break;
|
||||
}
|
||||
|
||||
component.GasThresholds = new();
|
||||
foreach (var (gas, id) in component.GasThresholdIds)
|
||||
if (_prototypeManager.TryIndex<AtmosAlarmThreshold>(id, out var gasThreshold))
|
||||
component.GasThresholds.Add(gas, new(gasThreshold));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAtmosMonitorStartup(EntityUid uid, AtmosMonitorComponent component, ComponentStartup args)
|
||||
{
|
||||
if (!HasComp<ApcPowerReceiverComponent>(uid)
|
||||
&& TryComp<AtmosDeviceComponent>(uid, out var atmosDeviceComponent))
|
||||
{
|
||||
_atmosDeviceSystem.LeaveAtmosphere(atmosDeviceComponent);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void BeforePacketRecv(EntityUid uid, AtmosMonitorComponent component, BeforePacketSentEvent args)
|
||||
{
|
||||
if (!component.NetEnabled) args.Cancel();
|
||||
}
|
||||
|
||||
private void OnPacketRecv(EntityUid uid, AtmosMonitorComponent component, DeviceNetworkPacketEvent args)
|
||||
{
|
||||
// sync the internal 'last alarm state' from
|
||||
// the other alarms, so that we can calculate
|
||||
// the highest network alarm state at any time
|
||||
if (!args.Data.TryGetValue(DeviceNetworkConstants.Command, out string? cmd))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (cmd)
|
||||
{
|
||||
case AtmosDeviceNetworkSystem.RegisterDevice:
|
||||
component.RegisteredDevices.Add(args.SenderAddress);
|
||||
break;
|
||||
case AtmosAlarmableSystem.ResetAll:
|
||||
Reset(uid);
|
||||
// Don't clear alarm states here.
|
||||
break;
|
||||
case AtmosMonitorSetThresholdCmd:
|
||||
if (args.Data.TryGetValue(AtmosMonitorThresholdData, out AtmosAlarmThreshold? thresholdData)
|
||||
&& args.Data.TryGetValue(AtmosMonitorThresholdDataType, out AtmosMonitorThresholdType? thresholdType))
|
||||
{
|
||||
args.Data.TryGetValue(AtmosMonitorThresholdGasType, out Gas? gas);
|
||||
SetThreshold(uid, thresholdType.Value, thresholdData, gas);
|
||||
}
|
||||
|
||||
break;
|
||||
case AtmosDeviceNetworkSystem.SyncData:
|
||||
var payload = new NetworkPayload();
|
||||
payload.Add(DeviceNetworkConstants.Command, AtmosDeviceNetworkSystem.SyncData);
|
||||
if (component.TileGas != null)
|
||||
{
|
||||
var gases = new Dictionary<Gas, float>();
|
||||
foreach (var gas in Enum.GetValues<Gas>())
|
||||
{
|
||||
gases.Add(gas, component.TileGas.GetMoles(gas));
|
||||
}
|
||||
|
||||
payload.Add(AtmosDeviceNetworkSystem.SyncData, new AtmosSensorData(
|
||||
component.TileGas.Pressure,
|
||||
component.TileGas.Temperature,
|
||||
component.TileGas.TotalMoles,
|
||||
component.LastAlarmState,
|
||||
gases,
|
||||
component.PressureThreshold ?? new(),
|
||||
component.TemperatureThreshold ?? new(),
|
||||
component.GasThresholds ?? new()
|
||||
));
|
||||
}
|
||||
|
||||
_deviceNetSystem.QueuePacket(uid, args.SenderAddress, payload);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPowerChangedEvent(EntityUid uid, AtmosMonitorComponent component, PowerChangedEvent args)
|
||||
{
|
||||
if (TryComp<AtmosDeviceComponent>(uid, out var atmosDeviceComponent))
|
||||
{
|
||||
if (!args.Powered)
|
||||
{
|
||||
if (atmosDeviceComponent.JoinedGrid != null)
|
||||
{
|
||||
_atmosDeviceSystem.LeaveAtmosphere(atmosDeviceComponent);
|
||||
component.TileGas = null;
|
||||
}
|
||||
}
|
||||
else if (args.Powered)
|
||||
{
|
||||
if (atmosDeviceComponent.JoinedGrid == null)
|
||||
{
|
||||
_atmosDeviceSystem.JoinAtmosphere(atmosDeviceComponent);
|
||||
var air = _atmosphereSystem.GetContainingMixture(uid, true);
|
||||
component.TileGas = air;
|
||||
}
|
||||
|
||||
Alert(uid, component.LastAlarmState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnFireEvent(EntityUid uid, AtmosMonitorComponent component, ref TileFireEvent args)
|
||||
{
|
||||
if (!this.IsPowered(uid, EntityManager))
|
||||
return;
|
||||
|
||||
// if we're monitoring for atmos fire, then we make it similar to a smoke detector
|
||||
// and just outright trigger a danger event
|
||||
//
|
||||
// somebody else can reset it :sunglasses:
|
||||
if (component.MonitorFire
|
||||
&& component.LastAlarmState != AtmosMonitorAlarmType.Danger)
|
||||
{
|
||||
component.TrippedThresholds.Add(AtmosMonitorThresholdType.Temperature);
|
||||
Alert(uid, AtmosMonitorAlarmType.Danger, null, component); // technically???
|
||||
}
|
||||
|
||||
// only monitor state elevation so that stuff gets alarmed quicker during a fire,
|
||||
// let the atmos update loop handle when temperature starts to reach different
|
||||
// thresholds and different states than normal -> warning -> danger
|
||||
if (component.TemperatureThreshold != null
|
||||
&& component.TemperatureThreshold.CheckThreshold(args.Temperature, out var temperatureState)
|
||||
&& temperatureState > component.LastAlarmState)
|
||||
{
|
||||
component.TrippedThresholds.Add(AtmosMonitorThresholdType.Temperature);
|
||||
Alert(uid, AtmosMonitorAlarmType.Danger, null, component);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAtmosUpdate(EntityUid uid, AtmosMonitorComponent component, AtmosDeviceUpdateEvent args)
|
||||
{
|
||||
if (!this.IsPowered(uid, EntityManager))
|
||||
return;
|
||||
|
||||
// can't hurt
|
||||
// (in case something is making AtmosDeviceUpdateEvents
|
||||
// outside the typical device loop)
|
||||
if (!TryComp<AtmosDeviceComponent>(uid, out var atmosDeviceComponent)
|
||||
|| atmosDeviceComponent.JoinedGrid == null)
|
||||
return;
|
||||
|
||||
// if we're not monitoring atmos, don't bother
|
||||
if (component.TemperatureThreshold == null
|
||||
&& component.PressureThreshold == null
|
||||
&& component.GasThresholds == null)
|
||||
return;
|
||||
|
||||
UpdateState(uid, component.TileGas, component);
|
||||
}
|
||||
|
||||
// Update checks the current air if it exceeds thresholds of
|
||||
// any kind.
|
||||
//
|
||||
// If any threshold exceeds the other, that threshold
|
||||
// immediately replaces the current recorded state.
|
||||
//
|
||||
// If the threshold does not match the current state
|
||||
// of the monitor, it is set in the Alert call.
|
||||
private void UpdateState(EntityUid uid, GasMixture? air, AtmosMonitorComponent? monitor = null)
|
||||
{
|
||||
if (air == null) return;
|
||||
|
||||
if (!Resolve(uid, ref monitor)) return;
|
||||
|
||||
AtmosMonitorAlarmType state = AtmosMonitorAlarmType.Normal;
|
||||
HashSet<AtmosMonitorThresholdType> alarmTypes = new(monitor.TrippedThresholds);
|
||||
|
||||
if (monitor.TemperatureThreshold != null
|
||||
&& monitor.TemperatureThreshold.CheckThreshold(air.Temperature, out var temperatureState))
|
||||
{
|
||||
if (temperatureState > state)
|
||||
{
|
||||
state = temperatureState;
|
||||
alarmTypes.Add(AtmosMonitorThresholdType.Temperature);
|
||||
}
|
||||
else if (temperatureState == AtmosMonitorAlarmType.Normal)
|
||||
{
|
||||
alarmTypes.Remove(AtmosMonitorThresholdType.Temperature);
|
||||
}
|
||||
}
|
||||
|
||||
if (monitor.PressureThreshold != null
|
||||
&& monitor.PressureThreshold.CheckThreshold(air.Pressure, out var pressureState)
|
||||
)
|
||||
{
|
||||
if (pressureState > state)
|
||||
{
|
||||
state = pressureState;
|
||||
alarmTypes.Add(AtmosMonitorThresholdType.Pressure);
|
||||
}
|
||||
else if (pressureState == AtmosMonitorAlarmType.Normal)
|
||||
{
|
||||
alarmTypes.Remove(AtmosMonitorThresholdType.Pressure);
|
||||
}
|
||||
}
|
||||
|
||||
if (monitor.GasThresholds != null)
|
||||
{
|
||||
var tripped = false;
|
||||
foreach (var (gas, threshold) in monitor.GasThresholds)
|
||||
{
|
||||
var gasRatio = air.GetMoles(gas) / air.TotalMoles;
|
||||
if (threshold.CheckThreshold(gasRatio, out var gasState)
|
||||
&& gasState > state)
|
||||
{
|
||||
state = gasState;
|
||||
tripped = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (tripped)
|
||||
{
|
||||
alarmTypes.Add(AtmosMonitorThresholdType.Gas);
|
||||
}
|
||||
else
|
||||
{
|
||||
alarmTypes.Remove(AtmosMonitorThresholdType.Gas);
|
||||
}
|
||||
}
|
||||
|
||||
// if the state of the current air doesn't match the last alarm state,
|
||||
// we update the state
|
||||
if (state != monitor.LastAlarmState || !alarmTypes.SetEquals(monitor.TrippedThresholds))
|
||||
{
|
||||
Alert(uid, state, alarmTypes, monitor);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Alerts the network that the state of a monitor has changed.
|
||||
/// </summary>
|
||||
/// <param name="state">The alarm state to set this monitor to.</param>
|
||||
/// <param name="alarms">The alarms that caused this alarm state.</param>
|
||||
public void Alert(EntityUid uid, AtmosMonitorAlarmType state, HashSet<AtmosMonitorThresholdType>? alarms = null, AtmosMonitorComponent? monitor = null)
|
||||
{
|
||||
if (!Resolve(uid, ref monitor)) return;
|
||||
|
||||
monitor.LastAlarmState = state;
|
||||
monitor.TrippedThresholds = alarms ?? monitor.TrippedThresholds;
|
||||
|
||||
BroadcastAlertPacket(monitor);
|
||||
|
||||
// TODO: Central system that grabs *all* alarms from wired network
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets a single monitor's alarm.
|
||||
/// </summary>
|
||||
private void Reset(EntityUid uid)
|
||||
{
|
||||
Alert(uid, AtmosMonitorAlarmType.Normal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Broadcasts an alert packet to all devices on the network,
|
||||
/// which consists of the current alarm types,
|
||||
/// the highest alarm currently cached by this monitor,
|
||||
/// and the current alarm state of the monitor (so other
|
||||
/// alarms can sync to it).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Alarmables use the highest alarm to ensure that a monitor's
|
||||
/// state doesn't override if the alarm is lower. The state
|
||||
/// is synced between monitors the moment a monitor sends out an alarm,
|
||||
/// or if it is explicitly synced (see ResetAll/Sync).
|
||||
/// </remarks>
|
||||
private void BroadcastAlertPacket(AtmosMonitorComponent monitor, TagComponent? tags = null)
|
||||
{
|
||||
if (!monitor.NetEnabled) return;
|
||||
|
||||
if (!Resolve(monitor.Owner, ref tags))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var payload = new NetworkPayload
|
||||
{
|
||||
[DeviceNetworkConstants.Command] = AtmosAlarmableSystem.AlertCmd,
|
||||
[DeviceNetworkConstants.CmdSetState] = monitor.LastAlarmState,
|
||||
[AtmosAlarmableSystem.AlertSource] = tags.Tags,
|
||||
[AtmosAlarmableSystem.AlertTypes] = monitor.TrippedThresholds
|
||||
};
|
||||
|
||||
foreach (var addr in monitor.RegisteredDevices)
|
||||
{
|
||||
_deviceNetSystem.QueuePacket(monitor.Owner, addr, payload);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a monitor's threshold.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of threshold to change.</param>
|
||||
/// <param name="threshold">Threshold data.</param>
|
||||
/// <param name="gas">Gas, if applicable.</param>
|
||||
public void SetThreshold(EntityUid uid, AtmosMonitorThresholdType type, AtmosAlarmThreshold threshold, Gas? gas = null, AtmosMonitorComponent? monitor = null)
|
||||
{
|
||||
if (!Resolve(uid, ref monitor)) return;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case AtmosMonitorThresholdType.Pressure:
|
||||
monitor.PressureThreshold = threshold;
|
||||
break;
|
||||
case AtmosMonitorThresholdType.Temperature:
|
||||
monitor.TemperatureThreshold = threshold;
|
||||
break;
|
||||
case AtmosMonitorThresholdType.Gas:
|
||||
if (gas == null || monitor.GasThresholds == null) return;
|
||||
monitor.GasThresholds[(Gas) gas] = threshold;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,60 +9,59 @@ using Content.Shared.Interaction;
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Robust.Server.GameObjects;
|
||||
|
||||
namespace Content.Server.Atmos.Monitor.Systems
|
||||
namespace Content.Server.Atmos.Monitor.Systems;
|
||||
|
||||
public sealed class FireAlarmSystem : EntitySystem
|
||||
{
|
||||
public sealed class FireAlarmSystem : EntitySystem
|
||||
[Dependency] private readonly AtmosDeviceNetworkSystem _atmosDevNet = default!;
|
||||
[Dependency] private readonly AtmosAlarmableSystem _atmosAlarmable = default!;
|
||||
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
[Dependency] private readonly AtmosDeviceNetworkSystem _atmosDevNet = default!;
|
||||
[Dependency] private readonly AtmosAlarmableSystem _atmosAlarmable = default!;
|
||||
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
|
||||
SubscribeLocalEvent<FireAlarmComponent, InteractHandEvent>(OnInteractHand);
|
||||
SubscribeLocalEvent<FireAlarmComponent, DeviceListUpdateEvent>(OnDeviceListSync);
|
||||
SubscribeLocalEvent<FireAlarmComponent, GotEmaggedEvent>(OnEmagged);
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
private void OnDeviceListSync(EntityUid uid, FireAlarmComponent component, DeviceListUpdateEvent args)
|
||||
{
|
||||
_atmosDevNet.Register(uid, null);
|
||||
_atmosDevNet.Sync(uid, null);
|
||||
}
|
||||
|
||||
private void OnInteractHand(EntityUid uid, FireAlarmComponent component, InteractHandEvent args)
|
||||
{
|
||||
if (!_interactionSystem.InRangeUnobstructed(args.User, args.Target))
|
||||
return;
|
||||
|
||||
if (this.IsPowered(uid, EntityManager))
|
||||
{
|
||||
SubscribeLocalEvent<FireAlarmComponent, InteractHandEvent>(OnInteractHand);
|
||||
SubscribeLocalEvent<FireAlarmComponent, DeviceListUpdateEvent>(OnDeviceListSync);
|
||||
SubscribeLocalEvent<FireAlarmComponent, GotEmaggedEvent>(OnEmagged);
|
||||
}
|
||||
|
||||
private void OnDeviceListSync(EntityUid uid, FireAlarmComponent component, DeviceListUpdateEvent args)
|
||||
{
|
||||
_atmosDevNet.Register(uid, null);
|
||||
_atmosDevNet.Sync(uid, null);
|
||||
}
|
||||
|
||||
private void OnInteractHand(EntityUid uid, FireAlarmComponent component, InteractHandEvent args)
|
||||
{
|
||||
if (!_interactionSystem.InRangeUnobstructed(args.User, args.Target))
|
||||
return;
|
||||
|
||||
if (this.IsPowered(uid, EntityManager))
|
||||
if (!_atmosAlarmable.TryGetHighestAlert(uid, out var alarm))
|
||||
{
|
||||
if (!_atmosAlarmable.TryGetHighestAlert(uid, out var alarm))
|
||||
{
|
||||
alarm = AtmosMonitorAlarmType.Normal;
|
||||
}
|
||||
alarm = AtmosMonitorAlarmType.Normal;
|
||||
}
|
||||
|
||||
if (alarm == AtmosMonitorAlarmType.Normal)
|
||||
{
|
||||
_atmosAlarmable.ForceAlert(uid, AtmosMonitorAlarmType.Danger);
|
||||
}
|
||||
else
|
||||
{
|
||||
_atmosAlarmable.ResetAllOnNetwork(uid);
|
||||
}
|
||||
if (alarm == AtmosMonitorAlarmType.Normal)
|
||||
{
|
||||
_atmosAlarmable.ForceAlert(uid, AtmosMonitorAlarmType.Danger);
|
||||
}
|
||||
else
|
||||
{
|
||||
_atmosAlarmable.ResetAllOnNetwork(uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEmagged(EntityUid uid, FireAlarmComponent component, GotEmaggedEvent args)
|
||||
private void OnEmagged(EntityUid uid, FireAlarmComponent component, GotEmaggedEvent args)
|
||||
{
|
||||
if (TryComp<AtmosMonitorComponent>(uid, out var atmosMonitor))
|
||||
{
|
||||
if (TryComp<AtmosMonitorComponent>(uid, out var atmosMonitor))
|
||||
if (atmosMonitor?.MonitorFire == true)
|
||||
{
|
||||
if (atmosMonitor?.MonitorFire == true)
|
||||
{
|
||||
atmosMonitor.MonitorFire = false;
|
||||
_atmosAlarmable.ForceAlert(uid, AtmosMonitorAlarmType.Emagged);
|
||||
args.Handled = true;
|
||||
}
|
||||
atmosMonitor.MonitorFire = false;
|
||||
_atmosAlarmable.ForceAlert(uid, AtmosMonitorAlarmType.Emagged);
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user