From 4e5adc2b86c0a6bf6dbf1a745382564abb018ae9 Mon Sep 17 00:00:00 2001 From: Kevin Zheng Date: Sat, 6 May 2023 22:49:11 -0800 Subject: [PATCH] Add interlocking airlocks (#14177) Co-authored-by: metalgearsloth --- .../Tests/PrototypeSaveTest.cs | 51 +++++++++++++ .../Components/DoorSignalControlComponent.cs | 6 ++ .../Systems/DoorSignalControlSystem.cs | 69 ++++++++++++++++-- Content.Server/Doors/Systems/DoorSystem.cs | 4 +- .../Components/OrGateComponent.cs | 28 +++++++ .../Components/SignalLinkerComponent.cs | 11 +++ .../Components/SignalTransmitterComponent.cs | 8 ++ .../Events/SignalReceivedEvent.cs | 23 ++++++ .../MachineLinking/System/OrGateSystem.cs | 63 ++++++++++++++++ .../System/SignalLinkerSystem.cs | 64 +++++++++++++--- .../DeviceLinking/SharedDeviceLinkSystem.cs | 1 + .../Doors/Systems/SharedDoorSystem.cs | 4 + .../en-US/machine-linking/receiver_ports.ftl | 7 +- .../machine-linking/transmitter_ports.ftl | 3 + .../Prototypes/DeviceLinking/sink_ports.yml | 5 ++ .../Prototypes/DeviceLinking/source_ports.yml | 5 ++ .../Doors/Airlocks/base_structureairlocks.yml | 4 + .../Prototypes/Entities/Structures/gates.yml | 26 +++++++ .../MachineLinking/receiver_ports.yml | 25 +++++++ .../MachineLinking/transmitter_ports.yml | 15 ++++ .../Objects/Devices/gates.rsi/meta.json | 14 ++++ .../Textures/Objects/Devices/gates.rsi/or.png | Bin 0 -> 5234 bytes 22 files changed, 415 insertions(+), 21 deletions(-) create mode 100644 Content.Server/MachineLinking/Components/OrGateComponent.cs create mode 100644 Content.Server/MachineLinking/System/OrGateSystem.cs create mode 100644 Resources/Prototypes/Entities/Structures/gates.yml create mode 100644 Resources/Textures/Objects/Devices/gates.rsi/meta.json create mode 100644 Resources/Textures/Objects/Devices/gates.rsi/or.png diff --git a/Content.IntegrationTests/Tests/PrototypeSaveTest.cs b/Content.IntegrationTests/Tests/PrototypeSaveTest.cs index eda8e10ae2..15a1080967 100644 --- a/Content.IntegrationTests/Tests/PrototypeSaveTest.cs +++ b/Content.IntegrationTests/Tests/PrototypeSaveTest.cs @@ -38,6 +38,57 @@ public sealed class PrototypeSaveTest { "Singularity", // physics collision uses "AllMask" (-1). The flag serializer currently fails to save this because this features un-named bits. "constructionghost", + // URGH door pr but I just don't + "BlastDoorBridgeOpen", + "Windoor", + "WindoorSecure", + "WindoorSecureCargoLocked", + "WindoorTheatreLocked", + "BlastDoorBridge", + "WindoorSecureJanitorLocked", + "ShuttersWindow", + "WindoorScienceLocked", + "WindoorJanitorLocked", + "WindoorEngineeringLocked", + "BlastDoorExterior2", + "WindoorChemistryLocked", + "BlastDoorExterior3", + "WindoorMedicalLocked", + "ShuttersNormalOpen", + "WindoorBarKitchenLocked", + "BlastDoorOpen", + "ShuttersRadiationOpen", + "BlastDoorWindowsOpen", + "WindoorBarLocked", + "WindoorChapelLocked", + "WindoorArmoryLocked", + "BlastDoorExterior3Open", + "WindoorCargoLocked", + "WindoorSecurityLocked", + "WindoorExternalLocked", + "WindoorBrigLocked", + "WindoorHydroponicsLocked", + "ShuttersWindowOpen", + "WindoorKitchenHydroponicsLocked", + "WindoorSecureChapelLocked", + "BlastDoorExterior1Open", + "WindoorKitchenLocked", + "BlastDoor", + "BlastDoorWindows", + "BlastDoorExterior1", + "BlastDoorExterior2Open", + "WindoorSecureKitchenLocked", + "WindoorHeadOfPersonnelLocked", + "ShuttersRadiation", + "ShuttersNormal", + "WindoorSecureSalvageLocked", + "WindoorServiceLocked", + "WindoorCommandLocked", + "AirlockMaintMedLocked", + "AirlockArmoryGlassLocked", + "AirlockExternalGlassLocked", + "AirlockFreezerKitchenHydroLocked", + "AirlockGlassShuttle", }; [Test] diff --git a/Content.Server/DeviceLinking/Components/DoorSignalControlComponent.cs b/Content.Server/DeviceLinking/Components/DoorSignalControlComponent.cs index 98c8792f77..b19fdb5f4d 100644 --- a/Content.Server/DeviceLinking/Components/DoorSignalControlComponent.cs +++ b/Content.Server/DeviceLinking/Components/DoorSignalControlComponent.cs @@ -14,5 +14,11 @@ namespace Content.Server.DeviceLinking.Components [DataField("togglePort", customTypeSerializer: typeof(PrototypeIdSerializer))] public string TogglePort = "Toggle"; + + [DataField("boltPort", customTypeSerializer: typeof(PrototypeIdSerializer))] + public string InBolt = "DoorBolt"; + + [DataField("onOpenPort", customTypeSerializer: typeof(PrototypeIdSerializer))] + public string OutOpen = "DoorStatus"; } } diff --git a/Content.Server/DeviceLinking/Systems/DoorSignalControlSystem.cs b/Content.Server/DeviceLinking/Systems/DoorSignalControlSystem.cs index 930beee53b..969161c166 100644 --- a/Content.Server/DeviceLinking/Systems/DoorSignalControlSystem.cs +++ b/Content.Server/DeviceLinking/Systems/DoorSignalControlSystem.cs @@ -1,28 +1,37 @@ using Content.Server.DeviceLinking.Components; -using Content.Server.DeviceLinking.Events; +using Content.Server.DeviceNetwork; using Content.Server.Doors.Systems; +using Content.Server.MachineLinking.Events; using Content.Server.MachineLinking.System; using Content.Shared.Doors.Components; +using Content.Shared.Doors; using JetBrains.Annotations; +using SignalReceivedEvent = Content.Server.DeviceLinking.Events.SignalReceivedEvent; namespace Content.Server.DeviceLinking.Systems { [UsedImplicitly] public sealed class DoorSignalControlSystem : EntitySystem { + [Dependency] private readonly AirlockSystem _airlockSystem = default!; [Dependency] private readonly DoorSystem _doorSystem = default!; [Dependency] private readonly DeviceLinkSystem _signalSystem = default!; + private const string DoorSignalState = "DoorState"; + public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnInit); SubscribeLocalEvent(OnSignalReceived); + SubscribeLocalEvent(OnStateChanged); } private void OnInit(EntityUid uid, DoorSignalControlComponent component, ComponentInit args) { + _signalSystem.EnsureSinkPorts(uid, component.OpenPort, component.ClosePort, component.TogglePort); + _signalSystem.EnsureSourcePorts(uid, component.OutOpen); } private void OnSignalReceived(EntityUid uid, DoorSignalControlComponent component, ref SignalReceivedEvent args) @@ -30,19 +39,67 @@ namespace Content.Server.DeviceLinking.Systems if (!TryComp(uid, out DoorComponent? door)) return; + var state = SignalState.Momentary; + args.Data?.TryGetValue(DoorSignalState, out state); + + if (args.Port == component.OpenPort) { - if (door.State != DoorState.Open) - _doorSystem.TryOpen(uid, door); + if (state == SignalState.High || state == SignalState.Momentary) + { + if (door.State != DoorState.Open) + _doorSystem.TryOpen(uid, door); + } } else if (args.Port == component.ClosePort) { - if (door.State != DoorState.Closed) - _doorSystem.TryClose(uid, door); + if (state == SignalState.High || state == SignalState.Momentary) + { + if (door.State != DoorState.Closed) + _doorSystem.TryClose(uid, door); + } } else if (args.Port == component.TogglePort) { - _doorSystem.TryToggleDoor(uid, door); + if (state == SignalState.High || state == SignalState.Momentary) + { + _doorSystem.TryToggleDoor(uid, door); + } + } + else if (args.Port == component.InBolt) + { + if (state == SignalState.High) + { + if(TryComp(uid, out var airlockComponent)) + _airlockSystem.SetBoltsWithAudio(uid, airlockComponent, true); + } + else + { + if(TryComp(uid, out var airlockComponent)) + _airlockSystem.SetBoltsWithAudio(uid, airlockComponent, false); + } + } + } + + private void OnStateChanged(EntityUid uid, DoorSignalControlComponent door, DoorStateChangedEvent args) + { + var data = new NetworkPayload() + { + { DoorSignalState, SignalState.Momentary } + }; + + if (args.State == DoorState.Closed) + { + data[DoorSignalState] = SignalState.Low; + _signalSystem.InvokePort(uid, door.OutOpen, data); + } + else if (args.State == DoorState.Open + || args.State == DoorState.Opening + || args.State == DoorState.Closing + || args.State == DoorState.Emagging) + { + data[DoorSignalState] = SignalState.High; + _signalSystem.InvokePort(uid, door.OutOpen, data); } } } diff --git a/Content.Server/Doors/Systems/DoorSystem.cs b/Content.Server/Doors/Systems/DoorSystem.cs index 7b34a3c08b..b7ac9086f6 100644 --- a/Content.Server/Doors/Systems/DoorSystem.cs +++ b/Content.Server/Doors/Systems/DoorSystem.cs @@ -1,8 +1,8 @@ -using System.Diagnostics.CodeAnalysis; using Content.Server.Access; using Content.Server.Atmos.Components; using Content.Server.Atmos.EntitySystems; using Content.Server.Construction; +using Content.Server.MachineLinking.System; using Content.Server.Tools.Systems; using Content.Shared.Access.Components; using Content.Shared.Access.Systems; @@ -30,9 +30,7 @@ public sealed class DoorSystem : SharedDoorSystem [Dependency] private readonly AccessReaderSystem _accessReaderSystem = default!; [Dependency] private readonly AirlockSystem _airlock = default!; [Dependency] private readonly AirtightSystem _airtightSystem = default!; - [Dependency] private readonly ConstructionSystem _constructionSystem = default!; [Dependency] private readonly SharedToolSystem _toolSystem = default!; - [Dependency] private readonly SharedContainerSystem _containerSystem = default!; public override void Initialize() { diff --git a/Content.Server/MachineLinking/Components/OrGateComponent.cs b/Content.Server/MachineLinking/Components/OrGateComponent.cs new file mode 100644 index 0000000000..2e088b7e71 --- /dev/null +++ b/Content.Server/MachineLinking/Components/OrGateComponent.cs @@ -0,0 +1,28 @@ +using Content.Server.MachineLinking.Events; +using Content.Shared.MachineLinking; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Server.MachineLinking.Components; + +[RegisterComponent] +public sealed class OrGateComponent : Component +{ + // Initial state + [ViewVariables] + public SignalState StateA1 = SignalState.Low; + + [ViewVariables] + public SignalState StateB1 = SignalState.Low; + + [ViewVariables] + public SignalState LastO1 = SignalState.Low; + + [ViewVariables] + public SignalState StateA2 = SignalState.Low; + + [ViewVariables] + public SignalState StateB2 = SignalState.Low; + + [ViewVariables] + public SignalState LastO2 = SignalState.Low; +} diff --git a/Content.Server/MachineLinking/Components/SignalLinkerComponent.cs b/Content.Server/MachineLinking/Components/SignalLinkerComponent.cs index b87a6970fb..a3ed0a4a13 100644 --- a/Content.Server/MachineLinking/Components/SignalLinkerComponent.cs +++ b/Content.Server/MachineLinking/Components/SignalLinkerComponent.cs @@ -20,5 +20,16 @@ namespace Content.Server.MachineLinking.Components [DataField("requiredQuality", customTypeSerializer: typeof(PrototypeIdSerializer))] [ViewVariables(VVAccess.ReadWrite)] public string? RequiredQuality; + + // Utility functions below to deal with linking entities with both Transmit and Receive components. + public bool LinkTX() + { + return SavedTransmitter == null; + } + + public bool LinkRX() + { + return SavedTransmitter != null && SavedReceiver == null; + } } } diff --git a/Content.Server/MachineLinking/Components/SignalTransmitterComponent.cs b/Content.Server/MachineLinking/Components/SignalTransmitterComponent.cs index 555fa76802..b3b48691a0 100644 --- a/Content.Server/MachineLinking/Components/SignalTransmitterComponent.cs +++ b/Content.Server/MachineLinking/Components/SignalTransmitterComponent.cs @@ -1,3 +1,4 @@ +using Content.Server.MachineLinking.Events; using Content.Server.MachineLinking.System; namespace Content.Server.MachineLinking.Components @@ -31,6 +32,13 @@ namespace Content.Server.MachineLinking.Components [ViewVariables(VVAccess.ReadWrite)] public float TransmissionRange = 30f; + /* + * Remember last output state to avoid re-raising a SignalChangedEvent if the signal + * level hasn't actually changed. + */ + [ViewVariables(VVAccess.ReadWrite)] + public SignalState LastState = SignalState.Low; + [DataField("outputs")] [Access(typeof(SignalLinkerSystem), Other = AccessPermissions.ReadExecute)] // FIXME Friends public Dictionary> Outputs = new(); diff --git a/Content.Server/MachineLinking/Events/SignalReceivedEvent.cs b/Content.Server/MachineLinking/Events/SignalReceivedEvent.cs index e69de29bb2..40d8e07c97 100644 --- a/Content.Server/MachineLinking/Events/SignalReceivedEvent.cs +++ b/Content.Server/MachineLinking/Events/SignalReceivedEvent.cs @@ -0,0 +1,23 @@ +namespace Content.Server.MachineLinking.Events +{ + public enum SignalState + { + Momentary, // Instantaneous pulse high, compatibility behavior + Low, + High + } + + public sealed class SignalReceivedEvent : EntityEventArgs + { + public readonly string Port; + public readonly SignalState State; + public readonly EntityUid? Trigger; + + public SignalReceivedEvent(string port, EntityUid? trigger, SignalState state) + { + Port = port; + Trigger = trigger; + State = state; + } + } +} diff --git a/Content.Server/MachineLinking/System/OrGateSystem.cs b/Content.Server/MachineLinking/System/OrGateSystem.cs new file mode 100644 index 0000000000..af5d9fb26f --- /dev/null +++ b/Content.Server/MachineLinking/System/OrGateSystem.cs @@ -0,0 +1,63 @@ +using Content.Server.MachineLinking.Components; +using Content.Server.MachineLinking.Events; +using JetBrains.Annotations; + +namespace Content.Server.MachineLinking.System +{ + [UsedImplicitly] + public sealed class OrGateSystem : EntitySystem + { + [Dependency] private readonly SignalLinkerSystem _signalSystem = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnSignalReceived); + } + + private void OnInit(EntityUid uid, OrGateComponent component, ComponentInit args) + { + _signalSystem.EnsureReceiverPorts(uid, "A1", "B1", "A2", "B2"); + _signalSystem.EnsureTransmitterPorts(uid, "O1", "O2"); + } + + private void OnSignalReceived(EntityUid uid, OrGateComponent component, SignalReceivedEvent args) + { + if (args.Port == "A1") + { + component.StateA1 = args.State; + } + else if (args.Port == "B1") + { + component.StateB1 = args.State; + } + else if (args.Port == "A2") + { + component.StateA2 = args.State; + } + else if (args.Port == "B2") + { + component.StateB2 = args.State; + } + + // O1 = A1 || B1 + var v1 = SignalState.Low; + if (component.StateA1 == SignalState.High || component.StateB1 == SignalState.High) + v1 = SignalState.High; + + if (v1 != component.LastO1) + _signalSystem.InvokePort(uid, "O1", v1); + component.LastO1 = v1; + + // O2 = A2 || B2 + var v2 = SignalState.Low; + if (component.StateA2 == SignalState.High || component.StateB2 == SignalState.High) + v2 = SignalState.High; + + if (v2 != component.LastO2) + _signalSystem.InvokePort(uid, "O2", v2); + component.LastO2 = v2; + } + } +} diff --git a/Content.Server/MachineLinking/System/SignalLinkerSystem.cs b/Content.Server/MachineLinking/System/SignalLinkerSystem.cs index 811a3f771a..6743919077 100644 --- a/Content.Server/MachineLinking/System/SignalLinkerSystem.cs +++ b/Content.Server/MachineLinking/System/SignalLinkerSystem.cs @@ -1,7 +1,7 @@ using System.Linq; using System.Diagnostics.CodeAnalysis; -using Content.Server.DeviceLinking.Events; using Content.Server.MachineLinking.Components; +using Content.Server.MachineLinking.Events; using Content.Server.Power.Components; using Content.Server.Tools; using Content.Shared.DeviceLinking.Events; @@ -11,6 +11,7 @@ using Content.Shared.Popups; using Robust.Server.GameObjects; using Content.Shared.Verbs; using Robust.Shared.Prototypes; +using SignalReceivedEvent = Content.Server.DeviceLinking.Events.SignalReceivedEvent; namespace Content.Server.MachineLinking.System { @@ -49,7 +50,7 @@ namespace Content.Server.MachineLinking.System var comp = EnsureComp(uid); foreach (var port in ports) { - comp.Inputs.TryAdd(port, new()); + comp.Inputs.TryAdd(port, new List()); } } @@ -58,7 +59,7 @@ namespace Content.Server.MachineLinking.System var comp = EnsureComp(uid); foreach (var port in ports) { - comp.Outputs.TryAdd(port, new()); + comp.Outputs.TryAdd(port, new List()); } } @@ -72,9 +73,11 @@ namespace Content.Server.MachineLinking.System if (!TryComp(args.Using, out SignalLinkerComponent? linker) || !IsLinkerInteractable(args.Using.Value, linker)) + { return; + } - AlternativeVerb verb = new() + var verb = new AlternativeVerb() { Text = Loc.GetString("signal-linking-verb-text-link-default"), IconEntity = args.Using @@ -130,13 +133,25 @@ namespace Content.Server.MachineLinking.System } public void InvokePort(EntityUid uid, string port, SignalTransmitterComponent? component = null) + { + InvokePort(uid, port, SignalState.Momentary, component); + } + + public void InvokePort(EntityUid uid, string port, SignalState state, SignalTransmitterComponent? component = null) { if (!Resolve(uid, ref component)) return; + if (state != SignalState.Momentary && state == component.LastState) + { + // no change in output signal + return; + } + if (!component.Outputs.TryGetValue(port, out var receivers)) return; + component.LastState = state; foreach (var receiver in receivers) { var eventArgs = new SignalReceivedEvent(receiver.Port, uid); @@ -149,6 +164,7 @@ namespace Content.Server.MachineLinking.System // validate links Dictionary uidCache = new(); foreach (var tport in transmitter.Outputs) + { foreach (var rport in tport.Value) { if (!uidCache.TryGetValue(rport.Uid, out var receiver)) @@ -158,6 +174,7 @@ namespace Content.Server.MachineLinking.System else if (!rpv.Contains(new(uid, tport.Key))) rpv.Add(new(uid, tport.Key)); } + } } private void OnReceiverStartup(EntityUid uid, SignalReceiverComponent receiver, ComponentStartup args) @@ -208,12 +225,16 @@ namespace Content.Server.MachineLinking.System private void OnTransmitterInteractUsing(EntityUid uid, SignalTransmitterComponent transmitter, InteractUsingEvent args) { - if (args.Handled) return; + if (args.Handled) + return; if (!TryComp(args.Used, out SignalLinkerComponent? linker) || !IsLinkerInteractable(args.Used, linker) || !TryComp(args.User, out ActorComponent? actor)) return; + if (!linker.LinkTX()) + return; + linker.SavedTransmitter = uid; if (!TryComp(linker.SavedReceiver, out SignalReceiverComponent? receiver)) @@ -233,12 +254,16 @@ namespace Content.Server.MachineLinking.System private void OnReceiverInteractUsing(EntityUid uid, SignalReceiverComponent receiver, InteractUsingEvent args) { - if (args.Handled) return; + if (args.Handled) + return; if (!TryComp(args.Used, out SignalLinkerComponent? linker) || !IsLinkerInteractable(args.Used, linker) || !TryComp(args.User, out ActorComponent? actor)) return; + if (!linker.LinkRX()) + return; + linker.SavedReceiver = uid; if (!TryComp(linker.SavedTransmitter, out SignalTransmitterComponent? transmitter)) @@ -274,10 +299,14 @@ namespace Content.Server.MachineLinking.System var outKeys = transmitter.Outputs.Keys.ToList(); var inKeys = receiver.Inputs.Keys.ToList(); List<(int, int)> links = new(); - for (int i = 0; i < outKeys.Count; i++) + for (var i = 0; i < outKeys.Count; i++) + { foreach (var re in transmitter.Outputs[outKeys[i]]) + { if (re.Uid == receiver.Owner) links.Add((i, inKeys.IndexOf(re.Port))); + } + } bui.SetState(new SignalPortsState($"{Name(transmitter.Owner)} ({transmitter.Owner})", outKeys, $"{Name(receiver.Owner)} ({receiver.Owner})", inKeys, links)); @@ -289,7 +318,9 @@ namespace Content.Server.MachineLinking.System { if (!transmitter.Outputs.TryGetValue(args.TransmitterPort, out var linkedReceivers) || !receiver.Inputs.TryGetValue(args.ReceiverPort, out var linkedTransmitters)) + { return false; + } quiet |= !user.HasValue; @@ -328,10 +359,12 @@ namespace Content.Server.MachineLinking.System linkedReceivers.Add(new(receiver.Owner, args.ReceiverPort)); linkedTransmitters.Add(new(transmitter.Owner, args.TransmitterPort)); if (!quiet) + { _popupSystem.PopupCursor(Loc.GetString("signal-linker-component-linked-port", - ("machine1", transmitter.Owner), ("port1", PortName(args.TransmitterPort)), - ("machine2", receiver.Owner), ("port2", PortName(args.ReceiverPort))), + ("machine1", transmitter.Owner), ("port1", PortName(args.TransmitterPort)), + ("machine2", receiver.Owner), ("port2", PortName(args.ReceiverPort))), user!.Value, PopupType.Medium); + } var newLink = new NewLinkEvent(user, transmitter.Owner, args.TransmitterPort, receiver.Owner, args.ReceiverPort); RaiseLocalEvent(receiver.Owner, newLink); @@ -353,12 +386,14 @@ namespace Content.Server.MachineLinking.System if (receivers.Contains(new(receiver.Owner, args.ReceiverPort)) || transmitters.Contains(new(transmitter.Owner, args.TransmitterPort))) - { // link already exists, remove it + { + // link already exists, remove it if (receivers.Remove(new(receiver.Owner, args.ReceiverPort)) && transmitters.Remove(new(transmitter.Owner, args.TransmitterPort))) { RaiseLocalEvent(receiver.Owner, new PortDisconnectedEvent(args.ReceiverPort), true); RaiseLocalEvent(transmitter.Owner, new PortDisconnectedEvent(args.TransmitterPort), true); + _popupSystem.PopupCursor(Loc.GetString("signal-linker-component-unlinked-port", ("machine1", transmitter.Owner), ("port1", PortName(args.TransmitterPort)), ("machine2", receiver.Owner), ("port2", PortName(args.ReceiverPort))), @@ -397,12 +432,16 @@ namespace Content.Server.MachineLinking.System return; foreach (var (port, receivers) in transmitter.Outputs) + { if (receivers.RemoveAll(id => id.Uid == receiver.Owner) > 0) RaiseLocalEvent(transmitter.Owner, new PortDisconnectedEvent(port), true); + } foreach (var (port, transmitters) in receiver.Inputs) + { if (transmitters.RemoveAll(id => id.Uid == transmitter.Owner) > 0) RaiseLocalEvent(receiver.Owner, new PortDisconnectedEvent(port), true); + } TryUpdateUI(linker, transmitter, receiver); } @@ -437,12 +476,16 @@ namespace Content.Server.MachineLinking.System // First, disconnect existing links. foreach (var (port, receivers) in transmitter.Outputs) + { if (receivers.RemoveAll(id => id.Uid == receiver.Owner) > 0) RaiseLocalEvent(transmitter.Owner, new PortDisconnectedEvent(port), true); + } foreach (var (port, transmitters) in receiver.Inputs) + { if (transmitters.RemoveAll(id => id.Uid == transmitter.Owner) > 0) RaiseLocalEvent(receiver.Owner, new PortDisconnectedEvent(port), true); + } // Then make any valid default connections. foreach (var outPort in transmitter.Outputs.Keys) @@ -474,6 +517,7 @@ namespace Content.Server.MachineLinking.System transmitterPower.Provider?.Net == receiverPower.Provider?.Net) return true; + // TODO: As elsewhere don't use mappos inrange. return Comp(transmitterComponent.Owner).MapPosition.InRange( Comp(receiverComponent.Owner).MapPosition, transmitterComponent.TransmissionRange); } diff --git a/Content.Shared/DeviceLinking/SharedDeviceLinkSystem.cs b/Content.Shared/DeviceLinking/SharedDeviceLinkSystem.cs index b6b784b63c..648596ffc1 100644 --- a/Content.Shared/DeviceLinking/SharedDeviceLinkSystem.cs +++ b/Content.Shared/DeviceLinking/SharedDeviceLinkSystem.cs @@ -442,6 +442,7 @@ public abstract class SharedDeviceLinkSystem : EntitySystem private bool InRange(EntityUid sourceUid, EntityUid sinkUid, float range) { + // TODO: This should be using an existing method and also coordinates inrange instead. return Transform(sourceUid).MapPosition.InRange(Transform(sinkUid).MapPosition, range); } diff --git a/Content.Shared/Doors/Systems/SharedDoorSystem.cs b/Content.Shared/Doors/Systems/SharedDoorSystem.cs index cf52ee5e3e..b4828d5e79 100644 --- a/Content.Shared/Doors/Systems/SharedDoorSystem.cs +++ b/Content.Shared/Doors/Systems/SharedDoorSystem.cs @@ -131,6 +131,10 @@ public abstract class SharedDoorSystem : EntitySystem if (!Resolve(uid, ref door)) return; + // If no change, return to avoid firing a new DoorStateChangedEvent. + if (state == door.State) + return; + switch (state) { case DoorState.Opening: diff --git a/Resources/Locale/en-US/machine-linking/receiver_ports.ftl b/Resources/Locale/en-US/machine-linking/receiver_ports.ftl index cbf2f537f9..723a733b31 100644 --- a/Resources/Locale/en-US/machine-linking/receiver_ports.ftl +++ b/Resources/Locale/en-US/machine-linking/receiver_ports.ftl @@ -1,5 +1,5 @@ -signal-port-name-toggle = Autoclose -signal-port-description-toggle = Toggles whether the device should automatically close. +signal-port-name-autoclose = Autoclose +signal-port-description-autoclose = Toggles whether the device should automatically close. signal-port-name-toggle = Toggle signal-port-description-toggle = Toggles the state of a device. @@ -22,6 +22,9 @@ signal-port-description-open = Opens a device. signal-port-name-close = Close signal-port-description-close = Closes a device. +signal-port-name-doorbolt = Door bolt +signal-port-description-doorbolt = Toggles door bolt. + signal-port-name-trigger = Trigger signal-port-description-trigger = Triggers some mechanism on the device. diff --git a/Resources/Locale/en-US/machine-linking/transmitter_ports.ftl b/Resources/Locale/en-US/machine-linking/transmitter_ports.ftl index 03fae00159..2b61bd4e11 100644 --- a/Resources/Locale/en-US/machine-linking/transmitter_ports.ftl +++ b/Resources/Locale/en-US/machine-linking/transmitter_ports.ftl @@ -13,6 +13,9 @@ signal-port-description-left = This port is invoked whenever the lever is moved signal-port-name-right = Right signal-port-description-right = This port is invoked whenever the lever is moved to the rightmost position. +signal-port-name-doorstatus = Door status +signal-port-description-doorstatus = This port is invoked whenever the door's status changes. + signal-port-name-middle = Middle signal-port-description-middle = This port is invoked whenever the lever is moved to the neutral position. diff --git a/Resources/Prototypes/DeviceLinking/sink_ports.yml b/Resources/Prototypes/DeviceLinking/sink_ports.yml index 19f3b4e1c7..37817eddaa 100644 --- a/Resources/Prototypes/DeviceLinking/sink_ports.yml +++ b/Resources/Prototypes/DeviceLinking/sink_ports.yml @@ -38,6 +38,11 @@ name: signal-port-name-close description: signal-port-description-close +- type: sinkPort + id: DoorBolt + name: signal-port-name-doorbolt + description: signal-port-description-doorbolt + - type: sinkPort id: Trigger name: signal-port-name-trigger diff --git a/Resources/Prototypes/DeviceLinking/source_ports.yml b/Resources/Prototypes/DeviceLinking/source_ports.yml index d01089205c..02f55f1798 100644 --- a/Resources/Prototypes/DeviceLinking/source_ports.yml +++ b/Resources/Prototypes/DeviceLinking/source_ports.yml @@ -34,6 +34,11 @@ description: signal-port-description-middle defaultLinks: [ Off, Close ] +- type: sourcePort + id: DoorStatus + name: signal-port-name-doorstatus + description: signal-port-description-status + - type: sourcePort id: OrderSender name: signal-port-name-order-sender diff --git a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml index 7e8100d10a..d2c2279b2d 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml @@ -89,6 +89,10 @@ - Close - Toggle - AutoClose + - DoorBolt + - type: DeviceLinkSource + ports: + - DoorStatus - type: UserInterface interfaces: - key: enum.WiresUiKey.Key diff --git a/Resources/Prototypes/Entities/Structures/gates.yml b/Resources/Prototypes/Entities/Structures/gates.yml new file mode 100644 index 0000000000..94f0cb46ce --- /dev/null +++ b/Resources/Prototypes/Entities/Structures/gates.yml @@ -0,0 +1,26 @@ +- type: entity + id: OrGate + name: MS7432 + description: Dual 2-Input OR Gate + parent: BaseItem + placement: + mode: SnapgridCenter + snap: + - Wallmount + components: + - type: Anchorable + - type: Sprite + sprite: Objects/Devices/gates.rsi + state: or + - type: Rotatable + - type: OrGate + - type: SignalReceiver + inputs: + A1: [] + B1: [] + A2: [] + B2: [] + - type: SignalTransmitter + outputs: + O1: [] + O2: [] diff --git a/Resources/Prototypes/MachineLinking/receiver_ports.yml b/Resources/Prototypes/MachineLinking/receiver_ports.yml index 923ab4d49b..9156fd2b59 100644 --- a/Resources/Prototypes/MachineLinking/receiver_ports.yml +++ b/Resources/Prototypes/MachineLinking/receiver_ports.yml @@ -72,3 +72,28 @@ id: ArtifactAnalyzerReceiver name: signal-port-name-artifact-analyzer-receiver description: signal-port-description-artifact-analyzer-receiver + +- type: receiverPort + id: DoorBolt + name: "Bolt" + description: "Bolt door when HIGH." + +- type: receiverPort + id: A1 + name: "Input A1" + description: "Input A1" + +- type: receiverPort + id: B1 + name: "Input B1" + description: "Input B1" + +- type: receiverPort + id: A2 + name: "Input A2" + description: "Input A2" + +- type: receiverPort + id: B2 + name: "Input B2" + description: "Input B2" diff --git a/Resources/Prototypes/MachineLinking/transmitter_ports.yml b/Resources/Prototypes/MachineLinking/transmitter_ports.yml index c4d25c6461..41a6a80307 100644 --- a/Resources/Prototypes/MachineLinking/transmitter_ports.yml +++ b/Resources/Prototypes/MachineLinking/transmitter_ports.yml @@ -67,3 +67,18 @@ name: signal-port-name-artifact-analyzer-sender description: signal-port-description-artifact-analyzer-sender defaultLinks: [ ArtifactAnalyzerReceiver ] + +- type: transmitterPort + id: DoorStatus + name: "Door Status" + description: "HIGH when door is open, LOW when door is closed." + +- type: transmitterPort + id: O1 + name: "Output 1" + description: "Output 1" + +- type: transmitterPort + id: O2 + name: "Output 2" + description: "Output 2" diff --git a/Resources/Textures/Objects/Devices/gates.rsi/meta.json b/Resources/Textures/Objects/Devices/gates.rsi/meta.json new file mode 100644 index 0000000000..b71ae48098 --- /dev/null +++ b/Resources/Textures/Objects/Devices/gates.rsi/meta.json @@ -0,0 +1,14 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Kevin Zheng 2022", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "or" + } + ] +} diff --git a/Resources/Textures/Objects/Devices/gates.rsi/or.png b/Resources/Textures/Objects/Devices/gates.rsi/or.png new file mode 100644 index 0000000000000000000000000000000000000000..513d4f5aeea8e6d672346b5852430e945b9b2c30 GIT binary patch literal 5234 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