Adds fire/air alarms (#5018)
Co-authored-by: Vera Aguilera Puerto <6766154+Zumorica@users.noreply.github.com> Co-authored-by: E F R <602406+Efruit@users.noreply.github.com> Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
This commit is contained in:
224
Content.Server/Atmos/Monitor/Components/AirAlarmComponent.cs
Normal file
224
Content.Server/Atmos/Monitor/Components/AirAlarmComponent.cs
Normal file
@@ -0,0 +1,224 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.Atmos.Monitor.Systems;
|
||||
using Content.Server.DeviceNetwork.Components;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.VendingMachines; // TODO: Move this out of vending machines???
|
||||
using Content.Server.WireHacking;
|
||||
using Content.Shared.Atmos.Monitor.Components;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using static Content.Shared.Wires.SharedWiresComponent;
|
||||
using static Content.Shared.Wires.SharedWiresComponent.WiresAction;
|
||||
|
||||
namespace Content.Server.Atmos.Monitor.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
public class AirAlarmComponent : Component, IWires
|
||||
{
|
||||
[ComponentDependency] public readonly ApcPowerReceiverComponent? DeviceRecvComponent = default!;
|
||||
[ComponentDependency] public readonly AtmosMonitorComponent? AtmosMonitorComponent = default!;
|
||||
[ComponentDependency] public readonly DeviceNetworkComponent? DeviceNetComponent = default!;
|
||||
[ComponentDependency] public readonly WiresComponent? WiresComponent = null;
|
||||
|
||||
private AirAlarmSystem? _airAlarmSystem;
|
||||
|
||||
[ViewVariables] public AirAlarmMode CurrentMode { get; set; }
|
||||
|
||||
// Remember to null this afterwards.
|
||||
[ViewVariables] public IAirAlarmModeUpdate? CurrentModeUpdater { get; set; }
|
||||
|
||||
public override string Name => "AirAlarm";
|
||||
|
||||
public Dictionary<string, IAtmosDeviceData> DeviceData = new();
|
||||
|
||||
public HashSet<NetUserId> ActivePlayers = new();
|
||||
|
||||
public bool FullAccess = false;
|
||||
public bool CanSync = true;
|
||||
|
||||
// <-- Wires -->
|
||||
|
||||
private CancellationTokenSource _powerPulsedCancel = new();
|
||||
private int PowerPulsedTimeout = 30;
|
||||
|
||||
private enum Wires
|
||||
{
|
||||
// Cutting this kills power.
|
||||
// Pulsing it disrupts power.
|
||||
Power,
|
||||
// Cutting this allows full access.
|
||||
// Pulsing this does nothing.
|
||||
Access,
|
||||
// Cutting/Remending this resets ONLY from panic mode.
|
||||
// Pulsing this sets panic mode.
|
||||
Panic,
|
||||
// Cutting this clears sync'd devices, and makes
|
||||
// the alarm unable to resync.
|
||||
// Pulsing this resyncs all devices (ofc current
|
||||
// implementation just auto-does this anyways)
|
||||
DeviceSync,
|
||||
// This does nothing. (placeholder for AI wire,
|
||||
// if that ever gets implemented)
|
||||
Dummy
|
||||
}
|
||||
|
||||
public void RegisterWires(WiresComponent.WiresBuilder builder)
|
||||
{
|
||||
foreach (var wire in Enum.GetValues<Wires>())
|
||||
builder.CreateWire(wire);
|
||||
|
||||
UpdateWires();
|
||||
}
|
||||
|
||||
public void UpdateWires()
|
||||
{
|
||||
if (_airAlarmSystem == null)
|
||||
_airAlarmSystem = EntitySystem.Get<AirAlarmSystem>();
|
||||
|
||||
if (WiresComponent == null) return;
|
||||
|
||||
var pwrLightState = (PowerPulsed, PowerCut) switch {
|
||||
(true, false) => StatusLightState.BlinkingFast,
|
||||
(_, true) => StatusLightState.Off,
|
||||
(_, _) => StatusLightState.On
|
||||
};
|
||||
|
||||
var powerLight = new StatusLightData(Color.Yellow, pwrLightState, "POWR");
|
||||
|
||||
var accessLight = new StatusLightData(
|
||||
Color.Green,
|
||||
WiresComponent.IsWireCut(Wires.Access) ? StatusLightState.Off : StatusLightState.On,
|
||||
"ACC"
|
||||
);
|
||||
|
||||
var panicLight = new StatusLightData(
|
||||
Color.Red,
|
||||
CurrentMode == AirAlarmMode.Panic ? StatusLightState.On : StatusLightState.Off,
|
||||
"PAN"
|
||||
);
|
||||
|
||||
var syncLightState = StatusLightState.BlinkingSlow;
|
||||
|
||||
if (AtmosMonitorComponent != null && !AtmosMonitorComponent.NetEnabled)
|
||||
syncLightState = StatusLightState.Off;
|
||||
else if (DeviceData.Count != 0)
|
||||
syncLightState = StatusLightState.On;
|
||||
|
||||
var syncLight = new StatusLightData(Color.Orange, syncLightState, "NET");
|
||||
|
||||
WiresComponent.SetStatus(AirAlarmWireStatus.Power, powerLight);
|
||||
WiresComponent.SetStatus(AirAlarmWireStatus.Access, accessLight);
|
||||
WiresComponent.SetStatus(AirAlarmWireStatus.Panic, panicLight);
|
||||
WiresComponent.SetStatus(AirAlarmWireStatus.DeviceSync, syncLight);
|
||||
}
|
||||
|
||||
private bool _powerCut;
|
||||
private bool PowerCut
|
||||
{
|
||||
get => _powerCut;
|
||||
set
|
||||
{
|
||||
_powerCut = value;
|
||||
SetPower();
|
||||
}
|
||||
}
|
||||
|
||||
private bool _powerPulsed;
|
||||
private bool PowerPulsed
|
||||
{
|
||||
get => _powerPulsed && !_powerCut;
|
||||
set
|
||||
{
|
||||
_powerPulsed = value;
|
||||
SetPower();
|
||||
}
|
||||
}
|
||||
|
||||
private void SetPower()
|
||||
{
|
||||
if (DeviceRecvComponent != null
|
||||
&& WiresComponent != null)
|
||||
DeviceRecvComponent.PowerDisabled = PowerPulsed || PowerCut;
|
||||
}
|
||||
|
||||
public void WiresUpdate(WiresUpdateEventArgs args)
|
||||
{
|
||||
if (DeviceNetComponent == null) return;
|
||||
|
||||
if (_airAlarmSystem == null)
|
||||
_airAlarmSystem = EntitySystem.Get<AirAlarmSystem>();
|
||||
|
||||
switch (args.Action)
|
||||
{
|
||||
case Pulse:
|
||||
switch (args.Identifier)
|
||||
{
|
||||
case Wires.Power:
|
||||
PowerPulsed = true;
|
||||
_powerPulsedCancel.Cancel();
|
||||
_powerPulsedCancel = new CancellationTokenSource();
|
||||
Owner.SpawnTimer(TimeSpan.FromSeconds(PowerPulsedTimeout),
|
||||
() => PowerPulsed = false,
|
||||
_powerPulsedCancel.Token);
|
||||
break;
|
||||
case Wires.Panic:
|
||||
if (CurrentMode != AirAlarmMode.Panic)
|
||||
_airAlarmSystem.SetMode(Owner, DeviceNetComponent.Address, AirAlarmMode.Panic, true, false);
|
||||
break;
|
||||
case Wires.DeviceSync:
|
||||
_airAlarmSystem.SyncAllDevices(Owner);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case Mend:
|
||||
switch (args.Identifier)
|
||||
{
|
||||
case Wires.Power:
|
||||
_powerPulsedCancel.Cancel();
|
||||
PowerPulsed = false;
|
||||
PowerCut = false;
|
||||
break;
|
||||
case Wires.Panic:
|
||||
if (CurrentMode == AirAlarmMode.Panic)
|
||||
_airAlarmSystem.SetMode(Owner, DeviceNetComponent.Address, AirAlarmMode.Filtering, true, false);
|
||||
break;
|
||||
case Wires.Access:
|
||||
FullAccess = false;
|
||||
break;
|
||||
case Wires.DeviceSync:
|
||||
if (AtmosMonitorComponent != null)
|
||||
AtmosMonitorComponent.NetEnabled = true;
|
||||
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case Cut:
|
||||
switch (args.Identifier)
|
||||
{
|
||||
case Wires.DeviceSync:
|
||||
DeviceData.Clear();
|
||||
if (AtmosMonitorComponent != null)
|
||||
{
|
||||
AtmosMonitorComponent.NetworkAlarmStates.Clear();
|
||||
AtmosMonitorComponent.NetEnabled = false;
|
||||
}
|
||||
|
||||
break;
|
||||
case Wires.Power:
|
||||
PowerCut = true;
|
||||
break;
|
||||
case Wires.Access:
|
||||
FullAccess = true;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
UpdateWires();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Shared.Atmos.Monitor;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
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
|
||||
// -> 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 class AtmosAlarmableComponent : Component
|
||||
{
|
||||
public override string Name => "AtmosAlarmable";
|
||||
|
||||
[ViewVariables]
|
||||
public List<EntityUid> LinkedMonitors { get; set; } = new();
|
||||
|
||||
[ViewVariables] public AtmosMonitorAlarmType LastAlarmState = AtmosMonitorAlarmType.Normal;
|
||||
[ViewVariables] public AtmosMonitorAlarmType HighestNetworkState = AtmosMonitorAlarmType.Normal;
|
||||
[ViewVariables] public bool IgnoreAlarms { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// List of prototypes that this alarmable can be
|
||||
/// alarmed by - must be a prototype with AtmosMonitor
|
||||
/// attached to it
|
||||
/// </summary>
|
||||
[DataField("alarmedBy")]
|
||||
public List<string> AlarmedByPrototypes { get; } = new();
|
||||
}
|
||||
}
|
||||
114
Content.Server/Atmos/Monitor/Components/AtmosMonitorComponent.cs
Normal file
114
Content.Server/Atmos/Monitor/Components/AtmosMonitorComponent.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.Atmos.Piping.Components;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Monitor;
|
||||
using Content.Shared.Sound;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.Atmos.Monitor.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
public class AtmosMonitorComponent : Component
|
||||
{
|
||||
public override string Name => "AtmosMonitor";
|
||||
|
||||
// Requires power. No logic related to this will be performed here, however,
|
||||
// save for ensuring that the data is valid upon set.
|
||||
// This is how the system discovers entities that are alarmable.
|
||||
[ComponentDependency] public readonly ApcPowerReceiverComponent? PowerRecvComponent = default!;
|
||||
[ComponentDependency] public readonly AtmosDeviceComponent? AtmosDeviceComponent = default!;
|
||||
|
||||
// Whether this monitor can send alarms,
|
||||
// or recieve atmos command events.
|
||||
//
|
||||
// Useful for wires; i.e., pulsing a monitor wire
|
||||
// will make it send an alert, and cutting
|
||||
// it will make it so that alerts are no longer
|
||||
// sent/receieved.
|
||||
//
|
||||
// Note that this cancels every single network
|
||||
// event, including ones that may not be
|
||||
// related to atmos monitor events.
|
||||
[ViewVariables]
|
||||
public bool NetEnabled = true;
|
||||
|
||||
// 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;
|
||||
|
||||
[ViewVariables]
|
||||
public AtmosAlarmThreshold? TemperatureThreshold;
|
||||
|
||||
[DataField("pressureThreshold", customTypeSerializer: (typeof(PrototypeIdSerializer<AtmosAlarmThreshold>)))]
|
||||
public readonly string? PressureThresholdId;
|
||||
|
||||
[ViewVariables]
|
||||
public AtmosAlarmThreshold? PressureThreshold;
|
||||
|
||||
// monitor fire - much different from temperature
|
||||
// since there's events for fire, setting this to true
|
||||
// will make the atmos monitor act like a smoke detector,
|
||||
// immediately signalling danger if there's a fire
|
||||
[DataField("monitorFire")]
|
||||
public bool MonitorFire = false;
|
||||
|
||||
[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
|
||||
// type serializer
|
||||
[DataField("gasThresholds")]
|
||||
public Dictionary<Gas, string>? GasThresholdIds;
|
||||
|
||||
[ViewVariables]
|
||||
public Dictionary<Gas, AtmosAlarmThreshold>? GasThresholds;
|
||||
|
||||
// Stores a reference to the gas on the tile this is on.
|
||||
[ViewVariables]
|
||||
public GasMixture? TileGas;
|
||||
|
||||
// Stores the last alarm state of this alarm.
|
||||
[ViewVariables]
|
||||
public AtmosMonitorAlarmType LastAlarmState = AtmosMonitorAlarmType.Normal;
|
||||
|
||||
// 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();
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
170
Content.Server/Atmos/Monitor/Components/FireAlarmComponent.cs
Normal file
170
Content.Server/Atmos/Monitor/Components/FireAlarmComponent.cs
Normal file
@@ -0,0 +1,170 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Content.Server.Atmos.Monitor.Systems;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.VendingMachines; // TODO: Move this out of vending machines???
|
||||
using Content.Server.WireHacking;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Atmos.Monitor;
|
||||
using Content.Shared.Atmos.Monitor.Components;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Maths;
|
||||
using static Content.Shared.Wires.SharedWiresComponent;
|
||||
using static Content.Shared.Wires.SharedWiresComponent.WiresAction;
|
||||
|
||||
|
||||
namespace Content.Server.Atmos.Monitor.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
public class FireAlarmComponent : Component, IWires
|
||||
{
|
||||
[ComponentDependency] public readonly ApcPowerReceiverComponent? DeviceRecvComponent = default!;
|
||||
[ComponentDependency] public readonly AtmosMonitorComponent? AtmosMonitorComponent = default!;
|
||||
[ComponentDependency] public readonly WiresComponent? WiresComponent = null;
|
||||
|
||||
private AtmosMonitorSystem? _atmosMonitorSystem;
|
||||
|
||||
public override string Name => "FireAlarm";
|
||||
|
||||
private CancellationTokenSource _powerPulsedCancel = new();
|
||||
private int PowerPulsedTimeout = 30;
|
||||
|
||||
// Much more simpler than the air alarm wire set.
|
||||
private enum Wires
|
||||
{
|
||||
// Cutting this kills power,
|
||||
// pulsing it disrupts.
|
||||
Power,
|
||||
// Cutting this disables network
|
||||
// connectivity,
|
||||
// pulsing it sets off an alarm.
|
||||
Alarm,
|
||||
Dummy1,
|
||||
Dummy2,
|
||||
}
|
||||
|
||||
private bool _powerCut;
|
||||
private bool PowerCut
|
||||
{
|
||||
get => _powerCut;
|
||||
set
|
||||
{
|
||||
_powerCut = value;
|
||||
SetPower();
|
||||
}
|
||||
}
|
||||
|
||||
private bool _powerPulsed;
|
||||
private bool PowerPulsed
|
||||
{
|
||||
get => _powerPulsed && !_powerCut;
|
||||
set
|
||||
{
|
||||
_powerPulsed = value;
|
||||
SetPower();
|
||||
}
|
||||
}
|
||||
|
||||
private void SetPower()
|
||||
{
|
||||
if (DeviceRecvComponent != null
|
||||
&& WiresComponent != null)
|
||||
DeviceRecvComponent.PowerDisabled = PowerPulsed || PowerCut;
|
||||
}
|
||||
|
||||
|
||||
public void RegisterWires(WiresComponent.WiresBuilder builder)
|
||||
{
|
||||
builder.CreateWire(Wires.Power);
|
||||
builder.CreateWire(Wires.Alarm);
|
||||
builder.CreateWire(Wires.Dummy1);
|
||||
builder.CreateWire(Wires.Dummy2);
|
||||
|
||||
UpdateWires();
|
||||
}
|
||||
|
||||
public void UpdateWires()
|
||||
{
|
||||
if (WiresComponent == null) return;
|
||||
|
||||
var powerLight = new StatusLightData(Color.Yellow, StatusLightState.On, "POWR");
|
||||
|
||||
if (PowerPulsed)
|
||||
powerLight = new StatusLightData(Color.Yellow, StatusLightState.BlinkingFast, "POWR");
|
||||
else if (PowerCut)
|
||||
powerLight = new StatusLightData(Color.Yellow, StatusLightState.Off, "POWR");
|
||||
|
||||
var syncLight = new StatusLightData(Color.Orange, StatusLightState.On, "NET");
|
||||
|
||||
if (AtmosMonitorComponent != null)
|
||||
if (!AtmosMonitorComponent.NetEnabled)
|
||||
syncLight = new StatusLightData(Color.Orange, StatusLightState.Off, "NET");
|
||||
else if (AtmosMonitorComponent.HighestAlarmInNetwork == AtmosMonitorAlarmType.Danger)
|
||||
syncLight = new StatusLightData(Color.Orange, StatusLightState.BlinkingFast, "NET");
|
||||
|
||||
WiresComponent.SetStatus(FireAlarmWireStatus.Power, powerLight);
|
||||
WiresComponent.SetStatus(FireAlarmWireStatus.Alarm, syncLight);
|
||||
}
|
||||
|
||||
public void WiresUpdate(WiresUpdateEventArgs args)
|
||||
{
|
||||
if (_atmosMonitorSystem == null)
|
||||
_atmosMonitorSystem = EntitySystem.Get<AtmosMonitorSystem>();
|
||||
|
||||
switch (args.Action)
|
||||
{
|
||||
case Pulse:
|
||||
switch (args.Identifier)
|
||||
{
|
||||
case Wires.Power:
|
||||
PowerPulsed = true;
|
||||
_powerPulsedCancel.Cancel();
|
||||
_powerPulsedCancel = new CancellationTokenSource();
|
||||
Owner.SpawnTimer(TimeSpan.FromSeconds(PowerPulsedTimeout),
|
||||
() => PowerPulsed = false,
|
||||
_powerPulsedCancel.Token);
|
||||
break;
|
||||
case Wires.Alarm:
|
||||
if (AtmosMonitorComponent != null)
|
||||
_atmosMonitorSystem.Alert(Owner, AtmosMonitorAlarmType.Danger);
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
case Mend:
|
||||
switch (args.Identifier)
|
||||
{
|
||||
case Wires.Power:
|
||||
_powerPulsedCancel.Cancel();
|
||||
PowerPulsed = false;
|
||||
PowerCut = false;
|
||||
break;
|
||||
case Wires.Alarm:
|
||||
if (AtmosMonitorComponent != null)
|
||||
AtmosMonitorComponent.NetEnabled = true;
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
case Cut:
|
||||
switch (args.Identifier)
|
||||
{
|
||||
case Wires.Power:
|
||||
PowerCut = true;
|
||||
break;
|
||||
case Wires.Alarm:
|
||||
if (AtmosMonitorComponent != null)
|
||||
AtmosMonitorComponent.NetEnabled = false;
|
||||
break;
|
||||
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
UpdateWires();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user