diff --git a/Content.Server/Atmos/Monitor/Components/AirAlarmComponent.cs b/Content.Server/Atmos/Monitor/Components/AirAlarmComponent.cs index d0c1a3a86c..7684f04bbc 100644 --- a/Content.Server/Atmos/Monitor/Components/AirAlarmComponent.cs +++ b/Content.Server/Atmos/Monitor/Components/AirAlarmComponent.cs @@ -1,7 +1,10 @@ +using Content.Server.DeviceLinking.Components; using Content.Shared.Atmos.Monitor; using Content.Shared.Atmos.Monitor.Components; using Content.Shared.Atmos.Piping.Unary.Components; +using Content.Shared.DeviceLinking; using Robust.Shared.Network; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; namespace Content.Server.Atmos.Monitor.Components; @@ -24,4 +27,28 @@ public sealed class AirAlarmComponent : Component public HashSet ActivePlayers = new(); public bool CanSync = true; + + /// + /// Previous alarm state for use with output ports. + /// + [DataField("state")] + public AtmosAlarmType State = AtmosAlarmType.Normal; + + /// + /// The port that gets set to high while the alarm is in the danger state, and low when not. + /// + [DataField("dangerPort", customTypeSerializer: typeof(PrototypeIdSerializer))] + public string DangerPort = "AirDanger"; + + /// + /// The port that gets set to high while the alarm is in the warning state, and low when not. + /// + [DataField("warningPort", customTypeSerializer: typeof(PrototypeIdSerializer))] + public string WarningPort = "AirWarning"; + + /// + /// The port that gets set to high while the alarm is in the normal state, and low when not. + /// + [DataField("normalPort", customTypeSerializer: typeof(PrototypeIdSerializer))] + public string NormalPort = "AirNormal"; } diff --git a/Content.Server/Atmos/Monitor/Systems/AirAlarmSystem.cs b/Content.Server/Atmos/Monitor/Systems/AirAlarmSystem.cs index 4ee9a9ac8e..f88a36557e 100644 --- a/Content.Server/Atmos/Monitor/Systems/AirAlarmSystem.cs +++ b/Content.Server/Atmos/Monitor/Systems/AirAlarmSystem.cs @@ -1,6 +1,7 @@ using System.Linq; using Content.Server.Atmos.Monitor.Components; using Content.Server.Atmos.Piping.Components; +using Content.Server.DeviceLinking.Systems; using Content.Server.DeviceNetwork; using Content.Server.DeviceNetwork.Components; using Content.Server.DeviceNetwork.Systems; @@ -13,6 +14,7 @@ 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.DeviceLinking; using Content.Shared.DeviceNetwork; using Content.Shared.DeviceNetwork.Systems; using Content.Shared.Interaction; @@ -32,14 +34,14 @@ namespace Content.Server.Atmos.Monitor.Systems; // response data in its data key. public sealed class AirAlarmSystem : EntitySystem { - [Dependency] private readonly DeviceNetworkSystem _deviceNet = default!; - [Dependency] private readonly DeviceListSystem _deviceListSystem = default!; - [Dependency] private readonly AtmosDeviceNetworkSystem _atmosDevNetSystem = default!; + [Dependency] private readonly AccessReaderSystem _access = default!; [Dependency] private readonly AtmosAlarmableSystem _atmosAlarmable = default!; - [Dependency] private readonly UserInterfaceSystem _uiSystem = default!; - [Dependency] private readonly AccessReaderSystem _accessSystem = default!; + [Dependency] private readonly AtmosDeviceNetworkSystem _atmosDevNet = default!; + [Dependency] private readonly DeviceNetworkSystem _deviceNet = default!; + [Dependency] private readonly DeviceLinkSystem _deviceLink = default!; + [Dependency] private readonly DeviceListSystem _deviceList = default!; [Dependency] private readonly PopupSystem _popup = default!; - [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; + [Dependency] private readonly UserInterfaceSystem _ui = default!; #region Device Network API @@ -57,8 +59,8 @@ public sealed class AirAlarmSystem : EntitySystem /// The data to send to the device. public void SetData(EntityUid uid, string address, IAtmosDeviceData data) { - _atmosDevNetSystem.SetDeviceState(uid, address, data); - _atmosDevNetSystem.Sync(uid, address); + _atmosDevNet.SetDeviceState(uid, address, data); + _atmosDevNet.Sync(uid, address); } /// @@ -66,7 +68,7 @@ public sealed class AirAlarmSystem : EntitySystem /// private void SyncAllDevices(EntityUid uid) { - _atmosDevNetSystem.Sync(uid, null); + _atmosDevNet.Sync(uid, null); } /// @@ -75,7 +77,7 @@ public sealed class AirAlarmSystem : EntitySystem /// The address of the device. private void SyncDevice(EntityUid uid, string address) { - _atmosDevNetSystem.Sync(uid, address); + _atmosDevNet.Sync(uid, address); } /// @@ -85,8 +87,8 @@ public sealed class AirAlarmSystem : EntitySystem /// private void SyncRegisterAllDevices(EntityUid uid) { - _atmosDevNetSystem.Register(uid, null); - _atmosDevNetSystem.Sync(uid, null); + _atmosDevNet.Register(uid, null); + _atmosDevNet.Sync(uid, null); } /// @@ -133,8 +135,7 @@ public sealed class AirAlarmSystem : EntitySystem /// The mode to sync with the rest of the network. private void SyncMode(EntityUid uid, AirAlarmMode mode) { - if (EntityManager.TryGetComponent(uid, out AtmosMonitorComponent? monitor) - && !monitor.NetEnabled) + if (TryComp(uid, out var monitor) && !monitor.NetEnabled) return; var payload = new NetworkPayload @@ -165,6 +166,8 @@ public sealed class AirAlarmSystem : EntitySystem SubscribeLocalEvent(OnTabChange); SubscribeLocalEvent(OnDeviceListUpdate); SubscribeLocalEvent(OnClose); + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent(OnShutdown); SubscribeLocalEvent(OnActivate); } @@ -179,7 +182,7 @@ public sealed class AirAlarmSystem : EntitySystem continue; } - _atmosDevNetSystem.Deregister(uid, deviceNet.Address); + _atmosDevNet.Deregister(uid, deviceNet.Address); } component.ScrubberData.Clear(); @@ -220,6 +223,18 @@ public sealed class AirAlarmSystem : EntitySystem RemoveActiveInterface(uid); } + private void OnInit(EntityUid uid, AirAlarmComponent comp, ComponentInit args) + { + _deviceLink.EnsureSourcePorts(uid, comp.DangerPort, comp.WarningPort, comp.NormalPort); + } + + private void OnMapInit(EntityUid uid, AirAlarmComponent comp, MapInitEvent args) + { + // for mapped linked air alarms, start with high so when it changes for the first time it goes from high to low + // without this the output would suddenly get sent a low signal after nothing which is bad + _deviceLink.SendSignal(uid, GetPort(comp), true); + } + private void OnShutdown(EntityUid uid, AirAlarmComponent component, ComponentShutdown args) { _activeUserInterfaces.Remove(uid); @@ -227,9 +242,6 @@ public sealed class AirAlarmSystem : EntitySystem private void OnActivate(EntityUid uid, AirAlarmComponent component, ActivateInWorldEvent args) { - if (!_interactionSystem.InRangeUnobstructed(args.User, args.Target)) - return; - if (!TryComp(args.User, out var actor)) return; @@ -242,9 +254,9 @@ public sealed class AirAlarmSystem : EntitySystem if (!this.IsPowered(uid, EntityManager)) return; - var ui = _uiSystem.GetUiOrNull(uid, SharedAirAlarmInterfaceKey.Key); + var ui = _ui.GetUiOrNull(uid, SharedAirAlarmInterfaceKey.Key); if (ui != null) - _uiSystem.OpenUi(ui, actor.PlayerSession); + _ui.OpenUi(ui, actor.PlayerSession); component.ActivePlayers.Add(actor.PlayerSession.UserId); AddActiveInterface(uid); SyncAllDevices(uid); @@ -268,12 +280,20 @@ public sealed class AirAlarmSystem : EntitySystem private void OnUpdateAlarmMode(EntityUid uid, AirAlarmComponent component, AirAlarmUpdateAlarmModeMessage args) { - var addr = string.Empty; - if (EntityManager.TryGetComponent(uid, out DeviceNetworkComponent? netConn)) addr = netConn.Address; if (AccessCheck(uid, args.Session.AttachedEntity, component)) + { + var addr = string.Empty; + if (TryComp(uid, out var netConn)) + { + addr = netConn.Address; + } + SetMode(uid, addr, args.Mode, false); + } else + { UpdateUI(uid, component); + } } private void OnUpdateAutoMode(EntityUid uid, AirAlarmComponent component, AirAlarmUpdateAutoModeMessage args) @@ -293,7 +313,7 @@ public sealed class AirAlarmSystem : EntitySystem private void OnUpdateDeviceData(EntityUid uid, AirAlarmComponent component, AirAlarmUpdateDeviceDataMessage args) { if (AccessCheck(uid, args.Session.AttachedEntity, component) - && _deviceListSystem.ExistsInDeviceList(uid, args.Address)) + && _deviceList.ExistsInDeviceList(uid, args.Address)) { SetDeviceData(uid, args.Address, args.Data); } @@ -334,10 +354,14 @@ public sealed class AirAlarmSystem : EntitySystem if (!Resolve(uid, ref component)) return false; - if (!EntityManager.TryGetComponent(uid, out AccessReaderComponent? reader) || user == null) + // if it has no access reader behave as if the user has AA + if (!TryComp(uid, out var reader)) + return true; + + if (user == null) return false; - if (!_accessSystem.IsAllowed(user.Value, reader)) + if (!_access.IsAllowed(user.Value, reader)) { _popup.PopupEntity(Loc.GetString("air-alarm-ui-access-denied"), user.Value, user.Value); return false; @@ -354,8 +378,10 @@ public sealed class AirAlarmSystem : EntitySystem } var addr = string.Empty; - if (EntityManager.TryGetComponent(uid, out DeviceNetworkComponent? netConn)) + if (TryComp(uid, out var netConn)) + { addr = netConn.Address; + } if (component.AutoMode) { @@ -369,9 +395,32 @@ public sealed class AirAlarmSystem : EntitySystem } } + if (component.State != args.AlarmType) + { + TryComp(uid, out var source); + + // send low to old state's port + _deviceLink.SendSignal(uid, GetPort(component), false, source); + + // send high to new state's port, along with updating the cached state + component.State = args.AlarmType; + _deviceLink.SendSignal(uid, GetPort(component), true, source); + } + UpdateUI(uid, component); } + private string GetPort(AirAlarmComponent comp) + { + if (comp.State == AtmosAlarmType.Danger) + return comp.DangerPort; + + if (comp.State == AtmosAlarmType.Warning) + return comp.WarningPort; + + return comp.NormalPort; + } + #endregion #region Air Alarm Settings @@ -517,7 +566,7 @@ public sealed class AirAlarmSystem : EntitySystem /// private void ForceCloseAllInterfaces(EntityUid uid) { - _uiSystem.TryCloseAll(uid, SharedAirAlarmInterfaceKey.Key); + _ui.TryCloseAll(uid, SharedAirAlarmInterfaceKey.Key); } private void OnAtmosUpdate(EntityUid uid, AirAlarmComponent alarm, AtmosDeviceUpdateEvent args) @@ -585,7 +634,7 @@ public sealed class AirAlarmSystem : EntitySystem highestAlarm = AtmosAlarmType.Normal; } - _uiSystem.TrySetUiState( + _ui.TrySetUiState( uid, SharedAirAlarmInterfaceKey.Key, new AirAlarmUIState(devNet.Address, deviceCount, pressure, temperature, dataToSend, alarm.CurrentMode, alarm.CurrentTab, highestAlarm.Value, alarm.AutoMode)); diff --git a/Content.Server/DeviceLinking/Systems/DeviceLinkSystem.cs b/Content.Server/DeviceLinking/Systems/DeviceLinkSystem.cs index edf937a30c..76f1d55a6b 100644 --- a/Content.Server/DeviceLinking/Systems/DeviceLinkSystem.cs +++ b/Content.Server/DeviceLinking/Systems/DeviceLinkSystem.cs @@ -1,3 +1,4 @@ +using Content.Server.DeviceLinking.Components; using Content.Server.DeviceLinking.Events; using Content.Server.DeviceNetwork; using Content.Server.DeviceNetwork.Components; @@ -95,11 +96,25 @@ public sealed class DeviceLinkSystem : SharedDeviceLinkSystem } } - _deviceNetworkSystem.QueuePacket(uid, sinkNetworkComponent.Address, payload, sinkNetworkComponent.ReceiveFrequency); + // force using wireless network so things like atmos devices are able to send signals + var network = (int) DeviceNetworkComponent.DeviceNetIdDefaults.Wireless; + _deviceNetworkSystem.QueuePacket(uid, sinkNetworkComponent.Address, payload, sinkNetworkComponent.ReceiveFrequency, network); } } } + /// + /// Helper function that invokes a port with a high/low binary logic signal. + /// + public void SendSignal(EntityUid uid, string port, bool signal, DeviceLinkSourceComponent? comp = null) + { + var data = new NetworkPayload + { + [DeviceNetworkConstants.LogicState] = signal ? SignalState.High : SignalState.Low + }; + InvokePort(uid, port, data, comp); + } + /// /// Checks if the payload has a port defined and if the port is present on the sink. /// Raises a containing the payload when the check passes diff --git a/Content.Server/DeviceLinking/Systems/LogicGateSystem.cs b/Content.Server/DeviceLinking/Systems/LogicGateSystem.cs index d8c87a90ee..360f86eebe 100644 --- a/Content.Server/DeviceLinking/Systems/LogicGateSystem.cs +++ b/Content.Server/DeviceLinking/Systems/LogicGateSystem.cs @@ -140,12 +140,7 @@ public sealed class LogicGateSystem : EntitySystem { comp.LastOutput = output; - var data = new NetworkPayload - { - [DeviceNetworkConstants.LogicState] = output ? SignalState.High : SignalState.Low - }; - - _deviceLink.InvokePort(uid, comp.OutputPort, data); + _deviceLink.SendSignal(uid, comp.OutputPort, output); } } } diff --git a/Content.Server/DeviceLinking/Systems/SignalSwitchSystem.cs b/Content.Server/DeviceLinking/Systems/SignalSwitchSystem.cs index 7da8532f2e..7bcc438b92 100644 --- a/Content.Server/DeviceLinking/Systems/SignalSwitchSystem.cs +++ b/Content.Server/DeviceLinking/Systems/SignalSwitchSystem.cs @@ -30,15 +30,11 @@ public sealed class SignalSwitchSystem : EntitySystem comp.State = !comp.State; _deviceLink.InvokePort(uid, comp.State ? comp.OnPort : comp.OffPort); - var data = new NetworkPayload - { - [DeviceNetworkConstants.LogicState] = comp.State ? SignalState.High : SignalState.Low - }; // only send status if it's a toggle switch and not a button if (comp.OnPort != comp.OffPort) { - _deviceLink.InvokePort(uid, comp.StatusPort, data); + _deviceLink.SendSignal(uid, comp.StatusPort, comp.State); } _audio.PlayPvs(comp.ClickSound, uid, AudioParams.Default.WithVariation(0.125f).WithVolume(8f)); diff --git a/Content.Server/DeviceNetwork/Systems/DeviceNetworkSystem.cs b/Content.Server/DeviceNetwork/Systems/DeviceNetworkSystem.cs index 75204d4885..d17093c294 100644 --- a/Content.Server/DeviceNetwork/Systems/DeviceNetworkSystem.cs +++ b/Content.Server/DeviceNetwork/Systems/DeviceNetworkSystem.cs @@ -66,7 +66,7 @@ namespace Content.Server.DeviceNetwork.Systems /// The frequency to send on /// The data to be sent /// Returns true when the packet was successfully enqueued. - public bool QueuePacket(EntityUid uid, string? address, NetworkPayload data, uint? frequency = null, DeviceNetworkComponent? device = null) + public bool QueuePacket(EntityUid uid, string? address, NetworkPayload data, uint? frequency = null, int? network = null, DeviceNetworkComponent? device = null) { if (!Resolve(uid, ref device, false)) return false; @@ -79,7 +79,9 @@ namespace Content.Server.DeviceNetwork.Systems if (frequency == null) return false; - _nextQueue.Enqueue(new DeviceNetworkPacketEvent(device.DeviceNetId, address, frequency.Value, device.Address, uid, data)); + network ??= device.DeviceNetId; + + _nextQueue.Enqueue(new DeviceNetworkPacketEvent(network.Value, address, frequency.Value, device.Address, uid, data)); return true; } diff --git a/Content.Server/DeviceNetwork/Systems/WiredNetworkSystem.cs b/Content.Server/DeviceNetwork/Systems/WiredNetworkSystem.cs index 384e701d24..758a333c7a 100644 --- a/Content.Server/DeviceNetwork/Systems/WiredNetworkSystem.cs +++ b/Content.Server/DeviceNetwork/Systems/WiredNetworkSystem.cs @@ -17,7 +17,7 @@ namespace Content.Server.DeviceNetwork.Systems /// private void OnBeforePacketSent(EntityUid uid, WiredNetworkComponent component, BeforePacketSentEvent args) { - if (EntityManager.GetComponent(uid).GridUid != args.SenderTransform.GridUid) + if (Transform(uid).GridUid != args.SenderTransform.GridUid) { args.Cancel(); } diff --git a/Content.Server/DeviceNetwork/Systems/WirelessNetworkSystem.cs b/Content.Server/DeviceNetwork/Systems/WirelessNetworkSystem.cs index cbf591624d..b27ef52eca 100644 --- a/Content.Server/DeviceNetwork/Systems/WirelessNetworkSystem.cs +++ b/Content.Server/DeviceNetwork/Systems/WirelessNetworkSystem.cs @@ -20,8 +20,11 @@ namespace Content.Server.DeviceNetwork.Systems var ownPosition = args.SenderPosition; var xform = Transform(uid); + // not a wireless to wireless connection, just let it happen + if (!TryComp(args.Sender, out var sendingComponent)) + return; + if (xform.MapID != args.SenderTransform.MapID - || !TryComp(args.Sender, out var sendingComponent) || (ownPosition - xform.WorldPosition).Length() > sendingComponent.Range) { args.Cancel(); diff --git a/Content.Server/Disposal/Mailing/MailingUnitSystem.cs b/Content.Server/Disposal/Mailing/MailingUnitSystem.cs index 6e08224778..4dea3d23e1 100644 --- a/Content.Server/Disposal/Mailing/MailingUnitSystem.cs +++ b/Content.Server/Disposal/Mailing/MailingUnitSystem.cs @@ -111,7 +111,7 @@ public sealed class MailingUnitSystem : EntitySystem [NetTarget] = component.Target }; - _deviceNetworkSystem.QueuePacket(uid, null, payload, null, device); + _deviceNetworkSystem.QueuePacket(uid, null, payload, null, null, device); } /// @@ -129,7 +129,7 @@ public sealed class MailingUnitSystem : EntitySystem }; component.TargetList.Clear(); - _deviceNetworkSystem.QueuePacket(uid, null, payload, null, device); + _deviceNetworkSystem.QueuePacket(uid, null, payload, null, null, device); } /// diff --git a/Resources/Locale/en-US/machine-linking/transmitter_ports.ftl b/Resources/Locale/en-US/machine-linking/transmitter_ports.ftl index 02ede1e1b6..154f20eee7 100644 --- a/Resources/Locale/en-US/machine-linking/transmitter_ports.ftl +++ b/Resources/Locale/en-US/machine-linking/transmitter_ports.ftl @@ -8,7 +8,7 @@ signal-port-name-off-transmitter = Off signal-port-description-off-transmitter = This port is invoked whenever the transmitter is turned off. signal-port-name-status-transmitter = Status -signal-port-description-logic-output = This port is invoked with HIGH or LOW depending on the transmitter status. +signal-port-description-status-transmitter = This port is invoked with HIGH or LOW depending on the transmitter status. signal-port-name-left = Left signal-port-description-left = This port is invoked whenever the lever is moved to the leftmost position. @@ -36,3 +36,12 @@ signal-port-description-logic-output-high = This port is invoked whenever the in signal-port-name-logic-output-low = Low Output signal-port-description-logic-output-low = This port is invoked whenever the input has a falling edge. + +signal-port-name-air-danger = Danger +signal-port-description-air-danger = This port is invoked with HIGH when in danger mode and LOW when not. + +signal-port-name-air-warning = Warning +signal-port-description-air-warning = This port is invoked with HIGH when in warning mode and LOW when not. + +signal-port-name-air-normal = Normal +signal-port-description-air-normal = This port is invoked with HIGH when in normal mode and LOW when not. diff --git a/Resources/Prototypes/DeviceLinking/source_ports.yml b/Resources/Prototypes/DeviceLinking/source_ports.yml index 45ee812f2e..73e96978a7 100644 --- a/Resources/Prototypes/DeviceLinking/source_ports.yml +++ b/Resources/Prototypes/DeviceLinking/source_ports.yml @@ -96,3 +96,21 @@ name: signal-port-name-logic-output-low description: signal-port-description-logic-output-low defaultLinks: [ Off, Close ] + +- type: sourcePort + id: AirDanger + name: signal-port-name-air-danger + description: signal-port-description-air-danger + defaultLinks: [ DoorBolt ] + +- type: sourcePort + id: AirWarning + name: signal-port-name-air-warning + description: signal-port-description-air-warning + defaultLinks: [ DoorBolt ] + +- type: sourcePort + id: AirNormal + name: signal-port-name-air-normal + description: signal-port-description-air-normal + defaultLinks: [ DoorBolt ] diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/air_alarm.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/air_alarm.yml index cd6a438ce6..d1c13736f0 100644 --- a/Resources/Prototypes/Entities/Structures/Wallmounts/air_alarm.yml +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/air_alarm.yml @@ -2,6 +2,10 @@ id: AirAlarm name: air alarm description: An air alarm. Alarms... air? + placement: + mode: SnapgridCenter + snap: + - Wallmount components: - type: WallMount - type: ApcPowerReceiver @@ -16,14 +20,22 @@ prefix: device-address-prefix-air-alarm sendBroadcastAttemptEvent: true - type: WiredNetworkConnection + # for output status ports + - type: WirelessNetworkConnection + range: 200 - type: DeviceList - type: DeviceNetworkRequiresPower + - type: DeviceLinkSource + ports: + - AirDanger + - AirWarning + - AirNormal - type: AtmosAlarmable syncWith: - - AirAlarm - - AirSensor - - GasVent - - GasScrubber + - AirAlarm + - AirSensor + - GasVent + - GasScrubber - type: AtmosAlarmableVisuals layerMap: "airAlarmBase" alarmStates: @@ -85,15 +97,15 @@ - !type:PlaySoundBehavior sound: path: /Audio/Effects/metalbreak.ogg - placement: - mode: SnapgridCenter - snap: - - Wallmount - type: entity id: AirAlarmAssembly name: air alarm assembly description: An air alarm. Doesn't look like it'll be alarming air any time soon. + placement: + mode: SnapgridCenter + snap: + - Wallmount components: - type: WallMount - type: Clickable @@ -115,7 +127,3 @@ node: assembly - type: Transform anchored: true - placement: - mode: SnapgridCenter - snap: - - Wallmount