]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Add interlocking airlocks (#14177)
authorKevin Zheng <kevinz5000@gmail.com>
Sun, 7 May 2023 06:49:11 +0000 (22:49 -0800)
committerGitHub <noreply@github.com>
Sun, 7 May 2023 06:49:11 +0000 (16:49 +1000)
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
22 files changed:
Content.IntegrationTests/Tests/PrototypeSaveTest.cs
Content.Server/DeviceLinking/Components/DoorSignalControlComponent.cs
Content.Server/DeviceLinking/Systems/DoorSignalControlSystem.cs
Content.Server/Doors/Systems/DoorSystem.cs
Content.Server/MachineLinking/Components/OrGateComponent.cs [new file with mode: 0644]
Content.Server/MachineLinking/Components/SignalLinkerComponent.cs
Content.Server/MachineLinking/Components/SignalTransmitterComponent.cs
Content.Server/MachineLinking/Events/SignalReceivedEvent.cs
Content.Server/MachineLinking/System/OrGateSystem.cs [new file with mode: 0644]
Content.Server/MachineLinking/System/SignalLinkerSystem.cs
Content.Shared/DeviceLinking/SharedDeviceLinkSystem.cs
Content.Shared/Doors/Systems/SharedDoorSystem.cs
Resources/Locale/en-US/machine-linking/receiver_ports.ftl
Resources/Locale/en-US/machine-linking/transmitter_ports.ftl
Resources/Prototypes/DeviceLinking/sink_ports.yml
Resources/Prototypes/DeviceLinking/source_ports.yml
Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml
Resources/Prototypes/Entities/Structures/gates.yml [new file with mode: 0644]
Resources/Prototypes/MachineLinking/receiver_ports.yml
Resources/Prototypes/MachineLinking/transmitter_ports.yml
Resources/Textures/Objects/Devices/gates.rsi/meta.json [new file with mode: 0644]
Resources/Textures/Objects/Devices/gates.rsi/or.png [new file with mode: 0644]

index eda8e10ae2731f7d842b24dffb9cac37bdb81f23..15a1080967702bc59d5fb6823488ec4795879a3d 100644 (file)
@@ -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]
index 98c8792f7776f112a17db457c3fa135bbb813a40..b19fdb5f4db6247e5880372da02ab406c9e075c4 100644 (file)
@@ -14,5 +14,11 @@ namespace Content.Server.DeviceLinking.Components
 
         [DataField("togglePort", customTypeSerializer: typeof(PrototypeIdSerializer<ReceiverPortPrototype>))]
         public string TogglePort = "Toggle";
+
+        [DataField("boltPort", customTypeSerializer: typeof(PrototypeIdSerializer<ReceiverPortPrototype>))]
+        public string InBolt = "DoorBolt";
+
+        [DataField("onOpenPort", customTypeSerializer: typeof(PrototypeIdSerializer<TransmitterPortPrototype>))]
+        public string OutOpen = "DoorStatus";
     }
 }
index 930beee53be5baf6faa2d4d099dc01f1bc942e57..969161c1661dbace1f3b83284c7595f4b3f6197b 100644 (file)
@@ -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<DoorSignalControlComponent, ComponentInit>(OnInit);
             SubscribeLocalEvent<DoorSignalControlComponent, SignalReceivedEvent>(OnSignalReceived);
+            SubscribeLocalEvent<DoorSignalControlComponent, DoorStateChangedEvent>(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<AirlockComponent>(uid, out var airlockComponent))
+                        _airlockSystem.SetBoltsWithAudio(uid, airlockComponent, true);
+                }
+                else
+                {
+                    if(TryComp<AirlockComponent>(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);
             }
         }
     }
index 7b34a3c08be968ad174b2e3e4dbdcc25edf33e18..b7ac9086f6017bbef6d1e8551bbe4d2d65b6b0c0 100644 (file)
@@ -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 (file)
index 0000000..2e088b7
--- /dev/null
@@ -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;
+}
index b87a6970fb09d7d4a5d8105b40e269cd7124c1ba..a3ed0a4a13af4e319f88d0196d12904f6399adec 100644 (file)
@@ -20,5 +20,16 @@ namespace Content.Server.MachineLinking.Components
         [DataField("requiredQuality", customTypeSerializer: typeof(PrototypeIdSerializer<ToolQualityPrototype>))]
         [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;
+        }
     }
 }
index 555fa76802704fd970e09978ae96a25d53457a12..b3b48691a0548e579a3c759c34313c513119308a 100644 (file)
@@ -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<string, List<PortIdentifier>> Outputs = new();
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..40d8e07c97425bc0436e7120ba08b7e846b057ba 100644 (file)
@@ -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 (file)
index 0000000..af5d9fb
--- /dev/null
@@ -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<OrGateComponent, ComponentInit>(OnInit);
+            SubscribeLocalEvent<OrGateComponent, SignalReceivedEvent>(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;
+        }
+    }
+}
index 811a3f771acf84f46441b0a0ea20c9445ed3392a..67439190773ec67c2b5844c7aaf43ed606f07804 100644 (file)
@@ -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<SignalReceiverComponent>(uid);
             foreach (var port in ports)
             {
-                comp.Inputs.TryAdd(port, new());
+                comp.Inputs.TryAdd(port, new List<PortIdentifier>());
             }
         }
 
@@ -58,7 +59,7 @@ namespace Content.Server.MachineLinking.System
             var comp = EnsureComp<SignalTransmitterComponent>(uid);
             foreach (var port in ports)
             {
-                comp.Outputs.TryAdd(port, new());
+                comp.Outputs.TryAdd(port, new List<PortIdentifier>());
             }
         }
 
@@ -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<EntityUid, SignalReceiverComponent?> 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<TransmitterPortPrototype>(args.TransmitterPort)),
-                    ("machine2", receiver.Owner), ("port2", PortName<ReceiverPortPrototype>(args.ReceiverPort))),
+                        ("machine1", transmitter.Owner), ("port1", PortName<TransmitterPortPrototype>(args.TransmitterPort)),
+                        ("machine2", receiver.Owner), ("port2", PortName<ReceiverPortPrototype>(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<TransmitterPortPrototype>(args.TransmitterPort)),
                         ("machine2", receiver.Owner), ("port2", PortName<ReceiverPortPrototype>(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<TransformComponent>(transmitterComponent.Owner).MapPosition.InRange(
                    Comp<TransformComponent>(receiverComponent.Owner).MapPosition, transmitterComponent.TransmissionRange);
         }
index b6b784b63c9a5a30587f86ed71b3b44084e2e73b..648596ffc1fe3d43f46a6da0ad7ab564a7996b65 100644 (file)
@@ -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);
     }
 
index cf52ee5e3e39596df17a2260d4d963ca432bdced..b4828d5e79e0df412e78be9e1debb6cc4100f468 100644 (file)
@@ -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:
index cbf2f537f9a5f95c881676ed4a21975814e82aa9..723a733b31441ce32a45db23003a8a3b1944456d 100644 (file)
@@ -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.
 
index 03fae00159976c4d0f1f96183e92ef017e19ce6b..2b61bd4e11af2767dadde021231a37c97e847729 100644 (file)
@@ -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.
 
index 19f3b4e1c71dfc72bf52fb2ae4e6bdad7990bf78..37817eddaa15c7f0a6d94420b96c5746a00e0dcc 100644 (file)
   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
index d01089205cccd3d79d89e6fe25451497b6f109b5..02f55f17989507750c49e4542ad0543af305c72b 100644 (file)
   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
index 7e8100d10a60d83a0d6a19e98ee49d6cb85e6273..d2c2279b2d679ff5df0490b0a2ed77622fe5575e 100644 (file)
       - 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 (file)
index 0000000..94f0cb4
--- /dev/null
@@ -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: []
index 923ab4d49b03b49f0dd8e3dd00a316ddeae9442c..9156fd2b596f40a4b62f9f5fe9ac961cc946540e 100644 (file)
   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"
index c4d25c6461227100ed95315a1520c690a1b4490b..41a6a8030771422f7e8c22199982867bde27b591 100644 (file)
   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 (file)
index 0000000..b71ae48
--- /dev/null
@@ -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 (file)
index 0000000..513d4f5
Binary files /dev/null and b/Resources/Textures/Objects/Devices/gates.rsi/or.png differ