diff --git a/Content.Client/Atmos/Monitor/AtmosMonitorVisualizer.cs b/Content.Client/Atmos/Monitor/AtmosMonitorVisualizer.cs index 81118d5c5c..1ad8504988 100644 --- a/Content.Client/Atmos/Monitor/AtmosMonitorVisualizer.cs +++ b/Content.Client/Atmos/Monitor/AtmosMonitorVisualizer.cs @@ -8,63 +8,62 @@ using Robust.Shared.IoC; using Robust.Shared.Maths; using Robust.Shared.Serialization.Manager.Attributes; -namespace Content.Client.Atmos.Monitor +namespace Content.Client.Atmos.Monitor; + +public sealed class AtmosMonitorVisualizer : AppearanceVisualizer { - public sealed class AtmosMonitorVisualizer : AppearanceVisualizer + [Dependency] IEntityManager _entityManager = default!; + [DataField("layerMap")] + private string _layerMap { get; } = string.Empty; + + [DataField("alarmStates")] + private readonly Dictionary _alarmStates = new(); + + [DataField("hideOnDepowered")] + private readonly List? _hideOnDepowered; + + // eh... + [DataField("setOnDepowered")] + private readonly Dictionary? _setOnDepowered; + + [Obsolete("Subscribe to your component being initialised instead.")] + public override void InitializeEntity(EntityUid entity) { - [Dependency] IEntityManager _entityManager = default!; - [DataField("layerMap")] - private string _layerMap { get; } = string.Empty; + base.InitializeEntity(entity); - [DataField("alarmStates")] - private readonly Dictionary _alarmStates = new(); + IoCManager.InjectDependencies(this); + } - [DataField("hideOnDepowered")] - private readonly List? _hideOnDepowered; + [Obsolete("Subscribe to AppearanceChangeEvent instead.")] + public override void OnChangeData(AppearanceComponent component) + { + if (!_entityManager.TryGetComponent(component.Owner, out var sprite)) + return; - // eh... - [DataField("setOnDepowered")] - private readonly Dictionary? _setOnDepowered; + if (!sprite.LayerMapTryGet(_layerMap, out int layer)) + return; - [Obsolete("Subscribe to your component being initialised instead.")] - public override void InitializeEntity(EntityUid entity) + if (component.TryGetData(PowerDeviceVisuals.Powered, out var powered)) { - base.InitializeEntity(entity); + if (_hideOnDepowered != null) + foreach (var visLayer in _hideOnDepowered) + if (sprite.LayerMapTryGet(visLayer, out int powerVisibilityLayer)) + sprite.LayerSetVisible(powerVisibilityLayer, powered); - IoCManager.InjectDependencies(this); + if (_setOnDepowered != null && !powered) + foreach (var (setLayer, state) in _setOnDepowered) + if (sprite.LayerMapTryGet(setLayer, out int setStateLayer)) + sprite.LayerSetState(setStateLayer, new RSI.StateId(state)); } - [Obsolete("Subscribe to AppearanceChangeEvent instead.")] - public override void OnChangeData(AppearanceComponent component) + if (component.TryGetData(AtmosMonitorVisuals.Offset, out Vector2 offset)) { - if (!_entityManager.TryGetComponent(component.Owner, out var sprite)) - return; - - if (!sprite.LayerMapTryGet(_layerMap, out int layer)) - return; - - if (component.TryGetData(PowerDeviceVisuals.Powered, out var powered)) - { - if (_hideOnDepowered != null) - foreach (var visLayer in _hideOnDepowered) - if (sprite.LayerMapTryGet(visLayer, out int powerVisibilityLayer)) - sprite.LayerSetVisible(powerVisibilityLayer, powered); - - if (_setOnDepowered != null && !powered) - foreach (var (setLayer, state) in _setOnDepowered) - if (sprite.LayerMapTryGet(setLayer, out int setStateLayer)) - sprite.LayerSetState(setStateLayer, new RSI.StateId(state)); - } - - if (component.TryGetData(AtmosMonitorVisuals.Offset, out Vector2 offset)) - { - sprite.Offset = offset; - } - - if (component.TryGetData(AtmosMonitorVisuals.AlarmType, out var alarmType) - && powered) - if (_alarmStates.TryGetValue(alarmType, out var state)) - sprite.LayerSetState(layer, new RSI.StateId(state)); + sprite.Offset = offset; } + + if (component.TryGetData(AtmosMonitorVisuals.AlarmType, out var alarmType) + && powered) + if (_alarmStates.TryGetValue(alarmType, out var state)) + sprite.LayerSetState(layer, new RSI.StateId(state)); } } diff --git a/Content.Client/Atmos/Monitor/UI/AirAlarmBoundUserInterface.cs b/Content.Client/Atmos/Monitor/UI/AirAlarmBoundUserInterface.cs index 8823ff8b3c..0bf37c9097 100644 --- a/Content.Client/Atmos/Monitor/UI/AirAlarmBoundUserInterface.cs +++ b/Content.Client/Atmos/Monitor/UI/AirAlarmBoundUserInterface.cs @@ -6,75 +6,74 @@ using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Log; -namespace Content.Client.Atmos.Monitor.UI +namespace Content.Client.Atmos.Monitor.UI; + +public sealed class AirAlarmBoundUserInterface : BoundUserInterface { - public sealed class AirAlarmBoundUserInterface : BoundUserInterface + private AirAlarmWindow? _window; + + public AirAlarmBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) + {} + + protected override void Open() { - private AirAlarmWindow? _window; + base.Open(); - public AirAlarmBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) - {} + _window = new AirAlarmWindow(); - protected override void Open() + if (State != null) UpdateState(State); + + _window.OpenCentered(); + + _window.OnClose += Close; + _window.AtmosDeviceDataChanged += OnDeviceDataChanged; + _window.AtmosAlarmThresholdChanged += OnThresholdChanged; + _window.AirAlarmModeChanged += OnAirAlarmModeChanged; + _window.ResyncAllRequested += ResyncAllDevices; + _window.AirAlarmTabChange += OnTabChanged; + } + + private void ResyncAllDevices() + { + SendMessage(new AirAlarmResyncAllDevicesMessage()); + } + + private void OnDeviceDataChanged(string address, IAtmosDeviceData data) + { + SendMessage(new AirAlarmUpdateDeviceDataMessage(address, data)); + } + + private void OnAirAlarmModeChanged(AirAlarmMode mode) + { + SendMessage(new AirAlarmUpdateAlarmModeMessage(mode)); + } + + private void OnThresholdChanged(string address, AtmosMonitorThresholdType type, AtmosAlarmThreshold threshold, Gas? gas = null) + { + SendMessage(new AirAlarmUpdateAlarmThresholdMessage(address, type, threshold, gas)); + } + + private void OnTabChanged(AirAlarmTab tab) + { + SendMessage(new AirAlarmTabSetMessage(tab)); + } + + protected override void UpdateState(BoundUserInterfaceState state) + { + base.UpdateState(state); + + if (state is not AirAlarmUIState cast || _window == null) { - base.Open(); - - _window = new AirAlarmWindow(); - - if (State != null) UpdateState(State); - - _window.OpenCentered(); - - _window.OnClose += Close; - _window.AtmosDeviceDataChanged += OnDeviceDataChanged; - _window.AtmosAlarmThresholdChanged += OnThresholdChanged; - _window.AirAlarmModeChanged += OnAirAlarmModeChanged; - _window.ResyncAllRequested += ResyncAllDevices; - _window.AirAlarmTabChange += OnTabChanged; + return; } - private void ResyncAllDevices() - { - SendMessage(new AirAlarmResyncAllDevicesMessage()); - } + _window.UpdateState(cast); + } - private void OnDeviceDataChanged(string address, IAtmosDeviceData data) - { - SendMessage(new AirAlarmUpdateDeviceDataMessage(address, data)); - } + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); - private void OnAirAlarmModeChanged(AirAlarmMode mode) - { - SendMessage(new AirAlarmUpdateAlarmModeMessage(mode)); - } - - private void OnThresholdChanged(string address, AtmosMonitorThresholdType type, AtmosAlarmThreshold threshold, Gas? gas = null) - { - SendMessage(new AirAlarmUpdateAlarmThresholdMessage(address, type, threshold, gas)); - } - - private void OnTabChanged(AirAlarmTab tab) - { - SendMessage(new AirAlarmTabSetMessage(tab)); - } - - protected override void UpdateState(BoundUserInterfaceState state) - { - base.UpdateState(state); - - if (state is not AirAlarmUIState cast || _window == null) - { - return; - } - - _window.UpdateState(cast); - } - - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - - if (disposing) _window?.Dispose(); - } + if (disposing) _window?.Dispose(); } } diff --git a/Content.Client/Atmos/Monitor/UI/AirAlarmWindow.xaml.cs b/Content.Client/Atmos/Monitor/UI/AirAlarmWindow.xaml.cs index 3e1bf5a614..517b02e022 100644 --- a/Content.Client/Atmos/Monitor/UI/AirAlarmWindow.xaml.cs +++ b/Content.Client/Atmos/Monitor/UI/AirAlarmWindow.xaml.cs @@ -13,140 +13,139 @@ using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.XAML; using Robust.Shared.Localization; -namespace Content.Client.Atmos.Monitor.UI +namespace Content.Client.Atmos.Monitor.UI; + +[GenerateTypedNameReferences] +public sealed partial class AirAlarmWindow : DefaultWindow { - [GenerateTypedNameReferences] - public sealed partial class AirAlarmWindow : DefaultWindow + public event Action? AtmosDeviceDataChanged; + public event Action? AtmosAlarmThresholdChanged; + public event Action? AirAlarmModeChanged; + public event Action? ResyncDeviceRequested; + public event Action? ResyncAllRequested; + public event Action? AirAlarmTabChange; + + private Label _address => CDeviceAddress; + private Label _deviceTotal => CDeviceTotal; + private RichTextLabel _pressure => CPressureLabel; + private RichTextLabel _temperature => CTemperatureLabel; + private RichTextLabel _alarmState => CStatusLabel; + + private TabContainer _tabContainer => CTabContainer; + private BoxContainer _ventDevices => CVentContainer; + private BoxContainer _scrubberDevices => CScrubberContainer; + + private Dictionary _pumps = new(); + private Dictionary _scrubbers = new(); + private Dictionary _sensors = new(); + private Button _resyncDevices => CResyncButton; + + + private Dictionary _gasLabels = new(); + + private OptionButton _modes => CModeButton; + + public AirAlarmWindow() { - public event Action? AtmosDeviceDataChanged; - public event Action? AtmosAlarmThresholdChanged; - public event Action? AirAlarmModeChanged; - public event Action? ResyncDeviceRequested; - public event Action? ResyncAllRequested; - public event Action? AirAlarmTabChange; + RobustXamlLoader.Load(this); - private Label _address => CDeviceAddress; - private Label _deviceTotal => CDeviceTotal; - private RichTextLabel _pressure => CPressureLabel; - private RichTextLabel _temperature => CTemperatureLabel; - private RichTextLabel _alarmState => CStatusLabel; + foreach (var mode in Enum.GetValues()) + _modes.AddItem($"{mode}", (int) mode); - private TabContainer _tabContainer => CTabContainer; - private BoxContainer _ventDevices => CVentContainer; - private BoxContainer _scrubberDevices => CScrubberContainer; - - private Dictionary _pumps = new(); - private Dictionary _scrubbers = new(); - private Dictionary _sensors = new(); - private Button _resyncDevices => CResyncButton; - - - private Dictionary _gasLabels = new(); - - private OptionButton _modes => CModeButton; - - public AirAlarmWindow() + _modes.OnItemSelected += args => { - RobustXamlLoader.Load(this); + _modes.SelectId(args.Id); + AirAlarmModeChanged!.Invoke((AirAlarmMode) args.Id); + }; - foreach (var mode in Enum.GetValues()) - _modes.AddItem($"{mode}", (int) mode); + _tabContainer.SetTabTitle(0, Loc.GetString("air-alarm-ui-window-tab-vents")); + _tabContainer.SetTabTitle(1, Loc.GetString("air-alarm-ui-window-tab-scrubbers")); + _tabContainer.SetTabTitle(2, Loc.GetString("air-alarm-ui-window-tab-sensors")); - _modes.OnItemSelected += args => - { - _modes.SelectId(args.Id); - AirAlarmModeChanged!.Invoke((AirAlarmMode) args.Id); - }; + _tabContainer.OnTabChanged += idx => + { + AirAlarmTabChange!((AirAlarmTab) idx); + }; - _tabContainer.SetTabTitle(0, Loc.GetString("air-alarm-ui-window-tab-vents")); - _tabContainer.SetTabTitle(1, Loc.GetString("air-alarm-ui-window-tab-scrubbers")); - _tabContainer.SetTabTitle(2, Loc.GetString("air-alarm-ui-window-tab-sensors")); + _resyncDevices.OnPressed += _ => + { + _ventDevices.RemoveAllChildren(); + _pumps.Clear(); + _scrubberDevices.RemoveAllChildren(); + _scrubbers.Clear(); + CSensorContainer.RemoveAllChildren(); + _sensors.Clear(); + ResyncAllRequested!.Invoke(); + }; + } - _tabContainer.OnTabChanged += idx => - { - AirAlarmTabChange!((AirAlarmTab) idx); - }; - - _resyncDevices.OnPressed += _ => - { - _ventDevices.RemoveAllChildren(); - _pumps.Clear(); - _scrubberDevices.RemoveAllChildren(); - _scrubbers.Clear(); - CSensorContainer.RemoveAllChildren(); - _sensors.Clear(); - ResyncAllRequested!.Invoke(); - }; + public void UpdateState(AirAlarmUIState state) + { + _address.Text = state.Address; + _deviceTotal.Text = $"{state.DeviceCount}"; + _pressure.SetMarkup(Loc.GetString("air-alarm-ui-window-pressure", ("pressure", $"{state.PressureAverage:0.##}"))); + _temperature.SetMarkup(Loc.GetString("air-alarm-ui-window-temperature", ("tempC", $"{TemperatureHelpers.KelvinToCelsius(state.TemperatureAverage):0.#}"), ("temperature", $"{state.TemperatureAverage:0.##}"))); + _alarmState.SetMarkup(Loc.GetString("air-alarm-ui-window-alarm-state", ("state", $"{state.AlarmType}"))); + UpdateModeSelector(state.Mode); + foreach (var (addr, dev) in state.DeviceData) + { + UpdateDeviceData(addr, dev); } - public void UpdateState(AirAlarmUIState state) + _tabContainer.CurrentTab = (int) state.Tab; + } + + public void UpdateModeSelector(AirAlarmMode mode) + { + _modes.SelectId((int) mode); + } + + public void UpdateDeviceData(string addr, IAtmosDeviceData device) + { + switch (device) { - _address.Text = state.Address; - _deviceTotal.Text = $"{state.DeviceCount}"; - _pressure.SetMarkup(Loc.GetString("air-alarm-ui-window-pressure", ("pressure", $"{state.PressureAverage:0.##}"))); - _temperature.SetMarkup(Loc.GetString("air-alarm-ui-window-temperature", ("tempC", $"{TemperatureHelpers.KelvinToCelsius(state.TemperatureAverage):0.#}"), ("temperature", $"{state.TemperatureAverage:0.##}"))); - _alarmState.SetMarkup(Loc.GetString("air-alarm-ui-window-alarm-state", ("state", $"{state.AlarmType}"))); - UpdateModeSelector(state.Mode); - foreach (var (addr, dev) in state.DeviceData) - { - UpdateDeviceData(addr, dev); - } + case GasVentPumpData pump: + if (!_pumps.TryGetValue(addr, out var pumpControl)) + { + var control= new PumpControl(pump, addr); + control.PumpDataChanged += AtmosDeviceDataChanged!.Invoke; + _pumps.Add(addr, control); + CVentContainer.AddChild(control); + } + else + { + pumpControl.ChangeData(pump); + } - _tabContainer.CurrentTab = (int) state.Tab; - } + break; + case GasVentScrubberData scrubber: + if (!_scrubbers.TryGetValue(addr, out var scrubberControl)) + { + var control = new ScrubberControl(scrubber, addr); + control.ScrubberDataChanged += AtmosDeviceDataChanged!.Invoke; + _scrubbers.Add(addr, control); + CScrubberContainer.AddChild(control); + } + else + { + scrubberControl.ChangeData(scrubber); + } - public void UpdateModeSelector(AirAlarmMode mode) - { - _modes.SelectId((int) mode); - } + break; + case AtmosSensorData sensor: + if (!_sensors.TryGetValue(addr, out var sensorControl)) + { + var control = new SensorInfo(sensor, addr); + control.OnThresholdUpdate += AtmosAlarmThresholdChanged; + _sensors.Add(addr, control); + CSensorContainer.AddChild(control); + } + else + { + sensorControl.ChangeData(sensor); + } - public void UpdateDeviceData(string addr, IAtmosDeviceData device) - { - switch (device) - { - case GasVentPumpData pump: - if (!_pumps.TryGetValue(addr, out var pumpControl)) - { - var control= new PumpControl(pump, addr); - control.PumpDataChanged += AtmosDeviceDataChanged!.Invoke; - _pumps.Add(addr, control); - CVentContainer.AddChild(control); - } - else - { - pumpControl.ChangeData(pump); - } - - break; - case GasVentScrubberData scrubber: - if (!_scrubbers.TryGetValue(addr, out var scrubberControl)) - { - var control = new ScrubberControl(scrubber, addr); - control.ScrubberDataChanged += AtmosDeviceDataChanged!.Invoke; - _scrubbers.Add(addr, control); - CScrubberContainer.AddChild(control); - } - else - { - scrubberControl.ChangeData(scrubber); - } - - break; - case AtmosSensorData sensor: - if (!_sensors.TryGetValue(addr, out var sensorControl)) - { - var control = new SensorInfo(sensor, addr); - control.OnThresholdUpdate += AtmosAlarmThresholdChanged; - _sensors.Add(addr, control); - CSensorContainer.AddChild(control); - } - else - { - sensorControl.ChangeData(sensor); - } - - break; - } + break; } } } diff --git a/Content.Client/Atmos/Monitor/UI/Widgets/PumpControl.xaml.cs b/Content.Client/Atmos/Monitor/UI/Widgets/PumpControl.xaml.cs index d4284a5826..e8844918c6 100644 --- a/Content.Client/Atmos/Monitor/UI/Widgets/PumpControl.xaml.cs +++ b/Content.Client/Atmos/Monitor/UI/Widgets/PumpControl.xaml.cs @@ -8,96 +8,95 @@ using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.XAML; using Robust.Shared.Localization; -namespace Content.Client.Atmos.Monitor.UI.Widgets +namespace Content.Client.Atmos.Monitor.UI.Widgets; + +[GenerateTypedNameReferences] +public sealed partial class PumpControl : BoxContainer { - [GenerateTypedNameReferences] - public sealed partial class PumpControl : BoxContainer + private GasVentPumpData _data; + private string _address; + + public event Action? PumpDataChanged; + + private CheckBox _enabled => CEnableDevice; + private CollapsibleHeading _addressLabel => CAddress; + private OptionButton _pumpDirection => CPumpDirection; + private OptionButton _pressureCheck => CPressureCheck; + private FloatSpinBox _externalBound => CExternalBound; + private FloatSpinBox _internalBound => CInternalBound; + + public PumpControl(GasVentPumpData data, string address) { - private GasVentPumpData _data; - private string _address; + RobustXamlLoader.Load(this); - public event Action? PumpDataChanged; + this.Name = address; - private CheckBox _enabled => CEnableDevice; - private CollapsibleHeading _addressLabel => CAddress; - private OptionButton _pumpDirection => CPumpDirection; - private OptionButton _pressureCheck => CPressureCheck; - private FloatSpinBox _externalBound => CExternalBound; - private FloatSpinBox _internalBound => CInternalBound; + _data = data; + _address = address; - public PumpControl(GasVentPumpData data, string address) + _addressLabel.Title = Loc.GetString("air-alarm-ui-atmos-net-device-label", ("address", $"{address}")); + + _enabled.Pressed = data.Enabled; + _enabled.OnToggled += _ => { - RobustXamlLoader.Load(this); + _data.Enabled = _enabled.Pressed; + PumpDataChanged?.Invoke(_address, _data); + }; - this.Name = address; - - _data = data; - _address = address; - - _addressLabel.Title = Loc.GetString("air-alarm-ui-atmos-net-device-label", ("address", $"{address}")); - - _enabled.Pressed = data.Enabled; - _enabled.OnToggled += _ => - { - _data.Enabled = _enabled.Pressed; - PumpDataChanged?.Invoke(_address, _data); - }; - - _internalBound.Value = (float) _data.InternalPressureBound; - _internalBound.OnValueChanged += _ => - { - _data.InternalPressureBound = _internalBound.Value; - PumpDataChanged?.Invoke(_address, _data); - }; - _internalBound.IsValid += value => value >= 0; - - _externalBound.Value = (float) _data.ExternalPressureBound; - _externalBound.OnValueChanged += _ => - { - _data.ExternalPressureBound = _externalBound.Value; - PumpDataChanged?.Invoke(_address, _data); - }; - _externalBound.IsValid += value => value >= 0; - - foreach (var value in Enum.GetValues()) - _pumpDirection.AddItem(Loc.GetString($"{value}"), (int) value); - - _pumpDirection.SelectId((int) _data.PumpDirection); - _pumpDirection.OnItemSelected += args => - { - _pumpDirection.SelectId(args.Id); - _data.PumpDirection = (VentPumpDirection) args.Id; - PumpDataChanged?.Invoke(_address, _data); - }; - - foreach (var value in Enum.GetValues()) - _pressureCheck.AddItem(Loc.GetString($"{value}"), (int) value); - - _pressureCheck.SelectId((int) _data.PressureChecks); - _pressureCheck.OnItemSelected += args => - { - _pressureCheck.SelectId(args.Id); - _data.PressureChecks = (VentPressureBound) args.Id; - PumpDataChanged?.Invoke(_address, _data); - }; - } - - public void ChangeData(GasVentPumpData data) + _internalBound.Value = (float) _data.InternalPressureBound; + _internalBound.OnValueChanged += _ => { - _data.Enabled = data.Enabled; - _enabled.Pressed = _data.Enabled; + _data.InternalPressureBound = _internalBound.Value; + PumpDataChanged?.Invoke(_address, _data); + }; + _internalBound.IsValid += value => value >= 0; - _data.PumpDirection = data.PumpDirection; - _pumpDirection.SelectId((int) _data.PumpDirection); + _externalBound.Value = (float) _data.ExternalPressureBound; + _externalBound.OnValueChanged += _ => + { + _data.ExternalPressureBound = _externalBound.Value; + PumpDataChanged?.Invoke(_address, _data); + }; + _externalBound.IsValid += value => value >= 0; - _data.PressureChecks = data.PressureChecks; - _pressureCheck.SelectId((int) _data.PressureChecks); + foreach (var value in Enum.GetValues()) + _pumpDirection.AddItem(Loc.GetString($"{value}"), (int) value); - _data.ExternalPressureBound = data.ExternalPressureBound; - _externalBound.Value = _data.ExternalPressureBound; + _pumpDirection.SelectId((int) _data.PumpDirection); + _pumpDirection.OnItemSelected += args => + { + _pumpDirection.SelectId(args.Id); + _data.PumpDirection = (VentPumpDirection) args.Id; + PumpDataChanged?.Invoke(_address, _data); + }; - _data.InternalPressureBound = data.InternalPressureBound; - _internalBound.Value = _data.InternalPressureBound; - } + foreach (var value in Enum.GetValues()) + _pressureCheck.AddItem(Loc.GetString($"{value}"), (int) value); + + _pressureCheck.SelectId((int) _data.PressureChecks); + _pressureCheck.OnItemSelected += args => + { + _pressureCheck.SelectId(args.Id); + _data.PressureChecks = (VentPressureBound) args.Id; + PumpDataChanged?.Invoke(_address, _data); + }; + } + + public void ChangeData(GasVentPumpData data) + { + _data.Enabled = data.Enabled; + _enabled.Pressed = _data.Enabled; + + _data.PumpDirection = data.PumpDirection; + _pumpDirection.SelectId((int) _data.PumpDirection); + + _data.PressureChecks = data.PressureChecks; + _pressureCheck.SelectId((int) _data.PressureChecks); + + _data.ExternalPressureBound = data.ExternalPressureBound; + _externalBound.Value = _data.ExternalPressureBound; + + _data.InternalPressureBound = data.InternalPressureBound; + _internalBound.Value = _data.InternalPressureBound; } } diff --git a/Content.Client/Atmos/Monitor/UI/Widgets/ScrubberControl.xaml.cs b/Content.Client/Atmos/Monitor/UI/Widgets/ScrubberControl.xaml.cs index 09c28c8b02..2f9187ab00 100644 --- a/Content.Client/Atmos/Monitor/UI/Widgets/ScrubberControl.xaml.cs +++ b/Content.Client/Atmos/Monitor/UI/Widgets/ScrubberControl.xaml.cs @@ -11,113 +11,112 @@ using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.XAML; using Robust.Shared.Localization; -namespace Content.Client.Atmos.Monitor.UI.Widgets +namespace Content.Client.Atmos.Monitor.UI.Widgets; + +[GenerateTypedNameReferences] +public sealed partial class ScrubberControl : BoxContainer { - [GenerateTypedNameReferences] - public sealed partial class ScrubberControl : BoxContainer + private GasVentScrubberData _data; + private string _address; + + public event Action? ScrubberDataChanged; + + private CheckBox _enabled => CEnableDevice; + private CollapsibleHeading _addressLabel => CAddress; + private OptionButton _pumpDirection => CPumpDirection; + private FloatSpinBox _volumeRate => CVolumeRate; + private CheckBox _wideNet => CWideNet; + + private GridContainer _gases => CGasContainer; + private Dictionary _gasControls = new(); + + public ScrubberControl(GasVentScrubberData data, string address) { - private GasVentScrubberData _data; - private string _address; + RobustXamlLoader.Load(this); - public event Action? ScrubberDataChanged; + this.Name = address; - private CheckBox _enabled => CEnableDevice; - private CollapsibleHeading _addressLabel => CAddress; - private OptionButton _pumpDirection => CPumpDirection; - private FloatSpinBox _volumeRate => CVolumeRate; - private CheckBox _wideNet => CWideNet; + _data = data; + _address = address; - private GridContainer _gases => CGasContainer; - private Dictionary _gasControls = new(); + _addressLabel.Title = Loc.GetString("air-alarm-ui-atmos-net-device-label", ("address", $"{address}")); - public ScrubberControl(GasVentScrubberData data, string address) + _enabled.Pressed = data.Enabled; + _enabled.OnToggled += _ => { - RobustXamlLoader.Load(this); + _data.Enabled = _enabled.Pressed; + ScrubberDataChanged?.Invoke(_address, _data); + }; - this.Name = address; + _wideNet.Pressed = data.WideNet; + _wideNet.OnToggled += _ => + { + _data.WideNet = _wideNet.Pressed; + ScrubberDataChanged?.Invoke(_address, _data); + }; - _data = data; - _address = address; + _volumeRate.Value = _data.VolumeRate; + _volumeRate.OnValueChanged += _ => + { + _data.VolumeRate = _volumeRate.Value; + ScrubberDataChanged?.Invoke(_address, _data); + }; + _volumeRate.IsValid += value => value >= 0; - _addressLabel.Title = Loc.GetString("air-alarm-ui-atmos-net-device-label", ("address", $"{address}")); + foreach (var value in Enum.GetValues()) + _pumpDirection.AddItem(Loc.GetString($"{value}"), (int) value); - _enabled.Pressed = data.Enabled; - _enabled.OnToggled += _ => + _pumpDirection.SelectId((int) _data.PumpDirection); + _pumpDirection.OnItemSelected += args => + { + _pumpDirection.SelectId(args.Id); + _data.PumpDirection = (ScrubberPumpDirection) args.Id; + ScrubberDataChanged?.Invoke(_address, _data); + }; + + foreach (var value in Enum.GetValues()) + { + var gasButton = new Button { - _data.Enabled = _enabled.Pressed; + Name = value.ToString(), + Text = Loc.GetString($"{value}"), + ToggleMode = true, + HorizontalExpand = true, + Pressed = _data.FilterGases.Contains(value) + }; + gasButton.OnToggled += args => + { + if (args.Pressed) + _data.FilterGases.Add(value); + else + _data.FilterGases.Remove(value); + ScrubberDataChanged?.Invoke(_address, _data); }; - - _wideNet.Pressed = data.WideNet; - _wideNet.OnToggled += _ => - { - _data.WideNet = _wideNet.Pressed; - ScrubberDataChanged?.Invoke(_address, _data); - }; - - _volumeRate.Value = _data.VolumeRate; - _volumeRate.OnValueChanged += _ => - { - _data.VolumeRate = _volumeRate.Value; - ScrubberDataChanged?.Invoke(_address, _data); - }; - _volumeRate.IsValid += value => value >= 0; - - foreach (var value in Enum.GetValues()) - _pumpDirection.AddItem(Loc.GetString($"{value}"), (int) value); - - _pumpDirection.SelectId((int) _data.PumpDirection); - _pumpDirection.OnItemSelected += args => - { - _pumpDirection.SelectId(args.Id); - _data.PumpDirection = (ScrubberPumpDirection) args.Id; - ScrubberDataChanged?.Invoke(_address, _data); - }; - - foreach (var value in Enum.GetValues()) - { - var gasButton = new Button - { - Name = value.ToString(), - Text = Loc.GetString($"{value}"), - ToggleMode = true, - HorizontalExpand = true, - Pressed = _data.FilterGases.Contains(value) - }; - gasButton.OnToggled += args => - { - if (args.Pressed) - _data.FilterGases.Add(value); - else - _data.FilterGases.Remove(value); - - ScrubberDataChanged?.Invoke(_address, _data); - }; - _gasControls.Add(value, gasButton); - _gases.AddChild(gasButton); - } - + _gasControls.Add(value, gasButton); + _gases.AddChild(gasButton); } - public void ChangeData(GasVentScrubberData data) - { - _data.Enabled = data.Enabled; - _enabled.Pressed = _data.Enabled; - - _data.PumpDirection = data.PumpDirection; - _pumpDirection.Select((int) _data.PumpDirection); - - _data.VolumeRate = data.VolumeRate; - _volumeRate.Value = _data.VolumeRate; - - _data.WideNet = data.WideNet; - _wideNet.Pressed = _data.WideNet; - - var intersect = _data.FilterGases.Intersect(data.FilterGases); - - foreach (var value in Enum.GetValues()) - if (!intersect.Contains(value)) - _gasControls[value].Pressed = false; - } } -} + + public void ChangeData(GasVentScrubberData data) + { + _data.Enabled = data.Enabled; + _enabled.Pressed = _data.Enabled; + + _data.PumpDirection = data.PumpDirection; + _pumpDirection.Select((int) _data.PumpDirection); + + _data.VolumeRate = data.VolumeRate; + _volumeRate.Value = _data.VolumeRate; + + _data.WideNet = data.WideNet; + _wideNet.Pressed = _data.WideNet; + + var intersect = _data.FilterGases.Intersect(data.FilterGases); + + foreach (var value in Enum.GetValues()) + if (!intersect.Contains(value)) + _gasControls[value].Pressed = false; + } +} \ No newline at end of file diff --git a/Content.Client/Atmos/Monitor/UI/Widgets/ThresholdControl.xaml.cs b/Content.Client/Atmos/Monitor/UI/Widgets/ThresholdControl.xaml.cs index b3da0b2d6c..138f1234df 100644 --- a/Content.Client/Atmos/Monitor/UI/Widgets/ThresholdControl.xaml.cs +++ b/Content.Client/Atmos/Monitor/UI/Widgets/ThresholdControl.xaml.cs @@ -11,286 +11,285 @@ using Robust.Shared.Localization; // holy FUCK // this technically works because some of this you can *not* do in XAML but holy FUCK -namespace Content.Client.Atmos.Monitor.UI.Widgets +namespace Content.Client.Atmos.Monitor.UI.Widgets; + +[GenerateTypedNameReferences] +public sealed partial class ThresholdControl : BoxContainer { - [GenerateTypedNameReferences] - public sealed partial class ThresholdControl : BoxContainer + private AtmosAlarmThreshold _threshold; + private AtmosMonitorThresholdType _type; + private Gas? _gas; + + public event Action? ThresholdDataChanged; + + private CollapsibleHeading _name => CName; + private CheckBox _ignore => CIgnore; + private BoxContainer _dangerBounds => CDangerBounds; + private BoxContainer _warningBounds => CWarningBounds; + private ThresholdBoundControl _upperBoundControl; + private ThresholdBoundControl _lowerBoundControl; + private ThresholdBoundControl _upperWarningBoundControl; + private ThresholdBoundControl _lowerWarningBoundControl; + + // i have played myself by making threshold values nullable to + // indicate validity/disabled status, with several layers of side effect + // dependent on the other three values when you change one :HECK: + public ThresholdControl(string name, AtmosAlarmThreshold threshold, AtmosMonitorThresholdType type, Gas? gas = null, float modifier = 1) { - private AtmosAlarmThreshold _threshold; - private AtmosMonitorThresholdType _type; - private Gas? _gas; + RobustXamlLoader.Load(this); - public event Action? ThresholdDataChanged; + _threshold = threshold; + _type = type; + _gas = gas; - private CollapsibleHeading _name => CName; - private CheckBox _ignore => CIgnore; - private BoxContainer _dangerBounds => CDangerBounds; - private BoxContainer _warningBounds => CWarningBounds; - private ThresholdBoundControl _upperBoundControl; - private ThresholdBoundControl _lowerBoundControl; - private ThresholdBoundControl _upperWarningBoundControl; - private ThresholdBoundControl _lowerWarningBoundControl; + _name.Title = name; - // i have played myself by making threshold values nullable to - // indicate validity/disabled status, with several layers of side effect - // dependent on the other three values when you change one :HECK: - public ThresholdControl(string name, AtmosAlarmThreshold threshold, AtmosMonitorThresholdType type, Gas? gas = null, float modifier = 1) + // i miss rust macros + + _upperBoundControl = new ThresholdBoundControl("upper-bound", _threshold.UpperBound, modifier); + _upperBoundControl.OnBoundChanged += value => { - RobustXamlLoader.Load(this); + // a lot of threshold logic is baked into the properties, + // so setting this just returns if a change occurred or not + _threshold.TrySetPrimaryBound(AtmosMonitorThresholdBound.Upper, value); + return _threshold.UpperBound; + }; + _upperBoundControl.OnBoundEnabled += () => + { + var value = 0f; - _threshold = threshold; - _type = type; - _gas = gas; + if (_threshold.LowerWarningBound != null) + value = (float) _threshold.LowerWarningBound + 0.1f; + else if (_threshold.LowerBound != null) + value = (float) _threshold.LowerBound + 0.1f; - _name.Title = name; + return value; + }; + _upperBoundControl.OnValidBoundChanged += () => + { + ThresholdDataChanged!.Invoke(_type, _threshold, _gas); + }; + _dangerBounds.AddChild(_upperBoundControl); - // i miss rust macros + _lowerBoundControl = new ThresholdBoundControl("lower-bound", _threshold.LowerBound, modifier); + _lowerBoundControl.OnBoundChanged += value => + { + _threshold.TrySetPrimaryBound(AtmosMonitorThresholdBound.Lower, value); + return _threshold.LowerBound; + }; + _lowerBoundControl.OnBoundEnabled += () => + { + var value = 0f; - _upperBoundControl = new ThresholdBoundControl("upper-bound", _threshold.UpperBound, modifier); - _upperBoundControl.OnBoundChanged += value => - { - // a lot of threshold logic is baked into the properties, - // so setting this just returns if a change occurred or not - _threshold.TrySetPrimaryBound(AtmosMonitorThresholdBound.Upper, value); - return _threshold.UpperBound; - }; - _upperBoundControl.OnBoundEnabled += () => - { - var value = 0f; + if (_threshold.UpperWarningBound != null) + value = (float) _threshold.UpperWarningBound - 0.1f; + else if (_threshold.UpperBound != null) + value = (float) _threshold.UpperBound - 0.1f; - if (_threshold.LowerWarningBound != null) - value = (float) _threshold.LowerWarningBound + 0.1f; - else if (_threshold.LowerBound != null) - value = (float) _threshold.LowerBound + 0.1f; + return value; + }; + _lowerBoundControl.OnValidBoundChanged += () => + ThresholdDataChanged!.Invoke(_type, _threshold, _gas); + _dangerBounds.AddChild(_lowerBoundControl); - return value; - }; - _upperBoundControl.OnValidBoundChanged += () => - { - ThresholdDataChanged!.Invoke(_type, _threshold, _gas); - }; - _dangerBounds.AddChild(_upperBoundControl); + _upperWarningBoundControl = new ThresholdBoundControl("upper-warning-bound", _threshold.UpperWarningBound, modifier); + _upperWarningBoundControl.OnBoundChanged += value => + { + _threshold.TrySetWarningBound(AtmosMonitorThresholdBound.Upper, value); + return _threshold.UpperWarningBound; + }; + _upperWarningBoundControl.OnBoundEnabled += () => + { + var value = 0f; - _lowerBoundControl = new ThresholdBoundControl("lower-bound", _threshold.LowerBound, modifier); - _lowerBoundControl.OnBoundChanged += value => - { - _threshold.TrySetPrimaryBound(AtmosMonitorThresholdBound.Lower, value); - return _threshold.LowerBound; - }; - _lowerBoundControl.OnBoundEnabled += () => - { - var value = 0f; + if (_threshold.LowerWarningBound != null) + value = (float) _threshold.LowerWarningBound + 0.1f; + else if (_threshold.LowerBound != null) + value = (float) _threshold.LowerBound + 0.1f; - if (_threshold.UpperWarningBound != null) - value = (float) _threshold.UpperWarningBound - 0.1f; - else if (_threshold.UpperBound != null) - value = (float) _threshold.UpperBound - 0.1f; + return value; + }; + _upperWarningBoundControl.OnValidBoundChanged += () => + ThresholdDataChanged!.Invoke(_type, _threshold, _gas); + _warningBounds.AddChild(_upperWarningBoundControl); - return value; - }; - _lowerBoundControl.OnValidBoundChanged += () => - ThresholdDataChanged!.Invoke(_type, _threshold, _gas); - _dangerBounds.AddChild(_lowerBoundControl); + _lowerWarningBoundControl = new ThresholdBoundControl("lower-warning-bound", _threshold.LowerWarningBound, modifier); + _lowerWarningBoundControl.OnBoundChanged += value => + { + _threshold.TrySetWarningBound(AtmosMonitorThresholdBound.Lower, value); + return _threshold.LowerWarningBound; + }; + _lowerWarningBoundControl.OnBoundEnabled += () => + { + var value = 0f; - _upperWarningBoundControl = new ThresholdBoundControl("upper-warning-bound", _threshold.UpperWarningBound, modifier); - _upperWarningBoundControl.OnBoundChanged += value => - { - _threshold.TrySetWarningBound(AtmosMonitorThresholdBound.Upper, value); - return _threshold.UpperWarningBound; - }; - _upperWarningBoundControl.OnBoundEnabled += () => - { - var value = 0f; + if (_threshold.UpperWarningBound != null) + value = (float) _threshold.UpperWarningBound - 0.1f; + else if (_threshold.UpperBound != null) + value = (float) _threshold.UpperBound - 0.1f; - if (_threshold.LowerWarningBound != null) - value = (float) _threshold.LowerWarningBound + 0.1f; - else if (_threshold.LowerBound != null) - value = (float) _threshold.LowerBound + 0.1f; + return value; + }; + _lowerWarningBoundControl.OnValidBoundChanged += () => + ThresholdDataChanged!.Invoke(_type, _threshold, _gas); - return value; - }; - _upperWarningBoundControl.OnValidBoundChanged += () => - ThresholdDataChanged!.Invoke(_type, _threshold, _gas); - _warningBounds.AddChild(_upperWarningBoundControl); + _warningBounds.AddChild(_lowerWarningBoundControl); - _lowerWarningBoundControl = new ThresholdBoundControl("lower-warning-bound", _threshold.LowerWarningBound, modifier); - _lowerWarningBoundControl.OnBoundChanged += value => - { - _threshold.TrySetWarningBound(AtmosMonitorThresholdBound.Lower, value); - return _threshold.LowerWarningBound; - }; - _lowerWarningBoundControl.OnBoundEnabled += () => - { - var value = 0f; + _ignore.OnToggled += args => + { + _threshold.Ignore = args.Pressed; + ThresholdDataChanged!.Invoke(_type, _threshold, _gas); + }; + _ignore.Pressed = _threshold.Ignore; + } - if (_threshold.UpperWarningBound != null) - value = (float) _threshold.UpperWarningBound - 0.1f; - else if (_threshold.UpperBound != null) - value = (float) _threshold.UpperBound - 0.1f; + public void UpdateThresholdData(AtmosAlarmThreshold threshold) + { + _upperBoundControl.SetValue(threshold.UpperBound); + _lowerBoundControl.SetValue(threshold.LowerBound); + _upperWarningBoundControl.SetValue(threshold.UpperWarningBound); + _lowerWarningBoundControl.SetValue(threshold.LowerWarningBound); + _ignore.Pressed = threshold.Ignore; + } - return value; - }; - _lowerWarningBoundControl.OnValidBoundChanged += () => - ThresholdDataChanged!.Invoke(_type, _threshold, _gas); - _warningBounds.AddChild(_lowerWarningBoundControl); + private sealed class ThresholdBoundControl : BoxContainer + { + // raw values to use in thresholds, prefer these + // over directly setting Modified(Value/LastValue) + // when working with the FloatSpinBox + private float? _value; + private float _lastValue; - _ignore.OnToggled += args => - { - _threshold.Ignore = args.Pressed; - ThresholdDataChanged!.Invoke(_type, _threshold, _gas); - }; - _ignore.Pressed = _threshold.Ignore; + // convenience thing for getting multiplied values + // and also setting value to a usable value + private float? ModifiedValue + { + get => _value * _modifier; + set => _value = value / _modifier; } - public void UpdateThresholdData(AtmosAlarmThreshold threshold) + private float ModifiedLastValue { - _upperBoundControl.SetValue(threshold.UpperBound); - _lowerBoundControl.SetValue(threshold.LowerBound); - _upperWarningBoundControl.SetValue(threshold.UpperWarningBound); - _lowerWarningBoundControl.SetValue(threshold.LowerWarningBound); - _ignore.Pressed = threshold.Ignore; + get => _lastValue * _modifier; + set => _lastValue = value / _modifier; } + private float _modifier; - private sealed class ThresholdBoundControl : BoxContainer + private FloatSpinBox _bound; + private CheckBox _boundEnabled; + + public event Action? OnValidBoundChanged; + public Func? OnBoundChanged; + public Func? OnBoundEnabled; + + public void SetValue(float? value) { - // raw values to use in thresholds, prefer these - // over directly setting Modified(Value/LastValue) - // when working with the FloatSpinBox - private float? _value; - private float _lastValue; + _value = value; - // convenience thing for getting multiplied values - // and also setting value to a usable value - private float? ModifiedValue + if (_value == null) { - get => _value * _modifier; - set => _value = value / _modifier; + _boundEnabled.Pressed = false; + _bound.Value = 0; } - - private float ModifiedLastValue + else { - get => _lastValue * _modifier; - set => _lastValue = value / _modifier; + _boundEnabled.Pressed = true; + _bound.Value = (float) ModifiedValue!; } + } - private float _modifier; + // Modifier indicates what factor the value should be multiplied by. + // Mostly useful to convert tiny decimals to human-readable 'percentages' + // (yes it's still a float, but floatspinbox unfucks that) + public ThresholdBoundControl(string name, float? value, float modifier = 1) + { + _modifier = modifier > 0 ? modifier : 1; + _value = value; - private FloatSpinBox _bound; - private CheckBox _boundEnabled; + this.HorizontalExpand = true; + this.Orientation = LayoutOrientation.Vertical; - public event Action? OnValidBoundChanged; - public Func? OnBoundChanged; - public Func? OnBoundEnabled; + this.AddChild(new Label { Text = Loc.GetString($"air-alarm-ui-thresholds-{name}") }); + _bound = new FloatSpinBox(.01f, 2); + this.AddChild(_bound); - public void SetValue(float? value) + _boundEnabled = new CheckBox + { + Text = Loc.GetString("Enabled") + }; + this.AddChild(_boundEnabled); + + _bound.Value = ModifiedValue ?? 0; + _lastValue = _value ?? 0; + _boundEnabled.Pressed = _value != null; + + _bound.OnValueChanged += ChangeValue; + _bound.IsValid += ValidateThreshold; + _boundEnabled.OnToggled += ToggleBound; + } + + private void ChangeValue(FloatSpinBox.FloatSpinBoxEventArgs args) + { + // ensure that the value in the spinbox is transformed + ModifiedValue = args.Value; + // set the value in the scope above + var value = OnBoundChanged!(_value); + // is the value not null, or has it changed? + if (value != null || value != _lastValue) { _value = value; - - if (_value == null) - { - _boundEnabled.Pressed = false; - _bound.Value = 0; - } - else - { - _boundEnabled.Pressed = true; - _bound.Value = (float) ModifiedValue!; - } - } - - // Modifier indicates what factor the value should be multiplied by. - // Mostly useful to convert tiny decimals to human-readable 'percentages' - // (yes it's still a float, but floatspinbox unfucks that) - public ThresholdBoundControl(string name, float? value, float modifier = 1) - { - _modifier = modifier > 0 ? modifier : 1; - _value = value; - - this.HorizontalExpand = true; - this.Orientation = LayoutOrientation.Vertical; - - this.AddChild(new Label { Text = Loc.GetString($"air-alarm-ui-thresholds-{name}") }); - _bound = new FloatSpinBox(.01f, 2); - this.AddChild(_bound); - - _boundEnabled = new CheckBox - { - Text = Loc.GetString("Enabled") - }; - this.AddChild(_boundEnabled); - - _bound.Value = ModifiedValue ?? 0; - _lastValue = _value ?? 0; - _boundEnabled.Pressed = _value != null; - - _bound.OnValueChanged += ChangeValue; - _bound.IsValid += ValidateThreshold; - _boundEnabled.OnToggled += ToggleBound; - } - - private void ChangeValue(FloatSpinBox.FloatSpinBoxEventArgs args) - { - // ensure that the value in the spinbox is transformed - ModifiedValue = args.Value; - // set the value in the scope above - var value = OnBoundChanged!(_value); - // is the value not null, or has it changed? - if (value != null || value != _lastValue) - { - _value = value; - _lastValue = (float) value!; - OnValidBoundChanged!.Invoke(); - } - // otherwise, just set it to the last known value - else - { - _value = _lastValue; - _bound.Value = ModifiedLastValue; - } - } - - private void ToggleBound(BaseButton.ButtonToggledEventArgs args) - { - if (args.Pressed) - { - var value = OnBoundChanged!(_lastValue); - - if (value != _lastValue) - { - value = OnBoundChanged!(OnBoundEnabled!()); - - if (value == null || value < 0) - { - // TODO: Improve UX here, this is ass - // basically this implies that the bound - // you currently have is too aggressive - // for the other set of values, so a - // default value (which is +/-0.1) can't - // be used - _boundEnabled.Pressed = false; - return; - } - } - - _value = value; - - _bound.Value = (float) ModifiedValue!; - _lastValue = (float) _value; - } - else - { - _value = null; - _bound.Value = 0f; - OnBoundChanged!(_value); - } - + _lastValue = (float) value!; OnValidBoundChanged!.Invoke(); } - - private bool ValidateThreshold(float value) => (_value != null) && (value >= 0); - + // otherwise, just set it to the last known value + else + { + _value = _lastValue; + _bound.Value = ModifiedLastValue; + } } + + private void ToggleBound(BaseButton.ButtonToggledEventArgs args) + { + if (args.Pressed) + { + var value = OnBoundChanged!(_lastValue); + + if (value != _lastValue) + { + value = OnBoundChanged!(OnBoundEnabled!()); + + if (value == null || value < 0) + { + // TODO: Improve UX here, this is ass + // basically this implies that the bound + // you currently have is too aggressive + // for the other set of values, so a + // default value (which is +/-0.1) can't + // be used + _boundEnabled.Pressed = false; + return; + } + } + + _value = value; + + _bound.Value = (float) ModifiedValue!; + _lastValue = (float) _value; + } + else + { + _value = null; + _bound.Value = 0f; + OnBoundChanged!(_value); + } + + OnValidBoundChanged!.Invoke(); + } + + private bool ValidateThreshold(float value) => (_value != null) && (value >= 0); + } } diff --git a/Content.Server/Atmos/Monitor/Components/AirAlarmComponent.cs b/Content.Server/Atmos/Monitor/Components/AirAlarmComponent.cs index 6d53721801..3d95b626df 100644 --- a/Content.Server/Atmos/Monitor/Components/AirAlarmComponent.cs +++ b/Content.Server/Atmos/Monitor/Components/AirAlarmComponent.cs @@ -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 DeviceData = new(); - public Dictionary VentData = new(); - public Dictionary ScrubberData = new(); - public Dictionary SensorData = new(); + public Dictionary DeviceData = new(); + public Dictionary VentData = new(); + public Dictionary ScrubberData = new(); + public Dictionary SensorData = new(); - public HashSet ActivePlayers = new(); + public HashSet ActivePlayers = new(); - public bool CanSync = true; - } + public bool CanSync = true; } diff --git a/Content.Server/Atmos/Monitor/Components/AtmosAlarmableComponent.cs b/Content.Server/Atmos/Monitor/Components/AtmosAlarmableComponent.cs index 273682331e..0e007798ca 100644 --- a/Content.Server/Atmos/Monitor/Components/AtmosAlarmableComponent.cs +++ b/Content.Server/Atmos/Monitor/Components/AtmosAlarmableComponent.cs @@ -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) + +/// +/// 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. +/// +[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 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; /// - /// 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. /// - [RegisterComponent] - public sealed class AtmosAlarmableComponent : Component - { - [ViewVariables] - public readonly Dictionary NetworkAlarmStates = new(); + [DataField("syncWith", customTypeSerializer: typeof(PrototypeIdHashSetSerializer))] + public HashSet SyncWithTags { get; } = new(); - [ViewVariables] public AtmosMonitorAlarmType LastAlarmState = AtmosMonitorAlarmType.Normal; + [DataField("monitorAlertTypes")] + public HashSet? 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; - - /// - /// List of tags to check for when synchronizing alarms. - /// - [DataField("syncWith", customTypeSerializer: typeof(PrototypeIdHashSetSerializer))] - public HashSet SyncWithTags { get; } = new(); - - [DataField("monitorAlertTypes")] - public HashSet? MonitorAlertTypes { get; } - - /// - /// If this device should receive only. If it can only - /// receive, that means that attempting to sync outwards - /// will result in nothing happening. - /// - [DataField("receiveOnly")] - public bool ReceiveOnly { get; } - } + /// + /// If this device should receive only. If it can only + /// receive, that means that attempting to sync outwards + /// will result in nothing happening. + /// + [DataField("receiveOnly")] + public bool ReceiveOnly { get; } } diff --git a/Content.Server/Atmos/Monitor/Components/AtmosMonitorComponent.cs b/Content.Server/Atmos/Monitor/Components/AtmosMonitorComponent.cs index 993d8ed7cc..db2f5302c1 100644 --- a/Content.Server/Atmos/Monitor/Components/AtmosMonitorComponent.cs +++ b/Content.Server/Atmos/Monitor/Components/AtmosMonitorComponent.cs @@ -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)))] - public readonly string? TemperatureThresholdId; + [DataField("temperatureThreshold", customTypeSerializer: (typeof(PrototypeIdSerializer)))] + public readonly string? TemperatureThresholdId; - [ViewVariables] - public AtmosAlarmThreshold? TemperatureThreshold; + [ViewVariables] + public AtmosAlarmThreshold? TemperatureThreshold; - [DataField("pressureThreshold", customTypeSerializer: (typeof(PrototypeIdSerializer)))] - public readonly string? PressureThresholdId; + [DataField("pressureThreshold", customTypeSerializer: (typeof(PrototypeIdSerializer)))] + 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? 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? GasThresholdIds; - [ViewVariables] - public Dictionary? GasThresholds; + [ViewVariables] + public Dictionary? 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 TrippedThresholds = new(); + [ViewVariables] public HashSet TrippedThresholds = new(); - /// - /// Registered devices in this atmos monitor. Alerts will be sent directly - /// to these devices. - /// - [ViewVariables] public HashSet RegisteredDevices = new(); - } + /// + /// Registered devices in this atmos monitor. Alerts will be sent directly + /// to these devices. + /// + [ViewVariables] public HashSet RegisteredDevices = new(); } diff --git a/Content.Server/Atmos/Monitor/Components/FireAlarmComponent.cs b/Content.Server/Atmos/Monitor/Components/FireAlarmComponent.cs index 7f9c072e7d..d04b79a79c 100644 --- a/Content.Server/Atmos/Monitor/Components/FireAlarmComponent.cs +++ b/Content.Server/Atmos/Monitor/Components/FireAlarmComponent.cs @@ -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 - { - } -} +} \ No newline at end of file diff --git a/Content.Server/Atmos/Monitor/Systems/AirAlarmModes.cs b/Content.Server/Atmos/Monitor/Systems/AirAlarmModes.cs index beb16cdd72..979653ea06 100644 --- a/Content.Server/Atmos/Monitor/Systems/AirAlarmModes.cs +++ b/Content.Server/Atmos/Monitor/Systems/AirAlarmModes.cs @@ -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; + +/// +/// This is an interface that air alarm modes use +/// in order to execute the defined modes. +/// +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. + /// + /// Executed the mode is set on an air alarm. + /// This is to ensure that modes like Filter/Panic + /// are immediately set. + /// + 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. +/// +/// An interface that AirAlarmSystem uses +/// in order to update air alarm modes that +/// need updating (e.g., Replace) +/// +public interface IAirAlarmModeUpdate { /// - /// 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. /// - 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. - /// - /// Executed the mode is set on an air alarm. - /// This is to ensure that modes like Filter/Panic - /// are immediately set. - /// - 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; } /// - /// 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. /// - 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 { - /// - /// 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. - /// - public string NetOwner { get; set; } - /// - /// This is executed every time the air alarm - /// update loop is fully executed. This should - /// be where all the logic goes. - /// - 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(); + AirAlarmSystem = EntitySystem.Get(); } +} - 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(); - AirAlarmSystem = EntitySystem.Get(); - } - } - - 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); } } } diff --git a/Content.Server/Atmos/Monitor/Systems/AirAlarmSystem.cs b/Content.Server/Atmos/Monitor/Systems/AirAlarmSystem.cs index cbcb4dc5c9..f204ce1882 100644 --- a/Content.Server/Atmos/Monitor/Systems/AirAlarmSystem.cs +++ b/Content.Server/Atmos/Monitor/Systems/AirAlarmSystem.cs @@ -18,517 +18,516 @@ using Content.Shared.Interaction; using Robust.Server.GameObjects; using Robust.Shared.Player; -namespace Content.Server.Atmos.Monitor.Systems +namespace Content.Server.Atmos.Monitor.Systems; + +// AirAlarm system - specific for atmos devices, rather than +// atmos monitors. +// +// oh boy, message passing! +// +// Commands should always be sent into packet's Command +// data key. In response, a packet will be transmitted +// with the response type as its command, and the +// response data in its data key. +public sealed class AirAlarmSystem : EntitySystem { - // AirAlarm system - specific for atmos devices, rather than - // atmos monitors. - // - // oh boy, message passing! - // - // Commands should always be sent into packet's Command - // data key. In response, a packet will be transmitted - // with the response type as its command, and the - // response data in its data key. - public sealed class AirAlarmSystem : EntitySystem + [Dependency] private readonly DeviceNetworkSystem _deviceNet = default!; + [Dependency] private readonly AtmosDeviceNetworkSystem _atmosDevNetSystem = default!; + [Dependency] private readonly AtmosAlarmableSystem _atmosAlarmable = default!; + [Dependency] private readonly UserInterfaceSystem _uiSystem = default!; + [Dependency] private readonly AccessReaderSystem _accessSystem = default!; + [Dependency] private readonly PopupSystem _popup = default!; + [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; + + #region Device Network API + + /// + /// Command to set an air alarm's mode. + /// + public const string AirAlarmSetMode = "air_alarm_set_mode"; + + // -- API -- + + /// + /// Set the data for an air alarm managed device. + /// + /// The address of the device. + /// The data to send to the device. + public void SetData(EntityUid uid, string address, IAtmosDeviceData data) { - [Dependency] private readonly DeviceNetworkSystem _deviceNet = default!; - [Dependency] private readonly AtmosDeviceNetworkSystem _atmosDevNetSystem = default!; - [Dependency] private readonly AtmosAlarmableSystem _atmosAlarmable = default!; - [Dependency] private readonly UserInterfaceSystem _uiSystem = default!; - [Dependency] private readonly AccessReaderSystem _accessSystem = default!; - [Dependency] private readonly PopupSystem _popup = default!; - [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; + _atmosDevNetSystem.SetDeviceState(uid, address, data); + _atmosDevNetSystem.Sync(uid, address); + } - #region Device Network API + /// + /// Broadcast a sync packet to an air alarm's local network. + /// + private void SyncAllDevices(EntityUid uid) + { + _atmosDevNetSystem.Sync(uid, null); + } - /// - /// Command to set an air alarm's mode. - /// - public const string AirAlarmSetMode = "air_alarm_set_mode"; + /// + /// Send a sync packet to a specific device from an air alarm. + /// + /// The address of the device. + private void SyncDevice(EntityUid uid, string address) + { + _atmosDevNetSystem.Sync(uid, address); + } - // -- API -- + /// + /// Register and synchronize with all devices + /// on this network. + /// + /// + public void SyncRegisterAllDevices(EntityUid uid) + { + _atmosDevNetSystem.Register(uid, null); + _atmosDevNetSystem.Sync(uid, null); + } - /// - /// Set the data for an air alarm managed device. - /// - /// The address of the device. - /// The data to send to the device. - public void SetData(EntityUid uid, string address, IAtmosDeviceData data) + /// + /// Synchronize all sensors on an air alarm, but only if its current tab is set to Sensors. + /// + /// + /// + private void SyncAllSensors(EntityUid uid, AirAlarmComponent? monitor = null) + { + if (!Resolve(uid, ref monitor)) { - _atmosDevNetSystem.SetDeviceState(uid, address, data); - _atmosDevNetSystem.Sync(uid, address); + return; } - /// - /// Broadcast a sync packet to an air alarm's local network. - /// - private void SyncAllDevices(EntityUid uid) + foreach (var addr in monitor.SensorData.Keys) { - _atmosDevNetSystem.Sync(uid, null); + SyncDevice(uid, addr); + } + } + + private void SetThreshold(EntityUid uid, string address, AtmosMonitorThresholdType type, + AtmosAlarmThreshold threshold, Gas? gas = null) + { + var payload = new NetworkPayload + { + [DeviceNetworkConstants.Command] = AtmosMonitorSystem.AtmosMonitorSetThresholdCmd, + [AtmosMonitorSystem.AtmosMonitorThresholdDataType] = type, + [AtmosMonitorSystem.AtmosMonitorThresholdData] = threshold, + }; + + if (gas != null) + { + payload.Add(AtmosMonitorSystem.AtmosMonitorThresholdGasType, gas); } - /// - /// Send a sync packet to a specific device from an air alarm. - /// - /// The address of the device. - private void SyncDevice(EntityUid uid, string address) + _deviceNet.QueuePacket(uid, address, payload); + + SyncDevice(uid, address); + } + + /// + /// Sync this air alarm's mode with the rest of the network. + /// + /// The mode to sync with the rest of the network. + public void SyncMode(EntityUid uid, AirAlarmMode mode) + { + if (EntityManager.TryGetComponent(uid, out AtmosMonitorComponent? monitor) + && !monitor.NetEnabled) + return; + + var payload = new NetworkPayload { - _atmosDevNetSystem.Sync(uid, address); + [DeviceNetworkConstants.Command] = AirAlarmSetMode, + [AirAlarmSetMode] = mode + }; + + _deviceNet.QueuePacket(uid, null, payload); + } + + #endregion + + #region Events + + public override void Initialize() + { + SubscribeLocalEvent(OnPacketRecv); + SubscribeLocalEvent(OnAtmosUpdate); + SubscribeLocalEvent(OnAtmosAlarm); + SubscribeLocalEvent(OnPowerChanged); + SubscribeLocalEvent(OnResyncAll); + SubscribeLocalEvent(OnUpdateAlarmMode); + SubscribeLocalEvent(OnUpdateThreshold); + SubscribeLocalEvent(OnUpdateDeviceData); + SubscribeLocalEvent(OnTabChange); + SubscribeLocalEvent(OnDeviceListUpdate); + SubscribeLocalEvent(OnClose); + SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnInteract); + } + + private void OnDeviceListUpdate(EntityUid uid, AirAlarmComponent component, DeviceListUpdateEvent args) + { + SyncRegisterAllDevices(uid); + } + + private void OnTabChange(EntityUid uid, AirAlarmComponent component, AirAlarmTabSetMessage msg) + { + component.CurrentTab = msg.Tab; + UpdateUI(uid, component); + } + + private void OnPowerChanged(EntityUid uid, AirAlarmComponent component, PowerChangedEvent args) + { + if (!args.Powered) + { + ForceCloseAllInterfaces(uid); + component.CurrentModeUpdater = null; + component.ScrubberData.Clear(); + component.SensorData.Clear(); + component.VentData.Clear(); + } + else + { + SyncAllDevices(uid); + } + } + + private void OnClose(EntityUid uid, AirAlarmComponent component, BoundUIClosedEvent args) + { + component.ActivePlayers.Remove(args.Session.UserId); + if (component.ActivePlayers.Count == 0) + RemoveActiveInterface(uid); + } + + private void OnShutdown(EntityUid uid, AirAlarmComponent component, ComponentShutdown args) + { + _activeUserInterfaces.Remove(uid); + } + + private void OnInteract(EntityUid uid, AirAlarmComponent component, InteractHandEvent args) + { + if (!_interactionSystem.InRangeUnobstructed(args.User, args.Target)) + return; + + if (!EntityManager.TryGetComponent(args.User, out ActorComponent? actor)) + return; + + if (EntityManager.TryGetComponent(uid, out WiresComponent? wire) && wire.IsPanelOpen) + { + args.Handled = false; + return; } - /// - /// Register and synchronize with all devices - /// on this network. - /// - /// - public void SyncRegisterAllDevices(EntityUid uid) + if (!this.IsPowered(uid, EntityManager)) + return; + + _uiSystem.GetUiOrNull(component.Owner, SharedAirAlarmInterfaceKey.Key)?.Open(actor.PlayerSession); + component.ActivePlayers.Add(actor.PlayerSession.UserId); + AddActiveInterface(uid); + SyncAllDevices(uid); + UpdateUI(uid, component); + } + + private void OnResyncAll(EntityUid uid, AirAlarmComponent component, AirAlarmResyncAllDevicesMessage args) + { + if (AccessCheck(uid, args.Session.AttachedEntity, component)) { - _atmosDevNetSystem.Register(uid, null); - _atmosDevNetSystem.Sync(uid, null); - } + component.VentData.Clear(); + component.ScrubberData.Clear(); + component.SensorData.Clear(); - /// - /// Synchronize all sensors on an air alarm, but only if its current tab is set to Sensors. - /// - /// - /// - private void SyncAllSensors(EntityUid uid, AirAlarmComponent? monitor = null) - { - if (!Resolve(uid, ref monitor)) - { - return; - } - - foreach (var addr in monitor.SensorData.Keys) - { - SyncDevice(uid, addr); - } - } - - private void SetThreshold(EntityUid uid, string address, AtmosMonitorThresholdType type, - AtmosAlarmThreshold threshold, Gas? gas = null) - { - var payload = new NetworkPayload - { - [DeviceNetworkConstants.Command] = AtmosMonitorSystem.AtmosMonitorSetThresholdCmd, - [AtmosMonitorSystem.AtmosMonitorThresholdDataType] = type, - [AtmosMonitorSystem.AtmosMonitorThresholdData] = threshold, - }; - - if (gas != null) - { - payload.Add(AtmosMonitorSystem.AtmosMonitorThresholdGasType, gas); - } - - _deviceNet.QueuePacket(uid, address, payload); - - SyncDevice(uid, address); - } - - /// - /// Sync this air alarm's mode with the rest of the network. - /// - /// The mode to sync with the rest of the network. - public void SyncMode(EntityUid uid, AirAlarmMode mode) - { - if (EntityManager.TryGetComponent(uid, out AtmosMonitorComponent? monitor) - && !monitor.NetEnabled) - return; - - var payload = new NetworkPayload - { - [DeviceNetworkConstants.Command] = AirAlarmSetMode, - [AirAlarmSetMode] = mode - }; - - _deviceNet.QueuePacket(uid, null, payload); - } - - #endregion - - #region Events - - public override void Initialize() - { - SubscribeLocalEvent(OnPacketRecv); - SubscribeLocalEvent(OnAtmosUpdate); - SubscribeLocalEvent(OnAtmosAlarm); - SubscribeLocalEvent(OnPowerChanged); - SubscribeLocalEvent(OnResyncAll); - SubscribeLocalEvent(OnUpdateAlarmMode); - SubscribeLocalEvent(OnUpdateThreshold); - SubscribeLocalEvent(OnUpdateDeviceData); - SubscribeLocalEvent(OnTabChange); - SubscribeLocalEvent(OnDeviceListUpdate); - SubscribeLocalEvent(OnClose); - SubscribeLocalEvent(OnShutdown); - SubscribeLocalEvent(OnInteract); - } - - private void OnDeviceListUpdate(EntityUid uid, AirAlarmComponent component, DeviceListUpdateEvent args) - { SyncRegisterAllDevices(uid); } - - private void OnTabChange(EntityUid uid, AirAlarmComponent component, AirAlarmTabSetMessage msg) - { - component.CurrentTab = msg.Tab; - UpdateUI(uid, component); - } - - private void OnPowerChanged(EntityUid uid, AirAlarmComponent component, PowerChangedEvent args) - { - if (!args.Powered) - { - ForceCloseAllInterfaces(uid); - component.CurrentModeUpdater = null; - component.ScrubberData.Clear(); - component.SensorData.Clear(); - component.VentData.Clear(); - } - else - { - SyncAllDevices(uid); - } - } - - private void OnClose(EntityUid uid, AirAlarmComponent component, BoundUIClosedEvent args) - { - component.ActivePlayers.Remove(args.Session.UserId); - if (component.ActivePlayers.Count == 0) - RemoveActiveInterface(uid); - } - - private void OnShutdown(EntityUid uid, AirAlarmComponent component, ComponentShutdown args) - { - _activeUserInterfaces.Remove(uid); - } - - private void OnInteract(EntityUid uid, AirAlarmComponent component, InteractHandEvent args) - { - if (!_interactionSystem.InRangeUnobstructed(args.User, args.Target)) - return; - - if (!EntityManager.TryGetComponent(args.User, out ActorComponent? actor)) - return; - - if (EntityManager.TryGetComponent(uid, out WiresComponent? wire) && wire.IsPanelOpen) - { - args.Handled = false; - return; - } - - if (!this.IsPowered(uid, EntityManager)) - return; - - _uiSystem.GetUiOrNull(component.Owner, SharedAirAlarmInterfaceKey.Key)?.Open(actor.PlayerSession); - component.ActivePlayers.Add(actor.PlayerSession.UserId); - AddActiveInterface(uid); - SyncAllDevices(uid); - UpdateUI(uid, component); - } - - private void OnResyncAll(EntityUid uid, AirAlarmComponent component, AirAlarmResyncAllDevicesMessage args) - { - if (AccessCheck(uid, args.Session.AttachedEntity, component)) - { - component.VentData.Clear(); - component.ScrubberData.Clear(); - component.SensorData.Clear(); - - SyncRegisterAllDevices(uid); - } - } - - private void OnUpdateAlarmMode(EntityUid uid, AirAlarmComponent component, AirAlarmUpdateAlarmModeMessage args) - { - string addr = string.Empty; - if (EntityManager.TryGetComponent(uid, out DeviceNetworkComponent? netConn)) addr = netConn.Address; - if (AccessCheck(uid, args.Session.AttachedEntity, component)) - SetMode(uid, addr, args.Mode, true, false); - else - UpdateUI(uid, component); - } - - private void OnUpdateThreshold(EntityUid uid, AirAlarmComponent component, AirAlarmUpdateAlarmThresholdMessage args) - { - if (AccessCheck(uid, args.Session.AttachedEntity, component)) - SetThreshold(uid, args.Address, args.Type, args.Threshold, args.Gas); - else - UpdateUI(uid, component); - } - - private void OnUpdateDeviceData(EntityUid uid, AirAlarmComponent component, AirAlarmUpdateDeviceDataMessage args) - { - if (AccessCheck(uid, args.Session.AttachedEntity, component)) - SetDeviceData(uid, args.Address, args.Data); - else - UpdateUI(uid, component); - } - - private bool AccessCheck(EntityUid uid, EntityUid? user, AirAlarmComponent? component = null) - { - if (!Resolve(uid, ref component)) - return false; - - if (!EntityManager.TryGetComponent(uid, out AccessReaderComponent? reader) || user == null) - return false; - - if (!_accessSystem.IsAllowed(user.Value, reader)) - { - _popup.PopupEntity(Loc.GetString("air-alarm-ui-access-denied"), user.Value, Filter.Entities(user.Value)); - return false; - } - - return true; - } - - private void OnAtmosAlarm(EntityUid uid, AirAlarmComponent component, AtmosAlarmEvent args) - { - if (component.ActivePlayers.Count != 0) - { - SyncAllDevices(uid); - } - - string addr = string.Empty; - if (EntityManager.TryGetComponent(uid, out DeviceNetworkComponent? netConn)) addr = netConn.Address; - - - if (args.AlarmType == AtmosMonitorAlarmType.Danger) - { - SetMode(uid, addr, AirAlarmMode.None, true, false); - } - else if (args.AlarmType == AtmosMonitorAlarmType.Normal) - { - SetMode(uid, addr, AirAlarmMode.Filtering, true, false); - } - - UpdateUI(uid, component); - } - - #endregion - - #region Air Alarm Settings - - /// - /// Set an air alarm's mode. - /// - /// The origin address of this mode set. Used for network sync. - /// The mode to set the alarm to. - /// Whether to sync this mode change to the network or not. Defaults to false. - /// Whether this change is for the UI only, or if it changes the air alarm's operating mode. Defaults to true. - public void SetMode(EntityUid uid, string origin, AirAlarmMode mode, bool sync = false, bool uiOnly = true, AirAlarmComponent? controller = null) - { - if (!Resolve(uid, ref controller)) return; - controller.CurrentMode = mode; - - // setting it to UI only maans we don't have - // to deal with the issue of not-single-owner - // alarm mode executors - if (!uiOnly) - { - var newMode = AirAlarmModeFactory.ModeToExecutor(mode); - if (newMode != null) - { - newMode.Execute(uid); - if (newMode is IAirAlarmModeUpdate updatedMode) - { - controller.CurrentModeUpdater = updatedMode; - controller.CurrentModeUpdater.NetOwner = origin; - } - else if (controller.CurrentModeUpdater != null) - controller.CurrentModeUpdater = null; - } - } - // only one air alarm in a network can use an air alarm mode - // that updates, so even if it's a ui-only change, - // we have to invalidte the last mode's updater and - // remove it because otherwise it'll execute a now - // invalid mode - else if (controller.CurrentModeUpdater != null - && controller.CurrentModeUpdater.NetOwner != origin) - controller.CurrentModeUpdater = null; - - UpdateUI(uid, controller); - - // setting sync deals with the issue of air alarms - // in the same network needing to have the same mode - // as other alarms - if (sync) SyncMode(uid, mode); - } - - /// - /// Sets device data. Practically a wrapper around the packet sending function, SetData. - /// - /// The address to send the new data to. - /// The device data to be sent. - public void SetDeviceData(EntityUid uid, string address, IAtmosDeviceData devData, AirAlarmComponent? controller = null) - { - if (!Resolve(uid, ref controller)) return; - - devData.Dirty = true; - SetData(uid, address, devData); - } - - private void OnPacketRecv(EntityUid uid, AirAlarmComponent controller, DeviceNetworkPacketEvent args) - { - if (!args.Data.TryGetValue(DeviceNetworkConstants.Command, out string? cmd)) - return; - - switch (cmd) - { - case AtmosDeviceNetworkSystem.SyncData: - if (!args.Data.TryGetValue(AtmosDeviceNetworkSystem.SyncData, out IAtmosDeviceData? data) - || !controller.CanSync) - break; - - // Save into component. - // Sync data to interface. - // _airAlarmDataSystem.UpdateDeviceData(uid, args.SenderAddress, data); - // - switch (data) - { - case GasVentPumpData ventData: - if (!controller.VentData.TryAdd(args.SenderAddress, ventData)) - controller.VentData[args.SenderAddress] = ventData; - break; - case GasVentScrubberData scrubberData: - if (!controller.ScrubberData.TryAdd(args.SenderAddress, scrubberData)) - controller.ScrubberData[args.SenderAddress] = scrubberData; - break; - case AtmosSensorData sensorData: - if (!controller.SensorData.TryAdd(args.SenderAddress, sensorData)) - controller.SensorData[args.SenderAddress] = sensorData; - break; - } - - UpdateUI(uid, controller); - - return; - case AirAlarmSetMode: - if (!args.Data.TryGetValue(AirAlarmSetMode, out AirAlarmMode alarmMode)) break; - - SetMode(uid, args.SenderAddress, alarmMode); - - return; - } - } - - #endregion - - #region UI - - // List of active user interfaces. - private HashSet _activeUserInterfaces = new(); - - /// - /// Adds an active interface to be updated. - /// - public void AddActiveInterface(EntityUid uid) => - _activeUserInterfaces.Add(uid); - - /// - /// Removes an active interface from the system update loop. - /// - public void RemoveActiveInterface(EntityUid uid) => - _activeUserInterfaces.Remove(uid); - - /// - /// Force closes all interfaces currently open related to this air alarm. - /// - public void ForceCloseAllInterfaces(EntityUid uid) - { - _uiSystem.TryCloseAll(uid, SharedAirAlarmInterfaceKey.Key); - } - - public void OnAtmosUpdate(EntityUid uid, AirAlarmComponent alarm, AtmosDeviceUpdateEvent args) - { - if (alarm.CurrentModeUpdater != null) - alarm.CurrentModeUpdater.Update(uid); - } - - public float CalculatePressureAverage(AirAlarmComponent alarm) - { - return alarm.SensorData.Count != 0 - ? alarm.SensorData.Values.Select(v => v.Pressure).Average() - : 0f; - } - - public float CalculateTemperatureAverage(AirAlarmComponent alarm) - { - return alarm.SensorData.Count != 0 - ? alarm.SensorData.Values.Select(v => v.Temperature).Average() - : 0f; - } - - public void UpdateUI(EntityUid uid, AirAlarmComponent? alarm = null, DeviceNetworkComponent? devNet = null, AtmosAlarmableComponent? alarmable = null) - { - if (!Resolve(uid, ref alarm, ref devNet, ref alarmable)) - { - return; - } - - var pressure = CalculatePressureAverage(alarm); - var temperature = CalculateTemperatureAverage(alarm); - var dataToSend = new Dictionary(); - - if (alarm.CurrentTab != AirAlarmTab.Settings) - { - switch (alarm.CurrentTab) - { - case AirAlarmTab.Vent: - foreach (var (addr, data) in alarm.VentData) - { - dataToSend.Add(addr, data); - } - - break; - case AirAlarmTab.Scrubber: - foreach (var (addr, data) in alarm.ScrubberData) - { - dataToSend.Add(addr, data); - } - - break; - case AirAlarmTab.Sensors: - foreach (var (addr, data) in alarm.SensorData) - { - dataToSend.Add(addr, data); - } - - break; - } - } - - var deviceCount = alarm.VentData.Count + alarm.ScrubberData.Count + alarm.SensorData.Count; - - if (!_atmosAlarmable.TryGetHighestAlert(uid, out var highestAlarm)) - { - highestAlarm = AtmosMonitorAlarmType.Normal; - } - - _uiSystem.TrySetUiState( - uid, - SharedAirAlarmInterfaceKey.Key, - new AirAlarmUIState(devNet.Address, deviceCount, pressure, temperature, dataToSend, alarm.CurrentMode, alarm.CurrentTab, highestAlarm.Value)); - } - - private const float _delay = 8f; - private float _timer = 0f; - - public override void Update(float frameTime) - { - _timer += frameTime; - if (_timer >= _delay) - { - _timer = 0f; - foreach (var uid in _activeUserInterfaces) - { - SyncAllSensors(uid); - } - } - } - - #endregion } + + private void OnUpdateAlarmMode(EntityUid uid, AirAlarmComponent component, AirAlarmUpdateAlarmModeMessage args) + { + string addr = string.Empty; + if (EntityManager.TryGetComponent(uid, out DeviceNetworkComponent? netConn)) addr = netConn.Address; + if (AccessCheck(uid, args.Session.AttachedEntity, component)) + SetMode(uid, addr, args.Mode, true, false); + else + UpdateUI(uid, component); + } + + private void OnUpdateThreshold(EntityUid uid, AirAlarmComponent component, AirAlarmUpdateAlarmThresholdMessage args) + { + if (AccessCheck(uid, args.Session.AttachedEntity, component)) + SetThreshold(uid, args.Address, args.Type, args.Threshold, args.Gas); + else + UpdateUI(uid, component); + } + + private void OnUpdateDeviceData(EntityUid uid, AirAlarmComponent component, AirAlarmUpdateDeviceDataMessage args) + { + if (AccessCheck(uid, args.Session.AttachedEntity, component)) + SetDeviceData(uid, args.Address, args.Data); + else + UpdateUI(uid, component); + } + + private bool AccessCheck(EntityUid uid, EntityUid? user, AirAlarmComponent? component = null) + { + if (!Resolve(uid, ref component)) + return false; + + if (!EntityManager.TryGetComponent(uid, out AccessReaderComponent? reader) || user == null) + return false; + + if (!_accessSystem.IsAllowed(user.Value, reader)) + { + _popup.PopupEntity(Loc.GetString("air-alarm-ui-access-denied"), user.Value, Filter.Entities(user.Value)); + return false; + } + + return true; + } + + private void OnAtmosAlarm(EntityUid uid, AirAlarmComponent component, AtmosAlarmEvent args) + { + if (component.ActivePlayers.Count != 0) + { + SyncAllDevices(uid); + } + + string addr = string.Empty; + if (EntityManager.TryGetComponent(uid, out DeviceNetworkComponent? netConn)) addr = netConn.Address; + + + if (args.AlarmType == AtmosMonitorAlarmType.Danger) + { + SetMode(uid, addr, AirAlarmMode.None, true, false); + } + else if (args.AlarmType == AtmosMonitorAlarmType.Normal) + { + SetMode(uid, addr, AirAlarmMode.Filtering, true, false); + } + + UpdateUI(uid, component); + } + + #endregion + + #region Air Alarm Settings + + /// + /// Set an air alarm's mode. + /// + /// The origin address of this mode set. Used for network sync. + /// The mode to set the alarm to. + /// Whether to sync this mode change to the network or not. Defaults to false. + /// Whether this change is for the UI only, or if it changes the air alarm's operating mode. Defaults to true. + public void SetMode(EntityUid uid, string origin, AirAlarmMode mode, bool sync = false, bool uiOnly = true, AirAlarmComponent? controller = null) + { + if (!Resolve(uid, ref controller)) return; + controller.CurrentMode = mode; + + // setting it to UI only maans we don't have + // to deal with the issue of not-single-owner + // alarm mode executors + if (!uiOnly) + { + var newMode = AirAlarmModeFactory.ModeToExecutor(mode); + if (newMode != null) + { + newMode.Execute(uid); + if (newMode is IAirAlarmModeUpdate updatedMode) + { + controller.CurrentModeUpdater = updatedMode; + controller.CurrentModeUpdater.NetOwner = origin; + } + else if (controller.CurrentModeUpdater != null) + controller.CurrentModeUpdater = null; + } + } + // only one air alarm in a network can use an air alarm mode + // that updates, so even if it's a ui-only change, + // we have to invalidte the last mode's updater and + // remove it because otherwise it'll execute a now + // invalid mode + else if (controller.CurrentModeUpdater != null + && controller.CurrentModeUpdater.NetOwner != origin) + controller.CurrentModeUpdater = null; + + UpdateUI(uid, controller); + + // setting sync deals with the issue of air alarms + // in the same network needing to have the same mode + // as other alarms + if (sync) SyncMode(uid, mode); + } + + /// + /// Sets device data. Practically a wrapper around the packet sending function, SetData. + /// + /// The address to send the new data to. + /// The device data to be sent. + public void SetDeviceData(EntityUid uid, string address, IAtmosDeviceData devData, AirAlarmComponent? controller = null) + { + if (!Resolve(uid, ref controller)) return; + + devData.Dirty = true; + SetData(uid, address, devData); + } + + private void OnPacketRecv(EntityUid uid, AirAlarmComponent controller, DeviceNetworkPacketEvent args) + { + if (!args.Data.TryGetValue(DeviceNetworkConstants.Command, out string? cmd)) + return; + + switch (cmd) + { + case AtmosDeviceNetworkSystem.SyncData: + if (!args.Data.TryGetValue(AtmosDeviceNetworkSystem.SyncData, out IAtmosDeviceData? data) + || !controller.CanSync) + break; + + // Save into component. + // Sync data to interface. + // _airAlarmDataSystem.UpdateDeviceData(uid, args.SenderAddress, data); + // + switch (data) + { + case GasVentPumpData ventData: + if (!controller.VentData.TryAdd(args.SenderAddress, ventData)) + controller.VentData[args.SenderAddress] = ventData; + break; + case GasVentScrubberData scrubberData: + if (!controller.ScrubberData.TryAdd(args.SenderAddress, scrubberData)) + controller.ScrubberData[args.SenderAddress] = scrubberData; + break; + case AtmosSensorData sensorData: + if (!controller.SensorData.TryAdd(args.SenderAddress, sensorData)) + controller.SensorData[args.SenderAddress] = sensorData; + break; + } + + UpdateUI(uid, controller); + + return; + case AirAlarmSetMode: + if (!args.Data.TryGetValue(AirAlarmSetMode, out AirAlarmMode alarmMode)) break; + + SetMode(uid, args.SenderAddress, alarmMode); + + return; + } + } + + #endregion + + #region UI + + // List of active user interfaces. + private HashSet _activeUserInterfaces = new(); + + /// + /// Adds an active interface to be updated. + /// + public void AddActiveInterface(EntityUid uid) => + _activeUserInterfaces.Add(uid); + + /// + /// Removes an active interface from the system update loop. + /// + public void RemoveActiveInterface(EntityUid uid) => + _activeUserInterfaces.Remove(uid); + + /// + /// Force closes all interfaces currently open related to this air alarm. + /// + public void ForceCloseAllInterfaces(EntityUid uid) + { + _uiSystem.TryCloseAll(uid, SharedAirAlarmInterfaceKey.Key); + } + + public void OnAtmosUpdate(EntityUid uid, AirAlarmComponent alarm, AtmosDeviceUpdateEvent args) + { + if (alarm.CurrentModeUpdater != null) + alarm.CurrentModeUpdater.Update(uid); + } + + public float CalculatePressureAverage(AirAlarmComponent alarm) + { + return alarm.SensorData.Count != 0 + ? alarm.SensorData.Values.Select(v => v.Pressure).Average() + : 0f; + } + + public float CalculateTemperatureAverage(AirAlarmComponent alarm) + { + return alarm.SensorData.Count != 0 + ? alarm.SensorData.Values.Select(v => v.Temperature).Average() + : 0f; + } + + public void UpdateUI(EntityUid uid, AirAlarmComponent? alarm = null, DeviceNetworkComponent? devNet = null, AtmosAlarmableComponent? alarmable = null) + { + if (!Resolve(uid, ref alarm, ref devNet, ref alarmable)) + { + return; + } + + var pressure = CalculatePressureAverage(alarm); + var temperature = CalculateTemperatureAverage(alarm); + var dataToSend = new Dictionary(); + + if (alarm.CurrentTab != AirAlarmTab.Settings) + { + switch (alarm.CurrentTab) + { + case AirAlarmTab.Vent: + foreach (var (addr, data) in alarm.VentData) + { + dataToSend.Add(addr, data); + } + + break; + case AirAlarmTab.Scrubber: + foreach (var (addr, data) in alarm.ScrubberData) + { + dataToSend.Add(addr, data); + } + + break; + case AirAlarmTab.Sensors: + foreach (var (addr, data) in alarm.SensorData) + { + dataToSend.Add(addr, data); + } + + break; + } + } + + var deviceCount = alarm.VentData.Count + alarm.ScrubberData.Count + alarm.SensorData.Count; + + if (!_atmosAlarmable.TryGetHighestAlert(uid, out var highestAlarm)) + { + highestAlarm = AtmosMonitorAlarmType.Normal; + } + + _uiSystem.TrySetUiState( + uid, + SharedAirAlarmInterfaceKey.Key, + new AirAlarmUIState(devNet.Address, deviceCount, pressure, temperature, dataToSend, alarm.CurrentMode, alarm.CurrentTab, highestAlarm.Value)); + } + + private const float _delay = 8f; + private float _timer = 0f; + + public override void Update(float frameTime) + { + _timer += frameTime; + if (_timer >= _delay) + { + _timer = 0f; + foreach (var uid in _activeUserInterfaces) + { + SyncAllSensors(uid); + } + } + } + + #endregion } diff --git a/Content.Server/Atmos/Monitor/Systems/AtmosAlarmableSystem.cs b/Content.Server/Atmos/Monitor/Systems/AtmosAlarmableSystem.cs index 4f32f8ea7d..8d62ebead4 100644 --- a/Content.Server/Atmos/Monitor/Systems/AtmosAlarmableSystem.cs +++ b/Content.Server/Atmos/Monitor/Systems/AtmosAlarmableSystem.cs @@ -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!; + + /// + /// An alarm. Has three valid states: Normal, Warning, Danger. + /// Will attempt to fetch the tags from the alarming entity + /// to send over. + /// + public const string AlertCmd = "atmos_alarm"; + + public const string AlertSource = "atmos_alarm_source"; + + public const string AlertTypes = "atmos_alarm_types"; + + /// + /// 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 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(OnPacketRecv); + SubscribeLocalEvent(OnPowerChange); + } - /// - /// An alarm. Has three valid states: Normal, Warning, Danger. - /// Will attempt to fetch the tags from the alarming entity - /// to send over. - /// - public const string AlertCmd = "atmos_alarm"; - - public const string AlertSource = "atmos_alarm_source"; - - public const string AlertTypes = "atmos_alarm_types"; - - /// - /// 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 const string ResetAll = "atmos_alarmable_reset_all"; - - public override void Initialize() + private void OnPowerChange(EntityUid uid, AtmosAlarmableComponent component, PowerChangedEvent args) + { + if (!args.Powered) { - SubscribeLocalEvent(OnPacketRecv); - SubscribeLocalEvent(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? 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? 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? 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); - } - - /// - /// 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. - /// - /// - /// - /// - 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); - } - - /// - /// Resets the state of this alarmable to normal. - /// - /// - /// - 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); - } - - /// - /// Tries to get the highest possible alert stored in this alarm. - /// - /// - /// - /// - /// - 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? 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? 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? 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); + } + + /// + /// 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. + /// + /// + /// + /// + 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); + } + + /// + /// Resets the state of this alarmable to normal. + /// + /// + /// + 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); + } + + /// + /// Tries to get the highest possible alert stored in this alarm. + /// + /// + /// + /// + /// + 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; } } diff --git a/Content.Server/Atmos/Monitor/Systems/AtmosMonitoringSystem.cs b/Content.Server/Atmos/Monitor/Systems/AtmosMonitoringSystem.cs index 3f425c7a3d..ab21b45ca0 100644 --- a/Content.Server/Atmos/Monitor/Systems/AtmosMonitoringSystem.cs +++ b/Content.Server/Atmos/Monitor/Systems/AtmosMonitoringSystem.cs @@ -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(OnAtmosMonitorInit); + SubscribeLocalEvent(OnAtmosMonitorStartup); + SubscribeLocalEvent(OnAtmosUpdate); + SubscribeLocalEvent(OnFireEvent); + SubscribeLocalEvent(OnPowerChangedEvent); + SubscribeLocalEvent(BeforePacketRecv); + SubscribeLocalEvent(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(component.TemperatureThresholdId)); - // Packet data - public const string AlertTypes = "atmos_monitor_alert_types"; + if (component.PressureThresholdId != null) + component.PressureThreshold = new(_prototypeManager.Index(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(OnAtmosMonitorInit); - SubscribeLocalEvent(OnAtmosMonitorStartup); - SubscribeLocalEvent(OnAtmosUpdate); - SubscribeLocalEvent(OnFireEvent); - SubscribeLocalEvent(OnPowerChangedEvent); - SubscribeLocalEvent(BeforePacketRecv); - SubscribeLocalEvent(OnPacketRecv); - } - - private void OnAtmosMonitorInit(EntityUid uid, AtmosMonitorComponent component, ComponentInit args) - { - if (component.TemperatureThresholdId != null) - component.TemperatureThreshold = new(_prototypeManager.Index(component.TemperatureThresholdId)); - - if (component.PressureThresholdId != null) - component.PressureThreshold = new(_prototypeManager.Index(component.PressureThresholdId)); - - if (component.GasThresholdIds != null) - { - component.GasThresholds = new(); - foreach (var (gas, id) in component.GasThresholdIds) - if (_prototypeManager.TryIndex(id, out var gasThreshold)) - component.GasThresholds.Add(gas, new(gasThreshold)); - } - } - - private void OnAtmosMonitorStartup(EntityUid uid, AtmosMonitorComponent component, ComponentStartup args) - { - if (!HasComp(uid) - && TryComp(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(); - foreach (var gas in Enum.GetValues()) - { - 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(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(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 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); - } - } - - /// - /// Alerts the network that the state of a monitor has changed. - /// - /// The alarm state to set this monitor to. - /// The alarms that caused this alarm state. - public void Alert(EntityUid uid, AtmosMonitorAlarmType state, HashSet? 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 - } - - /// - /// Resets a single monitor's alarm. - /// - private void Reset(EntityUid uid) - { - Alert(uid, AtmosMonitorAlarmType.Normal); - } - - /// - /// 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). - /// - /// - /// 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). - /// - 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); - } - } - - /// - /// Set a monitor's threshold. - /// - /// The type of threshold to change. - /// Threshold data. - /// Gas, if applicable. - 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(id, out var gasThreshold)) + component.GasThresholds.Add(gas, new(gasThreshold)); } } + + private void OnAtmosMonitorStartup(EntityUid uid, AtmosMonitorComponent component, ComponentStartup args) + { + if (!HasComp(uid) + && TryComp(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(); + foreach (var gas in Enum.GetValues()) + { + 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(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(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 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); + } + } + + /// + /// Alerts the network that the state of a monitor has changed. + /// + /// The alarm state to set this monitor to. + /// The alarms that caused this alarm state. + public void Alert(EntityUid uid, AtmosMonitorAlarmType state, HashSet? 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 + } + + /// + /// Resets a single monitor's alarm. + /// + private void Reset(EntityUid uid) + { + Alert(uid, AtmosMonitorAlarmType.Normal); + } + + /// + /// 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). + /// + /// + /// 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). + /// + 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); + } + } + + /// + /// Set a monitor's threshold. + /// + /// The type of threshold to change. + /// Threshold data. + /// Gas, if applicable. + 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; + } + + } } diff --git a/Content.Server/Atmos/Monitor/Systems/FireAlarmSystem.cs b/Content.Server/Atmos/Monitor/Systems/FireAlarmSystem.cs index 1d72aa8ff7..c419d80fe2 100644 --- a/Content.Server/Atmos/Monitor/Systems/FireAlarmSystem.cs +++ b/Content.Server/Atmos/Monitor/Systems/FireAlarmSystem.cs @@ -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(OnInteractHand); + SubscribeLocalEvent(OnDeviceListSync); + SubscribeLocalEvent(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(OnInteractHand); - SubscribeLocalEvent(OnDeviceListSync); - SubscribeLocalEvent(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(uid, out var atmosMonitor)) { - if (TryComp(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; } } } diff --git a/Content.Shared/Atmos/Monitor/AtmosAlarmThreshold.cs b/Content.Shared/Atmos/Monitor/AtmosAlarmThreshold.cs new file mode 100644 index 0000000000..442fac084b --- /dev/null +++ b/Content.Shared/Atmos/Monitor/AtmosAlarmThreshold.cs @@ -0,0 +1,270 @@ +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; + +namespace Content.Shared.Atmos.Monitor; + +// mostly based around floats and percentages, no literals +// except for the range boundaries +[Prototype("alarmThreshold")] +[Serializable, NetSerializable] +public sealed class AtmosAlarmThreshold : IPrototype, ISerializationHooks +{ + [IdDataFieldAttribute] + public string ID { get; } = default!; + [ViewVariables] + [DataField("ignore")] + public bool Ignore = false; + + // zero bounds are not allowed - just + // set the bound to null if you want + // to disable it + [ViewVariables] + [DataField("upperBound")] + public float? UpperBound { get; private set; } + + [ViewVariables] + [DataField("lowerBound")] + public float? LowerBound { get; private set; } + + // upper warning percentage + // must always cause UpperWarningBound + // to be smaller + [ViewVariables] + [DataField("upperWarnAround")] + public float? UpperWarningPercentage { get; private set; } + + // lower warning percentage + // must always cause LowerWarningBound + // to be larger + [ViewVariables] + [DataField("lowerWarnAround")] + public float? LowerWarningPercentage { get; private set; } + + [ViewVariables] + public float? UpperWarningBound + { + get => CalculateWarningBound(AtmosMonitorThresholdBound.Upper); + } + + [ViewVariables] + public float? LowerWarningBound + { + get => CalculateWarningBound(AtmosMonitorThresholdBound.Lower); + } + + public AtmosAlarmThreshold() + {} + + public AtmosAlarmThreshold(AtmosAlarmThreshold other) + { + Ignore = other.Ignore; + UpperBound = other.UpperBound; + LowerBound = other.LowerBound; + UpperWarningPercentage = other.UpperWarningPercentage; + LowerWarningPercentage = other.LowerWarningPercentage; + } + + void ISerializationHooks.AfterDeserialization() + { + if (UpperBound <= LowerBound) + UpperBound = null; + + if (LowerBound >= UpperBound) + LowerBound = null; + + if (UpperWarningPercentage != null) + TrySetWarningBound(AtmosMonitorThresholdBound.Upper, UpperBound * UpperWarningPercentage); + + if (LowerWarningPercentage != null) + TrySetWarningBound(AtmosMonitorThresholdBound.Lower, LowerBound * LowerWarningPercentage); + } + + // utility function to check a threshold against some calculated value + public bool CheckThreshold(float value, out AtmosMonitorAlarmType state) + { + state = AtmosMonitorAlarmType.Normal; + if (Ignore) return false; + + if (value >= UpperBound || value <= LowerBound) + { + state = AtmosMonitorAlarmType.Danger; + return true; + } + if (value >= UpperWarningBound || value <= LowerWarningBound) + { + state = AtmosMonitorAlarmType.Warning; + return true; + } + + return true; + } + + // set the primary bound, takes a hard value + public bool TrySetPrimaryBound(AtmosMonitorThresholdBound bound, float? input) + { + if (input == null) + { + switch (bound) + { + case AtmosMonitorThresholdBound.Upper: + UpperBound = null; + break; + case AtmosMonitorThresholdBound.Lower: + LowerBound = null; + break; + } + + return true; + } + + float value = (float) input; + + if (value <= 0f || float.IsNaN(value)) + return false; + + (float target, int compare)? targetValue = null; + switch (bound) + { + case AtmosMonitorThresholdBound.Upper: + if (float.IsPositiveInfinity(value)) + return false; + + if (LowerBound != null) + targetValue = ((float) LowerBound, -1); + break; + case AtmosMonitorThresholdBound.Lower: + if (float.IsNegativeInfinity(value)) + return false; + + if (UpperBound != null) + targetValue = ((float) UpperBound, 1); + break; + } + + bool isValid = true; + if (targetValue != null) + { + var result = targetValue.Value.target.CompareTo(value); + isValid = targetValue.Value.compare == result; + } + + if (isValid) + { + switch (bound) + { + case AtmosMonitorThresholdBound.Upper: + UpperBound = value; + return true; + case AtmosMonitorThresholdBound.Lower: + LowerBound = value; + return true; + } + } + + return false; + } + + // set the warning bound, takes a hard value + // + // this will always set the percentage and + // the raw value at the same time + public bool TrySetWarningBound(AtmosMonitorThresholdBound bound, float? input) + { + if (input == null) + { + switch (bound) + { + case AtmosMonitorThresholdBound.Upper: + UpperWarningPercentage = null; + break; + case AtmosMonitorThresholdBound.Lower: + LowerWarningPercentage = null; + break; + } + + return true; + } + + switch (bound) + { + case AtmosMonitorThresholdBound.Upper: + if (UpperBound == null) + return false; + + float upperWarning = (float) (input / UpperBound); + float upperTestValue = (upperWarning * (float) UpperBound); + + if (upperWarning > 1f + || upperTestValue < LowerWarningBound + || upperTestValue < LowerBound) + return false; + + UpperWarningPercentage = upperWarning; + + return true; + case AtmosMonitorThresholdBound.Lower: + if (LowerBound == null) + return false; + + float lowerWarning = (float) (input / LowerBound); + float testValue = (lowerWarning * (float) LowerBound); + + if (lowerWarning < 1f + || testValue > UpperWarningBound + || testValue > UpperBound) + return false; + + LowerWarningPercentage = lowerWarning; + + return true; + } + + return false; + } + + public float? CalculateWarningBound(AtmosMonitorThresholdBound bound) + { + float? value = null; + + switch (bound) + { + case AtmosMonitorThresholdBound.Upper: + if (UpperBound == null || UpperWarningPercentage == null) + break; + + value = UpperBound * UpperWarningPercentage; + break; + case AtmosMonitorThresholdBound.Lower: + if (LowerBound == null || LowerWarningPercentage == null) + break; + + value = LowerBound * LowerWarningPercentage; + break; + } + + return value; + } +} + +public enum AtmosMonitorThresholdBound +{ + Upper, + Lower +} + +// not really used in the prototype but in code, +// to differentiate between the different +// fields you can find this prototype in +public enum AtmosMonitorThresholdType +{ + Temperature, + Pressure, + Gas +} + +[Serializable, NetSerializable] +public enum AtmosMonitorVisuals : byte +{ + Offset, + AlarmType, +} diff --git a/Content.Shared/Atmos/Monitor/AtmosAlarmThresholdPrototype.cs b/Content.Shared/Atmos/Monitor/AtmosAlarmThresholdPrototype.cs deleted file mode 100644 index fd3c640ec6..0000000000 --- a/Content.Shared/Atmos/Monitor/AtmosAlarmThresholdPrototype.cs +++ /dev/null @@ -1,271 +0,0 @@ -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization; - -namespace Content.Shared.Atmos.Monitor -{ - // mostly based around floats and percentages, no literals - // except for the range boundaries - [Prototype("alarmThreshold")] - [Serializable, NetSerializable] - public sealed class AtmosAlarmThreshold : IPrototype, ISerializationHooks - { - [IdDataFieldAttribute] - public string ID { get; } = default!; - [ViewVariables] - [DataField("ignore")] - public bool Ignore = false; - - // zero bounds are not allowed - just - // set the bound to null if you want - // to disable it - [ViewVariables] - [DataField("upperBound")] - public float? UpperBound { get; private set; } - - [ViewVariables] - [DataField("lowerBound")] - public float? LowerBound { get; private set; } - - // upper warning percentage - // must always cause UpperWarningBound - // to be smaller - [ViewVariables] - [DataField("upperWarnAround")] - public float? UpperWarningPercentage { get; private set; } - - // lower warning percentage - // must always cause LowerWarningBound - // to be larger - [ViewVariables] - [DataField("lowerWarnAround")] - public float? LowerWarningPercentage { get; private set; } - - [ViewVariables] - public float? UpperWarningBound - { - get => CalculateWarningBound(AtmosMonitorThresholdBound.Upper); - } - - [ViewVariables] - public float? LowerWarningBound - { - get => CalculateWarningBound(AtmosMonitorThresholdBound.Lower); - } - - public AtmosAlarmThreshold() - {} - - public AtmosAlarmThreshold(AtmosAlarmThreshold other) - { - Ignore = other.Ignore; - UpperBound = other.UpperBound; - LowerBound = other.LowerBound; - UpperWarningPercentage = other.UpperWarningPercentage; - LowerWarningPercentage = other.LowerWarningPercentage; - } - - void ISerializationHooks.AfterDeserialization() - { - if (UpperBound <= LowerBound) - UpperBound = null; - - if (LowerBound >= UpperBound) - LowerBound = null; - - if (UpperWarningPercentage != null) - TrySetWarningBound(AtmosMonitorThresholdBound.Upper, UpperBound * UpperWarningPercentage); - - if (LowerWarningPercentage != null) - TrySetWarningBound(AtmosMonitorThresholdBound.Lower, LowerBound * LowerWarningPercentage); - } - - // utility function to check a threshold against some calculated value - public bool CheckThreshold(float value, out AtmosMonitorAlarmType state) - { - state = AtmosMonitorAlarmType.Normal; - if (Ignore) return false; - - if (value >= UpperBound || value <= LowerBound) - { - state = AtmosMonitorAlarmType.Danger; - return true; - } - if (value >= UpperWarningBound || value <= LowerWarningBound) - { - state = AtmosMonitorAlarmType.Warning; - return true; - } - - return true; - } - - // set the primary bound, takes a hard value - public bool TrySetPrimaryBound(AtmosMonitorThresholdBound bound, float? input) - { - if (input == null) - { - switch (bound) - { - case AtmosMonitorThresholdBound.Upper: - UpperBound = null; - break; - case AtmosMonitorThresholdBound.Lower: - LowerBound = null; - break; - } - - return true; - } - - float value = (float) input; - - if (value <= 0f || float.IsNaN(value)) - return false; - - (float target, int compare)? targetValue = null; - switch (bound) - { - case AtmosMonitorThresholdBound.Upper: - if (float.IsPositiveInfinity(value)) - return false; - - if (LowerBound != null) - targetValue = ((float) LowerBound, -1); - break; - case AtmosMonitorThresholdBound.Lower: - if (float.IsNegativeInfinity(value)) - return false; - - if (UpperBound != null) - targetValue = ((float) UpperBound, 1); - break; - } - - bool isValid = true; - if (targetValue != null) - { - var result = targetValue.Value.target.CompareTo(value); - isValid = targetValue.Value.compare == result; - } - - if (isValid) - { - switch (bound) - { - case AtmosMonitorThresholdBound.Upper: - UpperBound = value; - return true; - case AtmosMonitorThresholdBound.Lower: - LowerBound = value; - return true; - } - } - - return false; - } - - // set the warning bound, takes a hard value - // - // this will always set the percentage and - // the raw value at the same time - public bool TrySetWarningBound(AtmosMonitorThresholdBound bound, float? input) - { - if (input == null) - { - switch (bound) - { - case AtmosMonitorThresholdBound.Upper: - UpperWarningPercentage = null; - break; - case AtmosMonitorThresholdBound.Lower: - LowerWarningPercentage = null; - break; - } - - return true; - } - - switch (bound) - { - case AtmosMonitorThresholdBound.Upper: - if (UpperBound == null) - return false; - - float upperWarning = (float) (input / UpperBound); - float upperTestValue = (upperWarning * (float) UpperBound); - - if (upperWarning > 1f - || upperTestValue < LowerWarningBound - || upperTestValue < LowerBound) - return false; - - UpperWarningPercentage = upperWarning; - - return true; - case AtmosMonitorThresholdBound.Lower: - if (LowerBound == null) - return false; - - float lowerWarning = (float) (input / LowerBound); - float testValue = (lowerWarning * (float) LowerBound); - - if (lowerWarning < 1f - || testValue > UpperWarningBound - || testValue > UpperBound) - return false; - - LowerWarningPercentage = lowerWarning; - - return true; - } - - return false; - } - - public float? CalculateWarningBound(AtmosMonitorThresholdBound bound) - { - float? value = null; - - switch (bound) - { - case AtmosMonitorThresholdBound.Upper: - if (UpperBound == null || UpperWarningPercentage == null) - break; - - value = UpperBound * UpperWarningPercentage; - break; - case AtmosMonitorThresholdBound.Lower: - if (LowerBound == null || LowerWarningPercentage == null) - break; - - value = LowerBound * LowerWarningPercentage; - break; - } - - return value; - } - } - - public enum AtmosMonitorThresholdBound - { - Upper, - Lower - } - - // not really used in the prototype but in code, - // to differentiate between the different - // fields you can find this prototype in - public enum AtmosMonitorThresholdType - { - Temperature, - Pressure, - Gas - } - - [Serializable, NetSerializable] - public enum AtmosMonitorVisuals : byte - { - Offset, - AlarmType, - } -} diff --git a/Content.Shared/Atmos/Monitor/AtmosMonitorAlarmType.cs b/Content.Shared/Atmos/Monitor/AtmosMonitorAlarmType.cs index 27f0320448..c0cd58a48d 100644 --- a/Content.Shared/Atmos/Monitor/AtmosMonitorAlarmType.cs +++ b/Content.Shared/Atmos/Monitor/AtmosMonitorAlarmType.cs @@ -1,13 +1,12 @@ using Robust.Shared.Serialization; -namespace Content.Shared.Atmos.Monitor +namespace Content.Shared.Atmos.Monitor; + +[Serializable, NetSerializable] +public enum AtmosMonitorAlarmType : sbyte { - [Serializable, NetSerializable] - public enum AtmosMonitorAlarmType : sbyte - { - Normal = 0, - Warning = 1, - Danger = 2, // 1 << 1 is the exact same thing and we're not really doing **bitmasking** are we? - Emagged = 3, - } -} + Normal = 0, + Warning = 1, + Danger = 2, // 1 << 1 is the exact same thing and we're not really doing **bitmasking** are we? + Emagged = 3, +} \ No newline at end of file diff --git a/Content.Shared/Atmos/Monitor/Components/SharedAirAlarmComponent.cs b/Content.Shared/Atmos/Monitor/Components/SharedAirAlarmComponent.cs index 19076227a2..9b6258f07d 100644 --- a/Content.Shared/Atmos/Monitor/Components/SharedAirAlarmComponent.cs +++ b/Content.Shared/Atmos/Monitor/Components/SharedAirAlarmComponent.cs @@ -1,131 +1,130 @@ using Robust.Shared.Serialization; -namespace Content.Shared.Atmos.Monitor.Components +namespace Content.Shared.Atmos.Monitor.Components; + +[Serializable, NetSerializable] +public enum SharedAirAlarmInterfaceKey { - [Serializable, NetSerializable] - public enum SharedAirAlarmInterfaceKey + Key +} + +[Serializable, NetSerializable] +public enum AirAlarmMode +{ + None, + Filtering, + Fill, + Panic, + Replace +} + +[Serializable, NetSerializable] +public enum AirAlarmWireStatus +{ + Power, + Access, + Panic, + DeviceSync +} + +public interface IAtmosDeviceData +{ + public bool Enabled { get; set; } + public bool Dirty { get; set; } + public bool IgnoreAlarms { get; set; } +} + +[Serializable, NetSerializable] +public sealed class AirAlarmUIState : BoundUserInterfaceState +{ + public AirAlarmUIState(string address, int deviceCount, float pressureAverage, float temperatureAverage, Dictionary deviceData, AirAlarmMode mode, AirAlarmTab tab, AtmosMonitorAlarmType alarmType) { - Key + Address = address; + DeviceCount = deviceCount; + PressureAverage = pressureAverage; + TemperatureAverage = temperatureAverage; + DeviceData = deviceData; + Mode = mode; + Tab = tab; + AlarmType = alarmType; } - [Serializable, NetSerializable] - public enum AirAlarmMode + public string Address { get; } + public int DeviceCount { get; } + public float PressureAverage { get; } + public float TemperatureAverage { get; } + /// + /// Every single device data that can be seen from this + /// air alarm. This includes vents, scrubbers, and sensors. + /// The device data you get, however, depends on the current + /// selected tab. + /// + public Dictionary DeviceData { get; } + public AirAlarmMode Mode { get; } + public AirAlarmTab Tab { get; } + public AtmosMonitorAlarmType AlarmType { get; } +} + +[Serializable, NetSerializable] +public sealed class AirAlarmTabSetMessage : BoundUserInterfaceMessage +{ + public AirAlarmTabSetMessage(AirAlarmTab tab) { - None, - Filtering, - Fill, - Panic, - Replace + Tab = tab; } - [Serializable, NetSerializable] - public enum AirAlarmWireStatus + public AirAlarmTab Tab { get; } +} + +[Serializable, NetSerializable] +public sealed class AirAlarmResyncAllDevicesMessage : BoundUserInterfaceMessage +{} + +[Serializable, NetSerializable] +public sealed class AirAlarmUpdateAlarmModeMessage : BoundUserInterfaceMessage +{ + public AirAlarmMode Mode { get; } + + public AirAlarmUpdateAlarmModeMessage(AirAlarmMode mode) { - Power, - Access, - Panic, - DeviceSync - } - - public interface IAtmosDeviceData - { - public bool Enabled { get; set; } - public bool Dirty { get; set; } - public bool IgnoreAlarms { get; set; } - } - - [Serializable, NetSerializable] - public sealed class AirAlarmUIState : BoundUserInterfaceState - { - public AirAlarmUIState(string address, int deviceCount, float pressureAverage, float temperatureAverage, Dictionary deviceData, AirAlarmMode mode, AirAlarmTab tab, AtmosMonitorAlarmType alarmType) - { - Address = address; - DeviceCount = deviceCount; - PressureAverage = pressureAverage; - TemperatureAverage = temperatureAverage; - DeviceData = deviceData; - Mode = mode; - Tab = tab; - AlarmType = alarmType; - } - - public string Address { get; } - public int DeviceCount { get; } - public float PressureAverage { get; } - public float TemperatureAverage { get; } - /// - /// Every single device data that can be seen from this - /// air alarm. This includes vents, scrubbers, and sensors. - /// The device data you get, however, depends on the current - /// selected tab. - /// - public Dictionary DeviceData { get; } - public AirAlarmMode Mode { get; } - public AirAlarmTab Tab { get; } - public AtmosMonitorAlarmType AlarmType { get; } - } - - [Serializable, NetSerializable] - public sealed class AirAlarmTabSetMessage : BoundUserInterfaceMessage - { - public AirAlarmTabSetMessage(AirAlarmTab tab) - { - Tab = tab; - } - - public AirAlarmTab Tab { get; } - } - - [Serializable, NetSerializable] - public sealed class AirAlarmResyncAllDevicesMessage : BoundUserInterfaceMessage - {} - - [Serializable, NetSerializable] - public sealed class AirAlarmUpdateAlarmModeMessage : BoundUserInterfaceMessage - { - public AirAlarmMode Mode { get; } - - public AirAlarmUpdateAlarmModeMessage(AirAlarmMode mode) - { - Mode = mode; - } - } - - [Serializable, NetSerializable] - public sealed class AirAlarmUpdateDeviceDataMessage : BoundUserInterfaceMessage - { - public string Address { get; } - public IAtmosDeviceData Data { get; } - - public AirAlarmUpdateDeviceDataMessage(string addr, IAtmosDeviceData data) - { - Address = addr; - Data = data; - } - } - - [Serializable, NetSerializable] - public sealed class AirAlarmUpdateAlarmThresholdMessage : BoundUserInterfaceMessage - { - public string Address { get; } - public AtmosAlarmThreshold Threshold { get; } - public AtmosMonitorThresholdType Type { get; } - public Gas? Gas { get; } - - public AirAlarmUpdateAlarmThresholdMessage(string address, AtmosMonitorThresholdType type, AtmosAlarmThreshold threshold, Gas? gas = null) - { - Address = address; - Threshold = threshold; - Type = type; - Gas = gas; - } - } - - public enum AirAlarmTab - { - Vent, - Scrubber, - Sensors, - Settings + Mode = mode; } } + +[Serializable, NetSerializable] +public sealed class AirAlarmUpdateDeviceDataMessage : BoundUserInterfaceMessage +{ + public string Address { get; } + public IAtmosDeviceData Data { get; } + + public AirAlarmUpdateDeviceDataMessage(string addr, IAtmosDeviceData data) + { + Address = addr; + Data = data; + } +} + +[Serializable, NetSerializable] +public sealed class AirAlarmUpdateAlarmThresholdMessage : BoundUserInterfaceMessage +{ + public string Address { get; } + public AtmosAlarmThreshold Threshold { get; } + public AtmosMonitorThresholdType Type { get; } + public Gas? Gas { get; } + + public AirAlarmUpdateAlarmThresholdMessage(string address, AtmosMonitorThresholdType type, AtmosAlarmThreshold threshold, Gas? gas = null) + { + Address = address; + Threshold = threshold; + Type = type; + Gas = gas; + } +} + +public enum AirAlarmTab +{ + Vent, + Scrubber, + Sensors, + Settings +} diff --git a/Content.Shared/Atmos/Monitor/Components/SharedFireAlarmComponent.cs b/Content.Shared/Atmos/Monitor/Components/SharedFireAlarmComponent.cs index cb684f969a..c837f920aa 100644 --- a/Content.Shared/Atmos/Monitor/Components/SharedFireAlarmComponent.cs +++ b/Content.Shared/Atmos/Monitor/Components/SharedFireAlarmComponent.cs @@ -1,11 +1,10 @@ using Robust.Shared.Serialization; -namespace Content.Shared.Atmos.Monitor.Components +namespace Content.Shared.Atmos.Monitor.Components; + +[Serializable, NetSerializable] +public enum FireAlarmWireStatus { - [Serializable, NetSerializable] - public enum FireAlarmWireStatus - { - Power, - Alarm - } + Power, + Alarm }