file-scoped namespaces
This commit is contained in:
@@ -8,63 +8,62 @@ using Robust.Shared.IoC;
|
|||||||
using Robust.Shared.Maths;
|
using Robust.Shared.Maths;
|
||||||
using Robust.Shared.Serialization.Manager.Attributes;
|
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<AtmosMonitorAlarmType, string> _alarmStates = new();
|
||||||
|
|
||||||
|
[DataField("hideOnDepowered")]
|
||||||
|
private readonly List<string>? _hideOnDepowered;
|
||||||
|
|
||||||
|
// eh...
|
||||||
|
[DataField("setOnDepowered")]
|
||||||
|
private readonly Dictionary<string, string>? _setOnDepowered;
|
||||||
|
|
||||||
|
[Obsolete("Subscribe to your component being initialised instead.")]
|
||||||
|
public override void InitializeEntity(EntityUid entity)
|
||||||
{
|
{
|
||||||
[Dependency] IEntityManager _entityManager = default!;
|
base.InitializeEntity(entity);
|
||||||
[DataField("layerMap")]
|
|
||||||
private string _layerMap { get; } = string.Empty;
|
|
||||||
|
|
||||||
[DataField("alarmStates")]
|
IoCManager.InjectDependencies(this);
|
||||||
private readonly Dictionary<AtmosMonitorAlarmType, string> _alarmStates = new();
|
}
|
||||||
|
|
||||||
[DataField("hideOnDepowered")]
|
[Obsolete("Subscribe to AppearanceChangeEvent instead.")]
|
||||||
private readonly List<string>? _hideOnDepowered;
|
public override void OnChangeData(AppearanceComponent component)
|
||||||
|
{
|
||||||
|
if (!_entityManager.TryGetComponent<SpriteComponent>(component.Owner, out var sprite))
|
||||||
|
return;
|
||||||
|
|
||||||
// eh...
|
if (!sprite.LayerMapTryGet(_layerMap, out int layer))
|
||||||
[DataField("setOnDepowered")]
|
return;
|
||||||
private readonly Dictionary<string, string>? _setOnDepowered;
|
|
||||||
|
|
||||||
[Obsolete("Subscribe to your component being initialised instead.")]
|
if (component.TryGetData<bool>(PowerDeviceVisuals.Powered, out var powered))
|
||||||
public override void InitializeEntity(EntityUid entity)
|
|
||||||
{
|
{
|
||||||
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.")]
|
if (component.TryGetData<Vector2>(AtmosMonitorVisuals.Offset, out Vector2 offset))
|
||||||
public override void OnChangeData(AppearanceComponent component)
|
|
||||||
{
|
{
|
||||||
if (!_entityManager.TryGetComponent<SpriteComponent>(component.Owner, out var sprite))
|
sprite.Offset = offset;
|
||||||
return;
|
|
||||||
|
|
||||||
if (!sprite.LayerMapTryGet(_layerMap, out int layer))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (component.TryGetData<bool>(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<Vector2>(AtmosMonitorVisuals.Offset, out Vector2 offset))
|
|
||||||
{
|
|
||||||
sprite.Offset = offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (component.TryGetData<AtmosMonitorAlarmType>(AtmosMonitorVisuals.AlarmType, out var alarmType)
|
|
||||||
&& powered)
|
|
||||||
if (_alarmStates.TryGetValue(alarmType, out var state))
|
|
||||||
sprite.LayerSetState(layer, new RSI.StateId(state));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (component.TryGetData<AtmosMonitorAlarmType>(AtmosMonitorVisuals.AlarmType, out var alarmType)
|
||||||
|
&& powered)
|
||||||
|
if (_alarmStates.TryGetValue(alarmType, out var state))
|
||||||
|
sprite.LayerSetState(layer, new RSI.StateId(state));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,75 +6,74 @@ using Robust.Shared.GameObjects;
|
|||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
using Robust.Shared.Log;
|
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();
|
return;
|
||||||
|
|
||||||
_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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ResyncAllDevices()
|
_window.UpdateState(cast);
|
||||||
{
|
}
|
||||||
SendMessage(new AirAlarmResyncAllDevicesMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnDeviceDataChanged(string address, IAtmosDeviceData data)
|
protected override void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
SendMessage(new AirAlarmUpdateDeviceDataMessage(address, data));
|
base.Dispose(disposing);
|
||||||
}
|
|
||||||
|
|
||||||
private void OnAirAlarmModeChanged(AirAlarmMode mode)
|
if (disposing) _window?.Dispose();
|
||||||
{
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,140 +13,139 @@ using Robust.Client.UserInterface.CustomControls;
|
|||||||
using Robust.Client.UserInterface.XAML;
|
using Robust.Client.UserInterface.XAML;
|
||||||
using Robust.Shared.Localization;
|
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 event Action<string, IAtmosDeviceData>? AtmosDeviceDataChanged;
|
||||||
public sealed partial class AirAlarmWindow : DefaultWindow
|
public event Action<string, AtmosMonitorThresholdType, AtmosAlarmThreshold, Gas?>? AtmosAlarmThresholdChanged;
|
||||||
|
public event Action<AirAlarmMode>? AirAlarmModeChanged;
|
||||||
|
public event Action<string>? ResyncDeviceRequested;
|
||||||
|
public event Action? ResyncAllRequested;
|
||||||
|
public event Action<AirAlarmTab>? 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<string, PumpControl> _pumps = new();
|
||||||
|
private Dictionary<string, ScrubberControl> _scrubbers = new();
|
||||||
|
private Dictionary<string, SensorInfo> _sensors = new();
|
||||||
|
private Button _resyncDevices => CResyncButton;
|
||||||
|
|
||||||
|
|
||||||
|
private Dictionary<Gas, Label> _gasLabels = new();
|
||||||
|
|
||||||
|
private OptionButton _modes => CModeButton;
|
||||||
|
|
||||||
|
public AirAlarmWindow()
|
||||||
{
|
{
|
||||||
public event Action<string, IAtmosDeviceData>? AtmosDeviceDataChanged;
|
RobustXamlLoader.Load(this);
|
||||||
public event Action<string, AtmosMonitorThresholdType, AtmosAlarmThreshold, Gas?>? AtmosAlarmThresholdChanged;
|
|
||||||
public event Action<AirAlarmMode>? AirAlarmModeChanged;
|
|
||||||
public event Action<string>? ResyncDeviceRequested;
|
|
||||||
public event Action? ResyncAllRequested;
|
|
||||||
public event Action<AirAlarmTab>? AirAlarmTabChange;
|
|
||||||
|
|
||||||
private Label _address => CDeviceAddress;
|
foreach (var mode in Enum.GetValues<AirAlarmMode>())
|
||||||
private Label _deviceTotal => CDeviceTotal;
|
_modes.AddItem($"{mode}", (int) mode);
|
||||||
private RichTextLabel _pressure => CPressureLabel;
|
|
||||||
private RichTextLabel _temperature => CTemperatureLabel;
|
|
||||||
private RichTextLabel _alarmState => CStatusLabel;
|
|
||||||
|
|
||||||
private TabContainer _tabContainer => CTabContainer;
|
_modes.OnItemSelected += args =>
|
||||||
private BoxContainer _ventDevices => CVentContainer;
|
|
||||||
private BoxContainer _scrubberDevices => CScrubberContainer;
|
|
||||||
|
|
||||||
private Dictionary<string, PumpControl> _pumps = new();
|
|
||||||
private Dictionary<string, ScrubberControl> _scrubbers = new();
|
|
||||||
private Dictionary<string, SensorInfo> _sensors = new();
|
|
||||||
private Button _resyncDevices => CResyncButton;
|
|
||||||
|
|
||||||
|
|
||||||
private Dictionary<Gas, Label> _gasLabels = new();
|
|
||||||
|
|
||||||
private OptionButton _modes => CModeButton;
|
|
||||||
|
|
||||||
public AirAlarmWindow()
|
|
||||||
{
|
{
|
||||||
RobustXamlLoader.Load(this);
|
_modes.SelectId(args.Id);
|
||||||
|
AirAlarmModeChanged!.Invoke((AirAlarmMode) args.Id);
|
||||||
|
};
|
||||||
|
|
||||||
foreach (var mode in Enum.GetValues<AirAlarmMode>())
|
_tabContainer.SetTabTitle(0, Loc.GetString("air-alarm-ui-window-tab-vents"));
|
||||||
_modes.AddItem($"{mode}", (int) mode);
|
_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 =>
|
_tabContainer.OnTabChanged += idx =>
|
||||||
{
|
{
|
||||||
_modes.SelectId(args.Id);
|
AirAlarmTabChange!((AirAlarmTab) idx);
|
||||||
AirAlarmModeChanged!.Invoke((AirAlarmMode) args.Id);
|
};
|
||||||
};
|
|
||||||
|
|
||||||
_tabContainer.SetTabTitle(0, Loc.GetString("air-alarm-ui-window-tab-vents"));
|
_resyncDevices.OnPressed += _ =>
|
||||||
_tabContainer.SetTabTitle(1, Loc.GetString("air-alarm-ui-window-tab-scrubbers"));
|
{
|
||||||
_tabContainer.SetTabTitle(2, Loc.GetString("air-alarm-ui-window-tab-sensors"));
|
_ventDevices.RemoveAllChildren();
|
||||||
|
_pumps.Clear();
|
||||||
|
_scrubberDevices.RemoveAllChildren();
|
||||||
|
_scrubbers.Clear();
|
||||||
|
CSensorContainer.RemoveAllChildren();
|
||||||
|
_sensors.Clear();
|
||||||
|
ResyncAllRequested!.Invoke();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
_tabContainer.OnTabChanged += idx =>
|
public void UpdateState(AirAlarmUIState state)
|
||||||
{
|
{
|
||||||
AirAlarmTabChange!((AirAlarmTab) idx);
|
_address.Text = state.Address;
|
||||||
};
|
_deviceTotal.Text = $"{state.DeviceCount}";
|
||||||
|
_pressure.SetMarkup(Loc.GetString("air-alarm-ui-window-pressure", ("pressure", $"{state.PressureAverage:0.##}")));
|
||||||
_resyncDevices.OnPressed += _ =>
|
_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}")));
|
||||||
_ventDevices.RemoveAllChildren();
|
UpdateModeSelector(state.Mode);
|
||||||
_pumps.Clear();
|
foreach (var (addr, dev) in state.DeviceData)
|
||||||
_scrubberDevices.RemoveAllChildren();
|
{
|
||||||
_scrubbers.Clear();
|
UpdateDeviceData(addr, dev);
|
||||||
CSensorContainer.RemoveAllChildren();
|
|
||||||
_sensors.Clear();
|
|
||||||
ResyncAllRequested!.Invoke();
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
case GasVentPumpData pump:
|
||||||
_deviceTotal.Text = $"{state.DeviceCount}";
|
if (!_pumps.TryGetValue(addr, out var pumpControl))
|
||||||
_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.##}")));
|
var control= new PumpControl(pump, addr);
|
||||||
_alarmState.SetMarkup(Loc.GetString("air-alarm-ui-window-alarm-state", ("state", $"{state.AlarmType}")));
|
control.PumpDataChanged += AtmosDeviceDataChanged!.Invoke;
|
||||||
UpdateModeSelector(state.Mode);
|
_pumps.Add(addr, control);
|
||||||
foreach (var (addr, dev) in state.DeviceData)
|
CVentContainer.AddChild(control);
|
||||||
{
|
}
|
||||||
UpdateDeviceData(addr, dev);
|
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)
|
break;
|
||||||
{
|
case AtmosSensorData sensor:
|
||||||
_modes.SelectId((int) mode);
|
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)
|
break;
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,96 +8,95 @@ using Robust.Client.UserInterface.CustomControls;
|
|||||||
using Robust.Client.UserInterface.XAML;
|
using Robust.Client.UserInterface.XAML;
|
||||||
using Robust.Shared.Localization;
|
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]
|
private GasVentPumpData _data;
|
||||||
public sealed partial class PumpControl : BoxContainer
|
private string _address;
|
||||||
|
|
||||||
|
public event Action<string, IAtmosDeviceData>? 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;
|
RobustXamlLoader.Load(this);
|
||||||
private string _address;
|
|
||||||
|
|
||||||
public event Action<string, IAtmosDeviceData>? PumpDataChanged;
|
this.Name = address;
|
||||||
|
|
||||||
private CheckBox _enabled => CEnableDevice;
|
_data = data;
|
||||||
private CollapsibleHeading _addressLabel => CAddress;
|
_address = address;
|
||||||
private OptionButton _pumpDirection => CPumpDirection;
|
|
||||||
private OptionButton _pressureCheck => CPressureCheck;
|
|
||||||
private FloatSpinBox _externalBound => CExternalBound;
|
|
||||||
private FloatSpinBox _internalBound => CInternalBound;
|
|
||||||
|
|
||||||
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;
|
_internalBound.Value = (float) _data.InternalPressureBound;
|
||||||
|
_internalBound.OnValueChanged += _ =>
|
||||||
_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<VentPumpDirection>())
|
|
||||||
_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<VentPressureBound>())
|
|
||||||
_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;
|
_data.InternalPressureBound = _internalBound.Value;
|
||||||
_enabled.Pressed = _data.Enabled;
|
PumpDataChanged?.Invoke(_address, _data);
|
||||||
|
};
|
||||||
|
_internalBound.IsValid += value => value >= 0;
|
||||||
|
|
||||||
_data.PumpDirection = data.PumpDirection;
|
_externalBound.Value = (float) _data.ExternalPressureBound;
|
||||||
_pumpDirection.SelectId((int) _data.PumpDirection);
|
_externalBound.OnValueChanged += _ =>
|
||||||
|
{
|
||||||
|
_data.ExternalPressureBound = _externalBound.Value;
|
||||||
|
PumpDataChanged?.Invoke(_address, _data);
|
||||||
|
};
|
||||||
|
_externalBound.IsValid += value => value >= 0;
|
||||||
|
|
||||||
_data.PressureChecks = data.PressureChecks;
|
foreach (var value in Enum.GetValues<VentPumpDirection>())
|
||||||
_pressureCheck.SelectId((int) _data.PressureChecks);
|
_pumpDirection.AddItem(Loc.GetString($"{value}"), (int) value);
|
||||||
|
|
||||||
_data.ExternalPressureBound = data.ExternalPressureBound;
|
_pumpDirection.SelectId((int) _data.PumpDirection);
|
||||||
_externalBound.Value = _data.ExternalPressureBound;
|
_pumpDirection.OnItemSelected += args =>
|
||||||
|
{
|
||||||
|
_pumpDirection.SelectId(args.Id);
|
||||||
|
_data.PumpDirection = (VentPumpDirection) args.Id;
|
||||||
|
PumpDataChanged?.Invoke(_address, _data);
|
||||||
|
};
|
||||||
|
|
||||||
_data.InternalPressureBound = data.InternalPressureBound;
|
foreach (var value in Enum.GetValues<VentPressureBound>())
|
||||||
_internalBound.Value = _data.InternalPressureBound;
|
_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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,113 +11,112 @@ using Robust.Client.UserInterface.CustomControls;
|
|||||||
using Robust.Client.UserInterface.XAML;
|
using Robust.Client.UserInterface.XAML;
|
||||||
using Robust.Shared.Localization;
|
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]
|
private GasVentScrubberData _data;
|
||||||
public sealed partial class ScrubberControl : BoxContainer
|
private string _address;
|
||||||
|
|
||||||
|
public event Action<string, IAtmosDeviceData>? 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<Gas, Button> _gasControls = new();
|
||||||
|
|
||||||
|
public ScrubberControl(GasVentScrubberData data, string address)
|
||||||
{
|
{
|
||||||
private GasVentScrubberData _data;
|
RobustXamlLoader.Load(this);
|
||||||
private string _address;
|
|
||||||
|
|
||||||
public event Action<string, IAtmosDeviceData>? ScrubberDataChanged;
|
this.Name = address;
|
||||||
|
|
||||||
private CheckBox _enabled => CEnableDevice;
|
_data = data;
|
||||||
private CollapsibleHeading _addressLabel => CAddress;
|
_address = address;
|
||||||
private OptionButton _pumpDirection => CPumpDirection;
|
|
||||||
private FloatSpinBox _volumeRate => CVolumeRate;
|
|
||||||
private CheckBox _wideNet => CWideNet;
|
|
||||||
|
|
||||||
private GridContainer _gases => CGasContainer;
|
_addressLabel.Title = Loc.GetString("air-alarm-ui-atmos-net-device-label", ("address", $"{address}"));
|
||||||
private Dictionary<Gas, Button> _gasControls = new();
|
|
||||||
|
|
||||||
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;
|
_volumeRate.Value = _data.VolumeRate;
|
||||||
_address = address;
|
_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<ScrubberPumpDirection>())
|
||||||
|
_pumpDirection.AddItem(Loc.GetString($"{value}"), (int) value);
|
||||||
|
|
||||||
_enabled.Pressed = data.Enabled;
|
_pumpDirection.SelectId((int) _data.PumpDirection);
|
||||||
_enabled.OnToggled += _ =>
|
_pumpDirection.OnItemSelected += args =>
|
||||||
|
{
|
||||||
|
_pumpDirection.SelectId(args.Id);
|
||||||
|
_data.PumpDirection = (ScrubberPumpDirection) args.Id;
|
||||||
|
ScrubberDataChanged?.Invoke(_address, _data);
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var value in Enum.GetValues<Gas>())
|
||||||
|
{
|
||||||
|
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);
|
ScrubberDataChanged?.Invoke(_address, _data);
|
||||||
};
|
};
|
||||||
|
_gasControls.Add(value, gasButton);
|
||||||
_wideNet.Pressed = data.WideNet;
|
_gases.AddChild(gasButton);
|
||||||
_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<ScrubberPumpDirection>())
|
|
||||||
_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<Gas>())
|
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ChangeData(GasVentScrubberData data)
|
}
|
||||||
{
|
|
||||||
_data.Enabled = data.Enabled;
|
|
||||||
_enabled.Pressed = _data.Enabled;
|
|
||||||
|
|
||||||
_data.PumpDirection = data.PumpDirection;
|
public void ChangeData(GasVentScrubberData data)
|
||||||
_pumpDirection.Select((int) _data.PumpDirection);
|
{
|
||||||
|
_data.Enabled = data.Enabled;
|
||||||
|
_enabled.Pressed = _data.Enabled;
|
||||||
|
|
||||||
_data.VolumeRate = data.VolumeRate;
|
_data.PumpDirection = data.PumpDirection;
|
||||||
_volumeRate.Value = _data.VolumeRate;
|
_pumpDirection.Select((int) _data.PumpDirection);
|
||||||
|
|
||||||
_data.WideNet = data.WideNet;
|
_data.VolumeRate = data.VolumeRate;
|
||||||
_wideNet.Pressed = _data.WideNet;
|
_volumeRate.Value = _data.VolumeRate;
|
||||||
|
|
||||||
var intersect = _data.FilterGases.Intersect(data.FilterGases);
|
_data.WideNet = data.WideNet;
|
||||||
|
_wideNet.Pressed = _data.WideNet;
|
||||||
|
|
||||||
foreach (var value in Enum.GetValues<Gas>())
|
var intersect = _data.FilterGases.Intersect(data.FilterGases);
|
||||||
if (!intersect.Contains(value))
|
|
||||||
_gasControls[value].Pressed = false;
|
foreach (var value in Enum.GetValues<Gas>())
|
||||||
}
|
if (!intersect.Contains(value))
|
||||||
|
_gasControls[value].Pressed = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11,286 +11,285 @@ using Robust.Shared.Localization;
|
|||||||
// holy FUCK
|
// holy FUCK
|
||||||
// this technically works because some of this you can *not* do in XAML but 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]
|
private AtmosAlarmThreshold _threshold;
|
||||||
public sealed partial class ThresholdControl : BoxContainer
|
private AtmosMonitorThresholdType _type;
|
||||||
|
private Gas? _gas;
|
||||||
|
|
||||||
|
public event Action<AtmosMonitorThresholdType, AtmosAlarmThreshold, Gas?>? 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;
|
RobustXamlLoader.Load(this);
|
||||||
private AtmosMonitorThresholdType _type;
|
|
||||||
private Gas? _gas;
|
|
||||||
|
|
||||||
public event Action<AtmosMonitorThresholdType, AtmosAlarmThreshold, Gas?>? ThresholdDataChanged;
|
_threshold = threshold;
|
||||||
|
_type = type;
|
||||||
|
_gas = gas;
|
||||||
|
|
||||||
private CollapsibleHeading _name => CName;
|
_name.Title = name;
|
||||||
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
|
// i miss rust macros
|
||||||
// indicate validity/disabled status, with several layers of side effect
|
|
||||||
// dependent on the other three values when you change one :HECK:
|
_upperBoundControl = new ThresholdBoundControl("upper-bound", _threshold.UpperBound, modifier);
|
||||||
public ThresholdControl(string name, AtmosAlarmThreshold threshold, AtmosMonitorThresholdType type, Gas? gas = null, float modifier = 1)
|
_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;
|
if (_threshold.LowerWarningBound != null)
|
||||||
_type = type;
|
value = (float) _threshold.LowerWarningBound + 0.1f;
|
||||||
_gas = gas;
|
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);
|
if (_threshold.UpperWarningBound != null)
|
||||||
_upperBoundControl.OnBoundChanged += value =>
|
value = (float) _threshold.UpperWarningBound - 0.1f;
|
||||||
{
|
else if (_threshold.UpperBound != null)
|
||||||
// a lot of threshold logic is baked into the properties,
|
value = (float) _threshold.UpperBound - 0.1f;
|
||||||
// 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.LowerWarningBound != null)
|
return value;
|
||||||
value = (float) _threshold.LowerWarningBound + 0.1f;
|
};
|
||||||
else if (_threshold.LowerBound != null)
|
_lowerBoundControl.OnValidBoundChanged += () =>
|
||||||
value = (float) _threshold.LowerBound + 0.1f;
|
ThresholdDataChanged!.Invoke(_type, _threshold, _gas);
|
||||||
|
_dangerBounds.AddChild(_lowerBoundControl);
|
||||||
|
|
||||||
return value;
|
_upperWarningBoundControl = new ThresholdBoundControl("upper-warning-bound", _threshold.UpperWarningBound, modifier);
|
||||||
};
|
_upperWarningBoundControl.OnBoundChanged += value =>
|
||||||
_upperBoundControl.OnValidBoundChanged += () =>
|
{
|
||||||
{
|
_threshold.TrySetWarningBound(AtmosMonitorThresholdBound.Upper, value);
|
||||||
ThresholdDataChanged!.Invoke(_type, _threshold, _gas);
|
return _threshold.UpperWarningBound;
|
||||||
};
|
};
|
||||||
_dangerBounds.AddChild(_upperBoundControl);
|
_upperWarningBoundControl.OnBoundEnabled += () =>
|
||||||
|
{
|
||||||
|
var value = 0f;
|
||||||
|
|
||||||
_lowerBoundControl = new ThresholdBoundControl("lower-bound", _threshold.LowerBound, modifier);
|
if (_threshold.LowerWarningBound != null)
|
||||||
_lowerBoundControl.OnBoundChanged += value =>
|
value = (float) _threshold.LowerWarningBound + 0.1f;
|
||||||
{
|
else if (_threshold.LowerBound != null)
|
||||||
_threshold.TrySetPrimaryBound(AtmosMonitorThresholdBound.Lower, value);
|
value = (float) _threshold.LowerBound + 0.1f;
|
||||||
return _threshold.LowerBound;
|
|
||||||
};
|
|
||||||
_lowerBoundControl.OnBoundEnabled += () =>
|
|
||||||
{
|
|
||||||
var value = 0f;
|
|
||||||
|
|
||||||
if (_threshold.UpperWarningBound != null)
|
return value;
|
||||||
value = (float) _threshold.UpperWarningBound - 0.1f;
|
};
|
||||||
else if (_threshold.UpperBound != null)
|
_upperWarningBoundControl.OnValidBoundChanged += () =>
|
||||||
value = (float) _threshold.UpperBound - 0.1f;
|
ThresholdDataChanged!.Invoke(_type, _threshold, _gas);
|
||||||
|
_warningBounds.AddChild(_upperWarningBoundControl);
|
||||||
|
|
||||||
return value;
|
_lowerWarningBoundControl = new ThresholdBoundControl("lower-warning-bound", _threshold.LowerWarningBound, modifier);
|
||||||
};
|
_lowerWarningBoundControl.OnBoundChanged += value =>
|
||||||
_lowerBoundControl.OnValidBoundChanged += () =>
|
{
|
||||||
ThresholdDataChanged!.Invoke(_type, _threshold, _gas);
|
_threshold.TrySetWarningBound(AtmosMonitorThresholdBound.Lower, value);
|
||||||
_dangerBounds.AddChild(_lowerBoundControl);
|
return _threshold.LowerWarningBound;
|
||||||
|
};
|
||||||
|
_lowerWarningBoundControl.OnBoundEnabled += () =>
|
||||||
|
{
|
||||||
|
var value = 0f;
|
||||||
|
|
||||||
_upperWarningBoundControl = new ThresholdBoundControl("upper-warning-bound", _threshold.UpperWarningBound, modifier);
|
if (_threshold.UpperWarningBound != null)
|
||||||
_upperWarningBoundControl.OnBoundChanged += value =>
|
value = (float) _threshold.UpperWarningBound - 0.1f;
|
||||||
{
|
else if (_threshold.UpperBound != null)
|
||||||
_threshold.TrySetWarningBound(AtmosMonitorThresholdBound.Upper, value);
|
value = (float) _threshold.UpperBound - 0.1f;
|
||||||
return _threshold.UpperWarningBound;
|
|
||||||
};
|
|
||||||
_upperWarningBoundControl.OnBoundEnabled += () =>
|
|
||||||
{
|
|
||||||
var value = 0f;
|
|
||||||
|
|
||||||
if (_threshold.LowerWarningBound != null)
|
return value;
|
||||||
value = (float) _threshold.LowerWarningBound + 0.1f;
|
};
|
||||||
else if (_threshold.LowerBound != null)
|
_lowerWarningBoundControl.OnValidBoundChanged += () =>
|
||||||
value = (float) _threshold.LowerBound + 0.1f;
|
ThresholdDataChanged!.Invoke(_type, _threshold, _gas);
|
||||||
|
|
||||||
return value;
|
_warningBounds.AddChild(_lowerWarningBoundControl);
|
||||||
};
|
|
||||||
_upperWarningBoundControl.OnValidBoundChanged += () =>
|
|
||||||
ThresholdDataChanged!.Invoke(_type, _threshold, _gas);
|
|
||||||
_warningBounds.AddChild(_upperWarningBoundControl);
|
|
||||||
|
|
||||||
_lowerWarningBoundControl = new ThresholdBoundControl("lower-warning-bound", _threshold.LowerWarningBound, modifier);
|
_ignore.OnToggled += args =>
|
||||||
_lowerWarningBoundControl.OnBoundChanged += value =>
|
{
|
||||||
{
|
_threshold.Ignore = args.Pressed;
|
||||||
_threshold.TrySetWarningBound(AtmosMonitorThresholdBound.Lower, value);
|
ThresholdDataChanged!.Invoke(_type, _threshold, _gas);
|
||||||
return _threshold.LowerWarningBound;
|
};
|
||||||
};
|
_ignore.Pressed = _threshold.Ignore;
|
||||||
_lowerWarningBoundControl.OnBoundEnabled += () =>
|
}
|
||||||
{
|
|
||||||
var value = 0f;
|
|
||||||
|
|
||||||
if (_threshold.UpperWarningBound != null)
|
public void UpdateThresholdData(AtmosAlarmThreshold threshold)
|
||||||
value = (float) _threshold.UpperWarningBound - 0.1f;
|
{
|
||||||
else if (_threshold.UpperBound != null)
|
_upperBoundControl.SetValue(threshold.UpperBound);
|
||||||
value = (float) _threshold.UpperBound - 0.1f;
|
_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 =>
|
// convenience thing for getting multiplied values
|
||||||
{
|
// and also setting value to a usable value
|
||||||
_threshold.Ignore = args.Pressed;
|
private float? ModifiedValue
|
||||||
ThresholdDataChanged!.Invoke(_type, _threshold, _gas);
|
{
|
||||||
};
|
get => _value * _modifier;
|
||||||
_ignore.Pressed = _threshold.Ignore;
|
set => _value = value / _modifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateThresholdData(AtmosAlarmThreshold threshold)
|
private float ModifiedLastValue
|
||||||
{
|
{
|
||||||
_upperBoundControl.SetValue(threshold.UpperBound);
|
get => _lastValue * _modifier;
|
||||||
_lowerBoundControl.SetValue(threshold.LowerBound);
|
set => _lastValue = value / _modifier;
|
||||||
_upperWarningBoundControl.SetValue(threshold.UpperWarningBound);
|
|
||||||
_lowerWarningBoundControl.SetValue(threshold.LowerWarningBound);
|
|
||||||
_ignore.Pressed = threshold.Ignore;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private float _modifier;
|
||||||
|
|
||||||
private sealed class ThresholdBoundControl : BoxContainer
|
private FloatSpinBox _bound;
|
||||||
|
private CheckBox _boundEnabled;
|
||||||
|
|
||||||
|
public event Action? OnValidBoundChanged;
|
||||||
|
public Func<float?, float?>? OnBoundChanged;
|
||||||
|
public Func<float>? OnBoundEnabled;
|
||||||
|
|
||||||
|
public void SetValue(float? value)
|
||||||
{
|
{
|
||||||
// raw values to use in thresholds, prefer these
|
_value = value;
|
||||||
// over directly setting Modified(Value/LastValue)
|
|
||||||
// when working with the FloatSpinBox
|
|
||||||
private float? _value;
|
|
||||||
private float _lastValue;
|
|
||||||
|
|
||||||
// convenience thing for getting multiplied values
|
if (_value == null)
|
||||||
// and also setting value to a usable value
|
|
||||||
private float? ModifiedValue
|
|
||||||
{
|
{
|
||||||
get => _value * _modifier;
|
_boundEnabled.Pressed = false;
|
||||||
set => _value = value / _modifier;
|
_bound.Value = 0;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
private float ModifiedLastValue
|
|
||||||
{
|
{
|
||||||
get => _lastValue * _modifier;
|
_boundEnabled.Pressed = true;
|
||||||
set => _lastValue = value / _modifier;
|
_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;
|
this.HorizontalExpand = true;
|
||||||
private CheckBox _boundEnabled;
|
this.Orientation = LayoutOrientation.Vertical;
|
||||||
|
|
||||||
public event Action? OnValidBoundChanged;
|
this.AddChild(new Label { Text = Loc.GetString($"air-alarm-ui-thresholds-{name}") });
|
||||||
public Func<float?, float?>? OnBoundChanged;
|
_bound = new FloatSpinBox(.01f, 2);
|
||||||
public Func<float>? OnBoundEnabled;
|
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;
|
_value = value;
|
||||||
|
_lastValue = (float) 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
OnValidBoundChanged!.Invoke();
|
OnValidBoundChanged!.Invoke();
|
||||||
}
|
}
|
||||||
|
// otherwise, just set it to the last known value
|
||||||
private bool ValidateThreshold(float value) => (_value != null) && (value >= 0);
|
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);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,25 +3,24 @@ using Content.Shared.Atmos.Monitor.Components;
|
|||||||
using Content.Shared.Atmos.Piping.Unary.Components;
|
using Content.Shared.Atmos.Piping.Unary.Components;
|
||||||
using Robust.Shared.Network;
|
using Robust.Shared.Network;
|
||||||
|
|
||||||
namespace Content.Server.Atmos.Monitor.Components
|
namespace Content.Server.Atmos.Monitor.Components;
|
||||||
|
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class AirAlarmComponent : Component
|
||||||
{
|
{
|
||||||
[RegisterComponent]
|
[ViewVariables] public AirAlarmMode CurrentMode { get; set; } = AirAlarmMode.Filtering;
|
||||||
public sealed class AirAlarmComponent : Component
|
|
||||||
{
|
|
||||||
[ViewVariables] public AirAlarmMode CurrentMode { get; set; } = AirAlarmMode.Filtering;
|
|
||||||
|
|
||||||
// Remember to null this afterwards.
|
// Remember to null this afterwards.
|
||||||
[ViewVariables] public IAirAlarmModeUpdate? CurrentModeUpdater { get; set; }
|
[ViewVariables] public IAirAlarmModeUpdate? CurrentModeUpdater { get; set; }
|
||||||
|
|
||||||
[ViewVariables] public AirAlarmTab CurrentTab { get; set; }
|
[ViewVariables] public AirAlarmTab CurrentTab { get; set; }
|
||||||
|
|
||||||
public Dictionary<string, IAtmosDeviceData> DeviceData = new();
|
public Dictionary<string, IAtmosDeviceData> DeviceData = new();
|
||||||
public Dictionary<string, GasVentPumpData> VentData = new();
|
public Dictionary<string, GasVentPumpData> VentData = new();
|
||||||
public Dictionary<string, GasVentScrubberData> ScrubberData = new();
|
public Dictionary<string, GasVentScrubberData> ScrubberData = new();
|
||||||
public Dictionary<string, AtmosSensorData> SensorData = new();
|
public Dictionary<string, AtmosSensorData> SensorData = new();
|
||||||
|
|
||||||
public HashSet<NetUserId> ActivePlayers = new();
|
public HashSet<NetUserId> ActivePlayers = new();
|
||||||
|
|
||||||
public bool CanSync = true;
|
public bool CanSync = true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,60 +3,58 @@ using Content.Shared.Tag;
|
|||||||
using Robust.Shared.Audio;
|
using Robust.Shared.Audio;
|
||||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set;
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set;
|
||||||
|
|
||||||
namespace Content.Server.Atmos.Monitor.Components
|
namespace Content.Server.Atmos.Monitor.Components;
|
||||||
|
// AtmosAlarmables are entities that can be alarmed
|
||||||
|
// by a linked AtmosMonitor (alarmer?) if a threshold
|
||||||
|
// is passed in some way. The intended use is to
|
||||||
|
// do something in case something dangerous happens,
|
||||||
|
// e.g., activate firelocks in case a temperature
|
||||||
|
// threshold is reached
|
||||||
|
//
|
||||||
|
// It goes:
|
||||||
|
//
|
||||||
|
// AtmosMonitor -> AtmosDeviceUpdateEvent
|
||||||
|
// -> Threshold calculation
|
||||||
|
// -> AtmosAlarmEvent
|
||||||
|
// -> Everything linked to that monitor (targetted)
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A component to add to device network devices if you want them to be alarmed
|
||||||
|
/// by an atmospheric alarmer. This will store every single alert received, and
|
||||||
|
/// calculate the highest alert based on the alerts received. Equally, if you
|
||||||
|
/// link other alarmables to this, it will store the alerts from them to
|
||||||
|
/// calculate the highest network alert.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class AtmosAlarmableComponent : Component
|
||||||
{
|
{
|
||||||
// AtmosAlarmables are entities that can be alarmed
|
[ViewVariables]
|
||||||
// by a linked AtmosMonitor (alarmer?) if a threshold
|
public readonly Dictionary<string, AtmosMonitorAlarmType> NetworkAlarmStates = new();
|
||||||
// is passed in some way. The intended use is to
|
|
||||||
// do something in case something dangerous happens,
|
[ViewVariables] public AtmosMonitorAlarmType LastAlarmState = AtmosMonitorAlarmType.Normal;
|
||||||
// e.g., activate firelocks in case a temperature
|
|
||||||
// threshold is reached
|
[ViewVariables] public bool IgnoreAlarms { get; set; } = false;
|
||||||
//
|
|
||||||
// It goes:
|
[DataField("alarmSound")]
|
||||||
//
|
public SoundSpecifier AlarmSound { get; set; } = new SoundPathSpecifier("/Audio/Machines/alarm.ogg");
|
||||||
// AtmosMonitor -> AtmosDeviceUpdateEvent
|
|
||||||
// -> Threshold calculation
|
[DataField("alarmVolume")]
|
||||||
// -> AtmosAlarmEvent
|
public float AlarmVolume { get; set; } = -10;
|
||||||
// -> Everything linked to that monitor (targetted)
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A component to add to device network devices if you want them to be alarmed
|
/// List of tags to check for when synchronizing alarms.
|
||||||
/// by an atmospheric alarmer. This will store every single alert received, and
|
|
||||||
/// calculate the highest alert based on the alerts received. Equally, if you
|
|
||||||
/// link other alarmables to this, it will store the alerts from them to
|
|
||||||
/// calculate the highest network alert.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[RegisterComponent]
|
[DataField("syncWith", customTypeSerializer: typeof(PrototypeIdHashSetSerializer<TagPrototype>))]
|
||||||
public sealed class AtmosAlarmableComponent : Component
|
public HashSet<string> SyncWithTags { get; } = new();
|
||||||
{
|
|
||||||
[ViewVariables]
|
|
||||||
public readonly Dictionary<string, AtmosMonitorAlarmType> NetworkAlarmStates = new();
|
|
||||||
|
|
||||||
[ViewVariables] public AtmosMonitorAlarmType LastAlarmState = AtmosMonitorAlarmType.Normal;
|
[DataField("monitorAlertTypes")]
|
||||||
|
public HashSet<AtmosMonitorThresholdType>? MonitorAlertTypes { get; }
|
||||||
|
|
||||||
[ViewVariables] public bool IgnoreAlarms { get; set; } = false;
|
/// <summary>
|
||||||
|
/// If this device should receive only. If it can only
|
||||||
[DataField("alarmSound")]
|
/// receive, that means that attempting to sync outwards
|
||||||
public SoundSpecifier AlarmSound { get; set; } = new SoundPathSpecifier("/Audio/Machines/alarm.ogg");
|
/// will result in nothing happening.
|
||||||
|
/// </summary>
|
||||||
[DataField("alarmVolume")]
|
[DataField("receiveOnly")]
|
||||||
public float AlarmVolume { get; set; } = -10;
|
public bool ReceiveOnly { get; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// List of tags to check for when synchronizing alarms.
|
|
||||||
/// </summary>
|
|
||||||
[DataField("syncWith", customTypeSerializer: typeof(PrototypeIdHashSetSerializer<TagPrototype>))]
|
|
||||||
public HashSet<string> SyncWithTags { get; } = new();
|
|
||||||
|
|
||||||
[DataField("monitorAlertTypes")]
|
|
||||||
public HashSet<AtmosMonitorThresholdType>? MonitorAlertTypes { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// If this device should receive only. If it can only
|
|
||||||
/// receive, that means that attempting to sync outwards
|
|
||||||
/// will result in nothing happening.
|
|
||||||
/// </summary>
|
|
||||||
[DataField("receiveOnly")]
|
|
||||||
public bool ReceiveOnly { get; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,68 +3,67 @@ using Content.Shared.Atmos.Monitor;
|
|||||||
using Robust.Shared.Audio;
|
using Robust.Shared.Audio;
|
||||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
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]
|
// Whether this monitor can send alarms,
|
||||||
public sealed class AtmosMonitorComponent : Component
|
// or recieve atmos command events.
|
||||||
{
|
//
|
||||||
// Whether this monitor can send alarms,
|
// Useful for wires; i.e., pulsing a monitor wire
|
||||||
// or recieve atmos command events.
|
// will make it send an alert, and cutting
|
||||||
//
|
// it will make it so that alerts are no longer
|
||||||
// Useful for wires; i.e., pulsing a monitor wire
|
// sent/receieved.
|
||||||
// will make it send an alert, and cutting
|
//
|
||||||
// it will make it so that alerts are no longer
|
// Note that this cancels every single network
|
||||||
// sent/receieved.
|
// event, including ones that may not be
|
||||||
//
|
// related to atmos monitor events.
|
||||||
// Note that this cancels every single network
|
[ViewVariables]
|
||||||
// event, including ones that may not be
|
public bool NetEnabled = true;
|
||||||
// related to atmos monitor events.
|
|
||||||
[ViewVariables]
|
|
||||||
public bool NetEnabled = true;
|
|
||||||
|
|
||||||
[DataField("temperatureThreshold", customTypeSerializer: (typeof(PrototypeIdSerializer<AtmosAlarmThreshold>)))]
|
[DataField("temperatureThreshold", customTypeSerializer: (typeof(PrototypeIdSerializer<AtmosAlarmThreshold>)))]
|
||||||
public readonly string? TemperatureThresholdId;
|
public readonly string? TemperatureThresholdId;
|
||||||
|
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
public AtmosAlarmThreshold? TemperatureThreshold;
|
public AtmosAlarmThreshold? TemperatureThreshold;
|
||||||
|
|
||||||
[DataField("pressureThreshold", customTypeSerializer: (typeof(PrototypeIdSerializer<AtmosAlarmThreshold>)))]
|
[DataField("pressureThreshold", customTypeSerializer: (typeof(PrototypeIdSerializer<AtmosAlarmThreshold>)))]
|
||||||
public readonly string? PressureThresholdId;
|
public readonly string? PressureThresholdId;
|
||||||
|
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
public AtmosAlarmThreshold? PressureThreshold;
|
public AtmosAlarmThreshold? PressureThreshold;
|
||||||
|
|
||||||
// monitor fire - much different from temperature
|
// monitor fire - much different from temperature
|
||||||
// since there's events for fire, setting this to true
|
// since there's events for fire, setting this to true
|
||||||
// will make the atmos monitor act like a smoke detector,
|
// will make the atmos monitor act like a smoke detector,
|
||||||
// immediately signalling danger if there's a fire
|
// immediately signalling danger if there's a fire
|
||||||
[DataField("monitorFire")]
|
[DataField("monitorFire")]
|
||||||
public bool MonitorFire = false;
|
public bool MonitorFire = false;
|
||||||
|
|
||||||
// really messy but this is parsed at runtime after
|
// really messy but this is parsed at runtime after
|
||||||
// prototypes are initialized, there's no
|
// prototypes are initialized, there's no
|
||||||
// way without implementing a new
|
// way without implementing a new
|
||||||
// type serializer
|
// type serializer
|
||||||
[DataField("gasThresholds")]
|
[DataField("gasThresholds")]
|
||||||
public Dictionary<Gas, string>? GasThresholdIds;
|
public Dictionary<Gas, string>? GasThresholdIds;
|
||||||
|
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
public Dictionary<Gas, AtmosAlarmThreshold>? GasThresholds;
|
public Dictionary<Gas, AtmosAlarmThreshold>? GasThresholds;
|
||||||
|
|
||||||
// Stores a reference to the gas on the tile this is on.
|
// Stores a reference to the gas on the tile this is on.
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
public GasMixture? TileGas;
|
public GasMixture? TileGas;
|
||||||
|
|
||||||
// Stores the last alarm state of this alarm.
|
// Stores the last alarm state of this alarm.
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
public AtmosMonitorAlarmType LastAlarmState = AtmosMonitorAlarmType.Normal;
|
public AtmosMonitorAlarmType LastAlarmState = AtmosMonitorAlarmType.Normal;
|
||||||
|
|
||||||
[ViewVariables] public HashSet<AtmosMonitorThresholdType> TrippedThresholds = new();
|
[ViewVariables] public HashSet<AtmosMonitorThresholdType> TrippedThresholds = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Registered devices in this atmos monitor. Alerts will be sent directly
|
/// Registered devices in this atmos monitor. Alerts will be sent directly
|
||||||
/// to these devices.
|
/// to these devices.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ViewVariables] public HashSet<string> RegisteredDevices = new();
|
[ViewVariables] public HashSet<string> RegisteredDevices = new();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
namespace Content.Server.Atmos.Monitor.Components
|
namespace Content.Server.Atmos.Monitor.Components;
|
||||||
|
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class FireAlarmComponent : Component
|
||||||
{
|
{
|
||||||
[RegisterComponent]
|
|
||||||
public sealed class FireAlarmComponent : Component
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -5,210 +5,209 @@ using Content.Shared.Atmos;
|
|||||||
using Content.Shared.Atmos.Monitor.Components;
|
using Content.Shared.Atmos.Monitor.Components;
|
||||||
using Content.Shared.Atmos.Piping.Unary.Components;
|
using Content.Shared.Atmos.Piping.Unary.Components;
|
||||||
|
|
||||||
namespace Content.Server.Atmos.Monitor
|
namespace Content.Server.Atmos.Monitor;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This is an interface that air alarm modes use
|
||||||
|
/// in order to execute the defined modes.
|
||||||
|
/// </summary>
|
||||||
|
public interface IAirAlarmMode
|
||||||
|
{
|
||||||
|
// This is executed the moment the mode
|
||||||
|
// is set. This is to ensure that 'dumb'
|
||||||
|
// modes such as Filter/Panic are immediately
|
||||||
|
// set.
|
||||||
|
/// <summary>
|
||||||
|
/// Executed the mode is set on an air alarm.
|
||||||
|
/// This is to ensure that modes like Filter/Panic
|
||||||
|
/// are immediately set.
|
||||||
|
/// </summary>
|
||||||
|
public void Execute(EntityUid uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
// IAirAlarmModeUpdate
|
||||||
|
//
|
||||||
|
// This is an interface that AirAlarmSystem uses
|
||||||
|
// in order to 'update' air alarm modes so that
|
||||||
|
// modes like Replace can be implemented.
|
||||||
|
/// <summary>
|
||||||
|
/// An interface that AirAlarmSystem uses
|
||||||
|
/// in order to update air alarm modes that
|
||||||
|
/// need updating (e.g., Replace)
|
||||||
|
/// </summary>
|
||||||
|
public interface IAirAlarmModeUpdate
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This is an interface that air alarm modes use
|
/// This is checked by AirAlarmSystem when
|
||||||
/// in order to execute the defined modes.
|
/// a mode is updated. This should be set
|
||||||
|
/// to a DeviceNetwork address, or some
|
||||||
|
/// unique identifier that ID's the
|
||||||
|
/// owner of the mode's executor.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IAirAlarmMode
|
public string NetOwner { get; set; }
|
||||||
{
|
|
||||||
// This is executed the moment the mode
|
|
||||||
// is set. This is to ensure that 'dumb'
|
|
||||||
// modes such as Filter/Panic are immediately
|
|
||||||
// set.
|
|
||||||
/// <summary>
|
|
||||||
/// Executed the mode is set on an air alarm.
|
|
||||||
/// This is to ensure that modes like Filter/Panic
|
|
||||||
/// are immediately set.
|
|
||||||
/// </summary>
|
|
||||||
public void Execute(EntityUid uid);
|
|
||||||
}
|
|
||||||
|
|
||||||
// IAirAlarmModeUpdate
|
|
||||||
//
|
|
||||||
// This is an interface that AirAlarmSystem uses
|
|
||||||
// in order to 'update' air alarm modes so that
|
|
||||||
// modes like Replace can be implemented.
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An interface that AirAlarmSystem uses
|
/// This is executed every time the air alarm
|
||||||
/// in order to update air alarm modes that
|
/// update loop is fully executed. This should
|
||||||
/// need updating (e.g., Replace)
|
/// be where all the logic goes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IAirAlarmModeUpdate
|
public void Update(EntityUid uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class AirAlarmModeFactory
|
||||||
|
{
|
||||||
|
private static IAirAlarmMode _filterMode = new AirAlarmFilterMode();
|
||||||
|
private static IAirAlarmMode _fillMode = new AirAlarmFillMode();
|
||||||
|
private static IAirAlarmMode _panicMode = new AirAlarmPanicMode();
|
||||||
|
private static IAirAlarmMode _noneMode = new AirAlarmNoneMode();
|
||||||
|
|
||||||
|
// still not a fan since ReplaceMode must have an allocation
|
||||||
|
// but it's whatever
|
||||||
|
public static IAirAlarmMode? ModeToExecutor(AirAlarmMode mode) => mode switch
|
||||||
{
|
{
|
||||||
/// <summary>
|
AirAlarmMode.Filtering => _filterMode,
|
||||||
/// This is checked by AirAlarmSystem when
|
AirAlarmMode.Fill => _fillMode,
|
||||||
/// a mode is updated. This should be set
|
AirAlarmMode.Panic => _panicMode,
|
||||||
/// to a DeviceNetwork address, or some
|
AirAlarmMode.None => _noneMode,
|
||||||
/// unique identifier that ID's the
|
AirAlarmMode.Replace => new AirAlarmReplaceMode(),
|
||||||
/// owner of the mode's executor.
|
_ => null
|
||||||
/// </summary>
|
};
|
||||||
public string NetOwner { get; set; }
|
}
|
||||||
/// <summary>
|
|
||||||
/// This is executed every time the air alarm
|
// like a tiny little EntitySystem
|
||||||
/// update loop is fully executed. This should
|
public abstract class AirAlarmModeExecutor : IAirAlarmMode
|
||||||
/// be where all the logic goes.
|
{
|
||||||
/// </summary>
|
[Dependency] public readonly IEntityManager EntityManager = default!;
|
||||||
public void Update(EntityUid uid);
|
public readonly DeviceNetworkSystem DeviceNetworkSystem;
|
||||||
|
public readonly AirAlarmSystem AirAlarmSystem;
|
||||||
|
|
||||||
|
public abstract void Execute(EntityUid uid);
|
||||||
|
|
||||||
|
public AirAlarmModeExecutor()
|
||||||
|
{
|
||||||
|
IoCManager.InjectDependencies(this);
|
||||||
|
|
||||||
|
DeviceNetworkSystem = EntitySystem.Get<DeviceNetworkSystem>();
|
||||||
|
AirAlarmSystem = EntitySystem.Get<AirAlarmSystem>();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public sealed class AirAlarmModeFactory
|
public sealed class AirAlarmNoneMode : AirAlarmModeExecutor
|
||||||
|
{
|
||||||
|
public override void Execute(EntityUid uid)
|
||||||
{
|
{
|
||||||
private static IAirAlarmMode _filterMode = new AirAlarmFilterMode();
|
if (!EntityManager.TryGetComponent(uid, out AirAlarmComponent? alarm))
|
||||||
private static IAirAlarmMode _fillMode = new AirAlarmFillMode();
|
return;
|
||||||
private static IAirAlarmMode _panicMode = new AirAlarmPanicMode();
|
|
||||||
private static IAirAlarmMode _noneMode = new AirAlarmNoneMode();
|
|
||||||
|
|
||||||
// still not a fan since ReplaceMode must have an allocation
|
foreach (var (addr, device) in alarm.VentData)
|
||||||
// but it's whatever
|
|
||||||
public static IAirAlarmMode? ModeToExecutor(AirAlarmMode mode) => mode switch
|
|
||||||
{
|
{
|
||||||
AirAlarmMode.Filtering => _filterMode,
|
device.Enabled = false;
|
||||||
AirAlarmMode.Fill => _fillMode,
|
AirAlarmSystem.SetData(uid, addr, device);
|
||||||
AirAlarmMode.Panic => _panicMode,
|
|
||||||
AirAlarmMode.None => _noneMode,
|
|
||||||
AirAlarmMode.Replace => new AirAlarmReplaceMode(),
|
|
||||||
_ => null
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// like a tiny little EntitySystem
|
|
||||||
public abstract class AirAlarmModeExecutor : IAirAlarmMode
|
|
||||||
{
|
|
||||||
[Dependency] public readonly IEntityManager EntityManager = default!;
|
|
||||||
public readonly DeviceNetworkSystem DeviceNetworkSystem;
|
|
||||||
public readonly AirAlarmSystem AirAlarmSystem;
|
|
||||||
|
|
||||||
public abstract void Execute(EntityUid uid);
|
|
||||||
|
|
||||||
public AirAlarmModeExecutor()
|
|
||||||
{
|
|
||||||
IoCManager.InjectDependencies(this);
|
|
||||||
|
|
||||||
DeviceNetworkSystem = EntitySystem.Get<DeviceNetworkSystem>();
|
|
||||||
AirAlarmSystem = EntitySystem.Get<AirAlarmSystem>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class AirAlarmNoneMode : AirAlarmModeExecutor
|
|
||||||
{
|
|
||||||
public override void Execute(EntityUid uid)
|
|
||||||
{
|
|
||||||
if (!EntityManager.TryGetComponent(uid, out AirAlarmComponent? alarm))
|
|
||||||
return;
|
|
||||||
|
|
||||||
foreach (var (addr, device) in alarm.VentData)
|
|
||||||
{
|
|
||||||
device.Enabled = false;
|
|
||||||
AirAlarmSystem.SetData(uid, addr, device);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var (addr, device) in alarm.ScrubberData)
|
|
||||||
{
|
|
||||||
device.Enabled = false;
|
|
||||||
AirAlarmSystem.SetData(uid, addr, device);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class AirAlarmFilterMode : AirAlarmModeExecutor
|
|
||||||
{
|
|
||||||
public override void Execute(EntityUid uid)
|
|
||||||
{
|
|
||||||
if (!EntityManager.TryGetComponent(uid, out AirAlarmComponent? alarm))
|
|
||||||
return;
|
|
||||||
|
|
||||||
foreach (var (addr, device) in alarm.VentData)
|
|
||||||
{
|
|
||||||
AirAlarmSystem.SetData(uid, addr, GasVentPumpData.FilterModePreset);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var (addr, device) in alarm.ScrubberData)
|
|
||||||
{
|
|
||||||
AirAlarmSystem.SetData(uid, addr, GasVentScrubberData.FilterModePreset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class AirAlarmPanicMode : AirAlarmModeExecutor
|
|
||||||
{
|
|
||||||
public override void Execute(EntityUid uid)
|
|
||||||
{
|
|
||||||
if (!EntityManager.TryGetComponent(uid, out AirAlarmComponent? alarm))
|
|
||||||
return;
|
|
||||||
|
|
||||||
foreach (var (addr, device) in alarm.VentData)
|
|
||||||
{
|
|
||||||
AirAlarmSystem.SetData(uid, addr, GasVentPumpData.PanicModePreset);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var (addr, device) in alarm.ScrubberData)
|
|
||||||
{
|
|
||||||
AirAlarmSystem.SetData(uid, addr, GasVentScrubberData.PanicModePreset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class AirAlarmFillMode : AirAlarmModeExecutor
|
|
||||||
{
|
|
||||||
public override void Execute(EntityUid uid)
|
|
||||||
{
|
|
||||||
if (!EntityManager.TryGetComponent(uid, out AirAlarmComponent? alarm))
|
|
||||||
return;
|
|
||||||
|
|
||||||
foreach (var (addr, device) in alarm.VentData)
|
|
||||||
{
|
|
||||||
AirAlarmSystem.SetData(uid, addr, GasVentPumpData.FillModePreset);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var (addr, device) in alarm.ScrubberData)
|
|
||||||
{
|
|
||||||
AirAlarmSystem.SetData(uid, addr, GasVentScrubberData.FillModePreset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class AirAlarmReplaceMode : AirAlarmModeExecutor, IAirAlarmModeUpdate
|
|
||||||
{
|
|
||||||
private AirAlarmComponent? _alarm;
|
|
||||||
private float _lastPressure = Atmospherics.OneAtmosphere;
|
|
||||||
|
|
||||||
public string NetOwner { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
public override void Execute(EntityUid uid)
|
|
||||||
{
|
|
||||||
if (!EntityManager.TryGetComponent(uid, out _alarm))
|
|
||||||
return;
|
|
||||||
|
|
||||||
SetSiphon(uid);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Update(EntityUid uid)
|
foreach (var (addr, device) in alarm.ScrubberData)
|
||||||
{
|
{
|
||||||
if (_alarm == null)
|
device.Enabled = false;
|
||||||
return;
|
AirAlarmSystem.SetData(uid, addr, device);
|
||||||
|
}
|
||||||
// just a little pointer
|
}
|
||||||
|
}
|
||||||
_lastPressure = AirAlarmSystem.CalculatePressureAverage(_alarm);
|
|
||||||
if (_lastPressure <= 0.2f) // anything below and it might get stuck
|
public sealed class AirAlarmFilterMode : AirAlarmModeExecutor
|
||||||
{
|
{
|
||||||
AirAlarmSystem.SetMode(uid, NetOwner!, AirAlarmMode.Filtering, false, false);
|
public override void Execute(EntityUid uid)
|
||||||
}
|
{
|
||||||
}
|
if (!EntityManager.TryGetComponent(uid, out AirAlarmComponent? alarm))
|
||||||
|
return;
|
||||||
private void SetSiphon(EntityUid uid)
|
|
||||||
{
|
foreach (var (addr, device) in alarm.VentData)
|
||||||
foreach (var (addr, device) in _alarm!.VentData)
|
{
|
||||||
{
|
AirAlarmSystem.SetData(uid, addr, GasVentPumpData.FilterModePreset);
|
||||||
AirAlarmSystem.SetData(uid, addr, GasVentPumpData.ReplaceModePreset);
|
}
|
||||||
}
|
|
||||||
|
foreach (var (addr, device) in alarm.ScrubberData)
|
||||||
foreach (var (addr, device) in _alarm!.ScrubberData)
|
{
|
||||||
{
|
AirAlarmSystem.SetData(uid, addr, GasVentScrubberData.FilterModePreset);
|
||||||
AirAlarmSystem.SetData(uid, addr, GasVentScrubberData.ReplaceModePreset);
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class AirAlarmPanicMode : AirAlarmModeExecutor
|
||||||
|
{
|
||||||
|
public override void Execute(EntityUid uid)
|
||||||
|
{
|
||||||
|
if (!EntityManager.TryGetComponent(uid, out AirAlarmComponent? alarm))
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var (addr, device) in alarm.VentData)
|
||||||
|
{
|
||||||
|
AirAlarmSystem.SetData(uid, addr, GasVentPumpData.PanicModePreset);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var (addr, device) in alarm.ScrubberData)
|
||||||
|
{
|
||||||
|
AirAlarmSystem.SetData(uid, addr, GasVentScrubberData.PanicModePreset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class AirAlarmFillMode : AirAlarmModeExecutor
|
||||||
|
{
|
||||||
|
public override void Execute(EntityUid uid)
|
||||||
|
{
|
||||||
|
if (!EntityManager.TryGetComponent(uid, out AirAlarmComponent? alarm))
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var (addr, device) in alarm.VentData)
|
||||||
|
{
|
||||||
|
AirAlarmSystem.SetData(uid, addr, GasVentPumpData.FillModePreset);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var (addr, device) in alarm.ScrubberData)
|
||||||
|
{
|
||||||
|
AirAlarmSystem.SetData(uid, addr, GasVentScrubberData.FillModePreset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class AirAlarmReplaceMode : AirAlarmModeExecutor, IAirAlarmModeUpdate
|
||||||
|
{
|
||||||
|
private AirAlarmComponent? _alarm;
|
||||||
|
private float _lastPressure = Atmospherics.OneAtmosphere;
|
||||||
|
|
||||||
|
public string NetOwner { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public override void Execute(EntityUid uid)
|
||||||
|
{
|
||||||
|
if (!EntityManager.TryGetComponent(uid, out _alarm))
|
||||||
|
return;
|
||||||
|
|
||||||
|
SetSiphon(uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update(EntityUid uid)
|
||||||
|
{
|
||||||
|
if (_alarm == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// just a little pointer
|
||||||
|
|
||||||
|
_lastPressure = AirAlarmSystem.CalculatePressureAverage(_alarm);
|
||||||
|
if (_lastPressure <= 0.2f) // anything below and it might get stuck
|
||||||
|
{
|
||||||
|
AirAlarmSystem.SetMode(uid, NetOwner!, AirAlarmMode.Filtering, false, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetSiphon(EntityUid uid)
|
||||||
|
{
|
||||||
|
foreach (var (addr, device) in _alarm!.VentData)
|
||||||
|
{
|
||||||
|
AirAlarmSystem.SetData(uid, addr, GasVentPumpData.ReplaceModePreset);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var (addr, device) in _alarm!.ScrubberData)
|
||||||
|
{
|
||||||
|
AirAlarmSystem.SetData(uid, addr, GasVentScrubberData.ReplaceModePreset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -11,297 +11,296 @@ using Robust.Server.GameObjects;
|
|||||||
using Robust.Shared.Audio;
|
using Robust.Shared.Audio;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
namespace Content.Server.Atmos.Monitor.Systems
|
namespace Content.Server.Atmos.Monitor.Systems;
|
||||||
|
|
||||||
|
public sealed class AtmosAlarmableSystem : EntitySystem
|
||||||
{
|
{
|
||||||
public sealed class AtmosAlarmableSystem : EntitySystem
|
[Dependency] private readonly AppearanceSystem _appearance = default!;
|
||||||
|
[Dependency] private readonly AudioSystem _audioSystem = default!;
|
||||||
|
[Dependency] private readonly DeviceNetworkSystem _deviceNet = default!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An alarm. Has three valid states: Normal, Warning, Danger.
|
||||||
|
/// Will attempt to fetch the tags from the alarming entity
|
||||||
|
/// to send over.
|
||||||
|
/// </summary>
|
||||||
|
public const string AlertCmd = "atmos_alarm";
|
||||||
|
|
||||||
|
public const string AlertSource = "atmos_alarm_source";
|
||||||
|
|
||||||
|
public const string AlertTypes = "atmos_alarm_types";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Syncs alerts from this alarm receiver to other alarm receivers.
|
||||||
|
/// Creates a network effect as a result. Note: if the alert receiver
|
||||||
|
/// is not aware of the device beforehand, it will not sync.
|
||||||
|
/// </summary>
|
||||||
|
public const string SyncAlerts = "atmos_alarmable_sync_alerts";
|
||||||
|
|
||||||
|
public const string ResetAll = "atmos_alarmable_reset_all";
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
[Dependency] private readonly AppearanceSystem _appearance = default!;
|
SubscribeLocalEvent<AtmosAlarmableComponent, DeviceNetworkPacketEvent>(OnPacketRecv);
|
||||||
[Dependency] private readonly AudioSystem _audioSystem = default!;
|
SubscribeLocalEvent<AtmosAlarmableComponent, PowerChangedEvent>(OnPowerChange);
|
||||||
[Dependency] private readonly DeviceNetworkSystem _deviceNet = default!;
|
}
|
||||||
|
|
||||||
/// <summary>
|
private void OnPowerChange(EntityUid uid, AtmosAlarmableComponent component, PowerChangedEvent args)
|
||||||
/// An alarm. Has three valid states: Normal, Warning, Danger.
|
{
|
||||||
/// Will attempt to fetch the tags from the alarming entity
|
if (!args.Powered)
|
||||||
/// to send over.
|
|
||||||
/// </summary>
|
|
||||||
public const string AlertCmd = "atmos_alarm";
|
|
||||||
|
|
||||||
public const string AlertSource = "atmos_alarm_source";
|
|
||||||
|
|
||||||
public const string AlertTypes = "atmos_alarm_types";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Syncs alerts from this alarm receiver to other alarm receivers.
|
|
||||||
/// Creates a network effect as a result. Note: if the alert receiver
|
|
||||||
/// is not aware of the device beforehand, it will not sync.
|
|
||||||
/// </summary>
|
|
||||||
public const string SyncAlerts = "atmos_alarmable_sync_alerts";
|
|
||||||
|
|
||||||
public const string ResetAll = "atmos_alarmable_reset_all";
|
|
||||||
|
|
||||||
public override void Initialize()
|
|
||||||
{
|
{
|
||||||
SubscribeLocalEvent<AtmosAlarmableComponent, DeviceNetworkPacketEvent>(OnPacketRecv);
|
Reset(uid, component);
|
||||||
SubscribeLocalEvent<AtmosAlarmableComponent, PowerChangedEvent>(OnPowerChange);
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
private void OnPowerChange(EntityUid uid, AtmosAlarmableComponent component, PowerChangedEvent args)
|
|
||||||
{
|
{
|
||||||
if (!args.Powered)
|
TryUpdateAlert(
|
||||||
{
|
uid,
|
||||||
Reset(uid, component);
|
TryGetHighestAlert(uid, out var alarm) ? alarm.Value : AtmosMonitorAlarmType.Normal,
|
||||||
}
|
component,
|
||||||
else
|
|
||||||
{
|
|
||||||
TryUpdateAlert(
|
|
||||||
uid,
|
|
||||||
TryGetHighestAlert(uid, out var alarm) ? alarm.Value : AtmosMonitorAlarmType.Normal,
|
|
||||||
component,
|
|
||||||
false);
|
false);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnPacketRecv(EntityUid uid, AtmosAlarmableComponent component, DeviceNetworkPacketEvent args)
|
|
||||||
{
|
|
||||||
if (component.IgnoreAlarms) return;
|
|
||||||
|
|
||||||
if (!EntityManager.TryGetComponent(uid, out DeviceNetworkComponent? netConn))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!args.Data.TryGetValue(DeviceNetworkConstants.Command, out string? cmd)
|
|
||||||
|| !args.Data.TryGetValue(AlertSource, out HashSet<string>? sourceTags))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var isValid = sourceTags.Any(source => component.SyncWithTags.Contains(source));
|
|
||||||
|
|
||||||
if (!isValid)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (cmd)
|
|
||||||
{
|
|
||||||
case AlertCmd:
|
|
||||||
// Set the alert state, and then cache it so we can calculate
|
|
||||||
// the maximum alarm state at all times.
|
|
||||||
if (!args.Data.TryGetValue(DeviceNetworkConstants.CmdSetState, out AtmosMonitorAlarmType state))
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args.Data.TryGetValue(AlertTypes, out HashSet<AtmosMonitorThresholdType>? types) && component.MonitorAlertTypes != null)
|
|
||||||
{
|
|
||||||
isValid = types.Any(type => component.MonitorAlertTypes.Contains(type));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!component.NetworkAlarmStates.ContainsKey(args.SenderAddress))
|
|
||||||
{
|
|
||||||
if (!isValid)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
component.NetworkAlarmStates.Add(args.SenderAddress, state);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// This is because if the alert is no longer valid,
|
|
||||||
// it may mean that the threshold we need to look at has
|
|
||||||
// been removed from the threshold types passed:
|
|
||||||
// basically, we need to reset this state to normal here.
|
|
||||||
component.NetworkAlarmStates[args.SenderAddress] = isValid ? state : AtmosMonitorAlarmType.Normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!TryGetHighestAlert(uid, out var netMax, component))
|
|
||||||
{
|
|
||||||
netMax = AtmosMonitorAlarmType.Normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
TryUpdateAlert(uid, netMax.Value, component);
|
|
||||||
|
|
||||||
break;
|
|
||||||
case ResetAll:
|
|
||||||
Reset(uid, component);
|
|
||||||
break;
|
|
||||||
case SyncAlerts:
|
|
||||||
if (!args.Data.TryGetValue(SyncAlerts,
|
|
||||||
out IReadOnlyDictionary<string, AtmosMonitorAlarmType>? alarms))
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var (key, alarm) in alarms)
|
|
||||||
{
|
|
||||||
if (!component.NetworkAlarmStates.TryAdd(key, alarm))
|
|
||||||
{
|
|
||||||
component.NetworkAlarmStates[key] = alarm;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (TryGetHighestAlert(uid, out var maxAlert, component))
|
|
||||||
{
|
|
||||||
TryUpdateAlert(uid, maxAlert.Value, component);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void TryUpdateAlert(EntityUid uid, AtmosMonitorAlarmType type, AtmosAlarmableComponent alarmable, bool sync = true)
|
|
||||||
{
|
|
||||||
if (alarmable.LastAlarmState == type)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sync)
|
|
||||||
{
|
|
||||||
SyncAlertsToNetwork(uid, null, alarmable);
|
|
||||||
}
|
|
||||||
|
|
||||||
alarmable.LastAlarmState = type;
|
|
||||||
UpdateAppearance(uid, type);
|
|
||||||
PlayAlertSound(uid, type, alarmable);
|
|
||||||
RaiseLocalEvent(uid, new AtmosAlarmEvent(type), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SyncAlertsToNetwork(EntityUid uid, string? address = null, AtmosAlarmableComponent? alarmable = null, TagComponent? tags = null)
|
|
||||||
{
|
|
||||||
if (!Resolve(uid, ref alarmable, ref tags) || alarmable.ReceiveOnly)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var payload = new NetworkPayload
|
|
||||||
{
|
|
||||||
[DeviceNetworkConstants.Command] = SyncAlerts,
|
|
||||||
[SyncAlerts] = alarmable.NetworkAlarmStates,
|
|
||||||
[AlertSource] = tags.Tags
|
|
||||||
};
|
|
||||||
|
|
||||||
_deviceNet.QueuePacket(uid, address, payload);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Forces this alarmable to have a specific alert. This will not be reset until the alarmable
|
|
||||||
/// is manually reset. This will store the alarmable as a device in its network states.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="uid"></param>
|
|
||||||
/// <param name="alarmType"></param>
|
|
||||||
/// <param name="alarmable"></param>
|
|
||||||
public void ForceAlert(EntityUid uid, AtmosMonitorAlarmType alarmType,
|
|
||||||
AtmosAlarmableComponent? alarmable = null, DeviceNetworkComponent? devNet = null, TagComponent? tags = null)
|
|
||||||
{
|
|
||||||
if (!Resolve(uid, ref alarmable, ref devNet, ref tags))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
TryUpdateAlert(uid, alarmType, alarmable, false);
|
|
||||||
|
|
||||||
if (alarmable.ReceiveOnly)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!alarmable.NetworkAlarmStates.TryAdd(devNet.Address, alarmType))
|
|
||||||
{
|
|
||||||
alarmable.NetworkAlarmStates[devNet.Address] = alarmType;
|
|
||||||
}
|
|
||||||
|
|
||||||
var payload = new NetworkPayload
|
|
||||||
{
|
|
||||||
[DeviceNetworkConstants.Command] = AlertCmd,
|
|
||||||
[DeviceNetworkConstants.CmdSetState] = alarmType,
|
|
||||||
[AlertSource] = tags.Tags
|
|
||||||
};
|
|
||||||
|
|
||||||
_deviceNet.QueuePacket(uid, null, payload);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Resets the state of this alarmable to normal.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="uid"></param>
|
|
||||||
/// <param name="alarmable"></param>
|
|
||||||
public void Reset(EntityUid uid, AtmosAlarmableComponent? alarmable = null)
|
|
||||||
{
|
|
||||||
if (!Resolve(uid, ref alarmable))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
TryUpdateAlert(uid, AtmosMonitorAlarmType.Normal, alarmable, false);
|
|
||||||
|
|
||||||
alarmable.NetworkAlarmStates.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ResetAllOnNetwork(EntityUid uid, AtmosAlarmableComponent? alarmable = null, TagComponent? tags = null)
|
|
||||||
{
|
|
||||||
if (!Resolve(uid, ref alarmable, ref tags) || alarmable.ReceiveOnly)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Reset(uid, alarmable);
|
|
||||||
|
|
||||||
var payload = new NetworkPayload
|
|
||||||
{
|
|
||||||
[DeviceNetworkConstants.Command] = ResetAll,
|
|
||||||
[AlertSource] = tags.Tags
|
|
||||||
};
|
|
||||||
|
|
||||||
_deviceNet.QueuePacket(uid, null, payload);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Tries to get the highest possible alert stored in this alarm.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="uid"></param>
|
|
||||||
/// <param name="alarm"></param>
|
|
||||||
/// <param name="alarmable"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public bool TryGetHighestAlert(EntityUid uid, [NotNullWhen(true)] out AtmosMonitorAlarmType? alarm,
|
|
||||||
AtmosAlarmableComponent? alarmable = null)
|
|
||||||
{
|
|
||||||
alarm = null;
|
|
||||||
|
|
||||||
if (!Resolve(uid, ref alarmable))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var alarmState in alarmable.NetworkAlarmStates.Values)
|
|
||||||
{
|
|
||||||
alarm = alarm == null || alarm < alarmState ? alarmState : alarm;
|
|
||||||
}
|
|
||||||
|
|
||||||
return alarm != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PlayAlertSound(EntityUid uid, AtmosMonitorAlarmType alarm, AtmosAlarmableComponent alarmable)
|
|
||||||
{
|
|
||||||
if (alarm == AtmosMonitorAlarmType.Danger)
|
|
||||||
{
|
|
||||||
_audioSystem.PlayPvs(alarmable.AlarmSound, uid, AudioParams.Default.WithVolume(alarmable.AlarmVolume));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateAppearance(EntityUid uid, AtmosMonitorAlarmType alarm)
|
|
||||||
{
|
|
||||||
_appearance.SetData(uid, AtmosMonitorVisuals.AlarmType, alarm);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class AtmosAlarmEvent : EntityEventArgs
|
private void OnPacketRecv(EntityUid uid, AtmosAlarmableComponent component, DeviceNetworkPacketEvent args)
|
||||||
{
|
{
|
||||||
public AtmosMonitorAlarmType AlarmType { get; }
|
if (component.IgnoreAlarms) return;
|
||||||
|
|
||||||
public AtmosAlarmEvent(AtmosMonitorAlarmType netMax)
|
if (!EntityManager.TryGetComponent(uid, out DeviceNetworkComponent? netConn))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!args.Data.TryGetValue(DeviceNetworkConstants.Command, out string? cmd)
|
||||||
|
|| !args.Data.TryGetValue(AlertSource, out HashSet<string>? sourceTags))
|
||||||
{
|
{
|
||||||
AlarmType = netMax;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var isValid = sourceTags.Any(source => component.SyncWithTags.Contains(source));
|
||||||
|
|
||||||
|
if (!isValid)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (cmd)
|
||||||
|
{
|
||||||
|
case AlertCmd:
|
||||||
|
// Set the alert state, and then cache it so we can calculate
|
||||||
|
// the maximum alarm state at all times.
|
||||||
|
if (!args.Data.TryGetValue(DeviceNetworkConstants.CmdSetState, out AtmosMonitorAlarmType state))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.Data.TryGetValue(AlertTypes, out HashSet<AtmosMonitorThresholdType>? types) && component.MonitorAlertTypes != null)
|
||||||
|
{
|
||||||
|
isValid = types.Any(type => component.MonitorAlertTypes.Contains(type));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!component.NetworkAlarmStates.ContainsKey(args.SenderAddress))
|
||||||
|
{
|
||||||
|
if (!isValid)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
component.NetworkAlarmStates.Add(args.SenderAddress, state);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// This is because if the alert is no longer valid,
|
||||||
|
// it may mean that the threshold we need to look at has
|
||||||
|
// been removed from the threshold types passed:
|
||||||
|
// basically, we need to reset this state to normal here.
|
||||||
|
component.NetworkAlarmStates[args.SenderAddress] = isValid ? state : AtmosMonitorAlarmType.Normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TryGetHighestAlert(uid, out var netMax, component))
|
||||||
|
{
|
||||||
|
netMax = AtmosMonitorAlarmType.Normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
TryUpdateAlert(uid, netMax.Value, component);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case ResetAll:
|
||||||
|
Reset(uid, component);
|
||||||
|
break;
|
||||||
|
case SyncAlerts:
|
||||||
|
if (!args.Data.TryGetValue(SyncAlerts,
|
||||||
|
out IReadOnlyDictionary<string, AtmosMonitorAlarmType>? alarms))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var (key, alarm) in alarms)
|
||||||
|
{
|
||||||
|
if (!component.NetworkAlarmStates.TryAdd(key, alarm))
|
||||||
|
{
|
||||||
|
component.NetworkAlarmStates[key] = alarm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TryGetHighestAlert(uid, out var maxAlert, component))
|
||||||
|
{
|
||||||
|
TryUpdateAlert(uid, maxAlert.Value, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TryUpdateAlert(EntityUid uid, AtmosMonitorAlarmType type, AtmosAlarmableComponent alarmable, bool sync = true)
|
||||||
|
{
|
||||||
|
if (alarmable.LastAlarmState == type)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sync)
|
||||||
|
{
|
||||||
|
SyncAlertsToNetwork(uid, null, alarmable);
|
||||||
|
}
|
||||||
|
|
||||||
|
alarmable.LastAlarmState = type;
|
||||||
|
UpdateAppearance(uid, type);
|
||||||
|
PlayAlertSound(uid, type, alarmable);
|
||||||
|
RaiseLocalEvent(uid, new AtmosAlarmEvent(type), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SyncAlertsToNetwork(EntityUid uid, string? address = null, AtmosAlarmableComponent? alarmable = null, TagComponent? tags = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref alarmable, ref tags) || alarmable.ReceiveOnly)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload = new NetworkPayload
|
||||||
|
{
|
||||||
|
[DeviceNetworkConstants.Command] = SyncAlerts,
|
||||||
|
[SyncAlerts] = alarmable.NetworkAlarmStates,
|
||||||
|
[AlertSource] = tags.Tags
|
||||||
|
};
|
||||||
|
|
||||||
|
_deviceNet.QueuePacket(uid, address, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Forces this alarmable to have a specific alert. This will not be reset until the alarmable
|
||||||
|
/// is manually reset. This will store the alarmable as a device in its network states.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="uid"></param>
|
||||||
|
/// <param name="alarmType"></param>
|
||||||
|
/// <param name="alarmable"></param>
|
||||||
|
public void ForceAlert(EntityUid uid, AtmosMonitorAlarmType alarmType,
|
||||||
|
AtmosAlarmableComponent? alarmable = null, DeviceNetworkComponent? devNet = null, TagComponent? tags = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref alarmable, ref devNet, ref tags))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TryUpdateAlert(uid, alarmType, alarmable, false);
|
||||||
|
|
||||||
|
if (alarmable.ReceiveOnly)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!alarmable.NetworkAlarmStates.TryAdd(devNet.Address, alarmType))
|
||||||
|
{
|
||||||
|
alarmable.NetworkAlarmStates[devNet.Address] = alarmType;
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload = new NetworkPayload
|
||||||
|
{
|
||||||
|
[DeviceNetworkConstants.Command] = AlertCmd,
|
||||||
|
[DeviceNetworkConstants.CmdSetState] = alarmType,
|
||||||
|
[AlertSource] = tags.Tags
|
||||||
|
};
|
||||||
|
|
||||||
|
_deviceNet.QueuePacket(uid, null, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resets the state of this alarmable to normal.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="uid"></param>
|
||||||
|
/// <param name="alarmable"></param>
|
||||||
|
public void Reset(EntityUid uid, AtmosAlarmableComponent? alarmable = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref alarmable))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TryUpdateAlert(uid, AtmosMonitorAlarmType.Normal, alarmable, false);
|
||||||
|
|
||||||
|
alarmable.NetworkAlarmStates.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ResetAllOnNetwork(EntityUid uid, AtmosAlarmableComponent? alarmable = null, TagComponent? tags = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref alarmable, ref tags) || alarmable.ReceiveOnly)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Reset(uid, alarmable);
|
||||||
|
|
||||||
|
var payload = new NetworkPayload
|
||||||
|
{
|
||||||
|
[DeviceNetworkConstants.Command] = ResetAll,
|
||||||
|
[AlertSource] = tags.Tags
|
||||||
|
};
|
||||||
|
|
||||||
|
_deviceNet.QueuePacket(uid, null, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to get the highest possible alert stored in this alarm.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="uid"></param>
|
||||||
|
/// <param name="alarm"></param>
|
||||||
|
/// <param name="alarmable"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public bool TryGetHighestAlert(EntityUid uid, [NotNullWhen(true)] out AtmosMonitorAlarmType? alarm,
|
||||||
|
AtmosAlarmableComponent? alarmable = null)
|
||||||
|
{
|
||||||
|
alarm = null;
|
||||||
|
|
||||||
|
if (!Resolve(uid, ref alarmable))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var alarmState in alarmable.NetworkAlarmStates.Values)
|
||||||
|
{
|
||||||
|
alarm = alarm == null || alarm < alarmState ? alarmState : alarm;
|
||||||
|
}
|
||||||
|
|
||||||
|
return alarm != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PlayAlertSound(EntityUid uid, AtmosMonitorAlarmType alarm, AtmosAlarmableComponent alarmable)
|
||||||
|
{
|
||||||
|
if (alarm == AtmosMonitorAlarmType.Danger)
|
||||||
|
{
|
||||||
|
_audioSystem.PlayPvs(alarmable.AlarmSound, uid, AudioParams.Default.WithVolume(alarmable.AlarmVolume));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateAppearance(EntityUid uid, AtmosMonitorAlarmType alarm)
|
||||||
|
{
|
||||||
|
_appearance.SetData(uid, AtmosMonitorVisuals.AlarmType, alarm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class AtmosAlarmEvent : EntityEventArgs
|
||||||
|
{
|
||||||
|
public AtmosMonitorAlarmType AlarmType { get; }
|
||||||
|
|
||||||
|
public AtmosAlarmEvent(AtmosMonitorAlarmType netMax)
|
||||||
|
{
|
||||||
|
AlarmType = netMax;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,368 +16,365 @@ using Robust.Shared.Audio;
|
|||||||
using Robust.Shared.Player;
|
using Robust.Shared.Player;
|
||||||
using Robust.Shared.Prototypes;
|
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
|
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
|
||||||
// to it via local APC net, and starts sending updates of the
|
[Dependency] private readonly AtmosDeviceSystem _atmosDeviceSystem = default!;
|
||||||
// current atmosphere. Monitors fire (which always triggers as
|
[Dependency] private readonly DeviceNetworkSystem _deviceNetSystem = default!;
|
||||||
// a danger), and atmos (which triggers based on set thresholds).
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
public sealed class AtmosMonitorSystem : EntitySystem
|
|
||||||
|
// 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!;
|
SubscribeLocalEvent<AtmosMonitorComponent, ComponentInit>(OnAtmosMonitorInit);
|
||||||
[Dependency] private readonly AtmosDeviceSystem _atmosDeviceSystem = default!;
|
SubscribeLocalEvent<AtmosMonitorComponent, ComponentStartup>(OnAtmosMonitorStartup);
|
||||||
[Dependency] private readonly DeviceNetworkSystem _deviceNetSystem = default!;
|
SubscribeLocalEvent<AtmosMonitorComponent, AtmosDeviceUpdateEvent>(OnAtmosUpdate);
|
||||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
SubscribeLocalEvent<AtmosMonitorComponent, TileFireEvent>(OnFireEvent);
|
||||||
|
SubscribeLocalEvent<AtmosMonitorComponent, PowerChangedEvent>(OnPowerChangedEvent);
|
||||||
|
SubscribeLocalEvent<AtmosMonitorComponent, BeforePacketSentEvent>(BeforePacketRecv);
|
||||||
|
SubscribeLocalEvent<AtmosMonitorComponent, DeviceNetworkPacketEvent>(OnPacketRecv);
|
||||||
|
}
|
||||||
|
|
||||||
// Commands
|
private void OnAtmosMonitorInit(EntityUid uid, AtmosMonitorComponent component, ComponentInit args)
|
||||||
public const string AtmosMonitorSetThresholdCmd = "atmos_monitor_set_threshold";
|
{
|
||||||
|
if (component.TemperatureThresholdId != null)
|
||||||
|
component.TemperatureThreshold = new(_prototypeManager.Index<AtmosAlarmThreshold>(component.TemperatureThresholdId));
|
||||||
|
|
||||||
// Packet data
|
if (component.PressureThresholdId != null)
|
||||||
public const string AlertTypes = "atmos_monitor_alert_types";
|
component.PressureThreshold = new(_prototypeManager.Index<AtmosAlarmThreshold>(component.PressureThresholdId));
|
||||||
|
|
||||||
public const string AtmosMonitorThresholdData = "atmos_monitor_threshold_data";
|
if (component.GasThresholdIds != null)
|
||||||
|
|
||||||
public const string AtmosMonitorThresholdDataType = "atmos_monitor_threshold_type";
|
|
||||||
|
|
||||||
public const string AtmosMonitorThresholdGasType = "atmos_monitor_threshold_gas";
|
|
||||||
|
|
||||||
public override void Initialize()
|
|
||||||
{
|
{
|
||||||
SubscribeLocalEvent<AtmosMonitorComponent, ComponentInit>(OnAtmosMonitorInit);
|
component.GasThresholds = new();
|
||||||
SubscribeLocalEvent<AtmosMonitorComponent, ComponentStartup>(OnAtmosMonitorStartup);
|
foreach (var (gas, id) in component.GasThresholdIds)
|
||||||
SubscribeLocalEvent<AtmosMonitorComponent, AtmosDeviceUpdateEvent>(OnAtmosUpdate);
|
if (_prototypeManager.TryIndex<AtmosAlarmThreshold>(id, out var gasThreshold))
|
||||||
SubscribeLocalEvent<AtmosMonitorComponent, TileFireEvent>(OnFireEvent);
|
component.GasThresholds.Add(gas, new(gasThreshold));
|
||||||
SubscribeLocalEvent<AtmosMonitorComponent, PowerChangedEvent>(OnPowerChangedEvent);
|
|
||||||
SubscribeLocalEvent<AtmosMonitorComponent, BeforePacketSentEvent>(BeforePacketRecv);
|
|
||||||
SubscribeLocalEvent<AtmosMonitorComponent, DeviceNetworkPacketEvent>(OnPacketRecv);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnAtmosMonitorInit(EntityUid uid, AtmosMonitorComponent component, ComponentInit args)
|
|
||||||
{
|
|
||||||
if (component.TemperatureThresholdId != null)
|
|
||||||
component.TemperatureThreshold = new(_prototypeManager.Index<AtmosAlarmThreshold>(component.TemperatureThresholdId));
|
|
||||||
|
|
||||||
if (component.PressureThresholdId != null)
|
|
||||||
component.PressureThreshold = new(_prototypeManager.Index<AtmosAlarmThreshold>(component.PressureThresholdId));
|
|
||||||
|
|
||||||
if (component.GasThresholdIds != null)
|
|
||||||
{
|
|
||||||
component.GasThresholds = new();
|
|
||||||
foreach (var (gas, id) in component.GasThresholdIds)
|
|
||||||
if (_prototypeManager.TryIndex<AtmosAlarmThreshold>(id, out var gasThreshold))
|
|
||||||
component.GasThresholds.Add(gas, new(gasThreshold));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnAtmosMonitorStartup(EntityUid uid, AtmosMonitorComponent component, ComponentStartup args)
|
|
||||||
{
|
|
||||||
if (!HasComp<ApcPowerReceiverComponent>(uid)
|
|
||||||
&& TryComp<AtmosDeviceComponent>(uid, out var atmosDeviceComponent))
|
|
||||||
{
|
|
||||||
_atmosDeviceSystem.LeaveAtmosphere(atmosDeviceComponent);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void BeforePacketRecv(EntityUid uid, AtmosMonitorComponent component, BeforePacketSentEvent args)
|
|
||||||
{
|
|
||||||
if (!component.NetEnabled) args.Cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnPacketRecv(EntityUid uid, AtmosMonitorComponent component, DeviceNetworkPacketEvent args)
|
|
||||||
{
|
|
||||||
// sync the internal 'last alarm state' from
|
|
||||||
// the other alarms, so that we can calculate
|
|
||||||
// the highest network alarm state at any time
|
|
||||||
if (!args.Data.TryGetValue(DeviceNetworkConstants.Command, out string? cmd))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (cmd)
|
|
||||||
{
|
|
||||||
case AtmosDeviceNetworkSystem.RegisterDevice:
|
|
||||||
component.RegisteredDevices.Add(args.SenderAddress);
|
|
||||||
break;
|
|
||||||
case AtmosAlarmableSystem.ResetAll:
|
|
||||||
Reset(uid);
|
|
||||||
// Don't clear alarm states here.
|
|
||||||
break;
|
|
||||||
case AtmosMonitorSetThresholdCmd:
|
|
||||||
if (args.Data.TryGetValue(AtmosMonitorThresholdData, out AtmosAlarmThreshold? thresholdData)
|
|
||||||
&& args.Data.TryGetValue(AtmosMonitorThresholdDataType, out AtmosMonitorThresholdType? thresholdType))
|
|
||||||
{
|
|
||||||
args.Data.TryGetValue(AtmosMonitorThresholdGasType, out Gas? gas);
|
|
||||||
SetThreshold(uid, thresholdType.Value, thresholdData, gas);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case AtmosDeviceNetworkSystem.SyncData:
|
|
||||||
var payload = new NetworkPayload();
|
|
||||||
payload.Add(DeviceNetworkConstants.Command, AtmosDeviceNetworkSystem.SyncData);
|
|
||||||
if (component.TileGas != null)
|
|
||||||
{
|
|
||||||
var gases = new Dictionary<Gas, float>();
|
|
||||||
foreach (var gas in Enum.GetValues<Gas>())
|
|
||||||
{
|
|
||||||
gases.Add(gas, component.TileGas.GetMoles(gas));
|
|
||||||
}
|
|
||||||
|
|
||||||
payload.Add(AtmosDeviceNetworkSystem.SyncData, new AtmosSensorData(
|
|
||||||
component.TileGas.Pressure,
|
|
||||||
component.TileGas.Temperature,
|
|
||||||
component.TileGas.TotalMoles,
|
|
||||||
component.LastAlarmState,
|
|
||||||
gases,
|
|
||||||
component.PressureThreshold ?? new(),
|
|
||||||
component.TemperatureThreshold ?? new(),
|
|
||||||
component.GasThresholds ?? new()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
_deviceNetSystem.QueuePacket(uid, args.SenderAddress, payload);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnPowerChangedEvent(EntityUid uid, AtmosMonitorComponent component, PowerChangedEvent args)
|
|
||||||
{
|
|
||||||
if (TryComp<AtmosDeviceComponent>(uid, out var atmosDeviceComponent))
|
|
||||||
{
|
|
||||||
if (!args.Powered)
|
|
||||||
{
|
|
||||||
if (atmosDeviceComponent.JoinedGrid != null)
|
|
||||||
{
|
|
||||||
_atmosDeviceSystem.LeaveAtmosphere(atmosDeviceComponent);
|
|
||||||
component.TileGas = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (args.Powered)
|
|
||||||
{
|
|
||||||
if (atmosDeviceComponent.JoinedGrid == null)
|
|
||||||
{
|
|
||||||
_atmosDeviceSystem.JoinAtmosphere(atmosDeviceComponent);
|
|
||||||
var air = _atmosphereSystem.GetContainingMixture(uid, true);
|
|
||||||
component.TileGas = air;
|
|
||||||
}
|
|
||||||
|
|
||||||
Alert(uid, component.LastAlarmState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnFireEvent(EntityUid uid, AtmosMonitorComponent component, ref TileFireEvent args)
|
|
||||||
{
|
|
||||||
if (!this.IsPowered(uid, EntityManager))
|
|
||||||
return;
|
|
||||||
|
|
||||||
// if we're monitoring for atmos fire, then we make it similar to a smoke detector
|
|
||||||
// and just outright trigger a danger event
|
|
||||||
//
|
|
||||||
// somebody else can reset it :sunglasses:
|
|
||||||
if (component.MonitorFire
|
|
||||||
&& component.LastAlarmState != AtmosMonitorAlarmType.Danger)
|
|
||||||
{
|
|
||||||
component.TrippedThresholds.Add(AtmosMonitorThresholdType.Temperature);
|
|
||||||
Alert(uid, AtmosMonitorAlarmType.Danger, null, component); // technically???
|
|
||||||
}
|
|
||||||
|
|
||||||
// only monitor state elevation so that stuff gets alarmed quicker during a fire,
|
|
||||||
// let the atmos update loop handle when temperature starts to reach different
|
|
||||||
// thresholds and different states than normal -> warning -> danger
|
|
||||||
if (component.TemperatureThreshold != null
|
|
||||||
&& component.TemperatureThreshold.CheckThreshold(args.Temperature, out var temperatureState)
|
|
||||||
&& temperatureState > component.LastAlarmState)
|
|
||||||
{
|
|
||||||
component.TrippedThresholds.Add(AtmosMonitorThresholdType.Temperature);
|
|
||||||
Alert(uid, AtmosMonitorAlarmType.Danger, null, component);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnAtmosUpdate(EntityUid uid, AtmosMonitorComponent component, AtmosDeviceUpdateEvent args)
|
|
||||||
{
|
|
||||||
if (!this.IsPowered(uid, EntityManager))
|
|
||||||
return;
|
|
||||||
|
|
||||||
// can't hurt
|
|
||||||
// (in case something is making AtmosDeviceUpdateEvents
|
|
||||||
// outside the typical device loop)
|
|
||||||
if (!TryComp<AtmosDeviceComponent>(uid, out var atmosDeviceComponent)
|
|
||||||
|| atmosDeviceComponent.JoinedGrid == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// if we're not monitoring atmos, don't bother
|
|
||||||
if (component.TemperatureThreshold == null
|
|
||||||
&& component.PressureThreshold == null
|
|
||||||
&& component.GasThresholds == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
UpdateState(uid, component.TileGas, component);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update checks the current air if it exceeds thresholds of
|
|
||||||
// any kind.
|
|
||||||
//
|
|
||||||
// If any threshold exceeds the other, that threshold
|
|
||||||
// immediately replaces the current recorded state.
|
|
||||||
//
|
|
||||||
// If the threshold does not match the current state
|
|
||||||
// of the monitor, it is set in the Alert call.
|
|
||||||
private void UpdateState(EntityUid uid, GasMixture? air, AtmosMonitorComponent? monitor = null)
|
|
||||||
{
|
|
||||||
if (air == null) return;
|
|
||||||
|
|
||||||
if (!Resolve(uid, ref monitor)) return;
|
|
||||||
|
|
||||||
AtmosMonitorAlarmType state = AtmosMonitorAlarmType.Normal;
|
|
||||||
HashSet<AtmosMonitorThresholdType> alarmTypes = new(monitor.TrippedThresholds);
|
|
||||||
|
|
||||||
if (monitor.TemperatureThreshold != null
|
|
||||||
&& monitor.TemperatureThreshold.CheckThreshold(air.Temperature, out var temperatureState))
|
|
||||||
{
|
|
||||||
if (temperatureState > state)
|
|
||||||
{
|
|
||||||
state = temperatureState;
|
|
||||||
alarmTypes.Add(AtmosMonitorThresholdType.Temperature);
|
|
||||||
}
|
|
||||||
else if (temperatureState == AtmosMonitorAlarmType.Normal)
|
|
||||||
{
|
|
||||||
alarmTypes.Remove(AtmosMonitorThresholdType.Temperature);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (monitor.PressureThreshold != null
|
|
||||||
&& monitor.PressureThreshold.CheckThreshold(air.Pressure, out var pressureState)
|
|
||||||
)
|
|
||||||
{
|
|
||||||
if (pressureState > state)
|
|
||||||
{
|
|
||||||
state = pressureState;
|
|
||||||
alarmTypes.Add(AtmosMonitorThresholdType.Pressure);
|
|
||||||
}
|
|
||||||
else if (pressureState == AtmosMonitorAlarmType.Normal)
|
|
||||||
{
|
|
||||||
alarmTypes.Remove(AtmosMonitorThresholdType.Pressure);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (monitor.GasThresholds != null)
|
|
||||||
{
|
|
||||||
var tripped = false;
|
|
||||||
foreach (var (gas, threshold) in monitor.GasThresholds)
|
|
||||||
{
|
|
||||||
var gasRatio = air.GetMoles(gas) / air.TotalMoles;
|
|
||||||
if (threshold.CheckThreshold(gasRatio, out var gasState)
|
|
||||||
&& gasState > state)
|
|
||||||
{
|
|
||||||
state = gasState;
|
|
||||||
tripped = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tripped)
|
|
||||||
{
|
|
||||||
alarmTypes.Add(AtmosMonitorThresholdType.Gas);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
alarmTypes.Remove(AtmosMonitorThresholdType.Gas);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the state of the current air doesn't match the last alarm state,
|
|
||||||
// we update the state
|
|
||||||
if (state != monitor.LastAlarmState || !alarmTypes.SetEquals(monitor.TrippedThresholds))
|
|
||||||
{
|
|
||||||
Alert(uid, state, alarmTypes, monitor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Alerts the network that the state of a monitor has changed.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="state">The alarm state to set this monitor to.</param>
|
|
||||||
/// <param name="alarms">The alarms that caused this alarm state.</param>
|
|
||||||
public void Alert(EntityUid uid, AtmosMonitorAlarmType state, HashSet<AtmosMonitorThresholdType>? alarms = null, AtmosMonitorComponent? monitor = null)
|
|
||||||
{
|
|
||||||
if (!Resolve(uid, ref monitor)) return;
|
|
||||||
|
|
||||||
monitor.LastAlarmState = state;
|
|
||||||
monitor.TrippedThresholds = alarms ?? monitor.TrippedThresholds;
|
|
||||||
|
|
||||||
BroadcastAlertPacket(monitor);
|
|
||||||
|
|
||||||
// TODO: Central system that grabs *all* alarms from wired network
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Resets a single monitor's alarm.
|
|
||||||
/// </summary>
|
|
||||||
private void Reset(EntityUid uid)
|
|
||||||
{
|
|
||||||
Alert(uid, AtmosMonitorAlarmType.Normal);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Broadcasts an alert packet to all devices on the network,
|
|
||||||
/// which consists of the current alarm types,
|
|
||||||
/// the highest alarm currently cached by this monitor,
|
|
||||||
/// and the current alarm state of the monitor (so other
|
|
||||||
/// alarms can sync to it).
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// Alarmables use the highest alarm to ensure that a monitor's
|
|
||||||
/// state doesn't override if the alarm is lower. The state
|
|
||||||
/// is synced between monitors the moment a monitor sends out an alarm,
|
|
||||||
/// or if it is explicitly synced (see ResetAll/Sync).
|
|
||||||
/// </remarks>
|
|
||||||
private void BroadcastAlertPacket(AtmosMonitorComponent monitor, TagComponent? tags = null)
|
|
||||||
{
|
|
||||||
if (!monitor.NetEnabled) return;
|
|
||||||
|
|
||||||
if (!Resolve(monitor.Owner, ref tags))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var payload = new NetworkPayload
|
|
||||||
{
|
|
||||||
[DeviceNetworkConstants.Command] = AtmosAlarmableSystem.AlertCmd,
|
|
||||||
[DeviceNetworkConstants.CmdSetState] = monitor.LastAlarmState,
|
|
||||||
[AtmosAlarmableSystem.AlertSource] = tags.Tags,
|
|
||||||
[AtmosAlarmableSystem.AlertTypes] = monitor.TrippedThresholds
|
|
||||||
};
|
|
||||||
|
|
||||||
foreach (var addr in monitor.RegisteredDevices)
|
|
||||||
{
|
|
||||||
_deviceNetSystem.QueuePacket(monitor.Owner, addr, payload);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Set a monitor's threshold.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="type">The type of threshold to change.</param>
|
|
||||||
/// <param name="threshold">Threshold data.</param>
|
|
||||||
/// <param name="gas">Gas, if applicable.</param>
|
|
||||||
public void SetThreshold(EntityUid uid, AtmosMonitorThresholdType type, AtmosAlarmThreshold threshold, Gas? gas = null, AtmosMonitorComponent? monitor = null)
|
|
||||||
{
|
|
||||||
if (!Resolve(uid, ref monitor)) return;
|
|
||||||
|
|
||||||
switch (type)
|
|
||||||
{
|
|
||||||
case AtmosMonitorThresholdType.Pressure:
|
|
||||||
monitor.PressureThreshold = threshold;
|
|
||||||
break;
|
|
||||||
case AtmosMonitorThresholdType.Temperature:
|
|
||||||
monitor.TemperatureThreshold = threshold;
|
|
||||||
break;
|
|
||||||
case AtmosMonitorThresholdType.Gas:
|
|
||||||
if (gas == null || monitor.GasThresholds == null) return;
|
|
||||||
monitor.GasThresholds[(Gas) gas] = threshold;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnAtmosMonitorStartup(EntityUid uid, AtmosMonitorComponent component, ComponentStartup args)
|
||||||
|
{
|
||||||
|
if (!HasComp<ApcPowerReceiverComponent>(uid)
|
||||||
|
&& TryComp<AtmosDeviceComponent>(uid, out var atmosDeviceComponent))
|
||||||
|
{
|
||||||
|
_atmosDeviceSystem.LeaveAtmosphere(atmosDeviceComponent);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BeforePacketRecv(EntityUid uid, AtmosMonitorComponent component, BeforePacketSentEvent args)
|
||||||
|
{
|
||||||
|
if (!component.NetEnabled) args.Cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPacketRecv(EntityUid uid, AtmosMonitorComponent component, DeviceNetworkPacketEvent args)
|
||||||
|
{
|
||||||
|
// sync the internal 'last alarm state' from
|
||||||
|
// the other alarms, so that we can calculate
|
||||||
|
// the highest network alarm state at any time
|
||||||
|
if (!args.Data.TryGetValue(DeviceNetworkConstants.Command, out string? cmd))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (cmd)
|
||||||
|
{
|
||||||
|
case AtmosDeviceNetworkSystem.RegisterDevice:
|
||||||
|
component.RegisteredDevices.Add(args.SenderAddress);
|
||||||
|
break;
|
||||||
|
case AtmosAlarmableSystem.ResetAll:
|
||||||
|
Reset(uid);
|
||||||
|
// Don't clear alarm states here.
|
||||||
|
break;
|
||||||
|
case AtmosMonitorSetThresholdCmd:
|
||||||
|
if (args.Data.TryGetValue(AtmosMonitorThresholdData, out AtmosAlarmThreshold? thresholdData)
|
||||||
|
&& args.Data.TryGetValue(AtmosMonitorThresholdDataType, out AtmosMonitorThresholdType? thresholdType))
|
||||||
|
{
|
||||||
|
args.Data.TryGetValue(AtmosMonitorThresholdGasType, out Gas? gas);
|
||||||
|
SetThreshold(uid, thresholdType.Value, thresholdData, gas);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case AtmosDeviceNetworkSystem.SyncData:
|
||||||
|
var payload = new NetworkPayload();
|
||||||
|
payload.Add(DeviceNetworkConstants.Command, AtmosDeviceNetworkSystem.SyncData);
|
||||||
|
if (component.TileGas != null)
|
||||||
|
{
|
||||||
|
var gases = new Dictionary<Gas, float>();
|
||||||
|
foreach (var gas in Enum.GetValues<Gas>())
|
||||||
|
{
|
||||||
|
gases.Add(gas, component.TileGas.GetMoles(gas));
|
||||||
|
}
|
||||||
|
|
||||||
|
payload.Add(AtmosDeviceNetworkSystem.SyncData, new AtmosSensorData(
|
||||||
|
component.TileGas.Pressure,
|
||||||
|
component.TileGas.Temperature,
|
||||||
|
component.TileGas.TotalMoles,
|
||||||
|
component.LastAlarmState,
|
||||||
|
gases,
|
||||||
|
component.PressureThreshold ?? new(),
|
||||||
|
component.TemperatureThreshold ?? new(),
|
||||||
|
component.GasThresholds ?? new()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
_deviceNetSystem.QueuePacket(uid, args.SenderAddress, payload);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPowerChangedEvent(EntityUid uid, AtmosMonitorComponent component, PowerChangedEvent args)
|
||||||
|
{
|
||||||
|
if (TryComp<AtmosDeviceComponent>(uid, out var atmosDeviceComponent))
|
||||||
|
{
|
||||||
|
if (!args.Powered)
|
||||||
|
{
|
||||||
|
if (atmosDeviceComponent.JoinedGrid != null)
|
||||||
|
{
|
||||||
|
_atmosDeviceSystem.LeaveAtmosphere(atmosDeviceComponent);
|
||||||
|
component.TileGas = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (args.Powered)
|
||||||
|
{
|
||||||
|
if (atmosDeviceComponent.JoinedGrid == null)
|
||||||
|
{
|
||||||
|
_atmosDeviceSystem.JoinAtmosphere(atmosDeviceComponent);
|
||||||
|
var air = _atmosphereSystem.GetContainingMixture(uid, true);
|
||||||
|
component.TileGas = air;
|
||||||
|
}
|
||||||
|
|
||||||
|
Alert(uid, component.LastAlarmState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnFireEvent(EntityUid uid, AtmosMonitorComponent component, ref TileFireEvent args)
|
||||||
|
{
|
||||||
|
if (!this.IsPowered(uid, EntityManager))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// if we're monitoring for atmos fire, then we make it similar to a smoke detector
|
||||||
|
// and just outright trigger a danger event
|
||||||
|
//
|
||||||
|
// somebody else can reset it :sunglasses:
|
||||||
|
if (component.MonitorFire
|
||||||
|
&& component.LastAlarmState != AtmosMonitorAlarmType.Danger)
|
||||||
|
{
|
||||||
|
component.TrippedThresholds.Add(AtmosMonitorThresholdType.Temperature);
|
||||||
|
Alert(uid, AtmosMonitorAlarmType.Danger, null, component); // technically???
|
||||||
|
}
|
||||||
|
|
||||||
|
// only monitor state elevation so that stuff gets alarmed quicker during a fire,
|
||||||
|
// let the atmos update loop handle when temperature starts to reach different
|
||||||
|
// thresholds and different states than normal -> warning -> danger
|
||||||
|
if (component.TemperatureThreshold != null
|
||||||
|
&& component.TemperatureThreshold.CheckThreshold(args.Temperature, out var temperatureState)
|
||||||
|
&& temperatureState > component.LastAlarmState)
|
||||||
|
{
|
||||||
|
component.TrippedThresholds.Add(AtmosMonitorThresholdType.Temperature);
|
||||||
|
Alert(uid, AtmosMonitorAlarmType.Danger, null, component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAtmosUpdate(EntityUid uid, AtmosMonitorComponent component, AtmosDeviceUpdateEvent args)
|
||||||
|
{
|
||||||
|
if (!this.IsPowered(uid, EntityManager))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// can't hurt
|
||||||
|
// (in case something is making AtmosDeviceUpdateEvents
|
||||||
|
// outside the typical device loop)
|
||||||
|
if (!TryComp<AtmosDeviceComponent>(uid, out var atmosDeviceComponent)
|
||||||
|
|| atmosDeviceComponent.JoinedGrid == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// if we're not monitoring atmos, don't bother
|
||||||
|
if (component.TemperatureThreshold == null
|
||||||
|
&& component.PressureThreshold == null
|
||||||
|
&& component.GasThresholds == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
UpdateState(uid, component.TileGas, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update checks the current air if it exceeds thresholds of
|
||||||
|
// any kind.
|
||||||
|
//
|
||||||
|
// If any threshold exceeds the other, that threshold
|
||||||
|
// immediately replaces the current recorded state.
|
||||||
|
//
|
||||||
|
// If the threshold does not match the current state
|
||||||
|
// of the monitor, it is set in the Alert call.
|
||||||
|
private void UpdateState(EntityUid uid, GasMixture? air, AtmosMonitorComponent? monitor = null)
|
||||||
|
{
|
||||||
|
if (air == null) return;
|
||||||
|
|
||||||
|
if (!Resolve(uid, ref monitor)) return;
|
||||||
|
|
||||||
|
AtmosMonitorAlarmType state = AtmosMonitorAlarmType.Normal;
|
||||||
|
HashSet<AtmosMonitorThresholdType> alarmTypes = new(monitor.TrippedThresholds);
|
||||||
|
|
||||||
|
if (monitor.TemperatureThreshold != null
|
||||||
|
&& monitor.TemperatureThreshold.CheckThreshold(air.Temperature, out var temperatureState))
|
||||||
|
{
|
||||||
|
if (temperatureState > state)
|
||||||
|
{
|
||||||
|
state = temperatureState;
|
||||||
|
alarmTypes.Add(AtmosMonitorThresholdType.Temperature);
|
||||||
|
}
|
||||||
|
else if (temperatureState == AtmosMonitorAlarmType.Normal)
|
||||||
|
{
|
||||||
|
alarmTypes.Remove(AtmosMonitorThresholdType.Temperature);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (monitor.PressureThreshold != null
|
||||||
|
&& monitor.PressureThreshold.CheckThreshold(air.Pressure, out var pressureState)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (pressureState > state)
|
||||||
|
{
|
||||||
|
state = pressureState;
|
||||||
|
alarmTypes.Add(AtmosMonitorThresholdType.Pressure);
|
||||||
|
}
|
||||||
|
else if (pressureState == AtmosMonitorAlarmType.Normal)
|
||||||
|
{
|
||||||
|
alarmTypes.Remove(AtmosMonitorThresholdType.Pressure);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (monitor.GasThresholds != null)
|
||||||
|
{
|
||||||
|
var tripped = false;
|
||||||
|
foreach (var (gas, threshold) in monitor.GasThresholds)
|
||||||
|
{
|
||||||
|
var gasRatio = air.GetMoles(gas) / air.TotalMoles;
|
||||||
|
if (threshold.CheckThreshold(gasRatio, out var gasState)
|
||||||
|
&& gasState > state)
|
||||||
|
{
|
||||||
|
state = gasState;
|
||||||
|
tripped = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tripped)
|
||||||
|
{
|
||||||
|
alarmTypes.Add(AtmosMonitorThresholdType.Gas);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
alarmTypes.Remove(AtmosMonitorThresholdType.Gas);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the state of the current air doesn't match the last alarm state,
|
||||||
|
// we update the state
|
||||||
|
if (state != monitor.LastAlarmState || !alarmTypes.SetEquals(monitor.TrippedThresholds))
|
||||||
|
{
|
||||||
|
Alert(uid, state, alarmTypes, monitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Alerts the network that the state of a monitor has changed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="state">The alarm state to set this monitor to.</param>
|
||||||
|
/// <param name="alarms">The alarms that caused this alarm state.</param>
|
||||||
|
public void Alert(EntityUid uid, AtmosMonitorAlarmType state, HashSet<AtmosMonitorThresholdType>? alarms = null, AtmosMonitorComponent? monitor = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref monitor)) return;
|
||||||
|
|
||||||
|
monitor.LastAlarmState = state;
|
||||||
|
monitor.TrippedThresholds = alarms ?? monitor.TrippedThresholds;
|
||||||
|
|
||||||
|
BroadcastAlertPacket(monitor);
|
||||||
|
|
||||||
|
// TODO: Central system that grabs *all* alarms from wired network
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resets a single monitor's alarm.
|
||||||
|
/// </summary>
|
||||||
|
private void Reset(EntityUid uid)
|
||||||
|
{
|
||||||
|
Alert(uid, AtmosMonitorAlarmType.Normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Broadcasts an alert packet to all devices on the network,
|
||||||
|
/// which consists of the current alarm types,
|
||||||
|
/// the highest alarm currently cached by this monitor,
|
||||||
|
/// and the current alarm state of the monitor (so other
|
||||||
|
/// alarms can sync to it).
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Alarmables use the highest alarm to ensure that a monitor's
|
||||||
|
/// state doesn't override if the alarm is lower. The state
|
||||||
|
/// is synced between monitors the moment a monitor sends out an alarm,
|
||||||
|
/// or if it is explicitly synced (see ResetAll/Sync).
|
||||||
|
/// </remarks>
|
||||||
|
private void BroadcastAlertPacket(AtmosMonitorComponent monitor, TagComponent? tags = null)
|
||||||
|
{
|
||||||
|
if (!monitor.NetEnabled) return;
|
||||||
|
|
||||||
|
if (!Resolve(monitor.Owner, ref tags))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload = new NetworkPayload
|
||||||
|
{
|
||||||
|
[DeviceNetworkConstants.Command] = AtmosAlarmableSystem.AlertCmd,
|
||||||
|
[DeviceNetworkConstants.CmdSetState] = monitor.LastAlarmState,
|
||||||
|
[AtmosAlarmableSystem.AlertSource] = tags.Tags,
|
||||||
|
[AtmosAlarmableSystem.AlertTypes] = monitor.TrippedThresholds
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var addr in monitor.RegisteredDevices)
|
||||||
|
{
|
||||||
|
_deviceNetSystem.QueuePacket(monitor.Owner, addr, payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set a monitor's threshold.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">The type of threshold to change.</param>
|
||||||
|
/// <param name="threshold">Threshold data.</param>
|
||||||
|
/// <param name="gas">Gas, if applicable.</param>
|
||||||
|
public void SetThreshold(EntityUid uid, AtmosMonitorThresholdType type, AtmosAlarmThreshold threshold, Gas? gas = null, AtmosMonitorComponent? monitor = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref monitor)) return;
|
||||||
|
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case AtmosMonitorThresholdType.Pressure:
|
||||||
|
monitor.PressureThreshold = threshold;
|
||||||
|
break;
|
||||||
|
case AtmosMonitorThresholdType.Temperature:
|
||||||
|
monitor.TemperatureThreshold = threshold;
|
||||||
|
break;
|
||||||
|
case AtmosMonitorThresholdType.Gas:
|
||||||
|
if (gas == null || monitor.GasThresholds == null) return;
|
||||||
|
monitor.GasThresholds[(Gas) gas] = threshold;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,60 +9,59 @@ using Content.Shared.Interaction;
|
|||||||
using Content.Shared.Emag.Systems;
|
using Content.Shared.Emag.Systems;
|
||||||
using Robust.Server.GameObjects;
|
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!;
|
SubscribeLocalEvent<FireAlarmComponent, InteractHandEvent>(OnInteractHand);
|
||||||
[Dependency] private readonly AtmosAlarmableSystem _atmosAlarmable = default!;
|
SubscribeLocalEvent<FireAlarmComponent, DeviceListUpdateEvent>(OnDeviceListSync);
|
||||||
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
|
SubscribeLocalEvent<FireAlarmComponent, GotEmaggedEvent>(OnEmagged);
|
||||||
|
}
|
||||||
|
|
||||||
public override void Initialize()
|
private void OnDeviceListSync(EntityUid uid, FireAlarmComponent component, DeviceListUpdateEvent args)
|
||||||
|
{
|
||||||
|
_atmosDevNet.Register(uid, null);
|
||||||
|
_atmosDevNet.Sync(uid, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnInteractHand(EntityUid uid, FireAlarmComponent component, InteractHandEvent args)
|
||||||
|
{
|
||||||
|
if (!_interactionSystem.InRangeUnobstructed(args.User, args.Target))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (this.IsPowered(uid, EntityManager))
|
||||||
{
|
{
|
||||||
SubscribeLocalEvent<FireAlarmComponent, InteractHandEvent>(OnInteractHand);
|
if (!_atmosAlarmable.TryGetHighestAlert(uid, out var alarm))
|
||||||
SubscribeLocalEvent<FireAlarmComponent, DeviceListUpdateEvent>(OnDeviceListSync);
|
|
||||||
SubscribeLocalEvent<FireAlarmComponent, GotEmaggedEvent>(OnEmagged);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnDeviceListSync(EntityUid uid, FireAlarmComponent component, DeviceListUpdateEvent args)
|
|
||||||
{
|
|
||||||
_atmosDevNet.Register(uid, null);
|
|
||||||
_atmosDevNet.Sync(uid, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnInteractHand(EntityUid uid, FireAlarmComponent component, InteractHandEvent args)
|
|
||||||
{
|
|
||||||
if (!_interactionSystem.InRangeUnobstructed(args.User, args.Target))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (this.IsPowered(uid, EntityManager))
|
|
||||||
{
|
{
|
||||||
if (!_atmosAlarmable.TryGetHighestAlert(uid, out var alarm))
|
alarm = AtmosMonitorAlarmType.Normal;
|
||||||
{
|
}
|
||||||
alarm = AtmosMonitorAlarmType.Normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (alarm == AtmosMonitorAlarmType.Normal)
|
if (alarm == AtmosMonitorAlarmType.Normal)
|
||||||
{
|
{
|
||||||
_atmosAlarmable.ForceAlert(uid, AtmosMonitorAlarmType.Danger);
|
_atmosAlarmable.ForceAlert(uid, AtmosMonitorAlarmType.Danger);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_atmosAlarmable.ResetAllOnNetwork(uid);
|
_atmosAlarmable.ResetAllOnNetwork(uid);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void OnEmagged(EntityUid uid, FireAlarmComponent component, GotEmaggedEvent args)
|
private void OnEmagged(EntityUid uid, FireAlarmComponent component, GotEmaggedEvent args)
|
||||||
|
{
|
||||||
|
if (TryComp<AtmosMonitorComponent>(uid, out var atmosMonitor))
|
||||||
{
|
{
|
||||||
if (TryComp<AtmosMonitorComponent>(uid, out var atmosMonitor))
|
if (atmosMonitor?.MonitorFire == true)
|
||||||
{
|
{
|
||||||
if (atmosMonitor?.MonitorFire == true)
|
atmosMonitor.MonitorFire = false;
|
||||||
{
|
_atmosAlarmable.ForceAlert(uid, AtmosMonitorAlarmType.Emagged);
|
||||||
atmosMonitor.MonitorFire = false;
|
args.Handled = true;
|
||||||
_atmosAlarmable.ForceAlert(uid, AtmosMonitorAlarmType.Emagged);
|
|
||||||
args.Handled = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
270
Content.Shared/Atmos/Monitor/AtmosAlarmThreshold.cs
Normal file
270
Content.Shared/Atmos/Monitor/AtmosAlarmThreshold.cs
Normal file
@@ -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,
|
||||||
|
}
|
||||||
@@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +1,12 @@
|
|||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
namespace Content.Shared.Atmos.Monitor
|
namespace Content.Shared.Atmos.Monitor;
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public enum AtmosMonitorAlarmType : sbyte
|
||||||
{
|
{
|
||||||
[Serializable, NetSerializable]
|
Normal = 0,
|
||||||
public enum AtmosMonitorAlarmType : sbyte
|
Warning = 1,
|
||||||
{
|
Danger = 2, // 1 << 1 is the exact same thing and we're not really doing **bitmasking** are we?
|
||||||
Normal = 0,
|
Emagged = 3,
|
||||||
Warning = 1,
|
|
||||||
Danger = 2, // 1 << 1 is the exact same thing and we're not really doing **bitmasking** are we?
|
|
||||||
Emagged = 3,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,131 +1,130 @@
|
|||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
namespace Content.Shared.Atmos.Monitor.Components
|
namespace Content.Shared.Atmos.Monitor.Components;
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public enum SharedAirAlarmInterfaceKey
|
||||||
{
|
{
|
||||||
[Serializable, NetSerializable]
|
Key
|
||||||
public enum SharedAirAlarmInterfaceKey
|
}
|
||||||
|
|
||||||
|
[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<string, IAtmosDeviceData> 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 string Address { get; }
|
||||||
public enum AirAlarmMode
|
public int DeviceCount { get; }
|
||||||
|
public float PressureAverage { get; }
|
||||||
|
public float TemperatureAverage { get; }
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, IAtmosDeviceData> 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,
|
Tab = tab;
|
||||||
Filtering,
|
|
||||||
Fill,
|
|
||||||
Panic,
|
|
||||||
Replace
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Serializable, NetSerializable]
|
public AirAlarmTab Tab { get; }
|
||||||
public enum AirAlarmWireStatus
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed class AirAlarmResyncAllDevicesMessage : BoundUserInterfaceMessage
|
||||||
|
{}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed class AirAlarmUpdateAlarmModeMessage : BoundUserInterfaceMessage
|
||||||
|
{
|
||||||
|
public AirAlarmMode Mode { get; }
|
||||||
|
|
||||||
|
public AirAlarmUpdateAlarmModeMessage(AirAlarmMode mode)
|
||||||
{
|
{
|
||||||
Power,
|
Mode = mode;
|
||||||
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<string, IAtmosDeviceData> 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; }
|
|
||||||
/// <summary>
|
|
||||||
/// 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.
|
|
||||||
/// </summary>
|
|
||||||
public Dictionary<string, IAtmosDeviceData> 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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[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
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
namespace Content.Shared.Atmos.Monitor.Components
|
namespace Content.Shared.Atmos.Monitor.Components;
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public enum FireAlarmWireStatus
|
||||||
{
|
{
|
||||||
[Serializable, NetSerializable]
|
Power,
|
||||||
public enum FireAlarmWireStatus
|
Alarm
|
||||||
{
|
|
||||||
Power,
|
|
||||||
Alarm
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user