Merge pull request #10721 from vulppine/air-alarm-fixup
Air sensors & air alarm fixup
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
using Content.Shared.Atmos.Monitor;
|
||||
|
||||
namespace Content.Client.Atmos.Monitor;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed class AtmosAlarmableVisualsComponent : Component
|
||||
{
|
||||
[DataField("layerMap")]
|
||||
public string LayerMap { get; } = string.Empty;
|
||||
|
||||
[DataField("alarmStates")]
|
||||
public readonly Dictionary<AtmosAlarmType, string> AlarmStates = new();
|
||||
|
||||
[DataField("hideOnDepowered")]
|
||||
public readonly List<string>? HideOnDepowered;
|
||||
|
||||
// eh...
|
||||
[DataField("setOnDepowered")]
|
||||
public readonly Dictionary<string, string>? SetOnDepowered;
|
||||
}
|
||||
52
Content.Client/Atmos/Monitor/AtmosAlarmableVisualsSystem.cs
Normal file
52
Content.Client/Atmos/Monitor/AtmosAlarmableVisualsSystem.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Shared.Atmos.Monitor;
|
||||
using Content.Shared.Power;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Content.Client.Atmos.Monitor;
|
||||
|
||||
public sealed class AtmosAlarmableVisualsSystem : VisualizerSystem<AtmosAlarmableVisualsComponent>
|
||||
{
|
||||
protected override void OnAppearanceChange(EntityUid uid, AtmosAlarmableVisualsComponent component, ref AppearanceChangeEvent args)
|
||||
{
|
||||
if (args.Sprite == null || !args.Sprite.LayerMapTryGet(component.LayerMap, out var layer))
|
||||
return;
|
||||
|
||||
if (!args.AppearanceData.TryGetValue(PowerDeviceVisuals.Powered, out var poweredObject) ||
|
||||
poweredObject is not bool powered)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (component.HideOnDepowered != null)
|
||||
{
|
||||
foreach (var visLayer in component.HideOnDepowered)
|
||||
{
|
||||
if (args.Sprite.LayerMapTryGet(visLayer, out int powerVisibilityLayer))
|
||||
args.Sprite.LayerSetVisible(powerVisibilityLayer, powered);
|
||||
}
|
||||
}
|
||||
|
||||
if (component.SetOnDepowered != null && !powered)
|
||||
{
|
||||
foreach (var (setLayer, powerState) in component.SetOnDepowered)
|
||||
{
|
||||
if (args.Sprite.LayerMapTryGet(setLayer, out int setStateLayer))
|
||||
args.Sprite.LayerSetState(setStateLayer, new RSI.StateId(powerState));
|
||||
}
|
||||
}
|
||||
|
||||
if (args.AppearanceData.TryGetValue(AtmosMonitorVisuals.AlarmType, out var alarmTypeObject)
|
||||
&& alarmTypeObject is AtmosAlarmType alarmType
|
||||
&& powered
|
||||
&& component.AlarmStates.TryGetValue(alarmType, out var state))
|
||||
{
|
||||
args.Sprite.LayerSetState(layer, new RSI.StateId(state));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Shared.Atmos.Monitor;
|
||||
using Content.Shared.Power;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Content.Client.Atmos.Monitor
|
||||
{
|
||||
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)
|
||||
{
|
||||
base.InitializeEntity(entity);
|
||||
|
||||
IoCManager.InjectDependencies(this);
|
||||
}
|
||||
|
||||
[Obsolete("Subscribe to AppearanceChangeEvent instead.")]
|
||||
public override void OnChangeData(AppearanceComponent component)
|
||||
{
|
||||
if (!_entityManager.TryGetComponent<SpriteComponent>(component.Owner, out var sprite))
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,14 +6,15 @@ using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
|
||||
namespace Content.Client.Atmos.Monitor.UI
|
||||
namespace Content.Client.Atmos.Monitor.UI;
|
||||
|
||||
public sealed class AirAlarmBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
public sealed class AirAlarmBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
private AirAlarmWindow? _window;
|
||||
|
||||
public AirAlarmBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||
{}
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
@@ -21,7 +22,10 @@ namespace Content.Client.Atmos.Monitor.UI
|
||||
|
||||
_window = new AirAlarmWindow();
|
||||
|
||||
if (State != null) UpdateState(State);
|
||||
if (State != null)
|
||||
{
|
||||
UpdateState(State);
|
||||
}
|
||||
|
||||
_window.OpenCentered();
|
||||
|
||||
@@ -30,6 +34,7 @@ namespace Content.Client.Atmos.Monitor.UI
|
||||
_window.AtmosAlarmThresholdChanged += OnThresholdChanged;
|
||||
_window.AirAlarmModeChanged += OnAirAlarmModeChanged;
|
||||
_window.ResyncAllRequested += ResyncAllDevices;
|
||||
_window.AirAlarmTabChange += OnTabChanged;
|
||||
}
|
||||
|
||||
private void ResyncAllDevices()
|
||||
@@ -47,34 +52,26 @@ namespace Content.Client.Atmos.Monitor.UI
|
||||
SendMessage(new AirAlarmUpdateAlarmModeMessage(mode));
|
||||
}
|
||||
|
||||
private void OnThresholdChanged(AtmosMonitorThresholdType type, AtmosAlarmThreshold threshold, Gas? gas = null)
|
||||
private void OnThresholdChanged(string address, AtmosMonitorThresholdType type, AtmosAlarmThreshold threshold, Gas? gas = null)
|
||||
{
|
||||
SendMessage(new AirAlarmUpdateAlarmThresholdMessage(type, threshold, gas));
|
||||
SendMessage(new AirAlarmUpdateAlarmThresholdMessage(address, type, threshold, gas));
|
||||
}
|
||||
|
||||
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
|
||||
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)
|
||||
{
|
||||
if (_window == null)
|
||||
return;
|
||||
|
||||
switch (message)
|
||||
{
|
||||
case AirAlarmSetAddressMessage addrMsg:
|
||||
_window.SetAddress(addrMsg.Address);
|
||||
break;
|
||||
case AirAlarmUpdateDeviceDataMessage deviceMsg:
|
||||
_window.UpdateDeviceData(deviceMsg.Address, deviceMsg.Data);
|
||||
break;
|
||||
case AirAlarmUpdateAlarmModeMessage alarmMsg:
|
||||
_window.UpdateModeSelector(alarmMsg.Mode);
|
||||
break;
|
||||
case AirAlarmUpdateAlarmThresholdMessage thresholdMsg:
|
||||
_window.UpdateThreshold(ref thresholdMsg);
|
||||
break;
|
||||
case AirAlarmUpdateAirDataMessage airDataMsg:
|
||||
_window.UpdateGasData(ref airDataMsg.AirData);
|
||||
break;
|
||||
}
|
||||
|
||||
_window.UpdateState(cast);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
@@ -83,5 +80,4 @@ namespace Content.Client.Atmos.Monitor.UI
|
||||
|
||||
if (disposing) _window?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,20 +18,22 @@
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
<!-- Right column (address, device total) -->
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<BoxContainer Orientation="Horizontal" Margin="0 0 2 0" HorizontalExpand="True">
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
|
||||
<Label Text="{Loc 'air-alarm-ui-window-address-label'}" />
|
||||
<Label Text="{Loc 'air-alarm-ui-window-device-count-label'}" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
|
||||
<Label Name="CDeviceAddress" HorizontalAlignment="Right" />
|
||||
<Label Name="CDeviceTotal" HorizontalAlignment="Right" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
|
||||
<Button Name="CResyncButton" Text="{Loc 'air-alarm-ui-window-resync-devices-label'}" HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
<!-- Gas/Device Data -->
|
||||
<TabContainer Name="CTabContainer" VerticalExpand="True" Margin="0 0 0 2">
|
||||
<!-- Gas readout -->
|
||||
<ScrollContainer VerticalExpand="True">
|
||||
<BoxContainer Name="CGasContainer" Orientation="Vertical" VerticalExpand="True" Margin="2 2 2 2" />
|
||||
</ScrollContainer>
|
||||
<!-- Vent devices -->
|
||||
<ScrollContainer VerticalExpand="True">
|
||||
<BoxContainer Name="CVentContainer" Orientation="Vertical"/>
|
||||
@@ -40,32 +42,11 @@
|
||||
<ScrollContainer VerticalExpand="True">
|
||||
<BoxContainer Name="CScrubberContainer" Orientation="Vertical"/>
|
||||
</ScrollContainer>
|
||||
<!-- Alarm thresholds -->
|
||||
<!-- Sensors -->
|
||||
<ScrollContainer VerticalExpand="True">
|
||||
<BoxContainer Orientation="Vertical" VerticalExpand="True">
|
||||
<BoxContainer Name="CPressureThreshold" Orientation="Vertical" Margin="2 0 2 0" />
|
||||
<BoxContainer Name="CTemperatureThreshold" Orientation="Vertical" Margin="2 0 2 0" />
|
||||
<Collapsible Orientation="Vertical">
|
||||
<CollapsibleHeading Title="Gases" />
|
||||
<CollapsibleBody Margin="4 2 4 2">
|
||||
<BoxContainer Name="CGasThresholdContainer" Orientation="Vertical" Margin="2 0 2 0" />
|
||||
</CollapsibleBody>
|
||||
</Collapsible>
|
||||
</BoxContainer>
|
||||
<BoxContainer Name="CSensorContainer" Orientation="Vertical"/>
|
||||
</ScrollContainer>
|
||||
</TabContainer>
|
||||
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="0 0 0 2">
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<Label Text="{Loc 'air-alarm-ui-window-address-label'}" HorizontalExpand="True"/>
|
||||
<Label Name="CDeviceAddress" HorizontalAlignment="Right" HorizontalExpand="True" Margin="0 0 4 0" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<Label Text="{Loc 'air-alarm-ui-window-device-count-label'}" HorizontalExpand="True"/>
|
||||
<Label Name="CDeviceTotal" HorizontalAlignment="Right" HorizontalExpand="True" Margin="0 0 4 0" />
|
||||
</BoxContainer>
|
||||
<Button Name="CResyncButton" Text="{Loc 'air-alarm-ui-window-resync-devices-label'}" HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
<!-- Mode buttons -->
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Text="{Loc 'air-alarm-ui-window-mode-label'}" Margin="0 0 2 0" />
|
||||
|
||||
@@ -13,16 +13,17 @@ using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Localization;
|
||||
|
||||
namespace Content.Client.Atmos.Monitor.UI
|
||||
namespace Content.Client.Atmos.Monitor.UI;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class AirAlarmWindow : DefaultWindow
|
||||
{
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class AirAlarmWindow : DefaultWindow
|
||||
{
|
||||
public event Action<string, IAtmosDeviceData>? AtmosDeviceDataChanged;
|
||||
public event Action<AtmosMonitorThresholdType, AtmosAlarmThreshold, Gas?>? AtmosAlarmThresholdChanged;
|
||||
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;
|
||||
@@ -31,20 +32,14 @@ namespace Content.Client.Atmos.Monitor.UI
|
||||
private RichTextLabel _alarmState => CStatusLabel;
|
||||
|
||||
private TabContainer _tabContainer => CTabContainer;
|
||||
private BoxContainer _gasReadout => CGasContainer;
|
||||
private BoxContainer _ventDevices => CVentContainer;
|
||||
private BoxContainer _scrubberDevices => CScrubberContainer;
|
||||
private BoxContainer _pressureThreshold => CPressureThreshold;
|
||||
private BoxContainer _temperatureThreshold => CTemperatureThreshold;
|
||||
private BoxContainer _gasThreshold => CGasThresholdContainer;
|
||||
|
||||
private Dictionary<string, PumpControl> _pumps = new();
|
||||
private Dictionary<string, ScrubberControl> _scrubbers = new();
|
||||
private Dictionary<string, SensorInfo> _sensors = new();
|
||||
private Button _resyncDevices => CResyncButton;
|
||||
|
||||
private ThresholdControl? _pressureThresholdControl;
|
||||
private ThresholdControl? _temperatureThresholdControl;
|
||||
private Dictionary<Gas, ThresholdControl> _gasThresholdControls = new();
|
||||
|
||||
private Dictionary<Gas, Label> _gasLabels = new();
|
||||
|
||||
@@ -55,7 +50,9 @@ namespace Content.Client.Atmos.Monitor.UI
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
foreach (var mode in Enum.GetValues<AirAlarmMode>())
|
||||
{
|
||||
_modes.AddItem($"{mode}", (int) mode);
|
||||
}
|
||||
|
||||
_modes.OnItemSelected += args =>
|
||||
{
|
||||
@@ -63,17 +60,14 @@ namespace Content.Client.Atmos.Monitor.UI
|
||||
AirAlarmModeChanged!.Invoke((AirAlarmMode) args.Id);
|
||||
};
|
||||
|
||||
foreach (var gas in Enum.GetValues<Gas>())
|
||||
{
|
||||
var gasLabel = new Label();
|
||||
_gasReadout.AddChild(gasLabel);
|
||||
_gasLabels.Add(gas, gasLabel);
|
||||
}
|
||||
_tabContainer.SetTabTitle(0, Loc.GetString("air-alarm-ui-window-tab-vents"));
|
||||
_tabContainer.SetTabTitle(1, Loc.GetString("air-alarm-ui-window-tab-scrubbers"));
|
||||
_tabContainer.SetTabTitle(2, Loc.GetString("air-alarm-ui-window-tab-sensors"));
|
||||
|
||||
_tabContainer.SetTabTitle(0, Loc.GetString("air-alarm-ui-window-tab-gas"));
|
||||
_tabContainer.SetTabTitle(1, Loc.GetString("air-alarm-ui-window-tab-vents"));
|
||||
_tabContainer.SetTabTitle(2, Loc.GetString("air-alarm-ui-window-tab-scrubbers"));
|
||||
_tabContainer.SetTabTitle(3, Loc.GetString("air-alarm-ui-window-tab-thresholds"));
|
||||
_tabContainer.OnTabChanged += idx =>
|
||||
{
|
||||
AirAlarmTabChange!((AirAlarmTab) idx);
|
||||
};
|
||||
|
||||
_resyncDevices.OnPressed += _ =>
|
||||
{
|
||||
@@ -81,24 +75,26 @@ namespace Content.Client.Atmos.Monitor.UI
|
||||
_pumps.Clear();
|
||||
_scrubberDevices.RemoveAllChildren();
|
||||
_scrubbers.Clear();
|
||||
CSensorContainer.RemoveAllChildren();
|
||||
_sensors.Clear();
|
||||
ResyncAllRequested!.Invoke();
|
||||
};
|
||||
}
|
||||
|
||||
public void SetAddress(string address)
|
||||
public void UpdateState(AirAlarmUIState state)
|
||||
{
|
||||
_address.Text = address;
|
||||
_address.Text = state.Address;
|
||||
_deviceTotal.Text = $"{state.DeviceCount}";
|
||||
_pressure.SetMarkup(Loc.GetString("air-alarm-ui-window-pressure", ("pressure", $"{state.PressureAverage:0.##}")));
|
||||
_temperature.SetMarkup(Loc.GetString("air-alarm-ui-window-temperature", ("tempC", $"{TemperatureHelpers.KelvinToCelsius(state.TemperatureAverage):0.#}"), ("temperature", $"{state.TemperatureAverage:0.##}")));
|
||||
_alarmState.SetMarkup(Loc.GetString("air-alarm-ui-window-alarm-state", ("state", $"{state.AlarmType}")));
|
||||
UpdateModeSelector(state.Mode);
|
||||
foreach (var (addr, dev) in state.DeviceData)
|
||||
{
|
||||
UpdateDeviceData(addr, dev);
|
||||
}
|
||||
|
||||
public void UpdateGasData(ref AirAlarmAirData state)
|
||||
{
|
||||
_pressure.SetMarkup(Loc.GetString("air-alarm-ui-window-pressure", ("pressure", $"{state.Pressure:0.##}")));
|
||||
_temperature.SetMarkup(Loc.GetString("air-alarm-ui-window-temperature", ("tempC", $"{TemperatureHelpers.KelvinToCelsius(state.Temperature ?? 0):0.#}"), ("temperature", $"{state.Temperature:0.##}")));
|
||||
_alarmState.SetMarkup(Loc.GetString("air-alarm-ui-window-alarm-state", ("state", $"{state.AlarmState}")));
|
||||
|
||||
if (state.Gases != null)
|
||||
foreach (var (gas, amount) in state.Gases)
|
||||
_gasLabels[gas].Text = Loc.GetString("air-alarm-ui-gases", ("gas", $"{gas}"), ("amount", $"{amount:0.####}"), ("percentage", $"{(amount / state.TotalMoles):0.##}"));
|
||||
_tabContainer.CurrentTab = (int) state.Tab;
|
||||
}
|
||||
|
||||
public void UpdateModeSelector(AirAlarmMode mode)
|
||||
@@ -138,55 +134,20 @@ namespace Content.Client.Atmos.Monitor.UI
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
_deviceTotal.Text = $"{_pumps.Count + _scrubbers.Count}";
|
||||
}
|
||||
|
||||
public void UpdateThreshold(ref AirAlarmUpdateAlarmThresholdMessage message)
|
||||
case AtmosSensorData sensor:
|
||||
if (!_sensors.TryGetValue(addr, out var sensorControl))
|
||||
{
|
||||
switch (message.Type)
|
||||
{
|
||||
case AtmosMonitorThresholdType.Pressure:
|
||||
if (_pressureThresholdControl == null)
|
||||
{
|
||||
_pressureThresholdControl = new ThresholdControl(Loc.GetString("air-alarm-ui-thresholds-pressure-title"), message.Threshold, message.Type);
|
||||
_pressureThresholdControl.ThresholdDataChanged += AtmosAlarmThresholdChanged!.Invoke;
|
||||
_pressureThreshold.AddChild(_pressureThresholdControl);
|
||||
var control = new SensorInfo(sensor, addr);
|
||||
control.OnThresholdUpdate += AtmosAlarmThresholdChanged;
|
||||
_sensors.Add(addr, control);
|
||||
CSensorContainer.AddChild(control);
|
||||
}
|
||||
else
|
||||
{
|
||||
_pressureThresholdControl.UpdateThresholdData(message.Threshold);
|
||||
sensorControl.ChangeData(sensor);
|
||||
}
|
||||
|
||||
break;
|
||||
case AtmosMonitorThresholdType.Temperature:
|
||||
if (_temperatureThresholdControl == null)
|
||||
{
|
||||
_temperatureThresholdControl = new ThresholdControl(Loc.GetString("air-alarm-ui-thresholds-temperature-title"), message.Threshold, message.Type);
|
||||
_temperatureThresholdControl.ThresholdDataChanged += AtmosAlarmThresholdChanged!.Invoke;
|
||||
_temperatureThreshold.AddChild(_temperatureThresholdControl);
|
||||
}
|
||||
else
|
||||
{
|
||||
_temperatureThresholdControl.UpdateThresholdData(message.Threshold);
|
||||
}
|
||||
|
||||
break;
|
||||
case AtmosMonitorThresholdType.Gas:
|
||||
if (_gasThresholdControls.TryGetValue((Gas) message.Gas!, out var control))
|
||||
{
|
||||
control.UpdateThresholdData(message.Threshold);
|
||||
break;
|
||||
}
|
||||
|
||||
var gasThreshold = new ThresholdControl(Loc.GetString($"air-alarm-ui-thresholds-gas-title", ("gas", $"{(Gas) message.Gas!}")), message.Threshold, AtmosMonitorThresholdType.Gas, (Gas) message.Gas!, 100);
|
||||
gasThreshold.ThresholdDataChanged += AtmosAlarmThresholdChanged!.Invoke;
|
||||
_gasThresholdControls.Add((Gas) message.Gas!, gasThreshold);
|
||||
_gasThreshold.AddChild(gasThreshold);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,11 +8,11 @@ using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Localization;
|
||||
|
||||
namespace Content.Client.Atmos.Monitor.UI.Widgets
|
||||
namespace Content.Client.Atmos.Monitor.UI.Widgets;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class PumpControl : BoxContainer
|
||||
{
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class PumpControl : BoxContainer
|
||||
{
|
||||
private GasVentPumpData _data;
|
||||
private string _address;
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace Content.Client.Atmos.Monitor.UI.Widgets
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
this.Name = address;
|
||||
Name = address;
|
||||
|
||||
_data = data;
|
||||
_address = address;
|
||||
@@ -60,7 +60,9 @@ namespace Content.Client.Atmos.Monitor.UI.Widgets
|
||||
_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 =>
|
||||
@@ -71,7 +73,9 @@ namespace Content.Client.Atmos.Monitor.UI.Widgets
|
||||
};
|
||||
|
||||
foreach (var value in Enum.GetValues<VentPressureBound>())
|
||||
{
|
||||
_pressureCheck.AddItem(Loc.GetString($"{value}"), (int) value);
|
||||
}
|
||||
|
||||
_pressureCheck.SelectId((int) _data.PressureChecks);
|
||||
_pressureCheck.OnItemSelected += args =>
|
||||
@@ -99,5 +103,4 @@ namespace Content.Client.Atmos.Monitor.UI.Widgets
|
||||
_data.InternalPressureBound = data.InternalPressureBound;
|
||||
_internalBound.Value = _data.InternalPressureBound;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,11 +11,11 @@ using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Localization;
|
||||
|
||||
namespace Content.Client.Atmos.Monitor.UI.Widgets
|
||||
namespace Content.Client.Atmos.Monitor.UI.Widgets;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class ScrubberControl : BoxContainer
|
||||
{
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class ScrubberControl : BoxContainer
|
||||
{
|
||||
private GasVentScrubberData _data;
|
||||
private string _address;
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace Content.Client.Atmos.Monitor.UI.Widgets
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
this.Name = address;
|
||||
Name = address;
|
||||
|
||||
_data = data;
|
||||
_address = address;
|
||||
@@ -64,7 +64,9 @@ namespace Content.Client.Atmos.Monitor.UI.Widgets
|
||||
_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 =>
|
||||
@@ -116,6 +118,7 @@ namespace Content.Client.Atmos.Monitor.UI.Widgets
|
||||
var intersect = _data.FilterGases.Intersect(data.FilterGases);
|
||||
|
||||
foreach (var value in Enum.GetValues<Gas>())
|
||||
{
|
||||
if (!intersect.Contains(value))
|
||||
_gasControls[value].Pressed = false;
|
||||
}
|
||||
|
||||
42
Content.Client/Atmos/Monitor/UI/Widgets/SensorInfo.xaml
Normal file
42
Content.Client/Atmos/Monitor/UI/Widgets/SensorInfo.xaml
Normal file
@@ -0,0 +1,42 @@
|
||||
<BoxContainer xmlns="https://spacestation14.io" HorizontalExpand="True">
|
||||
<Collapsible Orientation="Vertical">
|
||||
<CollapsibleHeading Name="SensorAddress" />
|
||||
<CollapsibleBody Margin="2">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<BoxContainer Orientation="Vertical" Margin="0 0 2 0" HorizontalExpand="True">
|
||||
<Label Text="{Loc 'air-alarm-ui-window-pressure-label'}" />
|
||||
<Label Text="{Loc 'air-alarm-ui-window-temperature-label'}" />
|
||||
<Label Text="{Loc 'air-alarm-ui-window-alarm-state-label'}" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
|
||||
<RichTextLabel Name="PressureLabel" />
|
||||
<RichTextLabel Name="TemperatureLabel" />
|
||||
<RichTextLabel Name="AlarmStateLabel" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
<Collapsible Orientation="Vertical" Margin="2">
|
||||
<CollapsibleHeading Title="{Loc 'air-alarm-ui-sensor-gases'}" />
|
||||
<CollapsibleBody>
|
||||
<BoxContainer Name="GasContainer" Orientation="Vertical" Margin="2" />
|
||||
</CollapsibleBody>
|
||||
</Collapsible>
|
||||
<Collapsible Orientation="Vertical" Margin="2">
|
||||
<CollapsibleHeading Title="{Loc 'air-alarm-ui-sensor-thresholds'}" />
|
||||
<CollapsibleBody>
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<Control Name="PressureThresholdContainer" Margin="2 0 2 0" />
|
||||
<Control Name="TemperatureThresholdContainer" Margin="2 0 2 0" />
|
||||
<Collapsible Orientation="Vertical" Margin="2 0 2 0">
|
||||
<CollapsibleHeading Title="{Loc 'air-alarm-ui-sensor-gases'}" />
|
||||
<CollapsibleBody Margin="4 2 4 2">
|
||||
<BoxContainer Name="GasThresholds" Orientation="Vertical" Margin="2 0 2 0" />
|
||||
</CollapsibleBody>
|
||||
</Collapsible>
|
||||
</BoxContainer>
|
||||
</CollapsibleBody>
|
||||
</Collapsible>
|
||||
</BoxContainer>
|
||||
</CollapsibleBody>
|
||||
</Collapsible>
|
||||
</BoxContainer>
|
||||
105
Content.Client/Atmos/Monitor/UI/Widgets/SensorInfo.xaml.cs
Normal file
105
Content.Client/Atmos/Monitor/UI/Widgets/SensorInfo.xaml.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
using Content.Client.Message;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Monitor;
|
||||
using Content.Shared.Temperature;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client.Atmos.Monitor.UI.Widgets;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class SensorInfo : BoxContainer
|
||||
{
|
||||
public Action<string, AtmosMonitorThresholdType, AtmosAlarmThreshold, Gas?>? OnThresholdUpdate;
|
||||
private string _address;
|
||||
|
||||
private ThresholdControl _pressureThreshold;
|
||||
private ThresholdControl _temperatureThreshold;
|
||||
private Dictionary<Gas, ThresholdControl> _gasThresholds = new();
|
||||
private Dictionary<Gas, Label> _gasLabels = new();
|
||||
|
||||
public SensorInfo(AtmosSensorData data, string address)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
_address = address;
|
||||
|
||||
SensorAddress.Title = $"{address} : {data.AlarmState}";
|
||||
|
||||
PressureLabel.SetMarkup(Loc.GetString("air-alarm-ui-window-pressure", ("pressure", $"{data.Pressure:0.##}")));
|
||||
TemperatureLabel.SetMarkup(Loc.GetString("air-alarm-ui-window-temperature", ("tempC", $"{TemperatureHelpers.KelvinToCelsius(data.Temperature):0.#}"), ("temperature", $"{data.Temperature:0.##}")));
|
||||
AlarmStateLabel.SetMarkup(Loc.GetString("air-alarm-ui-window-alarm-state", ("state", $"{data.AlarmState}")));
|
||||
|
||||
foreach (var (gas, amount) in data.Gases)
|
||||
{
|
||||
var label = new Label();
|
||||
label.Text = Loc.GetString("air-alarm-ui-gases", ("gas", $"{gas}"),
|
||||
("amount", $"{amount:0.####}"),
|
||||
("percentage", $"{(amount / data.TotalMoles):0.##}"));
|
||||
GasContainer.AddChild(label);
|
||||
_gasLabels.Add(gas, label);
|
||||
}
|
||||
|
||||
_pressureThreshold =
|
||||
new ThresholdControl(Loc.GetString("air-alarm-ui-thresholds-pressure-title"), data.PressureThreshold, AtmosMonitorThresholdType.Pressure);
|
||||
PressureThresholdContainer.AddChild(_pressureThreshold);
|
||||
_temperatureThreshold = new ThresholdControl(Loc.GetString("air-alarm-ui-thresholds-temperature-title"), data.TemperatureThreshold,
|
||||
AtmosMonitorThresholdType.Temperature);
|
||||
TemperatureThresholdContainer.AddChild(_temperatureThreshold);
|
||||
|
||||
_pressureThreshold.ThresholdDataChanged += (type, threshold, arg3) =>
|
||||
{
|
||||
OnThresholdUpdate!(_address, type, threshold, arg3);
|
||||
};
|
||||
|
||||
_temperatureThreshold.ThresholdDataChanged += (type, threshold, arg3) =>
|
||||
{
|
||||
OnThresholdUpdate!(_address, type, threshold, arg3);
|
||||
};
|
||||
|
||||
foreach (var (gas, threshold) in data.GasThresholds)
|
||||
{
|
||||
var gasThresholdControl = new ThresholdControl(Loc.GetString($"air-alarm-ui-thresholds-gas-title", ("gas", $"{gas}")), threshold, AtmosMonitorThresholdType.Gas, gas, 100);
|
||||
gasThresholdControl.ThresholdDataChanged += (type, threshold, arg3) =>
|
||||
{
|
||||
OnThresholdUpdate!(_address, type, threshold, arg3);
|
||||
};
|
||||
|
||||
_gasThresholds.Add(gas, gasThresholdControl);
|
||||
GasThresholds.AddChild(gasThresholdControl);
|
||||
}
|
||||
}
|
||||
|
||||
public void ChangeData(AtmosSensorData data)
|
||||
{
|
||||
SensorAddress.Title = $"{_address} : {data.AlarmState}";
|
||||
PressureLabel.SetMarkup(Loc.GetString("air-alarm-ui-window-pressure", ("pressure", $"{data.Pressure:0.##}")));
|
||||
TemperatureLabel.SetMarkup(Loc.GetString("air-alarm-ui-window-temperature", ("tempC", $"{TemperatureHelpers.KelvinToCelsius(data.Temperature):0.#}"), ("temperature", $"{data.Temperature:0.##}")));
|
||||
AlarmStateLabel.SetMarkup(Loc.GetString("air-alarm-ui-window-alarm-state", ("state", $"{data.AlarmState}")));
|
||||
|
||||
foreach (var (gas, amount) in data.Gases)
|
||||
{
|
||||
if (!_gasLabels.TryGetValue(gas, out var label))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
label.Text = Loc.GetString("air-alarm-ui-gases", ("gas", $"{gas}"),
|
||||
("amount", $"{amount:0.####}"),
|
||||
("percentage", $"{(amount / data.TotalMoles):0.##}"));
|
||||
}
|
||||
|
||||
_pressureThreshold.UpdateThresholdData(data.PressureThreshold);
|
||||
_temperatureThreshold.UpdateThresholdData(data.TemperatureThreshold);
|
||||
foreach (var (gas, control) in _gasThresholds)
|
||||
{
|
||||
if (!data.GasThresholds.TryGetValue(gas, out var threshold))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
control.UpdateThresholdData(threshold);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,12 +8,14 @@ using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Localization;
|
||||
|
||||
// holy FUCK
|
||||
// this technically works because some of this you can *not* do in XAML but holy FUCK
|
||||
|
||||
namespace Content.Client.Atmos.Monitor.UI.Widgets
|
||||
namespace Content.Client.Atmos.Monitor.UI.Widgets;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class ThresholdControl : BoxContainer
|
||||
{
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class ThresholdControl : BoxContainer
|
||||
{
|
||||
private AtmosAlarmThreshold _threshold;
|
||||
private AtmosMonitorThresholdType _type;
|
||||
private Gas? _gas;
|
||||
@@ -206,18 +208,18 @@ namespace Content.Client.Atmos.Monitor.UI.Widgets
|
||||
_modifier = modifier > 0 ? modifier : 1;
|
||||
_value = value;
|
||||
|
||||
this.HorizontalExpand = true;
|
||||
this.Orientation = LayoutOrientation.Vertical;
|
||||
HorizontalExpand = true;
|
||||
Orientation = LayoutOrientation.Vertical;
|
||||
|
||||
this.AddChild(new Label { Text = Loc.GetString($"air-alarm-ui-thresholds-{name}") });
|
||||
AddChild(new Label { Text = Loc.GetString($"air-alarm-ui-thresholds-{name}") });
|
||||
_bound = new FloatSpinBox(.01f, 2);
|
||||
this.AddChild(_bound);
|
||||
AddChild(_bound);
|
||||
|
||||
_boundEnabled = new CheckBox
|
||||
{
|
||||
Text = Loc.GetString("Enabled")
|
||||
};
|
||||
this.AddChild(_boundEnabled);
|
||||
AddChild(_boundEnabled);
|
||||
|
||||
_bound.Value = ModifiedValue ?? 0;
|
||||
_lastValue = _value ?? 0;
|
||||
@@ -287,8 +289,9 @@ namespace Content.Client.Atmos.Monitor.UI.Widgets
|
||||
OnValidBoundChanged!.Invoke();
|
||||
}
|
||||
|
||||
private bool ValidateThreshold(float value) => (_value != null) && (value >= 0);
|
||||
|
||||
private bool ValidateThreshold(float value)
|
||||
{
|
||||
return _value != null && value >= 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,26 @@
|
||||
using Content.Shared.Atmos.Monitor;
|
||||
using Content.Shared.Atmos.Monitor.Components;
|
||||
using Content.Shared.Atmos.Piping.Unary.Components;
|
||||
using Robust.Shared.Network;
|
||||
|
||||
namespace Content.Server.Atmos.Monitor.Components
|
||||
namespace Content.Server.Atmos.Monitor.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed class AirAlarmComponent : Component
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed class AirAlarmComponent : Component
|
||||
{
|
||||
[ViewVariables] public AirAlarmMode CurrentMode { get; set; } = AirAlarmMode.Filtering;
|
||||
|
||||
// Remember to null this afterwards.
|
||||
[ViewVariables] public IAirAlarmModeUpdate? CurrentModeUpdater { get; set; }
|
||||
|
||||
public Dictionary<string, IAtmosDeviceData> DeviceData = new();
|
||||
[ViewVariables] public AirAlarmTab CurrentTab { get; set; }
|
||||
|
||||
public readonly HashSet<string> KnownDevices = new();
|
||||
public readonly Dictionary<string, GasVentPumpData> VentData = new();
|
||||
public readonly Dictionary<string, GasVentScrubberData> ScrubberData = new();
|
||||
public readonly Dictionary<string, AtmosSensorData> SensorData = new();
|
||||
|
||||
public HashSet<NetUserId> ActivePlayers = new();
|
||||
|
||||
public bool CanSync = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,60 @@
|
||||
using Content.Shared.Atmos.Monitor;
|
||||
using Content.Shared.Tag;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set;
|
||||
|
||||
namespace Content.Server.Atmos.Monitor.Components
|
||||
namespace Content.Server.Atmos.Monitor.Components;
|
||||
// AtmosAlarmables are entities that can be alarmed
|
||||
// by a linked AtmosMonitor (alarmer?) if a threshold
|
||||
// is passed in some way. The intended use is to
|
||||
// do something in case something dangerous happens,
|
||||
// e.g., activate firelocks in case a temperature
|
||||
// threshold is reached
|
||||
//
|
||||
// It goes:
|
||||
//
|
||||
// AtmosMonitor -> AtmosDeviceUpdateEvent
|
||||
// -> Threshold calculation
|
||||
// -> AtmosAlarmEvent
|
||||
// -> Everything linked to that monitor (targetted)
|
||||
|
||||
/// <summary>
|
||||
/// A component to add to device network devices if you want them to be alarmed
|
||||
/// by an atmospheric alarmer. This will store every single alert received, and
|
||||
/// calculate the highest alert based on the alerts received. Equally, if you
|
||||
/// link other alarmables to this, it will store the alerts from them to
|
||||
/// calculate the highest network alert.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class AtmosAlarmableComponent : Component
|
||||
{
|
||||
// AtmosAlarmables are entities that can be alarmed
|
||||
// by a linked AtmosMonitor (alarmer?) if a threshold
|
||||
// is passed in some way. The intended use is to
|
||||
// do something in case something dangerous happens,
|
||||
// e.g., activate firelocks in case a temperature
|
||||
// threshold is reached
|
||||
//
|
||||
// It goes:
|
||||
//
|
||||
// AtmosMonitor -> AtmosDeviceUpdateEvent
|
||||
// -> Threshold calculation
|
||||
// -> AtmosMonitorAlarmEvent
|
||||
// -> 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 monitor.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class AtmosAlarmableComponent : Component
|
||||
{
|
||||
[ViewVariables]
|
||||
public List<EntityUid> LinkedMonitors { get; set; } = new();
|
||||
public readonly Dictionary<string, AtmosAlarmType> NetworkAlarmStates = new();
|
||||
|
||||
[ViewVariables] public AtmosAlarmType LastAlarmState = AtmosAlarmType.Normal;
|
||||
|
||||
[ViewVariables] public AtmosMonitorAlarmType LastAlarmState = AtmosMonitorAlarmType.Normal;
|
||||
[ViewVariables] public AtmosMonitorAlarmType HighestNetworkState = AtmosMonitorAlarmType.Normal;
|
||||
[ViewVariables] public bool IgnoreAlarms { get; set; } = false;
|
||||
|
||||
[DataField("alarmSound")]
|
||||
public SoundSpecifier AlarmSound { get; set; } = new SoundPathSpecifier("/Audio/Machines/alarm.ogg");
|
||||
|
||||
[DataField("alarmVolume")]
|
||||
public float AlarmVolume { get; set; } = -10;
|
||||
|
||||
/// <summary>
|
||||
/// List of prototypes that this alarmable can be
|
||||
/// alarmed by - must be a prototype with AtmosMonitor
|
||||
/// attached to it
|
||||
/// List of tags to check for when synchronizing alarms.
|
||||
/// </summary>
|
||||
[DataField("alarmedBy")]
|
||||
public List<string> AlarmedByPrototypes { get; } = new();
|
||||
}
|
||||
[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,11 +3,11 @@ using Content.Shared.Atmos.Monitor;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.Atmos.Monitor.Components
|
||||
namespace Content.Server.Atmos.Monitor.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed class AtmosMonitorComponent : Component
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed class AtmosMonitorComponent : Component
|
||||
{
|
||||
// Whether this monitor can send alarms,
|
||||
// or recieve atmos command events.
|
||||
//
|
||||
@@ -22,11 +22,6 @@ namespace Content.Server.Atmos.Monitor.Components
|
||||
[ViewVariables]
|
||||
public bool NetEnabled = true;
|
||||
|
||||
// Entities that the monitor will alarm. Stores only EntityUids, is populated
|
||||
// when this component starts up.
|
||||
[ViewVariables]
|
||||
public List<EntityUid> LinkedEntities = new();
|
||||
|
||||
[DataField("temperatureThreshold", customTypeSerializer: (typeof(PrototypeIdSerializer<AtmosAlarmThreshold>)))]
|
||||
public readonly string? TemperatureThresholdId;
|
||||
|
||||
@@ -46,15 +41,6 @@ namespace Content.Server.Atmos.Monitor.Components
|
||||
[DataField("monitorFire")]
|
||||
public bool MonitorFire = false;
|
||||
|
||||
[DataField("displayMaxAlarmInNet")]
|
||||
public bool DisplayMaxAlarmInNet = false;
|
||||
|
||||
[DataField("alarmSound")]
|
||||
public SoundSpecifier AlarmSound { get; set; } = new SoundPathSpecifier("/Audio/Machines/alarm.ogg");
|
||||
|
||||
[DataField("alarmVolume")]
|
||||
public float AlarmVolume { get; set; } = -10;
|
||||
|
||||
// really messy but this is parsed at runtime after
|
||||
// prototypes are initialized, there's no
|
||||
// way without implementing a new
|
||||
@@ -71,30 +57,13 @@ namespace Content.Server.Atmos.Monitor.Components
|
||||
|
||||
// Stores the last alarm state of this alarm.
|
||||
[ViewVariables]
|
||||
public AtmosMonitorAlarmType LastAlarmState = AtmosMonitorAlarmType.Normal;
|
||||
public AtmosAlarmType LastAlarmState = AtmosAlarmType.Normal;
|
||||
|
||||
// feeling real dirty about this one
|
||||
// Caches the alarm states it recieves from the rest of the network.
|
||||
// This is so that the highest alarm in the network can be calculated
|
||||
// from any monitor without having to reping every alarm.
|
||||
[ViewVariables]
|
||||
public Dictionary<string, AtmosMonitorAlarmType> NetworkAlarmStates = new();
|
||||
[ViewVariables] public HashSet<AtmosMonitorThresholdType> TrippedThresholds = new();
|
||||
|
||||
// Calculates the highest alarm in the network, including itself.
|
||||
[ViewVariables]
|
||||
public AtmosMonitorAlarmType HighestAlarmInNetwork
|
||||
{
|
||||
get
|
||||
{
|
||||
var state = AtmosMonitorAlarmType.Normal;
|
||||
foreach (var (_, netState) in NetworkAlarmStates)
|
||||
if (state < netState)
|
||||
state = netState;
|
||||
|
||||
if (LastAlarmState > state) state = LastAlarmState;
|
||||
|
||||
return state;
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Registered devices in this atmos monitor. Alerts will be sent directly
|
||||
/// to these devices.
|
||||
/// </summary>
|
||||
[ViewVariables] public HashSet<string> RegisteredDevices = new();
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
namespace Content.Server.Atmos.Monitor.Components
|
||||
namespace Content.Server.Atmos.Monitor.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed class FireAlarmComponent : Component
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed class FireAlarmComponent : Component
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -5,14 +5,14 @@ using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Monitor.Components;
|
||||
using Content.Shared.Atmos.Piping.Unary.Components;
|
||||
|
||||
namespace Content.Server.Atmos.Monitor
|
||||
namespace Content.Server.Atmos.Monitor;
|
||||
|
||||
/// <summary>
|
||||
/// This is an interface that air alarm modes use
|
||||
/// in order to execute the defined modes.
|
||||
/// </summary>
|
||||
public interface IAirAlarmMode
|
||||
{
|
||||
/// <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
|
||||
@@ -23,20 +23,20 @@ namespace Content.Server.Atmos.Monitor
|
||||
/// 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
|
||||
{
|
||||
// IAirAlarmModeUpdate
|
||||
//
|
||||
// This is an interface that AirAlarmSystem uses
|
||||
// in order to 'update' air alarm modes so that
|
||||
// modes like Replace can be implemented.
|
||||
/// <summary>
|
||||
/// An interface that AirAlarmSystem uses
|
||||
/// in order to update air alarm modes that
|
||||
/// need updating (e.g., Replace)
|
||||
/// </summary>
|
||||
public interface IAirAlarmModeUpdate
|
||||
{
|
||||
/// <summary>
|
||||
/// This is checked by AirAlarmSystem when
|
||||
/// a mode is updated. This should be set
|
||||
@@ -51,10 +51,10 @@ namespace Content.Server.Atmos.Monitor
|
||||
/// be where all the logic goes.
|
||||
/// </summary>
|
||||
public void Update(EntityUid uid);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class AirAlarmModeFactory
|
||||
{
|
||||
public sealed class AirAlarmModeFactory
|
||||
{
|
||||
private static IAirAlarmMode _filterMode = new AirAlarmFilterMode();
|
||||
private static IAirAlarmMode _fillMode = new AirAlarmFillMode();
|
||||
private static IAirAlarmMode _panicMode = new AirAlarmPanicMode();
|
||||
@@ -62,20 +62,22 @@ namespace Content.Server.Atmos.Monitor
|
||||
|
||||
// still not a fan since ReplaceMode must have an allocation
|
||||
// but it's whatever
|
||||
public static IAirAlarmMode? ModeToExecutor(AirAlarmMode mode) => mode switch
|
||||
public static IAirAlarmMode? ModeToExecutor(AirAlarmMode mode)
|
||||
{
|
||||
return mode switch
|
||||
{
|
||||
AirAlarmMode.Filtering => _filterMode,
|
||||
AirAlarmMode.Fill => _fillMode,
|
||||
AirAlarmMode.Panic => _panicMode,
|
||||
AirAlarmMode.None => _noneMode,
|
||||
AirAlarmMode.Replace => new AirAlarmReplaceMode(),
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// like a tiny little EntitySystem
|
||||
public abstract class AirAlarmModeExecutor : IAirAlarmMode
|
||||
{
|
||||
// like a tiny little EntitySystem
|
||||
public abstract class AirAlarmModeExecutor : IAirAlarmMode
|
||||
{
|
||||
[Dependency] public readonly IEntityManager EntityManager = default!;
|
||||
public readonly DeviceNetworkSystem DeviceNetworkSystem;
|
||||
public readonly AirAlarmSystem AirAlarmSystem;
|
||||
@@ -89,149 +91,82 @@ namespace Content.Server.Atmos.Monitor
|
||||
DeviceNetworkSystem = EntitySystem.Get<DeviceNetworkSystem>();
|
||||
AirAlarmSystem = EntitySystem.Get<AirAlarmSystem>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class AirAlarmNoneMode : AirAlarmModeExecutor
|
||||
{
|
||||
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.DeviceData)
|
||||
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 sealed class AirAlarmFilterMode : AirAlarmModeExecutor
|
||||
{
|
||||
public override void Execute(EntityUid uid)
|
||||
{
|
||||
if (!EntityManager.TryGetComponent(uid, out AirAlarmComponent? alarm))
|
||||
return;
|
||||
|
||||
foreach (var (addr, device) in alarm.DeviceData)
|
||||
foreach (var (addr, device) in alarm.VentData)
|
||||
{
|
||||
switch (device)
|
||||
{
|
||||
case GasVentPumpData pumpData:
|
||||
AirAlarmSystem.SetData(uid, addr, GasVentPumpData.FilterModePreset);
|
||||
break;
|
||||
case GasVentScrubberData scrubberData:
|
||||
}
|
||||
|
||||
foreach (var (addr, device) in alarm.ScrubberData)
|
||||
{
|
||||
AirAlarmSystem.SetData(uid, addr, GasVentScrubberData.FilterModePreset);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.DeviceData)
|
||||
{
|
||||
switch (device)
|
||||
{
|
||||
case GasVentPumpData pumpData:
|
||||
AirAlarmSystem.SetData(uid, addr, GasVentPumpData.PanicModePreset);
|
||||
break;
|
||||
case GasVentScrubberData scrubberData:
|
||||
AirAlarmSystem.SetData(uid, addr, GasVentScrubberData.PanicModePreset);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.DeviceData)
|
||||
{
|
||||
switch (device)
|
||||
{
|
||||
case GasVentPumpData pumpData:
|
||||
AirAlarmSystem.SetData(uid, addr, GasVentPumpData.FillModePreset);
|
||||
break;
|
||||
case GasVentScrubberData scrubberData:
|
||||
AirAlarmSystem.SetData(uid, addr, GasVentScrubberData.FillModePreset);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class AirAlarmReplaceMode : AirAlarmModeExecutor, IAirAlarmModeUpdate
|
||||
{
|
||||
private Dictionary<string, IAtmosDeviceData> _devices = new();
|
||||
private float _lastPressure = Atmospherics.OneAtmosphere;
|
||||
private AtmosMonitorComponent? _monitor;
|
||||
private AtmosAlarmableComponent? _alarmable;
|
||||
|
||||
public string NetOwner { get; set; } = string.Empty;
|
||||
|
||||
public override void Execute(EntityUid uid)
|
||||
{
|
||||
if (!EntityManager.TryGetComponent(uid, out AirAlarmComponent? alarm)
|
||||
|| !EntityManager.TryGetComponent(uid, out AtmosMonitorComponent? monitor)
|
||||
|| !EntityManager.TryGetComponent(uid, out AtmosAlarmableComponent? alarmable))
|
||||
return;
|
||||
|
||||
_devices = alarm.DeviceData;
|
||||
_monitor = monitor;
|
||||
_alarmable = alarmable;
|
||||
_alarmable.IgnoreAlarms = true;
|
||||
SetSiphon(uid);
|
||||
}
|
||||
|
||||
public void Update(EntityUid uid)
|
||||
{
|
||||
if (_monitor == null
|
||||
|| _alarmable == null
|
||||
|| _monitor.TileGas == null)
|
||||
return;
|
||||
|
||||
// just a little pointer
|
||||
var mixture = _monitor.TileGas;
|
||||
|
||||
_lastPressure = mixture.Pressure;
|
||||
if (_lastPressure <= 0.2f) // anything below and it might get stuck
|
||||
{
|
||||
_alarmable.IgnoreAlarms = false;
|
||||
AirAlarmSystem.SetMode(uid, NetOwner!, AirAlarmMode.Filtering, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetSiphon(EntityUid uid)
|
||||
{
|
||||
foreach (var (addr, device) in _devices)
|
||||
{
|
||||
switch (device)
|
||||
{
|
||||
case GasVentPumpData pumpData:
|
||||
pumpData = GasVentPumpData.PanicModePreset;
|
||||
pumpData.IgnoreAlarms = true;
|
||||
AirAlarmSystem.SetData(uid, addr, pumpData);
|
||||
break;
|
||||
case GasVentScrubberData scrubberData:
|
||||
scrubberData = GasVentScrubberData.PanicModePreset;
|
||||
scrubberData.IgnoreAlarms = true;
|
||||
AirAlarmSystem.SetData(uid, addr, scrubberData);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Atmos.Monitor.Components;
|
||||
using Content.Server.Atmos.Piping.Components;
|
||||
using Content.Server.DeviceNetwork;
|
||||
@@ -12,59 +13,39 @@ using Content.Shared.Access.Systems;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Monitor;
|
||||
using Content.Shared.Atmos.Monitor.Components;
|
||||
using Content.Shared.Atmos.Piping.Unary.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Server.Atmos.Monitor.Systems
|
||||
namespace Content.Server.Atmos.Monitor.Systems;
|
||||
|
||||
// AirAlarm system - specific for atmos devices, rather than
|
||||
// atmos monitors.
|
||||
//
|
||||
// oh boy, message passing!
|
||||
//
|
||||
// Commands should always be sent into packet's Command
|
||||
// data key. In response, a packet will be transmitted
|
||||
// with the response type as its command, and the
|
||||
// response data in its data key.
|
||||
public sealed class AirAlarmSystem : EntitySystem
|
||||
{
|
||||
// AirAlarm system - specific for atmos devices, rather than
|
||||
// atmos monitors.
|
||||
//
|
||||
// oh boy, message passing!
|
||||
//
|
||||
// Commands should always be sent into packet's Command
|
||||
// data key. In response, a packet will be transmitted
|
||||
// with the response type as its command, and the
|
||||
// response data in its data key.
|
||||
public sealed class AirAlarmSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly DeviceNetworkSystem _deviceNet = default!;
|
||||
[Dependency] private readonly AtmosMonitorSystem _atmosMonitorSystem = default!;
|
||||
[Dependency] private readonly AtmosDeviceNetworkSystem _atmosDevNetSystem = default!;
|
||||
[Dependency] private readonly AtmosAlarmableSystem _atmosAlarmable = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
|
||||
[Dependency] private readonly AccessReaderSystem _accessSystem = default!;
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
|
||||
|
||||
#region Device Network API
|
||||
/// <summary>
|
||||
/// Command to set device data within the air alarm's network.
|
||||
/// </summary>
|
||||
public const string AirAlarmSetData = "air_alarm_set_device_data";
|
||||
|
||||
/// <summary>
|
||||
/// Command to request a sync from devices in an air alarm's network.
|
||||
/// </summary>
|
||||
public const string AirAlarmSyncCmd = "air_alarm_sync_devices";
|
||||
|
||||
/// <summary>
|
||||
/// Command to set an air alarm's mode.
|
||||
/// </summary>
|
||||
public const string AirAlarmSetMode = "air_alarm_set_mode";
|
||||
|
||||
// -- Packet Data --
|
||||
|
||||
/// <summary>
|
||||
/// Data response to an AirAlarmSetData command.
|
||||
/// </summary>
|
||||
public const string AirAlarmSetDataStatus = "air_alarm_set_device_data_status";
|
||||
|
||||
/// <summary>
|
||||
/// Data response to an AirAlarmSync command. Contains
|
||||
/// IAtmosDeviceData in this system's implementation.
|
||||
/// </summary>
|
||||
public const string AirAlarmSyncData = "air_alarm_device_sync_data";
|
||||
|
||||
// -- API --
|
||||
|
||||
/// <summary>
|
||||
@@ -74,61 +55,81 @@ namespace Content.Server.Atmos.Monitor.Systems
|
||||
/// <param name="data">The data to send to the device.</param>
|
||||
public void SetData(EntityUid uid, string address, IAtmosDeviceData data)
|
||||
{
|
||||
if (EntityManager.TryGetComponent(uid, out AtmosMonitorComponent? monitor)
|
||||
&& !monitor.NetEnabled)
|
||||
return;
|
||||
|
||||
var payload = new NetworkPayload
|
||||
{
|
||||
[DeviceNetworkConstants.Command] = AirAlarmSetData,
|
||||
// [AirAlarmTypeData] = type,
|
||||
[AirAlarmSetData] = data
|
||||
};
|
||||
|
||||
_deviceNet.QueuePacket(uid, address, payload);
|
||||
_atmosDevNetSystem.SetDeviceState(uid, address, data);
|
||||
_atmosDevNetSystem.Sync(uid, address);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Broadcast a sync packet to an air alarm's local network.
|
||||
/// </summary>
|
||||
public void SyncAllDevices(EntityUid uid)
|
||||
private void SyncAllDevices(EntityUid uid)
|
||||
{
|
||||
if (EntityManager.TryGetComponent(uid, out AtmosMonitorComponent? monitor)
|
||||
&& !monitor.NetEnabled)
|
||||
return;
|
||||
|
||||
var payload = new NetworkPayload
|
||||
{
|
||||
[DeviceNetworkConstants.Command] = AirAlarmSyncCmd
|
||||
};
|
||||
|
||||
_deviceNet.QueuePacket(uid, null, payload);
|
||||
_atmosDevNetSystem.Sync(uid, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a sync packet to a specific device from an air alarm.
|
||||
/// </summary>
|
||||
/// <param name="address">The address of the device.</param>
|
||||
public void SyncDevice(EntityUid uid, string address)
|
||||
private void SyncDevice(EntityUid uid, string address)
|
||||
{
|
||||
_atmosDevNetSystem.Sync(uid, address);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register and synchronize with all devices
|
||||
/// on this network.
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
private void SyncRegisterAllDevices(EntityUid uid)
|
||||
{
|
||||
_atmosDevNetSystem.Register(uid, null);
|
||||
_atmosDevNetSystem.Sync(uid, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Synchronize all sensors on an air alarm, but only if its current tab is set to Sensors.
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="monitor"></param>
|
||||
private void SyncAllSensors(EntityUid uid, AirAlarmComponent? monitor = null)
|
||||
{
|
||||
if (!Resolve(uid, ref monitor))
|
||||
{
|
||||
if (EntityManager.TryGetComponent(uid, out AtmosMonitorComponent? monitor)
|
||||
&& !monitor.NetEnabled)
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var addr in monitor.SensorData.Keys)
|
||||
{
|
||||
SyncDevice(uid, addr);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetThreshold(EntityUid uid, string address, AtmosMonitorThresholdType type,
|
||||
AtmosAlarmThreshold threshold, Gas? gas = null)
|
||||
{
|
||||
var payload = new NetworkPayload
|
||||
{
|
||||
[DeviceNetworkConstants.Command] = AirAlarmSyncCmd
|
||||
[DeviceNetworkConstants.Command] = AtmosMonitorSystem.AtmosMonitorSetThresholdCmd,
|
||||
[AtmosMonitorSystem.AtmosMonitorThresholdDataType] = type,
|
||||
[AtmosMonitorSystem.AtmosMonitorThresholdData] = threshold,
|
||||
};
|
||||
|
||||
if (gas != null)
|
||||
{
|
||||
payload.Add(AtmosMonitorSystem.AtmosMonitorThresholdGasType, gas);
|
||||
}
|
||||
|
||||
_deviceNet.QueuePacket(uid, address, payload);
|
||||
|
||||
SyncDevice(uid, address);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sync this air alarm's mode with the rest of the network.
|
||||
/// </summary>
|
||||
/// <param name="mode">The mode to sync with the rest of the network.</param>
|
||||
public void SyncMode(EntityUid uid, AirAlarmMode mode)
|
||||
private void SyncMode(EntityUid uid, AirAlarmMode mode)
|
||||
{
|
||||
if (EntityManager.TryGetComponent(uid, out AtmosMonitorComponent? monitor)
|
||||
&& !monitor.NetEnabled)
|
||||
@@ -151,24 +152,40 @@ namespace Content.Server.Atmos.Monitor.Systems
|
||||
{
|
||||
SubscribeLocalEvent<AirAlarmComponent, DeviceNetworkPacketEvent>(OnPacketRecv);
|
||||
SubscribeLocalEvent<AirAlarmComponent, AtmosDeviceUpdateEvent>(OnAtmosUpdate);
|
||||
SubscribeLocalEvent<AirAlarmComponent, AtmosMonitorAlarmEvent>(OnAtmosAlarm);
|
||||
SubscribeLocalEvent<AirAlarmComponent, AtmosAlarmEvent>(OnAtmosAlarm);
|
||||
SubscribeLocalEvent<AirAlarmComponent, PowerChangedEvent>(OnPowerChanged);
|
||||
SubscribeLocalEvent<AirAlarmComponent, AirAlarmResyncAllDevicesMessage>(OnResyncAll);
|
||||
SubscribeLocalEvent<AirAlarmComponent, AirAlarmUpdateAlarmModeMessage>(OnUpdateAlarmMode);
|
||||
SubscribeLocalEvent<AirAlarmComponent, AirAlarmUpdateAlarmThresholdMessage>(OnUpdateThreshold);
|
||||
SubscribeLocalEvent<AirAlarmComponent, AirAlarmUpdateDeviceDataMessage>(OnUpdateDeviceData);
|
||||
SubscribeLocalEvent<AirAlarmComponent, AirAlarmTabSetMessage>(OnTabChange);
|
||||
SubscribeLocalEvent<AirAlarmComponent, DeviceListUpdateEvent>(OnDeviceListUpdate);
|
||||
SubscribeLocalEvent<AirAlarmComponent, BoundUIClosedEvent>(OnClose);
|
||||
SubscribeLocalEvent<AirAlarmComponent, ComponentShutdown>(OnShutdown);
|
||||
SubscribeLocalEvent<AirAlarmComponent, InteractHandEvent>(OnInteract);
|
||||
}
|
||||
|
||||
private void OnDeviceListUpdate(EntityUid uid, AirAlarmComponent component, DeviceListUpdateEvent args)
|
||||
{
|
||||
SyncRegisterAllDevices(uid);
|
||||
}
|
||||
|
||||
private void OnTabChange(EntityUid uid, AirAlarmComponent component, AirAlarmTabSetMessage msg)
|
||||
{
|
||||
component.CurrentTab = msg.Tab;
|
||||
UpdateUI(uid, component);
|
||||
}
|
||||
|
||||
private void OnPowerChanged(EntityUid uid, AirAlarmComponent component, PowerChangedEvent args)
|
||||
{
|
||||
if (!args.Powered)
|
||||
{
|
||||
ForceCloseAllInterfaces(uid);
|
||||
component.CurrentModeUpdater = null;
|
||||
component.DeviceData.Clear();
|
||||
component.KnownDevices.Clear();
|
||||
component.ScrubberData.Clear();
|
||||
component.SensorData.Clear();
|
||||
component.VentData.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -208,38 +225,41 @@ namespace Content.Server.Atmos.Monitor.Systems
|
||||
_uiSystem.GetUiOrNull(component.Owner, SharedAirAlarmInterfaceKey.Key)?.Open(actor.PlayerSession);
|
||||
component.ActivePlayers.Add(actor.PlayerSession.UserId);
|
||||
AddActiveInterface(uid);
|
||||
SendAddress(uid);
|
||||
SendAlarmMode(uid);
|
||||
SendThresholds(uid);
|
||||
SyncAllDevices(uid);
|
||||
SendAirData(uid);
|
||||
UpdateUI(uid, component);
|
||||
}
|
||||
|
||||
private void OnResyncAll(EntityUid uid, AirAlarmComponent component, AirAlarmResyncAllDevicesMessage args)
|
||||
{
|
||||
if (AccessCheck(uid, args.Session.AttachedEntity, component))
|
||||
if (!AccessCheck(uid, args.Session.AttachedEntity, component))
|
||||
{
|
||||
component.DeviceData.Clear();
|
||||
SyncAllDevices(uid);
|
||||
return;
|
||||
}
|
||||
|
||||
component.KnownDevices.Clear();
|
||||
component.VentData.Clear();
|
||||
component.ScrubberData.Clear();
|
||||
component.SensorData.Clear();
|
||||
|
||||
SyncRegisterAllDevices(uid);
|
||||
}
|
||||
|
||||
private void OnUpdateAlarmMode(EntityUid uid, AirAlarmComponent component, AirAlarmUpdateAlarmModeMessage args)
|
||||
{
|
||||
string addr = string.Empty;
|
||||
var addr = string.Empty;
|
||||
if (EntityManager.TryGetComponent(uid, out DeviceNetworkComponent? netConn)) addr = netConn.Address;
|
||||
if (AccessCheck(uid, args.Session.AttachedEntity, component))
|
||||
SetMode(uid, addr, args.Mode, true, false);
|
||||
else
|
||||
SendAlarmMode(uid);
|
||||
UpdateUI(uid, component);
|
||||
}
|
||||
|
||||
private void OnUpdateThreshold(EntityUid uid, AirAlarmComponent component, AirAlarmUpdateAlarmThresholdMessage args)
|
||||
{
|
||||
if (AccessCheck(uid, args.Session.AttachedEntity, component))
|
||||
SetThreshold(uid, args.Threshold, args.Type, args.Gas);
|
||||
SetThreshold(uid, args.Address, args.Type, args.Threshold, args.Gas);
|
||||
else
|
||||
SendThresholds(uid);
|
||||
UpdateUI(uid, component);
|
||||
}
|
||||
|
||||
private void OnUpdateDeviceData(EntityUid uid, AirAlarmComponent component, AirAlarmUpdateDeviceDataMessage args)
|
||||
@@ -247,7 +267,7 @@ namespace Content.Server.Atmos.Monitor.Systems
|
||||
if (AccessCheck(uid, args.Session.AttachedEntity, component))
|
||||
SetDeviceData(uid, args.Address, args.Data);
|
||||
else
|
||||
SyncDevice(uid, args.Address);
|
||||
UpdateUI(uid, component);
|
||||
}
|
||||
|
||||
private bool AccessCheck(EntityUid uid, EntityUid? user, AirAlarmComponent? component = null)
|
||||
@@ -267,55 +287,33 @@ namespace Content.Server.Atmos.Monitor.Systems
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnAtmosAlarm(EntityUid uid, AirAlarmComponent component, AtmosMonitorAlarmEvent args)
|
||||
private void OnAtmosAlarm(EntityUid uid, AirAlarmComponent component, AtmosAlarmEvent args)
|
||||
{
|
||||
if (component.ActivePlayers.Count != 0)
|
||||
{
|
||||
SyncAllDevices(uid);
|
||||
SendAirData(uid);
|
||||
}
|
||||
|
||||
string addr = string.Empty;
|
||||
var addr = string.Empty;
|
||||
if (EntityManager.TryGetComponent(uid, out DeviceNetworkComponent? netConn)) addr = netConn.Address;
|
||||
|
||||
|
||||
if (args.HighestNetworkType == AtmosMonitorAlarmType.Danger)
|
||||
if (args.AlarmType == AtmosAlarmType.Danger)
|
||||
{
|
||||
SetMode(uid, addr, AirAlarmMode.None, true);
|
||||
// set mode to off to mimic the vents/scrubbers being turned off
|
||||
// update UI
|
||||
//
|
||||
// no, the mode isn't processed here - it's literally just
|
||||
// set to what mimics 'off'
|
||||
SetMode(uid, addr, AirAlarmMode.None, true, false);
|
||||
}
|
||||
else if (args.HighestNetworkType == AtmosMonitorAlarmType.Normal)
|
||||
else if (args.AlarmType == AtmosAlarmType.Normal)
|
||||
{
|
||||
// if the mode is still set to off, set it to filtering instead
|
||||
// alternatively, set it to the last saved mode
|
||||
//
|
||||
// no, this still doesn't execute the mode
|
||||
SetMode(uid, addr, AirAlarmMode.Filtering, true);
|
||||
SetMode(uid, addr, AirAlarmMode.Filtering, true, false);
|
||||
}
|
||||
|
||||
UpdateUI(uid, component);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Air Alarm Settings
|
||||
|
||||
/// <summary>
|
||||
/// Set a threshold on an air alarm.
|
||||
/// </summary>
|
||||
/// <param name="threshold">New threshold data.</param>
|
||||
public void SetThreshold(EntityUid uid, AtmosAlarmThreshold threshold, AtmosMonitorThresholdType type, Gas? gas = null, AirAlarmComponent? controller = null)
|
||||
{
|
||||
if (!Resolve(uid, ref controller)) return;
|
||||
|
||||
_atmosMonitorSystem.SetThreshold(uid, type, threshold, gas);
|
||||
|
||||
// TODO: Use BUI states instead...
|
||||
_uiSystem.TrySendUiMessage(uid, SharedAirAlarmInterfaceKey.Key, new AirAlarmUpdateAlarmThresholdMessage(type, threshold, gas));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set an air alarm's mode.
|
||||
/// </summary>
|
||||
@@ -328,7 +326,7 @@ namespace Content.Server.Atmos.Monitor.Systems
|
||||
if (!Resolve(uid, ref controller)) return;
|
||||
controller.CurrentMode = mode;
|
||||
|
||||
// setting it to UI only maans we don't have
|
||||
// setting it to UI only means we don't have
|
||||
// to deal with the issue of not-single-owner
|
||||
// alarm mode executors
|
||||
if (!uiOnly)
|
||||
@@ -348,17 +346,16 @@ namespace Content.Server.Atmos.Monitor.Systems
|
||||
}
|
||||
// only one air alarm in a network can use an air alarm mode
|
||||
// that updates, so even if it's a ui-only change,
|
||||
// we have to invalidte the last mode's updater and
|
||||
// we have to invalidate the last mode's updater and
|
||||
// remove it because otherwise it'll execute a now
|
||||
// invalid mode
|
||||
else if (controller.CurrentModeUpdater != null
|
||||
&& controller.CurrentModeUpdater.NetOwner != origin)
|
||||
{
|
||||
controller.CurrentModeUpdater = null;
|
||||
}
|
||||
|
||||
// controller.SendMessage(new AirAlarmUpdateAlarmModeMessage(mode));
|
||||
// TODO: Use BUI states instead...
|
||||
_uiSystem.TrySendUiMessage(uid, SharedAirAlarmInterfaceKey.Key, new AirAlarmUpdateAlarmModeMessage(mode));
|
||||
|
||||
UpdateUI(uid, controller);
|
||||
|
||||
// setting sync deals with the issue of air alarms
|
||||
// in the same network needing to have the same mode
|
||||
@@ -371,9 +368,12 @@ namespace Content.Server.Atmos.Monitor.Systems
|
||||
/// </summary>
|
||||
/// <param name="address">The address to send the new data to.</param>
|
||||
/// <param name="devData">The device data to be sent.</param>
|
||||
public void SetDeviceData(EntityUid uid, string address, IAtmosDeviceData devData, AirAlarmComponent? controller = null)
|
||||
private void SetDeviceData(EntityUid uid, string address, IAtmosDeviceData devData, AirAlarmComponent? controller = null)
|
||||
{
|
||||
if (!Resolve(uid, ref controller)) return;
|
||||
if (!Resolve(uid, ref controller))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
devData.Dirty = true;
|
||||
SetData(uid, address, devData);
|
||||
@@ -386,28 +386,32 @@ namespace Content.Server.Atmos.Monitor.Systems
|
||||
|
||||
switch (cmd)
|
||||
{
|
||||
case AirAlarmSyncData:
|
||||
if (!args.Data.TryGetValue(AirAlarmSyncData, out IAtmosDeviceData? data)
|
||||
|| data == null
|
||||
|| !controller.CanSync) break;
|
||||
case AtmosDeviceNetworkSystem.SyncData:
|
||||
if (!args.Data.TryGetValue(AtmosDeviceNetworkSystem.SyncData, out IAtmosDeviceData? data)
|
||||
|| !controller.CanSync)
|
||||
break;
|
||||
|
||||
// Save into component.
|
||||
// Sync data to interface.
|
||||
// _airAlarmDataSystem.UpdateDeviceData(uid, args.SenderAddress, data);
|
||||
//
|
||||
_uiSystem.TrySendUiMessage(uid, SharedAirAlarmInterfaceKey.Key, new AirAlarmUpdateDeviceDataMessage(args.SenderAddress, data));
|
||||
// if (HasComp<WiresComponent>(uid)) controller.UpdateWires();
|
||||
if (!controller.DeviceData.TryAdd(args.SenderAddress, data))
|
||||
controller.DeviceData[args.SenderAddress] = data;
|
||||
switch (data)
|
||||
{
|
||||
case GasVentPumpData ventData:
|
||||
if (!controller.VentData.TryAdd(args.SenderAddress, ventData))
|
||||
controller.VentData[args.SenderAddress] = ventData;
|
||||
break;
|
||||
case GasVentScrubberData scrubberData:
|
||||
if (!controller.ScrubberData.TryAdd(args.SenderAddress, scrubberData))
|
||||
controller.ScrubberData[args.SenderAddress] = scrubberData;
|
||||
break;
|
||||
case AtmosSensorData sensorData:
|
||||
if (!controller.SensorData.TryAdd(args.SenderAddress, sensorData))
|
||||
controller.SensorData[args.SenderAddress] = sensorData;
|
||||
break;
|
||||
}
|
||||
|
||||
return;
|
||||
case AirAlarmSetDataStatus:
|
||||
if (!args.Data.TryGetValue(AirAlarmSetDataStatus, out bool dataStatus)) break;
|
||||
controller.KnownDevices.Add(args.SenderAddress);
|
||||
|
||||
// Sync data to interface.
|
||||
// This should say if the result
|
||||
// failed, or succeeded. Don't save it.l
|
||||
SyncDevice(uid, args.SenderAddress);
|
||||
UpdateUI(uid, controller);
|
||||
|
||||
return;
|
||||
case AirAlarmSetMode:
|
||||
@@ -424,127 +428,118 @@ namespace Content.Server.Atmos.Monitor.Systems
|
||||
#region UI
|
||||
|
||||
// List of active user interfaces.
|
||||
private HashSet<EntityUid> _activeUserInterfaces = new();
|
||||
private readonly HashSet<EntityUid> _activeUserInterfaces = new();
|
||||
|
||||
/// <summary>
|
||||
/// Adds an active interface to be updated.
|
||||
/// </summary>
|
||||
public void AddActiveInterface(EntityUid uid) =>
|
||||
private void AddActiveInterface(EntityUid uid)
|
||||
{
|
||||
_activeUserInterfaces.Add(uid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes an active interface from the system update loop.
|
||||
/// </summary>
|
||||
public void RemoveActiveInterface(EntityUid uid) =>
|
||||
private void RemoveActiveInterface(EntityUid uid)
|
||||
{
|
||||
_activeUserInterfaces.Remove(uid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Force closes all interfaces currently open related to this air alarm.
|
||||
/// </summary>
|
||||
public void ForceCloseAllInterfaces(EntityUid uid) =>
|
||||
private void ForceCloseAllInterfaces(EntityUid uid)
|
||||
{
|
||||
_uiSystem.TryCloseAll(uid, SharedAirAlarmInterfaceKey.Key);
|
||||
|
||||
private void SendAddress(EntityUid uid, DeviceNetworkComponent? netConn = null)
|
||||
{
|
||||
if (!Resolve(uid, ref netConn)) return;
|
||||
|
||||
// TODO: Use BUI states instead...
|
||||
_uiSystem.TrySendUiMessage(uid, SharedAirAlarmInterfaceKey.Key, new AirAlarmSetAddressMessage(netConn.Address));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update an interface's air data. This is all the 'hot' data
|
||||
/// that an air alarm contains server-side. Updated with a whopping 8
|
||||
/// delay automatically once a UI is in the loop.
|
||||
/// </summary>
|
||||
public void SendAirData(EntityUid uid, AirAlarmComponent? alarm = null, AtmosMonitorComponent? monitor = null, ApcPowerReceiverComponent? power = null)
|
||||
private void OnAtmosUpdate(EntityUid uid, AirAlarmComponent alarm, AtmosDeviceUpdateEvent args)
|
||||
{
|
||||
if (!Resolve(uid, ref alarm, ref monitor, ref power)) return;
|
||||
|
||||
if (!power.Powered) return;
|
||||
|
||||
|
||||
if (monitor.TileGas != null)
|
||||
{
|
||||
var gases = new Dictionary<Gas, float>();
|
||||
|
||||
foreach (var gas in Enum.GetValues<Gas>())
|
||||
gases.Add(gas, monitor.TileGas.GetMoles(gas));
|
||||
|
||||
var airData = new AirAlarmAirData(monitor.TileGas.Pressure, monitor.TileGas.Temperature, monitor.TileGas.TotalMoles, monitor.LastAlarmState, gases);
|
||||
|
||||
// TODO: Use BUI states instead...
|
||||
_uiSystem.TrySendUiMessage(uid, SharedAirAlarmInterfaceKey.Key, new AirAlarmUpdateAirDataMessage(airData));
|
||||
}
|
||||
alarm.CurrentModeUpdater?.Update(uid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send an air alarm mode to any open interface related to an air alarm.
|
||||
/// </summary>
|
||||
public void SendAlarmMode(EntityUid uid, AtmosMonitorComponent? monitor = null, ApcPowerReceiverComponent? power = null, AirAlarmComponent? controller = null)
|
||||
public float CalculatePressureAverage(AirAlarmComponent alarm)
|
||||
{
|
||||
if (!Resolve(uid, ref monitor, ref power, ref controller)
|
||||
|| !power.Powered) return;
|
||||
|
||||
// TODO: Use BUI states instead...
|
||||
_uiSystem.TrySendUiMessage(uid, SharedAirAlarmInterfaceKey.Key, new AirAlarmUpdateAlarmModeMessage(controller.CurrentMode));
|
||||
return alarm.SensorData.Count != 0
|
||||
? alarm.SensorData.Values.Select(v => v.Pressure).Average()
|
||||
: 0f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send all thresholds to any open interface related to a given air alarm.
|
||||
/// </summary>
|
||||
public void SendThresholds(EntityUid uid, AtmosMonitorComponent? monitor = null, ApcPowerReceiverComponent? power = null, AirAlarmComponent? controller = null)
|
||||
public float CalculateTemperatureAverage(AirAlarmComponent alarm)
|
||||
{
|
||||
if (!Resolve(uid, ref monitor, ref power, ref controller)
|
||||
|| !power.Powered) return;
|
||||
return alarm.SensorData.Count != 0
|
||||
? alarm.SensorData.Values.Select(v => v.Temperature).Average()
|
||||
: 0f;
|
||||
}
|
||||
|
||||
if (monitor.PressureThreshold == null
|
||||
&& monitor.TemperatureThreshold == null
|
||||
&& monitor.GasThresholds == null)
|
||||
public void UpdateUI(EntityUid uid, AirAlarmComponent? alarm = null, DeviceNetworkComponent? devNet = null, AtmosAlarmableComponent? alarmable = null)
|
||||
{
|
||||
if (!Resolve(uid, ref alarm, ref devNet, ref alarmable))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Use BUI states instead...
|
||||
if (monitor.PressureThreshold != null)
|
||||
var pressure = CalculatePressureAverage(alarm);
|
||||
var temperature = CalculateTemperatureAverage(alarm);
|
||||
var dataToSend = new Dictionary<string, IAtmosDeviceData>();
|
||||
|
||||
if (alarm.CurrentTab != AirAlarmTab.Settings)
|
||||
{
|
||||
_uiSystem.TrySendUiMessage(uid, SharedAirAlarmInterfaceKey.Key, new AirAlarmUpdateAlarmThresholdMessage(AtmosMonitorThresholdType.Pressure, monitor.PressureThreshold));
|
||||
}
|
||||
|
||||
if (monitor.TemperatureThreshold != null)
|
||||
switch (alarm.CurrentTab)
|
||||
{
|
||||
_uiSystem.TrySendUiMessage(uid, SharedAirAlarmInterfaceKey.Key, new AirAlarmUpdateAlarmThresholdMessage(AtmosMonitorThresholdType.Temperature, monitor.TemperatureThreshold));
|
||||
}
|
||||
|
||||
if (monitor.GasThresholds != null)
|
||||
case AirAlarmTab.Vent:
|
||||
foreach (var (addr, data) in alarm.VentData)
|
||||
{
|
||||
foreach (var (gas, threshold) in monitor.GasThresholds)
|
||||
_uiSystem.TrySendUiMessage(uid, SharedAirAlarmInterfaceKey.Key, new AirAlarmUpdateAlarmThresholdMessage(AtmosMonitorThresholdType.Gas, threshold, gas));
|
||||
}
|
||||
dataToSend.Add(addr, data);
|
||||
}
|
||||
|
||||
public void OnAtmosUpdate(EntityUid uid, AirAlarmComponent alarm, AtmosDeviceUpdateEvent args)
|
||||
break;
|
||||
case AirAlarmTab.Scrubber:
|
||||
foreach (var (addr, data) in alarm.ScrubberData)
|
||||
{
|
||||
if (alarm.CurrentModeUpdater != null)
|
||||
alarm.CurrentModeUpdater.Update(uid);
|
||||
dataToSend.Add(addr, data);
|
||||
}
|
||||
|
||||
private const float _delay = 8f;
|
||||
private float _timer = 0f;
|
||||
break;
|
||||
case AirAlarmTab.Sensors:
|
||||
foreach (var (addr, data) in alarm.SensorData)
|
||||
{
|
||||
dataToSend.Add(addr, data);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var deviceCount = alarm.KnownDevices.Count;
|
||||
|
||||
if (!_atmosAlarmable.TryGetHighestAlert(uid, out var highestAlarm))
|
||||
{
|
||||
highestAlarm = AtmosAlarmType.Normal;
|
||||
}
|
||||
|
||||
_uiSystem.TrySetUiState(
|
||||
uid,
|
||||
SharedAirAlarmInterfaceKey.Key,
|
||||
new AirAlarmUIState(devNet.Address, deviceCount, pressure, temperature, dataToSend, alarm.CurrentMode, alarm.CurrentTab, highestAlarm.Value));
|
||||
}
|
||||
|
||||
private const float Delay = 8f;
|
||||
private float _timer;
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
_timer += frameTime;
|
||||
if (_timer >= _delay)
|
||||
if (_timer >= Delay)
|
||||
{
|
||||
_timer = 0f;
|
||||
foreach (var uid in _activeUserInterfaces)
|
||||
{
|
||||
// TODO: Awful idea, use BUI states instead...
|
||||
SendAirData(uid);
|
||||
_uiSystem.TrySetUiState(uid, SharedAirAlarmInterfaceKey.Key, new AirAlarmUIState());
|
||||
SyncAllSensors(uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,74 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Server.Atmos.Monitor.Components;
|
||||
using Content.Server.DeviceNetwork;
|
||||
using Content.Server.DeviceNetwork.Components;
|
||||
using Content.Server.DeviceNetwork.Systems;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Shared.Atmos.Monitor;
|
||||
using Content.Shared.Tag;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Atmos.Monitor.Systems
|
||||
namespace Content.Server.Atmos.Monitor.Systems;
|
||||
|
||||
public sealed class AtmosAlarmableSystem : EntitySystem
|
||||
{
|
||||
public sealed class AtmosAlarmableSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly AppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly AudioSystem _audioSystem = default!;
|
||||
[Dependency] private readonly DeviceNetworkSystem _deviceNet = default!;
|
||||
|
||||
/// <summary>
|
||||
/// An alarm. Has three valid states: Normal, Warning, Danger.
|
||||
/// Will attempt to fetch the tags from the alarming entity
|
||||
/// to send over.
|
||||
/// </summary>
|
||||
public const string AlertCmd = "atmos_alarm";
|
||||
|
||||
public const string AlertSource = "atmos_alarm_source";
|
||||
|
||||
public const string AlertTypes = "atmos_alarm_types";
|
||||
|
||||
/// <summary>
|
||||
/// Syncs alerts from this alarm receiver to other alarm receivers.
|
||||
/// Creates a network effect as a result. Note: if the alert receiver
|
||||
/// is not aware of the device beforehand, it will not sync.
|
||||
/// </summary>
|
||||
public const string SyncAlerts = "atmos_alarmable_sync_alerts";
|
||||
|
||||
public const string ResetAll = "atmos_alarmable_reset_all";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<AtmosAlarmableComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<AtmosAlarmableComponent, DeviceNetworkPacketEvent>(OnPacketRecv);
|
||||
SubscribeLocalEvent<AtmosAlarmableComponent, PowerChangedEvent>(OnPowerChange);
|
||||
}
|
||||
|
||||
private void OnInit(EntityUid uid, AtmosAlarmableComponent component, ComponentInit args)
|
||||
{
|
||||
TryUpdateAlert(
|
||||
uid,
|
||||
TryGetHighestAlert(uid, out var alarm) ? alarm.Value : AtmosAlarmType.Normal,
|
||||
component,
|
||||
false);
|
||||
}
|
||||
|
||||
private void OnPowerChange(EntityUid uid, AtmosAlarmableComponent component, PowerChangedEvent args)
|
||||
{
|
||||
if (!args.Powered)
|
||||
{
|
||||
Reset(uid, component);
|
||||
}
|
||||
else
|
||||
{
|
||||
TryUpdateAlert(
|
||||
uid,
|
||||
TryGetHighestAlert(uid, out var alarm) ? alarm.Value : AtmosAlarmType.Normal,
|
||||
component,
|
||||
false);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPacketRecv(EntityUid uid, AtmosAlarmableComponent component, DeviceNetworkPacketEvent args)
|
||||
@@ -20,23 +78,239 @@ namespace Content.Server.Atmos.Monitor.Systems
|
||||
if (!EntityManager.TryGetComponent(uid, out DeviceNetworkComponent? netConn))
|
||||
return;
|
||||
|
||||
if (args.Data.TryGetValue(DeviceNetworkConstants.Command, out string? cmd)
|
||||
&& cmd == AtmosMonitorSystem.AtmosMonitorAlarmCmd)
|
||||
if (!args.Data.TryGetValue(DeviceNetworkConstants.Command, out string? cmd)
|
||||
|| !args.Data.TryGetValue(AlertSource, out HashSet<string>? sourceTags))
|
||||
{
|
||||
// does it have a state & network max state?
|
||||
// does it have a source?
|
||||
// and can this be alarmed by the source?
|
||||
// if so, raise an alarm
|
||||
if (args.Data.TryGetValue(DeviceNetworkConstants.CmdSetState, out AtmosMonitorAlarmType state)
|
||||
&& args.Data.TryGetValue(AtmosMonitorSystem.AtmosMonitorAlarmNetMax, out AtmosMonitorAlarmType netMax)
|
||||
&& args.Data.TryGetValue(AtmosMonitorSystem.AtmosMonitorAlarmSrc, out string? source)
|
||||
&& component.AlarmedByPrototypes.Contains(source))
|
||||
return;
|
||||
}
|
||||
|
||||
var isValid = sourceTags.Any(source => component.SyncWithTags.Contains(source));
|
||||
|
||||
if (!isValid)
|
||||
{
|
||||
component.LastAlarmState = state;
|
||||
component.HighestNetworkState = netMax;
|
||||
RaiseLocalEvent(component.Owner, new AtmosMonitorAlarmEvent(state, netMax), true);
|
||||
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 AtmosAlarmType 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 : AtmosAlarmType.Normal;
|
||||
}
|
||||
|
||||
if (!TryGetHighestAlert(uid, out var netMax, component))
|
||||
{
|
||||
netMax = AtmosAlarmType.Normal;
|
||||
}
|
||||
|
||||
TryUpdateAlert(uid, netMax.Value, component);
|
||||
|
||||
break;
|
||||
case ResetAll:
|
||||
Reset(uid, component);
|
||||
break;
|
||||
case SyncAlerts:
|
||||
if (!args.Data.TryGetValue(SyncAlerts,
|
||||
out IReadOnlyDictionary<string, AtmosAlarmType>? 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, AtmosAlarmType 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, AtmosAlarmType 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, AtmosAlarmType.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 AtmosAlarmType? 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, AtmosAlarmType alarm, AtmosAlarmableComponent alarmable)
|
||||
{
|
||||
if (alarm == AtmosAlarmType.Danger)
|
||||
{
|
||||
_audioSystem.PlayPvs(alarmable.AlarmSound, uid, AudioParams.Default.WithVolume(alarmable.AlarmVolume));
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateAppearance(EntityUid uid, AtmosAlarmType alarm)
|
||||
{
|
||||
_appearance.SetData(uid, AtmosMonitorVisuals.AlarmType, alarm);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class AtmosAlarmEvent : EntityEventArgs
|
||||
{
|
||||
public AtmosAlarmType AlarmType { get; }
|
||||
|
||||
public AtmosAlarmEvent(AtmosAlarmType netMax)
|
||||
{
|
||||
AlarmType = netMax;
|
||||
}
|
||||
}
|
||||
|
||||
55
Content.Server/Atmos/Monitor/Systems/AtmosDeviceNetwork.cs
Normal file
55
Content.Server/Atmos/Monitor/Systems/AtmosDeviceNetwork.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using Content.Server.DeviceNetwork;
|
||||
using Content.Server.DeviceNetwork.Systems;
|
||||
using Content.Shared.Atmos.Monitor.Components;
|
||||
|
||||
namespace Content.Server.Atmos.Monitor.Systems;
|
||||
|
||||
/// <summary>
|
||||
/// Generic device network commands useful for atmos devices,
|
||||
/// as well as some helper commands.
|
||||
/// </summary>
|
||||
public sealed class AtmosDeviceNetworkSystem : EntitySystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Register a device's address on this device.
|
||||
/// </summary>
|
||||
public const string RegisterDevice = "atmos_register_device";
|
||||
|
||||
/// <summary>
|
||||
/// Synchronize the data this device has with the sender.
|
||||
/// </summary>
|
||||
public const string SyncData = "atmos_sync_data";
|
||||
|
||||
[Dependency] private readonly DeviceNetworkSystem _deviceNet = default!;
|
||||
|
||||
public void Register(EntityUid uid, string? address)
|
||||
{
|
||||
var registerPayload = new NetworkPayload
|
||||
{
|
||||
[DeviceNetworkConstants.Command] = RegisterDevice
|
||||
};
|
||||
|
||||
_deviceNet.QueuePacket(uid, address, registerPayload);
|
||||
}
|
||||
|
||||
public void Sync(EntityUid uid, string? address)
|
||||
{
|
||||
var syncPayload = new NetworkPayload
|
||||
{
|
||||
[DeviceNetworkConstants.Command] = SyncData
|
||||
};
|
||||
|
||||
_deviceNet.QueuePacket(uid, address, syncPayload);
|
||||
}
|
||||
|
||||
public void SetDeviceState(EntityUid uid, string address, IAtmosDeviceData data)
|
||||
{
|
||||
var payload = new NetworkPayload()
|
||||
{
|
||||
[DeviceNetworkConstants.Command] = DeviceNetworkConstants.CmdSetState,
|
||||
[DeviceNetworkConstants.CmdSetState] = data
|
||||
};
|
||||
|
||||
_deviceNet.QueuePacket(uid, address, payload);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Atmos.Monitor.Components;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Atmos.Piping.EntitySystems;
|
||||
@@ -9,62 +10,39 @@ using Content.Server.Power.Components;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Monitor;
|
||||
using Content.Shared.Tag;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Atmos.Monitor.Systems
|
||||
namespace Content.Server.Atmos.Monitor.Systems;
|
||||
|
||||
// AtmosMonitorSystem. Grabs all the AtmosAlarmables connected
|
||||
// to it via local APC net, and starts sending updates of the
|
||||
// current atmosphere. Monitors fire (which always triggers as
|
||||
// a danger), and atmos (which triggers based on set thresholds).
|
||||
public sealed class AtmosMonitorSystem : EntitySystem
|
||||
{
|
||||
// AtmosMonitorSystem. Grabs all the AtmosAlarmables connected
|
||||
// to it via local APC net, and starts sending updates of the
|
||||
// current atmosphere. Monitors fire (which always triggers as
|
||||
// a danger), and atmos (which triggers based on set thresholds).
|
||||
public sealed class AtmosMonitorSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
|
||||
[Dependency] private readonly AtmosDeviceSystem _atmosDeviceSystem = default!;
|
||||
[Dependency] private readonly DeviceNetworkSystem _deviceNetSystem = default!;
|
||||
[Dependency] private readonly TransformSystem _transformSystem = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
// Commands
|
||||
/// <summary>
|
||||
/// Command to alarm the network that something has happened.
|
||||
/// </summary>
|
||||
public const string AtmosMonitorAlarmCmd = "atmos_monitor_alarm_update";
|
||||
|
||||
/// <summary>
|
||||
/// Command to sync this monitor's alarm state with the rest of the network.
|
||||
/// </summary>
|
||||
public const string AtmosMonitorAlarmSyncCmd = "atmos_monitor_alarm_sync";
|
||||
|
||||
/// <summary>
|
||||
/// Command to reset all alarms on a network.
|
||||
/// </summary>
|
||||
public const string AtmosMonitorAlarmResetAllCmd = "atmos_monitor_alarm_reset_all";
|
||||
public const string AtmosMonitorSetThresholdCmd = "atmos_monitor_set_threshold";
|
||||
|
||||
// Packet data
|
||||
/// <summary>
|
||||
/// Data response that contains the threshold types in an atmos monitor alarm.
|
||||
/// </summary>
|
||||
public const string AtmosMonitorAlarmThresholdTypes = "atmos_monitor_alarm_threshold_types";
|
||||
public const string AtmosMonitorThresholdData = "atmos_monitor_threshold_data";
|
||||
|
||||
/// <summary>
|
||||
/// Data response that contains the source of an atmos alarm.
|
||||
/// </summary>
|
||||
public const string AtmosMonitorAlarmSrc = "atmos_monitor_alarm_source";
|
||||
public const string AtmosMonitorThresholdDataType = "atmos_monitor_threshold_type";
|
||||
|
||||
/// <summary>
|
||||
/// Data response that contains the maximum alarm in an atmos alarm network.
|
||||
/// </summary>
|
||||
public const string AtmosMonitorAlarmNetMax = "atmos_monitor_alarm_net_max";
|
||||
public const string AtmosMonitorThresholdGasType = "atmos_monitor_threshold_gas";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<AtmosMonitorComponent, ComponentInit>(OnAtmosMonitorInit);
|
||||
SubscribeLocalEvent<AtmosMonitorComponent, ComponentStartup>(OnAtmosMonitorStartup);
|
||||
SubscribeLocalEvent<AtmosMonitorComponent, ComponentShutdown>(OnAtmosMonitorShutdown);
|
||||
SubscribeLocalEvent<AtmosMonitorComponent, AtmosDeviceUpdateEvent>(OnAtmosUpdate);
|
||||
SubscribeLocalEvent<AtmosMonitorComponent, TileFireEvent>(OnFireEvent);
|
||||
SubscribeLocalEvent<AtmosMonitorComponent, PowerChangedEvent>(OnPowerChangedEvent);
|
||||
@@ -75,17 +53,19 @@ namespace Content.Server.Atmos.Monitor.Systems
|
||||
private void OnAtmosMonitorInit(EntityUid uid, AtmosMonitorComponent component, ComponentInit args)
|
||||
{
|
||||
if (component.TemperatureThresholdId != null)
|
||||
component.TemperatureThreshold = _prototypeManager.Index<AtmosAlarmThreshold>(component.TemperatureThresholdId);
|
||||
component.TemperatureThreshold = new(_prototypeManager.Index<AtmosAlarmThreshold>(component.TemperatureThresholdId));
|
||||
|
||||
if (component.PressureThresholdId != null)
|
||||
component.PressureThreshold = _prototypeManager.Index<AtmosAlarmThreshold>(component.PressureThresholdId);
|
||||
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, gasThreshold);
|
||||
component.GasThresholds.Add(gas, new(gasThreshold));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,59 +75,7 @@ namespace Content.Server.Atmos.Monitor.Systems
|
||||
&& TryComp<AtmosDeviceComponent>(uid, out var atmosDeviceComponent))
|
||||
{
|
||||
_atmosDeviceSystem.LeaveAtmosphere(atmosDeviceComponent);
|
||||
return;
|
||||
}
|
||||
|
||||
_checkPos.Add(uid);
|
||||
}
|
||||
|
||||
private void OnAtmosMonitorShutdown(EntityUid uid, AtmosMonitorComponent component, ComponentShutdown args)
|
||||
{
|
||||
if (_checkPos.Contains(uid)) _checkPos.Remove(uid);
|
||||
}
|
||||
|
||||
// hackiest shit ever but there's no PostStartup event
|
||||
private HashSet<EntityUid> _checkPos = new();
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
foreach (var uid in _checkPos)
|
||||
OpenAirOrReposition(uid);
|
||||
}
|
||||
|
||||
private void OpenAirOrReposition(EntityUid uid, AtmosMonitorComponent? component = null, AppearanceComponent? appearance = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component, ref appearance)) return;
|
||||
|
||||
var transform = Transform(component.Owner);
|
||||
|
||||
if (transform.GridUid == null)
|
||||
return;
|
||||
|
||||
// atmos alarms will first attempt to get the air
|
||||
// directly underneath it - if not, then it will
|
||||
// instead place itself directly in front of the tile
|
||||
// it is facing, and then visually shift itself back
|
||||
// via sprite offsets (SS13 style but fuck it)
|
||||
var coords = transform.Coordinates;
|
||||
var pos = _transformSystem.GetGridOrMapTilePosition(uid, transform);
|
||||
|
||||
if (_atmosphereSystem.IsTileAirBlocked(transform.GridUid.Value, pos))
|
||||
{
|
||||
var rotPos = transform.LocalRotation.RotateVec(new Vector2(0, -1));
|
||||
transform.Anchored = false;
|
||||
coords = coords.Offset(rotPos);
|
||||
transform.Coordinates = coords;
|
||||
|
||||
appearance.SetData(AtmosMonitorVisuals.Offset, - new Vector2i(0, -1));
|
||||
|
||||
transform.Anchored = true;
|
||||
}
|
||||
|
||||
GasMixture? air = _atmosphereSystem.GetContainingMixture(uid, true);
|
||||
component.TileGas = air;
|
||||
|
||||
_checkPos.Remove(uid);
|
||||
}
|
||||
|
||||
private void BeforePacketRecv(EntityUid uid, AtmosMonitorComponent component, BeforePacketSentEvent args)
|
||||
@@ -160,43 +88,55 @@ namespace Content.Server.Atmos.Monitor.Systems
|
||||
// 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)
|
||||
|| !EntityManager.TryGetComponent(uid, out AtmosAlarmableComponent? alarmable)
|
||||
|| !EntityManager.TryGetComponent(uid, out DeviceNetworkComponent? netConn))
|
||||
if (!args.Data.TryGetValue(DeviceNetworkConstants.Command, out string? cmd))
|
||||
{
|
||||
return;
|
||||
|
||||
// ignore packets from self, ignore from different frequency
|
||||
if (netConn.Address == args.SenderAddress) return;
|
||||
}
|
||||
|
||||
switch (cmd)
|
||||
{
|
||||
// sync on alarm or explicit sync
|
||||
case AtmosMonitorAlarmCmd:
|
||||
case AtmosMonitorAlarmSyncCmd:
|
||||
if (args.Data.TryGetValue(AtmosMonitorAlarmSrc, out string? src)
|
||||
&& alarmable.AlarmedByPrototypes.Contains(src)
|
||||
&& args.Data.TryGetValue(DeviceNetworkConstants.CmdSetState, out AtmosMonitorAlarmType state)
|
||||
&& !component.NetworkAlarmStates.TryAdd(args.SenderAddress, state))
|
||||
component.NetworkAlarmStates[args.SenderAddress] = state;
|
||||
case AtmosDeviceNetworkSystem.RegisterDevice:
|
||||
component.RegisteredDevices.Add(args.SenderAddress);
|
||||
break;
|
||||
case AtmosMonitorAlarmResetAllCmd:
|
||||
if (args.Data.TryGetValue(AtmosMonitorAlarmSrc, out string? resetSrc)
|
||||
&& alarmable.AlarmedByPrototypes.Contains(resetSrc))
|
||||
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))
|
||||
{
|
||||
component.LastAlarmState = AtmosMonitorAlarmType.Normal;
|
||||
component.NetworkAlarmStates.Clear();
|
||||
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;
|
||||
}
|
||||
|
||||
if (component.DisplayMaxAlarmInNet)
|
||||
{
|
||||
if (EntityManager.TryGetComponent(component.Owner, out AppearanceComponent? appearanceComponent))
|
||||
appearanceComponent.SetData(AtmosMonitorVisuals.AlarmType, component.HighestAlarmInNetwork);
|
||||
|
||||
if (component.HighestAlarmInNetwork == AtmosMonitorAlarmType.Danger) PlayAlertSound(uid, component);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void OnPowerChangedEvent(EntityUid uid, AtmosMonitorComponent component, PowerChangedEvent args)
|
||||
@@ -210,10 +150,6 @@ namespace Content.Server.Atmos.Monitor.Systems
|
||||
_atmosDeviceSystem.LeaveAtmosphere(atmosDeviceComponent);
|
||||
component.TileGas = null;
|
||||
}
|
||||
|
||||
// clear memory when power cycled
|
||||
component.LastAlarmState = AtmosMonitorAlarmType.Normal;
|
||||
component.NetworkAlarmStates.Clear();
|
||||
}
|
||||
else if (args.Powered)
|
||||
{
|
||||
@@ -223,11 +159,10 @@ namespace Content.Server.Atmos.Monitor.Systems
|
||||
var air = _atmosphereSystem.GetContainingMixture(uid, true);
|
||||
component.TileGas = air;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (EntityManager.TryGetComponent(component.Owner, out AppearanceComponent? appearanceComponent))
|
||||
appearanceComponent.SetData(AtmosMonitorVisuals.AlarmType, component.LastAlarmState);
|
||||
Alert(uid, component.LastAlarmState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnFireEvent(EntityUid uid, AtmosMonitorComponent component, ref TileFireEvent args)
|
||||
@@ -240,8 +175,11 @@ namespace Content.Server.Atmos.Monitor.Systems
|
||||
//
|
||||
// somebody else can reset it :sunglasses:
|
||||
if (component.MonitorFire
|
||||
&& component.LastAlarmState != AtmosMonitorAlarmType.Danger)
|
||||
Alert(uid, AtmosMonitorAlarmType.Danger, new []{ AtmosMonitorThresholdType.Temperature }, component); // technically???
|
||||
&& component.LastAlarmState != AtmosAlarmType.Danger)
|
||||
{
|
||||
component.TrippedThresholds.Add(AtmosMonitorThresholdType.Temperature);
|
||||
Alert(uid, AtmosAlarmType.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
|
||||
@@ -249,7 +187,10 @@ namespace Content.Server.Atmos.Monitor.Systems
|
||||
if (component.TemperatureThreshold != null
|
||||
&& component.TemperatureThreshold.CheckThreshold(args.Temperature, out var temperatureState)
|
||||
&& temperatureState > component.LastAlarmState)
|
||||
Alert(uid, AtmosMonitorAlarmType.Danger, new []{ AtmosMonitorThresholdType.Temperature }, component);
|
||||
{
|
||||
component.TrippedThresholds.Add(AtmosMonitorThresholdType.Temperature);
|
||||
Alert(uid, AtmosAlarmType.Danger, null, component);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAtmosUpdate(EntityUid uid, AtmosMonitorComponent component, AtmosDeviceUpdateEvent args)
|
||||
@@ -287,27 +228,41 @@ namespace Content.Server.Atmos.Monitor.Systems
|
||||
|
||||
if (!Resolve(uid, ref monitor)) return;
|
||||
|
||||
AtmosMonitorAlarmType state = AtmosMonitorAlarmType.Normal;
|
||||
List<AtmosMonitorThresholdType> alarmTypes = new();
|
||||
var state = AtmosAlarmType.Normal;
|
||||
HashSet<AtmosMonitorThresholdType> alarmTypes = new(monitor.TrippedThresholds);
|
||||
|
||||
if (monitor.TemperatureThreshold != null
|
||||
&& monitor.TemperatureThreshold.CheckThreshold(air.Temperature, out var temperatureState)
|
||||
&& temperatureState > state)
|
||||
&& monitor.TemperatureThreshold.CheckThreshold(air.Temperature, out var temperatureState))
|
||||
{
|
||||
if (temperatureState > state)
|
||||
{
|
||||
state = temperatureState;
|
||||
alarmTypes.Add(AtmosMonitorThresholdType.Temperature);
|
||||
}
|
||||
else if (temperatureState == AtmosAlarmType.Normal)
|
||||
{
|
||||
alarmTypes.Remove(AtmosMonitorThresholdType.Temperature);
|
||||
}
|
||||
}
|
||||
|
||||
if (monitor.PressureThreshold != null
|
||||
&& monitor.PressureThreshold.CheckThreshold(air.Pressure, out var pressureState)
|
||||
&& pressureState > state)
|
||||
)
|
||||
{
|
||||
if (pressureState > state)
|
||||
{
|
||||
state = pressureState;
|
||||
alarmTypes.Add(AtmosMonitorThresholdType.Pressure);
|
||||
}
|
||||
else if (pressureState == AtmosAlarmType.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;
|
||||
@@ -315,14 +270,23 @@ namespace Content.Server.Atmos.Monitor.Systems
|
||||
&& 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)
|
||||
if (state != monitor.LastAlarmState || !alarmTypes.SetEquals(monitor.TrippedThresholds))
|
||||
{
|
||||
Alert(uid, state, alarmTypes, monitor);
|
||||
}
|
||||
@@ -333,76 +297,24 @@ namespace Content.Server.Atmos.Monitor.Systems
|
||||
/// </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, IEnumerable<AtmosMonitorThresholdType>? alarms = null, AtmosMonitorComponent? monitor = null)
|
||||
public void Alert(EntityUid uid, AtmosAlarmType state, HashSet<AtmosMonitorThresholdType>? alarms = null, AtmosMonitorComponent? monitor = null)
|
||||
{
|
||||
if (!Resolve(uid, ref monitor)) return;
|
||||
|
||||
monitor.LastAlarmState = state;
|
||||
if (EntityManager.TryGetComponent(monitor.Owner, out AppearanceComponent? appearanceComponent))
|
||||
appearanceComponent.SetData(AtmosMonitorVisuals.AlarmType, monitor.LastAlarmState);
|
||||
monitor.TrippedThresholds = alarms ?? monitor.TrippedThresholds;
|
||||
|
||||
BroadcastAlertPacket(monitor, alarms);
|
||||
BroadcastAlertPacket(monitor);
|
||||
|
||||
if (state == AtmosMonitorAlarmType.Danger) PlayAlertSound(uid, monitor);
|
||||
|
||||
if (EntityManager.TryGetComponent(monitor.Owner, out AtmosAlarmableComponent? alarmable)
|
||||
&& !alarmable.IgnoreAlarms)
|
||||
RaiseLocalEvent(monitor.Owner, new AtmosMonitorAlarmEvent(monitor.LastAlarmState, monitor.HighestAlarmInNetwork), true);
|
||||
// TODO: Central system that grabs *all* alarms from wired network
|
||||
}
|
||||
|
||||
private void PlayAlertSound(EntityUid uid, AtmosMonitorComponent? monitor = null)
|
||||
{
|
||||
if (!Resolve(uid, ref monitor)) return;
|
||||
|
||||
SoundSystem.Play(monitor.AlarmSound.GetSound(), Filter.Pvs(uid), uid, AudioParams.Default.WithVolume(monitor.AlarmVolume));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets a single monitor's alarm.
|
||||
/// </summary>
|
||||
public void Reset(EntityUid uid) =>
|
||||
Alert(uid, AtmosMonitorAlarmType.Normal);
|
||||
|
||||
/// <summary>
|
||||
/// Resets a network's alarms, using this monitor as a source.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The resulting packet will have this monitor set as the source, using its prototype ID if it has one - otherwise just sending an empty string.
|
||||
/// </remarks>
|
||||
public void ResetAll(EntityUid uid, AtmosMonitorComponent? monitor = null)
|
||||
private void Reset(EntityUid uid)
|
||||
{
|
||||
if (!Resolve(uid, ref monitor)) return;
|
||||
|
||||
var prototype = Prototype(monitor.Owner);
|
||||
var payload = new NetworkPayload
|
||||
{
|
||||
[DeviceNetworkConstants.Command] = AtmosMonitorAlarmResetAllCmd,
|
||||
[AtmosMonitorAlarmSrc] = prototype != null ? prototype.ID : string.Empty
|
||||
};
|
||||
|
||||
_deviceNetSystem.QueuePacket(monitor.Owner, null, payload);
|
||||
monitor.NetworkAlarmStates.Clear();
|
||||
|
||||
Alert(uid, AtmosMonitorAlarmType.Normal, null, monitor);
|
||||
}
|
||||
|
||||
// (TODO: maybe just cache monitors in other monitors?)
|
||||
/// <summary>
|
||||
/// Syncs the current state of this monitor to the network (to avoid alerting other monitors).
|
||||
/// </summary>
|
||||
private void Sync(AtmosMonitorComponent monitor)
|
||||
{
|
||||
if (!monitor.NetEnabled) return;
|
||||
|
||||
var prototype = Prototype(monitor.Owner);
|
||||
var payload = new NetworkPayload
|
||||
{
|
||||
[DeviceNetworkConstants.Command] = AtmosMonitorAlarmSyncCmd,
|
||||
[DeviceNetworkConstants.CmdSetState] = monitor.LastAlarmState,
|
||||
[AtmosMonitorAlarmSrc] = prototype != null ? prototype.ID : string.Empty
|
||||
};
|
||||
|
||||
_deviceNetSystem.QueuePacket(monitor.Owner, null, payload);
|
||||
Alert(uid, AtmosAlarmType.Normal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -418,25 +330,27 @@ namespace Content.Server.Atmos.Monitor.Systems
|
||||
/// 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, IEnumerable<AtmosMonitorThresholdType>? alarms = null)
|
||||
private void BroadcastAlertPacket(AtmosMonitorComponent monitor, TagComponent? tags = null)
|
||||
{
|
||||
if (!monitor.NetEnabled) return;
|
||||
|
||||
string source = string.Empty;
|
||||
if (alarms == null) alarms = new List<AtmosMonitorThresholdType>();
|
||||
var prototype = Prototype(monitor.Owner);
|
||||
if (prototype != null) source = prototype.ID;
|
||||
if (!Resolve(monitor.Owner, ref tags))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var payload = new NetworkPayload
|
||||
{
|
||||
[DeviceNetworkConstants.Command] = AtmosMonitorAlarmCmd,
|
||||
[DeviceNetworkConstants.Command] = AtmosAlarmableSystem.AlertCmd,
|
||||
[DeviceNetworkConstants.CmdSetState] = monitor.LastAlarmState,
|
||||
[AtmosMonitorAlarmNetMax] = monitor.HighestAlarmInNetwork,
|
||||
[AtmosMonitorAlarmThresholdTypes] = alarms,
|
||||
[AtmosMonitorAlarmSrc] = source
|
||||
[AtmosAlarmableSystem.AlertSource] = tags.Tags,
|
||||
[AtmosAlarmableSystem.AlertTypes] = monitor.TrippedThresholds
|
||||
};
|
||||
|
||||
_deviceNetSystem.QueuePacket(monitor.Owner, null, payload);
|
||||
foreach (var addr in monitor.RegisteredDevices)
|
||||
{
|
||||
_deviceNetSystem.QueuePacket(monitor.Owner, addr, payload);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -464,17 +378,4 @@ namespace Content.Server.Atmos.Monitor.Systems
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class AtmosMonitorAlarmEvent : EntityEventArgs
|
||||
{
|
||||
public AtmosMonitorAlarmType Type { get; }
|
||||
public AtmosMonitorAlarmType HighestNetworkType { get; }
|
||||
|
||||
public AtmosMonitorAlarmEvent(AtmosMonitorAlarmType type, AtmosMonitorAlarmType netMax)
|
||||
{
|
||||
Type = type;
|
||||
HighestNetworkType = netMax;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Server.AlertLevel;
|
||||
using Content.Server.Atmos.Monitor.Components;
|
||||
using Content.Server.DeviceNetwork.Systems;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Shared.AlertLevel;
|
||||
@@ -8,50 +9,57 @@ using Content.Shared.Interaction;
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Robust.Server.GameObjects;
|
||||
|
||||
namespace Content.Server.Atmos.Monitor.Systems
|
||||
namespace Content.Server.Atmos.Monitor.Systems;
|
||||
|
||||
public sealed class FireAlarmSystem : EntitySystem
|
||||
{
|
||||
public sealed class FireAlarmSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly AtmosMonitorSystem _monitorSystem = default!;
|
||||
[Dependency] private readonly AtmosDeviceNetworkSystem _atmosDevNet = default!;
|
||||
[Dependency] private readonly AtmosAlarmableSystem _atmosAlarmable = default!;
|
||||
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<FireAlarmComponent, InteractHandEvent>(OnInteractHand);
|
||||
SubscribeLocalEvent<FireAlarmComponent, DeviceListUpdateEvent>(OnDeviceListSync);
|
||||
SubscribeLocalEvent<FireAlarmComponent, GotEmaggedEvent>(OnEmagged);
|
||||
}
|
||||
|
||||
private void OnDeviceListSync(EntityUid uid, FireAlarmComponent component, DeviceListUpdateEvent args)
|
||||
{
|
||||
_atmosDevNet.Register(uid, null);
|
||||
_atmosDevNet.Sync(uid, null);
|
||||
}
|
||||
|
||||
private void OnInteractHand(EntityUid uid, FireAlarmComponent component, InteractHandEvent args)
|
||||
{
|
||||
if (!_interactionSystem.InRangeUnobstructed(args.User, args.Target))
|
||||
return;
|
||||
|
||||
if (EntityManager.TryGetComponent(args.User, out ActorComponent? actor)
|
||||
&& EntityManager.TryGetComponent(uid, out AtmosMonitorComponent? monitor)
|
||||
&& this.IsPowered(uid, EntityManager))
|
||||
if (this.IsPowered(uid, EntityManager))
|
||||
{
|
||||
if (monitor.HighestAlarmInNetwork == AtmosMonitorAlarmType.Normal)
|
||||
if (!_atmosAlarmable.TryGetHighestAlert(uid, out var alarm))
|
||||
{
|
||||
_monitorSystem.Alert(uid, AtmosMonitorAlarmType.Danger);
|
||||
alarm = AtmosAlarmType.Normal;
|
||||
}
|
||||
|
||||
if (alarm == AtmosAlarmType.Normal)
|
||||
{
|
||||
_atmosAlarmable.ForceAlert(uid, AtmosAlarmType.Danger);
|
||||
}
|
||||
else
|
||||
{
|
||||
_monitorSystem.ResetAll(uid);
|
||||
_atmosAlarmable.ResetAllOnNetwork(uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEmagged(EntityUid uid, FireAlarmComponent component, GotEmaggedEvent args)
|
||||
{
|
||||
if (TryComp<AtmosMonitorComponent>(uid, out var atmosMonitor))
|
||||
if (TryComp<AtmosAlarmableComponent>(uid, out var alarmable))
|
||||
{
|
||||
if (atmosMonitor?.MonitorFire == true)
|
||||
{
|
||||
atmosMonitor.MonitorFire = false;
|
||||
_monitorSystem.Alert(uid, AtmosMonitorAlarmType.Emagged);
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
// Remove the atmos alarmable component permanently from this device.
|
||||
_atmosAlarmable.ForceAlert(uid, AtmosAlarmType.Emagged, alarmable);
|
||||
RemCompDeferred<AtmosAlarmableComponent>(uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ public sealed class AirAlarmPanicWire : BaseWireAction
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_airAlarmSystem = EntitySystem.Get<AirAlarmSystem>();
|
||||
_airAlarmSystem = EntityManager.System<AirAlarmSystem>();
|
||||
}
|
||||
|
||||
public override bool Cut(EntityUid user, Wire wire)
|
||||
|
||||
@@ -17,7 +17,7 @@ public sealed class AtmosMonitorDeviceNetWire : BaseWireAction
|
||||
private string _text = "NETW";
|
||||
private Color _color = Color.Orange;
|
||||
|
||||
private AtmosMonitorSystem _atmosMonitorSystem = default!;
|
||||
private AtmosAlarmableSystem _atmosAlarmableSystem = default!;
|
||||
|
||||
public override object StatusKey { get; } = AtmosMonitorAlarmWireActionKeys.Network;
|
||||
|
||||
@@ -27,7 +27,12 @@ public sealed class AtmosMonitorDeviceNetWire : BaseWireAction
|
||||
|
||||
if (IsPowered(wire.Owner) && EntityManager.TryGetComponent<AtmosMonitorComponent>(wire.Owner, out var monitor))
|
||||
{
|
||||
lightState = monitor.HighestAlarmInNetwork == AtmosMonitorAlarmType.Danger
|
||||
if (!_atmosAlarmableSystem.TryGetHighestAlert(wire.Owner, out var alarm))
|
||||
{
|
||||
alarm = AtmosAlarmType.Normal;
|
||||
}
|
||||
|
||||
lightState = alarm == AtmosAlarmType.Danger
|
||||
? StatusLightState.BlinkingFast
|
||||
: StatusLightState.On;
|
||||
}
|
||||
@@ -42,14 +47,14 @@ public sealed class AtmosMonitorDeviceNetWire : BaseWireAction
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_atmosMonitorSystem = EntitySystem.Get<AtmosMonitorSystem>();
|
||||
_atmosAlarmableSystem = EntityManager.System<AtmosAlarmableSystem>();
|
||||
}
|
||||
|
||||
public override bool Cut(EntityUid user, Wire wire)
|
||||
{
|
||||
if (EntityManager.TryGetComponent<AtmosMonitorComponent>(wire.Owner, out var monitor))
|
||||
if (EntityManager.TryGetComponent<AtmosAlarmableComponent>(wire.Owner, out var monitor))
|
||||
{
|
||||
monitor.NetEnabled = false;
|
||||
monitor.IgnoreAlarms = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -57,9 +62,9 @@ public sealed class AtmosMonitorDeviceNetWire : BaseWireAction
|
||||
|
||||
public override bool Mend(EntityUid user, Wire wire)
|
||||
{
|
||||
if (EntityManager.TryGetComponent<AtmosMonitorComponent>(wire.Owner, out var monitor))
|
||||
if (EntityManager.TryGetComponent<AtmosAlarmableComponent>(wire.Owner, out var monitor))
|
||||
{
|
||||
monitor.NetEnabled = true;
|
||||
monitor.IgnoreAlarms = false;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -69,7 +74,7 @@ public sealed class AtmosMonitorDeviceNetWire : BaseWireAction
|
||||
{
|
||||
if (_alarmOnPulse)
|
||||
{
|
||||
_atmosMonitorSystem.Alert(wire.Owner, AtmosMonitorAlarmType.Danger);
|
||||
_atmosAlarmableSystem.ForceAlert(wire.Owner, AtmosAlarmType.Danger);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -38,7 +38,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
SubscribeLocalEvent<GasVentPumpComponent, AtmosDeviceUpdateEvent>(OnGasVentPumpUpdated);
|
||||
SubscribeLocalEvent<GasVentPumpComponent, AtmosDeviceDisabledEvent>(OnGasVentPumpLeaveAtmosphere);
|
||||
SubscribeLocalEvent<GasVentPumpComponent, AtmosDeviceEnabledEvent>(OnGasVentPumpEnterAtmosphere);
|
||||
SubscribeLocalEvent<GasVentPumpComponent, AtmosMonitorAlarmEvent>(OnAtmosAlarm);
|
||||
SubscribeLocalEvent<GasVentPumpComponent, AtmosAlarmEvent>(OnAtmosAlarm);
|
||||
SubscribeLocalEvent<GasVentPumpComponent, PowerChangedEvent>(OnPowerChanged);
|
||||
SubscribeLocalEvent<GasVentPumpComponent, DeviceNetworkPacketEvent>(OnPacketRecv);
|
||||
SubscribeLocalEvent<GasVentPumpComponent, ComponentInit>(OnInit);
|
||||
@@ -158,13 +158,13 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
UpdateState(uid, component);
|
||||
}
|
||||
|
||||
private void OnAtmosAlarm(EntityUid uid, GasVentPumpComponent component, AtmosMonitorAlarmEvent args)
|
||||
private void OnAtmosAlarm(EntityUid uid, GasVentPumpComponent component, AtmosAlarmEvent args)
|
||||
{
|
||||
if (args.HighestNetworkType == AtmosMonitorAlarmType.Danger)
|
||||
if (args.AlarmType == AtmosAlarmType.Danger)
|
||||
{
|
||||
component.Enabled = false;
|
||||
}
|
||||
else if (args.HighestNetworkType == AtmosMonitorAlarmType.Normal)
|
||||
else if (args.AlarmType == AtmosAlarmType.Normal)
|
||||
{
|
||||
component.Enabled = true;
|
||||
}
|
||||
@@ -181,7 +181,6 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
private void OnPacketRecv(EntityUid uid, GasVentPumpComponent component, DeviceNetworkPacketEvent args)
|
||||
{
|
||||
if (!EntityManager.TryGetComponent(uid, out DeviceNetworkComponent? netConn)
|
||||
|| !EntityManager.TryGetComponent(uid, out AtmosAlarmableComponent? alarmable)
|
||||
|| !args.Data.TryGetValue(DeviceNetworkConstants.Command, out var cmd))
|
||||
return;
|
||||
|
||||
@@ -189,24 +188,19 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
|
||||
switch (cmd)
|
||||
{
|
||||
case AirAlarmSystem.AirAlarmSyncCmd:
|
||||
payload.Add(DeviceNetworkConstants.Command, AirAlarmSystem.AirAlarmSyncData);
|
||||
payload.Add(AirAlarmSystem.AirAlarmSyncData, component.ToAirAlarmData());
|
||||
case AtmosDeviceNetworkSystem.SyncData:
|
||||
payload.Add(DeviceNetworkConstants.Command, AtmosDeviceNetworkSystem.SyncData);
|
||||
payload.Add(AtmosDeviceNetworkSystem.SyncData, component.ToAirAlarmData());
|
||||
|
||||
_deviceNetSystem.QueuePacket(uid, args.SenderAddress, payload, device: netConn);
|
||||
|
||||
return;
|
||||
case AirAlarmSystem.AirAlarmSetData:
|
||||
if (!args.Data.TryGetValue(AirAlarmSystem.AirAlarmSetData, out GasVentPumpData? setData))
|
||||
case DeviceNetworkConstants.CmdSetState:
|
||||
if (!args.Data.TryGetValue(DeviceNetworkConstants.CmdSetState, out GasVentPumpData? setData))
|
||||
break;
|
||||
|
||||
component.FromAirAlarmData(setData);
|
||||
UpdateState(uid, component);
|
||||
alarmable.IgnoreAlarms = setData.IgnoreAlarms;
|
||||
payload.Add(DeviceNetworkConstants.Command, AirAlarmSystem.AirAlarmSetDataStatus);
|
||||
payload.Add(AirAlarmSystem.AirAlarmSetDataStatus, true);
|
||||
|
||||
_deviceNetSystem.QueuePacket(uid, null, payload, device: netConn);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
SubscribeLocalEvent<GasVentScrubberComponent, AtmosDeviceUpdateEvent>(OnVentScrubberUpdated);
|
||||
SubscribeLocalEvent<GasVentScrubberComponent, AtmosDeviceEnabledEvent>(OnVentScrubberEnterAtmosphere);
|
||||
SubscribeLocalEvent<GasVentScrubberComponent, AtmosDeviceDisabledEvent>(OnVentScrubberLeaveAtmosphere);
|
||||
SubscribeLocalEvent<GasVentScrubberComponent, AtmosMonitorAlarmEvent>(OnAtmosAlarm);
|
||||
SubscribeLocalEvent<GasVentScrubberComponent, AtmosAlarmEvent>(OnAtmosAlarm);
|
||||
SubscribeLocalEvent<GasVentScrubberComponent, PowerChangedEvent>(OnPowerChanged);
|
||||
SubscribeLocalEvent<GasVentScrubberComponent, DeviceNetworkPacketEvent>(OnPacketRecv);
|
||||
}
|
||||
@@ -124,13 +124,13 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnAtmosAlarm(EntityUid uid, GasVentScrubberComponent component, AtmosMonitorAlarmEvent args)
|
||||
private void OnAtmosAlarm(EntityUid uid, GasVentScrubberComponent component, AtmosAlarmEvent args)
|
||||
{
|
||||
if (args.HighestNetworkType == AtmosMonitorAlarmType.Danger)
|
||||
if (args.AlarmType == AtmosAlarmType.Danger)
|
||||
{
|
||||
component.Enabled = false;
|
||||
}
|
||||
else if (args.HighestNetworkType == AtmosMonitorAlarmType.Normal)
|
||||
else if (args.AlarmType == AtmosAlarmType.Normal)
|
||||
{
|
||||
component.Enabled = true;
|
||||
}
|
||||
@@ -147,7 +147,6 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
private void OnPacketRecv(EntityUid uid, GasVentScrubberComponent component, DeviceNetworkPacketEvent args)
|
||||
{
|
||||
if (!EntityManager.TryGetComponent(uid, out DeviceNetworkComponent? netConn)
|
||||
|| !EntityManager.TryGetComponent(uid, out AtmosAlarmableComponent? alarmable)
|
||||
|| !args.Data.TryGetValue(DeviceNetworkConstants.Command, out var cmd))
|
||||
return;
|
||||
|
||||
@@ -155,24 +154,19 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
|
||||
switch (cmd)
|
||||
{
|
||||
case AirAlarmSystem.AirAlarmSyncCmd:
|
||||
payload.Add(DeviceNetworkConstants.Command, AirAlarmSystem.AirAlarmSyncData);
|
||||
payload.Add(AirAlarmSystem.AirAlarmSyncData, component.ToAirAlarmData());
|
||||
case AtmosDeviceNetworkSystem.SyncData:
|
||||
payload.Add(DeviceNetworkConstants.Command, AtmosDeviceNetworkSystem.SyncData);
|
||||
payload.Add(AtmosDeviceNetworkSystem.SyncData, component.ToAirAlarmData());
|
||||
|
||||
_deviceNetSystem.QueuePacket(uid, args.SenderAddress, payload, device: netConn);
|
||||
|
||||
return;
|
||||
case AirAlarmSystem.AirAlarmSetData:
|
||||
if (!args.Data.TryGetValue(AirAlarmSystem.AirAlarmSetData, out GasVentScrubberData? setData))
|
||||
case DeviceNetworkConstants.CmdSetState:
|
||||
if (!args.Data.TryGetValue(DeviceNetworkConstants.CmdSetState, out GasVentScrubberData? setData))
|
||||
break;
|
||||
|
||||
component.FromAirAlarmData(setData);
|
||||
UpdateState(uid, component);
|
||||
alarmable.IgnoreAlarms = setData.IgnoreAlarms;
|
||||
payload.Add(DeviceNetworkConstants.Command, AirAlarmSystem.AirAlarmSetDataStatus);
|
||||
payload.Add(AirAlarmSystem.AirAlarmSetDataStatus, true);
|
||||
|
||||
_deviceNetSystem.QueuePacket(uid, null, payload, device: netConn);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Server.DeviceNetwork.Components;
|
||||
using System.Linq;
|
||||
using Content.Server.DeviceNetwork.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
@@ -25,7 +26,10 @@ public sealed class DeviceListSystem : EntitySystem
|
||||
if (!merge)
|
||||
deviceList.Devices.Clear();
|
||||
|
||||
deviceList.Devices.UnionWith(devices);
|
||||
var devicesList = devices.ToList();
|
||||
deviceList.Devices.UnionWith(devicesList);
|
||||
|
||||
RaiseLocalEvent(uid, new DeviceListUpdateEvent(devicesList));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -91,3 +95,13 @@ public sealed class DeviceListSystem : EntitySystem
|
||||
args.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class DeviceListUpdateEvent : EntityEventArgs
|
||||
{
|
||||
public DeviceListUpdateEvent(List<EntityUid> devices)
|
||||
{
|
||||
Devices = devices;
|
||||
}
|
||||
|
||||
public List<EntityUid> Devices { get; }
|
||||
}
|
||||
|
||||
@@ -30,6 +30,8 @@ public sealed class NetworkConfiguratorSystem : EntitySystem
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<NetworkConfiguratorComponent, MapInitEvent>(OnMapInit);
|
||||
|
||||
//Interaction
|
||||
SubscribeLocalEvent<NetworkConfiguratorComponent, AfterInteractEvent>((uid, component, args) => OnUsed(uid, component, args.Target, args.User, args.CanReach)); //TODO: Replace with utility verb?
|
||||
|
||||
@@ -63,6 +65,12 @@ public sealed class NetworkConfiguratorSystem : EntitySystem
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMapInit(EntityUid uid, NetworkConfiguratorComponent component, MapInitEvent args)
|
||||
{
|
||||
component.Devices.Clear();
|
||||
UpdateUiState(uid, component);
|
||||
}
|
||||
|
||||
private void TryAddNetworkDevice(EntityUid? targetUid, EntityUid configuratorUid, EntityUid userUid,
|
||||
NetworkConfiguratorComponent? configurator = null)
|
||||
{
|
||||
@@ -77,19 +85,35 @@ public sealed class NetworkConfiguratorSystem : EntitySystem
|
||||
if (!targetUid.HasValue || !Resolve(targetUid.Value, ref device, false))
|
||||
return;
|
||||
|
||||
if (string.IsNullOrEmpty(device.Address))
|
||||
var address = device.Address;
|
||||
if (string.IsNullOrEmpty(address))
|
||||
{
|
||||
_popupSystem.PopupCursor(Loc.GetString("network-configurator-device-failed", ("device", targetUid)), Filter.Entities(userUid));
|
||||
// This primarily checks if the entity in question is pre-map init or not.
|
||||
// This is because otherwise, anything that uses DeviceNetwork will not
|
||||
// have an address populated, as all devices that use DeviceNetwork
|
||||
// obtain their address on map init. If the entity is post-map init,
|
||||
// and it still doesn't have an address, it will fail. Otherwise,
|
||||
// it stores the entity's UID as a string for visual effect, that way
|
||||
// a mapper can reference the devices they've gathered by UID, instead of
|
||||
// by device network address. These entries, if the multitool is still in
|
||||
// the map after it being saved, are cleared upon mapinit.
|
||||
if (MetaData(targetUid.Value).EntityLifeStage == EntityLifeStage.MapInitialized)
|
||||
{
|
||||
_popupSystem.PopupCursor(Loc.GetString("network-configurator-device-failed", ("device", targetUid)),
|
||||
Filter.Entities(userUid));
|
||||
return;
|
||||
}
|
||||
|
||||
address = $"UID: {targetUid.Value.ToString()}";
|
||||
}
|
||||
|
||||
if (configurator.Devices.ContainsValue(targetUid.Value))
|
||||
{
|
||||
_popupSystem.PopupCursor(Loc.GetString("network-configurator-device-already-saved", ("device", targetUid)), Filter.Entities(userUid));
|
||||
return;
|
||||
}
|
||||
|
||||
configurator.Devices.Add(device.Address, targetUid.Value);
|
||||
configurator.Devices.Add(address, targetUid.Value);
|
||||
_popupSystem.PopupCursor(Loc.GetString("network-configurator-device-saved", ("address", device.Address), ("device", targetUid)),
|
||||
Filter.Entities(userUid), PopupType.Medium);
|
||||
|
||||
|
||||
@@ -25,6 +25,8 @@ namespace Content.Server.Doors.Components
|
||||
[DataField("lockedPryTimeModifier")]
|
||||
public float LockedPryTimeModifier = 1.5f;
|
||||
|
||||
[DataField("autocloseDelay")] public TimeSpan AutocloseDelay = TimeSpan.FromSeconds(3f);
|
||||
|
||||
public bool EmergencyPressureStop()
|
||||
{
|
||||
var doorSys = EntitySystem.Get<DoorSystem>();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Content.Server.Atmos.Monitor.Components;
|
||||
using Content.Server.Atmos.Monitor.Systems;
|
||||
using Content.Server.Doors.Components;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Shared.Atmos.Monitor;
|
||||
using Content.Shared.Doors;
|
||||
using Content.Shared.Doors.Components;
|
||||
@@ -12,6 +13,7 @@ namespace Content.Server.Doors.Systems
|
||||
public sealed class FirelockSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedDoorSystem _doorSystem = default!;
|
||||
[Dependency] private readonly AtmosAlarmableSystem _atmosAlarmable = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -21,9 +23,10 @@ namespace Content.Server.Doors.Systems
|
||||
SubscribeLocalEvent<FirelockComponent, BeforeDoorDeniedEvent>(OnBeforeDoorDenied);
|
||||
SubscribeLocalEvent<FirelockComponent, DoorGetPryTimeModifierEvent>(OnDoorGetPryTimeModifier);
|
||||
SubscribeLocalEvent<FirelockComponent, BeforeDoorPryEvent>(OnBeforeDoorPry);
|
||||
SubscribeLocalEvent<FirelockComponent, DoorStateChangedEvent>(OnUpdateState);
|
||||
|
||||
SubscribeLocalEvent<FirelockComponent, BeforeDoorAutoCloseEvent>(OnBeforeDoorAutoclose);
|
||||
SubscribeLocalEvent<FirelockComponent, AtmosMonitorAlarmEvent>(OnAtmosAlarm);
|
||||
SubscribeLocalEvent<FirelockComponent, AtmosAlarmEvent>(OnAtmosAlarm);
|
||||
}
|
||||
|
||||
private void OnBeforeDoorOpened(EntityUid uid, FirelockComponent component, BeforeDoorOpenedEvent args)
|
||||
@@ -60,30 +63,40 @@ namespace Content.Server.Doors.Systems
|
||||
}
|
||||
}
|
||||
|
||||
private void OnUpdateState(EntityUid uid, FirelockComponent component, DoorStateChangedEvent args)
|
||||
{
|
||||
var ev = new BeforeDoorAutoCloseEvent();
|
||||
RaiseLocalEvent(uid, ev);
|
||||
if (ev.Cancelled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_doorSystem.SetNextStateChange(uid, component.AutocloseDelay);
|
||||
}
|
||||
|
||||
private void OnBeforeDoorAutoclose(EntityUid uid, FirelockComponent component, BeforeDoorAutoCloseEvent args)
|
||||
{
|
||||
if (!this.IsPowered(uid, EntityManager))
|
||||
args.Cancel();
|
||||
|
||||
// Make firelocks autoclose, but only if the last alarm type it
|
||||
// remembers was a danger. This is to prevent people from
|
||||
// flooding hallways with endless bad air/fire.
|
||||
if (!EntityManager.TryGetComponent(uid, out AtmosAlarmableComponent? alarmable))
|
||||
{
|
||||
args.Cancel();
|
||||
return;
|
||||
}
|
||||
if (alarmable.HighestNetworkState != AtmosMonitorAlarmType.Danger)
|
||||
if (_atmosAlarmable.TryGetHighestAlert(uid, out var alarm) && alarm != AtmosAlarmType.Danger || alarm == null)
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
private void OnAtmosAlarm(EntityUid uid, FirelockComponent component, AtmosMonitorAlarmEvent args)
|
||||
private void OnAtmosAlarm(EntityUid uid, FirelockComponent component, AtmosAlarmEvent args)
|
||||
{
|
||||
if (!TryComp<DoorComponent>(uid, out var doorComponent)) return;
|
||||
|
||||
if (args.HighestNetworkType == AtmosMonitorAlarmType.Normal)
|
||||
if (args.AlarmType == AtmosAlarmType.Normal)
|
||||
{
|
||||
if (doorComponent.State == DoorState.Closed)
|
||||
_doorSystem.TryOpen(uid);
|
||||
}
|
||||
else if (args.HighestNetworkType == AtmosMonitorAlarmType.Danger)
|
||||
else if (args.AlarmType == AtmosAlarmType.Danger)
|
||||
{
|
||||
component.EmergencyPressureStop();
|
||||
}
|
||||
|
||||
267
Content.Shared/Atmos/Monitor/AtmosAlarmThreshold.cs
Normal file
267
Content.Shared/Atmos/Monitor/AtmosAlarmThreshold.cs
Normal file
@@ -0,0 +1,267 @@
|
||||
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
|
||||
{
|
||||
[IdDataField]
|
||||
public string ID { get; } = default!;
|
||||
[ViewVariables]
|
||||
[DataField("ignore")]
|
||||
public bool Ignore;
|
||||
|
||||
// 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 => CalculateWarningBound(AtmosMonitorThresholdBound.Upper);
|
||||
|
||||
[ViewVariables]
|
||||
public float? LowerWarningBound => 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 AtmosAlarmType state)
|
||||
{
|
||||
state = AtmosAlarmType.Normal;
|
||||
if (Ignore)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (value >= UpperBound || value <= LowerBound)
|
||||
{
|
||||
state = AtmosAlarmType.Danger;
|
||||
return true;
|
||||
}
|
||||
if (value >= UpperWarningBound || value <= LowerWarningBound)
|
||||
{
|
||||
state = AtmosAlarmType.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;
|
||||
}
|
||||
|
||||
var 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;
|
||||
}
|
||||
|
||||
var 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;
|
||||
|
||||
var upperWarning = (float) (input / UpperBound);
|
||||
var 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;
|
||||
|
||||
var lowerWarning = (float) (input / LowerBound);
|
||||
var 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
|
||||
{
|
||||
AlarmType,
|
||||
}
|
||||
@@ -1,259 +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);
|
||||
}
|
||||
|
||||
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 false;
|
||||
}
|
||||
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
12
Content.Shared/Atmos/Monitor/AtmosAlarmType.cs
Normal file
12
Content.Shared/Atmos/Monitor/AtmosAlarmType.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Atmos.Monitor;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum AtmosAlarmType : sbyte
|
||||
{
|
||||
Normal = 0,
|
||||
Warning = 1,
|
||||
Danger = 2, // 1 << 1 is the exact same thing and we're not really doing **bitmasking** are we?
|
||||
Emagged = 3,
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Atmos.Monitor
|
||||
{
|
||||
[Serializable, NetSerializable]
|
||||
public enum AtmosMonitorAlarmType : sbyte
|
||||
{
|
||||
Normal = 0,
|
||||
Warning = 1,
|
||||
Danger = 2, // 1 << 1 is the exact same thing and we're not really doing **bitmasking** are we?
|
||||
Emagged = 3,
|
||||
}
|
||||
}
|
||||
51
Content.Shared/Atmos/Monitor/AtmosSensorData.cs
Normal file
51
Content.Shared/Atmos/Monitor/AtmosSensorData.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using Content.Shared.Atmos.Monitor.Components;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Atmos.Monitor;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class AtmosSensorData : IAtmosDeviceData
|
||||
{
|
||||
public AtmosSensorData(float pressure, float temperature, float totalMoles, AtmosAlarmType alarmState, Dictionary<Gas, float> gases, AtmosAlarmThreshold pressureThreshold, AtmosAlarmThreshold temperatureThreshold, Dictionary<Gas, AtmosAlarmThreshold> gasThresholds)
|
||||
{
|
||||
Pressure = pressure;
|
||||
Temperature = temperature;
|
||||
TotalMoles = totalMoles;
|
||||
AlarmState = alarmState;
|
||||
Gases = gases;
|
||||
PressureThreshold = pressureThreshold;
|
||||
TemperatureThreshold = temperatureThreshold;
|
||||
GasThresholds = gasThresholds;
|
||||
}
|
||||
|
||||
public bool Enabled { get; set; }
|
||||
public bool Dirty { get; set; }
|
||||
public bool IgnoreAlarms { get; set; }
|
||||
|
||||
/// Most fields are readonly, because it's data that's meant to be transmitted.
|
||||
|
||||
/// <summary>
|
||||
/// Current pressure detected by this sensor.
|
||||
/// </summary>
|
||||
public float Pressure { get; }
|
||||
/// <summary>
|
||||
/// Current temperature detected by this sensor.
|
||||
/// </summary>
|
||||
public float Temperature { get; }
|
||||
/// <summary>
|
||||
/// Current amount of moles detected by this sensor.
|
||||
/// </summary>
|
||||
public float TotalMoles { get; }
|
||||
/// <summary>
|
||||
/// Current alarm state of this sensor. Does not reflect the highest alarm state on the network.
|
||||
/// </summary>
|
||||
public AtmosAlarmType AlarmState { get; }
|
||||
/// <summary>
|
||||
/// Current number of gases on this sensor.
|
||||
/// </summary>
|
||||
public Dictionary<Gas, float> Gases { get; }
|
||||
|
||||
public AtmosAlarmThreshold PressureThreshold { get; }
|
||||
public AtmosAlarmThreshold TemperatureThreshold { get; }
|
||||
public Dictionary<Gas, AtmosAlarmThreshold> GasThresholds { get; }
|
||||
}
|
||||
@@ -1,106 +1,98 @@
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Atmos.Monitor.Components
|
||||
{
|
||||
[Serializable, NetSerializable]
|
||||
public enum SharedAirAlarmInterfaceKey
|
||||
{
|
||||
Key
|
||||
}
|
||||
namespace Content.Shared.Atmos.Monitor.Components;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum AirAlarmMode
|
||||
{
|
||||
[Serializable, NetSerializable]
|
||||
public enum SharedAirAlarmInterfaceKey
|
||||
{
|
||||
Key
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum AirAlarmMode
|
||||
{
|
||||
None,
|
||||
Filtering,
|
||||
Fill,
|
||||
Panic,
|
||||
Replace
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum AirAlarmWireStatus
|
||||
{
|
||||
[Serializable, NetSerializable]
|
||||
public enum AirAlarmWireStatus
|
||||
{
|
||||
Power,
|
||||
Access,
|
||||
Panic,
|
||||
DeviceSync
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public readonly struct AirAlarmAirData
|
||||
{
|
||||
public readonly float? Pressure { get; }
|
||||
public readonly float? Temperature { get; }
|
||||
public readonly float? TotalMoles { get; }
|
||||
public readonly AtmosMonitorAlarmType AlarmState { get; }
|
||||
|
||||
private readonly Dictionary<Gas, float>? _gases;
|
||||
public readonly IReadOnlyDictionary<Gas, float>? Gases { get => _gases; }
|
||||
|
||||
public AirAlarmAirData(float? pressure, float? temperature, float? moles, AtmosMonitorAlarmType state, Dictionary<Gas, float>? gases)
|
||||
{
|
||||
Pressure = pressure;
|
||||
Temperature = temperature;
|
||||
TotalMoles = moles;
|
||||
AlarmState = state;
|
||||
_gases = gases;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IAtmosDeviceData
|
||||
{
|
||||
public interface IAtmosDeviceData
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
public bool Dirty { get; set; }
|
||||
public bool IgnoreAlarms { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
// would be nice to include the entire state here
|
||||
// but it's already handled by messages
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class AirAlarmUIState : BoundUserInterfaceState
|
||||
{}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class AirAlarmResyncAllDevicesMessage : BoundUserInterfaceMessage
|
||||
{}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class AirAlarmSetAddressMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public string Address { get; }
|
||||
|
||||
public AirAlarmSetAddressMessage(string address)
|
||||
[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, AtmosAlarmType alarmType)
|
||||
{
|
||||
Address = address;
|
||||
}
|
||||
DeviceCount = deviceCount;
|
||||
PressureAverage = pressureAverage;
|
||||
TemperatureAverage = temperatureAverage;
|
||||
DeviceData = deviceData;
|
||||
Mode = mode;
|
||||
Tab = tab;
|
||||
AlarmType = alarmType;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class AirAlarmUpdateAirDataMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public AirAlarmAirData AirData;
|
||||
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 AtmosAlarmType AlarmType { get; }
|
||||
}
|
||||
|
||||
public AirAlarmUpdateAirDataMessage(AirAlarmAirData airData)
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class AirAlarmTabSetMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public AirAlarmTabSetMessage(AirAlarmTab tab)
|
||||
{
|
||||
AirData = airData;
|
||||
}
|
||||
Tab = tab;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class AirAlarmUpdateAlarmModeMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
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
|
||||
{
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class AirAlarmUpdateDeviceDataMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public string Address { get; }
|
||||
public IAtmosDeviceData Data { get; }
|
||||
|
||||
@@ -109,22 +101,29 @@ namespace Content.Shared.Atmos.Monitor.Components
|
||||
Address = addr;
|
||||
Data = data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class AirAlarmUpdateAlarmThresholdMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
[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(AtmosMonitorThresholdType type, AtmosAlarmThreshold threshold, Gas? gas = null)
|
||||
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 +0,0 @@
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Atmos.Monitor.Components
|
||||
{
|
||||
[Serializable, NetSerializable]
|
||||
public enum FireAlarmWireStatus
|
||||
{
|
||||
Power,
|
||||
Alarm
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,17 @@ namespace Content.Shared.Atmos.Piping.Unary.Components
|
||||
ExternalPressureBound = Atmospherics.OneAtmosphere,
|
||||
InternalPressureBound = 0f
|
||||
};
|
||||
|
||||
public static GasVentPumpData ReplaceModePreset = new GasVentPumpData
|
||||
{
|
||||
Enabled = false,
|
||||
IgnoreAlarms = true,
|
||||
Dirty = true,
|
||||
PumpDirection = VentPumpDirection.Releasing,
|
||||
PressureChecks = VentPressureBound.ExternalBound,
|
||||
ExternalPressureBound = Atmospherics.OneAtmosphere,
|
||||
InternalPressureBound = 0f
|
||||
};
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace Content.Shared.Atmos.Piping.Unary.Components
|
||||
public static GasVentScrubberData FilterModePreset = new GasVentScrubberData
|
||||
{
|
||||
Enabled = true,
|
||||
FilterGases = GasVentScrubberData.DefaultFilterGases,
|
||||
FilterGases = new(GasVentScrubberData.DefaultFilterGases),
|
||||
PumpDirection = ScrubberPumpDirection.Scrubbing,
|
||||
VolumeRate = 200f,
|
||||
WideNet = false
|
||||
@@ -40,7 +40,7 @@ namespace Content.Shared.Atmos.Piping.Unary.Components
|
||||
{
|
||||
Enabled = false,
|
||||
Dirty = true,
|
||||
FilterGases = GasVentScrubberData.DefaultFilterGases,
|
||||
FilterGases = new(GasVentScrubberData.DefaultFilterGases),
|
||||
PumpDirection = ScrubberPumpDirection.Scrubbing,
|
||||
VolumeRate = 200f,
|
||||
WideNet = false
|
||||
@@ -50,7 +50,18 @@ namespace Content.Shared.Atmos.Piping.Unary.Components
|
||||
{
|
||||
Enabled = true,
|
||||
Dirty = true,
|
||||
FilterGases = GasVentScrubberData.DefaultFilterGases,
|
||||
FilterGases = new(GasVentScrubberData.DefaultFilterGases),
|
||||
PumpDirection = ScrubberPumpDirection.Siphoning,
|
||||
VolumeRate = 200f,
|
||||
WideNet = false
|
||||
};
|
||||
|
||||
public static GasVentScrubberData ReplaceModePreset = new GasVentScrubberData
|
||||
{
|
||||
Enabled = true,
|
||||
IgnoreAlarms = true,
|
||||
Dirty = true,
|
||||
FilterGases = new(GasVentScrubberData.DefaultFilterGases),
|
||||
PumpDirection = ScrubberPumpDirection.Siphoning,
|
||||
VolumeRate = 200f,
|
||||
WideNet = false
|
||||
|
||||
@@ -18,10 +18,9 @@ air-alarm-ui-window-pressure = {$pressure} kPa
|
||||
air-alarm-ui-window-temperature = {$tempC} C ({$temperature} K)
|
||||
air-alarm-ui-window-alarm-state = {$state}
|
||||
|
||||
air-alarm-ui-window-tab-gas = Gases
|
||||
air-alarm-ui-window-tab-vents = Vents
|
||||
air-alarm-ui-window-tab-scrubbers = Scrubbers
|
||||
air-alarm-ui-window-tab-thresholds = Thresholds
|
||||
air-alarm-ui-window-tab-sensors = Sensors
|
||||
|
||||
air-alarm-ui-gases = {$gas}: {$amount} mol ({$percentage}%)
|
||||
|
||||
@@ -48,6 +47,8 @@ air-alarm-ui-scrubber-wide-net-label = WideNet
|
||||
|
||||
### Thresholds
|
||||
|
||||
air-alarm-ui-sensor-gases = Gases
|
||||
air-alarm-ui-sensor-thresholds = Thresholds
|
||||
air-alarm-ui-thresholds-pressure-title = Pressure (kPa)
|
||||
air-alarm-ui-thresholds-temperature-title = Temperature (K)
|
||||
air-alarm-ui-thresholds-gas-title = {$gas} (%)
|
||||
|
||||
@@ -19,3 +19,4 @@ device-frequency-prototype-name-surveillance-camera-entertainment = Entertainmen
|
||||
# prefixes for randomly generated device addresses
|
||||
device-address-prefix-vent = Vnt-
|
||||
device-address-prefix-scrubber = Scr-
|
||||
device-address-prefix-sensor = Sns-
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
description: Apply crowbar.
|
||||
components:
|
||||
- type: AtmosAlarmable
|
||||
alarmedBy:
|
||||
syncWith:
|
||||
- FireAlarm
|
||||
- AirAlarm
|
||||
- type: ApcPowerReceiver
|
||||
|
||||
@@ -32,9 +32,25 @@
|
||||
receiveFrequencyId: AtmosMonitor
|
||||
transmitFrequencyId: AtmosMonitor
|
||||
prefix: device-address-prefix-vent
|
||||
- type: AtmosAlarmable
|
||||
alarmedBy:
|
||||
- AirAlarm
|
||||
sendBroadcastAttemptEvent: true
|
||||
- type: WiredNetworkConnection
|
||||
- type: AtmosDevice
|
||||
- type: AtmosMonitor
|
||||
temperatureThreshold: stationTemperature
|
||||
pressureThreshold: stationPressure
|
||||
gasThresholds:
|
||||
Oxygen: stationOxygen
|
||||
Nitrogen: ignore
|
||||
CarbonDioxide: stationCO2
|
||||
Plasma: danger # everything below is usually bad
|
||||
Tritium: danger
|
||||
WaterVapor: danger
|
||||
Miasma: danger
|
||||
NitrousOxide: danger
|
||||
Frezon: danger
|
||||
- type: Tag
|
||||
tags:
|
||||
- GasVent
|
||||
- type: Sprite
|
||||
netsync: false
|
||||
drawdepth: FloorObjects
|
||||
@@ -108,9 +124,22 @@
|
||||
receiveFrequencyId: AtmosMonitor
|
||||
transmitFrequencyId: AtmosMonitor
|
||||
prefix: device-address-prefix-scrubber
|
||||
- type: AtmosAlarmable
|
||||
alarmedBy:
|
||||
- AirAlarm
|
||||
- type: AtmosMonitor
|
||||
temperatureThreshold: stationTemperature
|
||||
pressureThreshold: stationPressure
|
||||
gasThresholds:
|
||||
Oxygen: stationOxygen
|
||||
Nitrogen: ignore
|
||||
CarbonDioxide: stationCO2
|
||||
Plasma: danger # everything below is usually bad
|
||||
Tritium: danger
|
||||
WaterVapor: danger
|
||||
Miasma: danger
|
||||
NitrousOxide: danger
|
||||
Frezon: danger
|
||||
- type: Tag
|
||||
tags:
|
||||
- GasScrubber
|
||||
- type: Sprite
|
||||
netsync: false
|
||||
drawdepth: FloorObjects
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
- type: entity
|
||||
id: AirSensor
|
||||
name: air sensor
|
||||
description: Air sensor. It senses air.
|
||||
placement:
|
||||
mode: SnapgridCenter
|
||||
components:
|
||||
- type: Transform
|
||||
anchored: true
|
||||
- type: Damageable
|
||||
damageContainer: Inorganic
|
||||
damageModifierSet: Metallic
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
!type:DamageTrigger
|
||||
damage: 100
|
||||
behaviors:
|
||||
- !type:DoActsBehavior
|
||||
acts: ["Destruction"]
|
||||
- type: Physics
|
||||
canCollide: false
|
||||
- type: Fixtures
|
||||
fixtures:
|
||||
- shape:
|
||||
!type:PhysShapeAabb
|
||||
bounds: "-0.25,-0.25,0.25,0.25"
|
||||
mass: 5
|
||||
mask:
|
||||
- ItemMask
|
||||
restitution: 0.3
|
||||
friction: 0.2
|
||||
- type: Clickable
|
||||
- type: InteractionOutline
|
||||
- type: ApcPowerReceiver
|
||||
- type: ExtensionCableReceiver
|
||||
- type: DeviceNetwork
|
||||
deviceNetId: AtmosDevices
|
||||
receiveFrequencyId: AtmosMonitor
|
||||
transmitFrequencyId: AtmosMonitor
|
||||
prefix: device-address-prefix-sensor
|
||||
sendBroadcastAttemptEvent: true
|
||||
- type: WiredNetworkConnection
|
||||
- type: AtmosDevice
|
||||
- type: AtmosMonitor
|
||||
temperatureThreshold: stationTemperature
|
||||
pressureThreshold: stationPressure
|
||||
gasThresholds:
|
||||
Oxygen: stationOxygen
|
||||
Nitrogen: ignore
|
||||
CarbonDioxide: stationCO2
|
||||
Plasma: danger # everything below is usually bad
|
||||
Tritium: danger
|
||||
WaterVapor: danger
|
||||
Miasma: danger
|
||||
NitrousOxide: danger
|
||||
Frezon: danger
|
||||
- type: Tag
|
||||
tags:
|
||||
- AirSensor
|
||||
- type: AccessReader
|
||||
access: [ [ "Atmospherics" ] ]
|
||||
- type: GenericVisualizer
|
||||
visuals:
|
||||
enum.PowerDeviceVisuals.Powered:
|
||||
sensor:
|
||||
True: { state: gsensor1 }
|
||||
False: { state: gsensor0 }
|
||||
- type: Construction
|
||||
graph: AirSensor
|
||||
node: sensor
|
||||
- type: Sprite
|
||||
netsync: false
|
||||
drawdepth: FloorObjects
|
||||
sprite: Structures/Specific/Atmospherics/sensor.rsi
|
||||
layers:
|
||||
- state: gsensor1
|
||||
map: [ "sensor" ]
|
||||
|
||||
- type: entity
|
||||
parent: BaseItem
|
||||
id: AirSensorAssembly
|
||||
name: air sensor assembly
|
||||
description: Air sensor assembly. An assembly of air sensors?
|
||||
components:
|
||||
- type: Item
|
||||
size: 10
|
||||
- type: Anchorable
|
||||
- type: Construction
|
||||
graph: AirSensor
|
||||
node: assembly
|
||||
- type: Sprite
|
||||
netsync: false
|
||||
drawdepth: FloorObjects
|
||||
sprite: Structures/Specific/Atmospherics/sensor.rsi
|
||||
layers:
|
||||
- state: gsensor0
|
||||
@@ -16,21 +16,23 @@
|
||||
sendBroadcastAttemptEvent: true
|
||||
- type: WiredNetworkConnection
|
||||
- type: DeviceList
|
||||
- type: AtmosMonitor
|
||||
temperatureThreshold: stationTemperature
|
||||
pressureThreshold: stationPressure
|
||||
gasThresholds:
|
||||
Oxygen: stationOxygen
|
||||
Nitrogen: ignore
|
||||
CarbonDioxide: stationCO2
|
||||
Plasma: danger # everything below is usually bad
|
||||
Tritium: danger
|
||||
WaterVapor: danger
|
||||
Miasma: danger
|
||||
NitrousOxide: danger
|
||||
Frezon: danger
|
||||
- type: AtmosAlarmable
|
||||
alarmedBy: ["AirAlarm"]
|
||||
syncWith:
|
||||
- AirAlarm
|
||||
- AirSensor
|
||||
- GasVent
|
||||
- GasScrubber
|
||||
- type: AtmosAlarmableVisuals
|
||||
layerMap: "airAlarmBase"
|
||||
alarmStates:
|
||||
Normal: alarm0
|
||||
Warning: alarm2
|
||||
Danger: alarm1
|
||||
setOnDepowered:
|
||||
airAlarmBase: alarmp
|
||||
- type: Tag
|
||||
tags:
|
||||
- AirAlarm
|
||||
- type: AtmosDevice
|
||||
- type: AirAlarm
|
||||
- type: Clickable
|
||||
@@ -47,15 +49,6 @@
|
||||
- type: AccessReader
|
||||
access: [["Atmospherics"]]
|
||||
- type: Appearance
|
||||
visuals:
|
||||
- type: AtmosMonitorVisualizer
|
||||
layerMap: "airAlarmBase"
|
||||
alarmStates:
|
||||
Normal: alarm0
|
||||
Warning: alarm2
|
||||
Danger: alarm1
|
||||
setOnDepowered:
|
||||
airAlarmBase: alarmp
|
||||
- type: WiresVisuals
|
||||
- type: Sprite
|
||||
sprite: Structures/Wallmounts/air_monitors.rsi
|
||||
|
||||
@@ -13,26 +13,35 @@
|
||||
deviceNetId: AtmosDevices
|
||||
receiveFrequencyId: AtmosMonitor
|
||||
transmitFrequencyId: AtmosMonitor
|
||||
- type: ApcNetworkConnection
|
||||
- type: AtmosMonitor
|
||||
monitorFire: true
|
||||
displayMaxAlarmInNet: true
|
||||
sendBroadcastAttemptEvent: true
|
||||
- type: DeviceList
|
||||
- type: WiredNetworkConnection
|
||||
- type: AtmosDevice
|
||||
- type: AtmosAlarmable
|
||||
alarmedBy: ["FireAlarm"] # alarm itself, network effect
|
||||
- type: Clickable
|
||||
- type: InteractionOutline
|
||||
- type: FireAlarm
|
||||
- type: Appearance
|
||||
visuals:
|
||||
- type: AtmosMonitorVisualizer
|
||||
syncWith:
|
||||
- FireAlarm
|
||||
- AirSensor
|
||||
- GasVent
|
||||
- GasScrubber
|
||||
monitorAlertTypes:
|
||||
- Temperature
|
||||
- type: AtmosAlarmableVisuals
|
||||
layerMap: "fireAlarmState"
|
||||
alarmStates:
|
||||
Normal: fire_off
|
||||
Warning: fire_off # shouldn't be alarming at a warning
|
||||
Danger: fire_on
|
||||
Emagged: fire_emagged
|
||||
hideOnDepowered: ["fireAlarmState"]
|
||||
hideOnDepowered: [ "fireAlarmState" ]
|
||||
- type: Tag
|
||||
tags:
|
||||
- FireAlarm
|
||||
- type: Clickable
|
||||
- type: InteractionOutline
|
||||
- type: FireAlarm
|
||||
- type: AccessReader
|
||||
access: [ [ "Atmospherics" ] ]
|
||||
- type: Appearance
|
||||
- type: WiresVisuals
|
||||
- type: AlertLevelDisplay
|
||||
alertVisuals:
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
- type: constructionGraph
|
||||
id: AirSensor
|
||||
start: start
|
||||
graph:
|
||||
- node: start
|
||||
edges:
|
||||
- to: assembly
|
||||
steps:
|
||||
- material: Steel
|
||||
amount: 2
|
||||
doAfter: 1
|
||||
- node: assembly
|
||||
entity: AirSensorAssembly
|
||||
actions:
|
||||
- !type:SpriteStateChange
|
||||
state: gsensor0
|
||||
edges:
|
||||
- to: start
|
||||
conditions:
|
||||
- !type:EntityAnchored
|
||||
anchored: false
|
||||
completed:
|
||||
- !type:SpawnPrototype
|
||||
prototype: SheetSteel1
|
||||
amount: 2
|
||||
- !type:DeleteEntity {}
|
||||
steps:
|
||||
- tool: Screwing
|
||||
doAfter: 2
|
||||
- to: sensor
|
||||
conditions:
|
||||
- !type:EntityAnchored {}
|
||||
steps:
|
||||
- tool: Welding
|
||||
doAfter: 5
|
||||
- node: sensor
|
||||
entity: AirSensor
|
||||
edges:
|
||||
- to: assembly
|
||||
steps:
|
||||
- tool: Welding
|
||||
doAfter: 5
|
||||
@@ -284,6 +284,21 @@
|
||||
conditions:
|
||||
- !type:WallmountCondition {}
|
||||
|
||||
- type: construction
|
||||
name: air sensor
|
||||
id: AirSensor
|
||||
graph: AirSensor
|
||||
startNode: start
|
||||
targetNode: sensor
|
||||
category: construction-category-structures
|
||||
description: An air sensor. Senses air.
|
||||
icon:
|
||||
sprite: Structures/Specific/Atmospherics/sensor.rsi
|
||||
state: gsensor1
|
||||
placementMode: SnapgridCenter
|
||||
objectType: Structure
|
||||
canRotate: true
|
||||
|
||||
# ATMOS PIPES
|
||||
- type: construction
|
||||
name: gas pipe half
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
# Alphabetical order is now apparently required.
|
||||
|
||||
- type: Tag
|
||||
id: AirAlarm
|
||||
|
||||
- type: Tag
|
||||
id: AirAlarmElectronics
|
||||
|
||||
- type: Tag
|
||||
id: AirSensor
|
||||
|
||||
- type: Tag
|
||||
id: ATVKeys
|
||||
|
||||
@@ -180,6 +186,9 @@
|
||||
- type: Tag
|
||||
id: ExplosivePassable
|
||||
|
||||
- type: Tag
|
||||
id: FireAlarm
|
||||
|
||||
- type: Tag
|
||||
id: FireAlarmElectronics
|
||||
|
||||
@@ -210,6 +219,12 @@
|
||||
- type: Tag
|
||||
id: ForceNoFixRotations # fixrotations command WON'T target this
|
||||
|
||||
- type: Tag
|
||||
id: GasScrubber
|
||||
|
||||
- type: Tag
|
||||
id: GasVent
|
||||
|
||||
- type: Tag
|
||||
id: Gauze
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 166 B |
Binary file not shown.
|
After Width: | Height: | Size: 164 B |
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"version": 1,
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"license": "CC 3.0 BY-SA",
|
||||
"copyright": "Taken from /tg/station on commit https://github.com/tgstation/tgstation/commit/1dd5021b2c5b64e954935bdf77bc973c34016a04",
|
||||
"states": [
|
||||
{ "name": "gsensor0" },
|
||||
{ "name": "gsensor1" }
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user