]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Device Linking and better linking ui (#13645)
authorJulian Giebel <juliangiebel@live.de>
Sun, 7 May 2023 06:07:24 +0000 (08:07 +0200)
committerGitHub <noreply@github.com>
Sun, 7 May 2023 06:07:24 +0000 (16:07 +1000)
Co-authored-by: AJCM-git <60196617+AJCM-git@users.noreply.github.com>
Co-authored-by: Visne <39844191+Visne@users.noreply.github.com>
Co-authored-by: ElectroJr <leonsfriedrich@gmail.com>
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
100 files changed:
Content.Client/NetworkConfigurator/NetworkConfiguratorBoundUserInterface.cs
Content.Client/NetworkConfigurator/NetworkConfiguratorConfigurationMenu.xaml
Content.Client/NetworkConfigurator/NetworkConfiguratorLinkMenu.xaml [new file with mode: 0644]
Content.Client/NetworkConfigurator/NetworkConfiguratorLinkMenu.xaml.cs [new file with mode: 0644]
Content.Client/NetworkConfigurator/NetworkConfiguratorLinkOverlay.cs
Content.Client/NetworkConfigurator/NetworkConfiguratorListMenu.xaml
Content.Client/NetworkConfigurator/Systems/DeviceListSystem.cs
Content.Client/NetworkConfigurator/Systems/NetworkConfiguratorSystem.cs
Content.IntegrationTests/Tests/EntityTest.cs
Content.Server/Atmos/Piping/Binary/EntitySystems/SignalControlledValveSystem.cs
Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentPumpSystem.cs
Content.Server/Cargo/Systems/CargoSystem.Orders.cs
Content.Server/Cargo/Systems/CargoSystem.Telepad.cs
Content.Server/Cloning/CloningConsoleSystem.cs
Content.Server/Cloning/CloningSystem.cs
Content.Server/DeviceLinking/Components/AutoLinkReceiverComponent.cs [moved from Content.Server/MachineLinking/Components/AutoLinkReceiverComponent.cs with 84% similarity]
Content.Server/DeviceLinking/Components/AutoLinkTransmitterComponent.cs [moved from Content.Server/MachineLinking/Components/AutoLinkTransmitterComponent.cs with 85% similarity]
Content.Server/DeviceLinking/Components/DoorSignalControlComponent.cs [moved from Content.Server/MachineLinking/Components/DoorSignalControlComponent.cs with 93% similarity]
Content.Server/DeviceLinking/Components/SignalSwitchComponent.cs [moved from Content.Server/MachineLinking/Components/SignalSwitchComponent.cs with 95% similarity]
Content.Server/DeviceLinking/Components/SignallerComponent.cs [moved from Content.Server/MachineLinking/Components/SignallerComponent.cs with 91% similarity]
Content.Server/DeviceLinking/Components/TwoWayLeverComponent.cs [moved from Content.Server/MachineLinking/Components/TwoWayLeverComponent.cs with 90% similarity]
Content.Server/DeviceLinking/Events/SignalReceivedEvent.cs [new file with mode: 0644]
Content.Server/DeviceLinking/Systems/AutoLinkSystem.cs [moved from Content.Server/MachineLinking/System/AutoLinkSystem.cs with 73% similarity]
Content.Server/DeviceLinking/Systems/DeviceLinkSystem.cs [new file with mode: 0644]
Content.Server/DeviceLinking/Systems/DoorSignalControlSystem.cs [moved from Content.Server/MachineLinking/System/DoorSignalControlSystem.cs with 75% similarity]
Content.Server/DeviceLinking/Systems/SignalSwitchSystem.cs [moved from Content.Server/MachineLinking/System/SignalSwitchSystem.cs with 78% similarity]
Content.Server/DeviceLinking/Systems/SignallerSystem.cs [moved from Content.Server/MachineLinking/System/SignallerSystem.cs with 79% similarity]
Content.Server/DeviceLinking/Systems/TwoWayLeverSystem.cs [moved from Content.Server/MachineLinking/System/TwoWayLeverSystem.cs with 92% similarity]
Content.Server/DeviceNetwork/Systems/DeviceListSystem.cs
Content.Server/DeviceNetwork/Systems/NetworkConfiguratorSystem.cs
Content.Server/Doors/Systems/AirlockSystem.cs
Content.Server/Explosion/EntitySystems/TriggerSystem.Signal.cs
Content.Server/Light/EntitySystems/PoweredLightSystem.cs
Content.Server/MachineLinking/Components/SignalTransmitterComponent.cs
Content.Server/MachineLinking/Events/SignalReceivedEvent.cs
Content.Server/MachineLinking/System/SignalLinkerSystem.cs
Content.Server/Medical/MedicalScannerSystem.cs
Content.Server/Physics/Controllers/ConveyorController.cs
Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactAnalyzerSystem.cs
Content.Shared/Access/Systems/AccessReaderSystem.cs
Content.Shared/DeviceLinking/DeviceLinkSinkComponent.cs [new file with mode: 0644]
Content.Shared/DeviceLinking/DeviceLinkSourceComponent.cs [new file with mode: 0644]
Content.Shared/DeviceLinking/DevicePortPrototype.cs [new file with mode: 0644]
Content.Shared/DeviceLinking/Events/LinkAttemptEvent.cs [new file with mode: 0644]
Content.Shared/DeviceLinking/Events/NewLinkEvent.cs [new file with mode: 0644]
Content.Shared/DeviceLinking/Events/PortDisconnectedEvent.cs [moved from Content.Server/MachineLinking/Events/PortDisconnectedEvent.cs with 81% similarity]
Content.Shared/DeviceLinking/SharedDeviceLinkSystem.cs [new file with mode: 0644]
Content.Shared/DeviceLinking/TwoWayLeverSignal.cs [moved from Content.Shared/MachineLinking/TwoWayLeverSignal.cs with 87% similarity]
Content.Shared/DeviceNetwork/Components/DeviceListComponent.cs
Content.Shared/DeviceNetwork/Components/NetworkConfiguratorComponent.cs
Content.Shared/DeviceNetwork/NetworkConfiguratorUIMessages.cs
Content.Shared/DeviceNetwork/NetworkConfiguratorUserInterfaceState.cs
Content.Shared/DeviceNetwork/Systems/SharedDeviceListSystem.cs
Content.Shared/DeviceNetwork/Systems/SharedNetworkConfiguratorSystem.cs
Content.Shared/MachineLinking/Events/LinkAttemptEvent.cs [deleted file]
Content.Shared/MachineLinking/Events/NewLinkEvent.cs [deleted file]
Resources/Audio/Machines/attributions.yml
Resources/Locale/en-US/devices/device-network.ftl
Resources/Locale/en-US/devices/network-configurator.ftl
Resources/Locale/en-US/guidebook/guides.ftl
Resources/Locale/en-US/machine-linking/port-selector.ftl
Resources/Locale/en-US/machine-linking/receiver_ports.ftl
Resources/Prototypes/Catalog/Fills/Items/belt.yml
Resources/Prototypes/Device/devicenet_frequencies.yml
Resources/Prototypes/DeviceLinking/sink_ports.yml [new file with mode: 0644]
Resources/Prototypes/DeviceLinking/source_ports.yml [new file with mode: 0644]
Resources/Prototypes/Entities/Clothing/Belt/belts.yml
Resources/Prototypes/Entities/Objects/Devices/Electronics/signaller.yml
Resources/Prototypes/Entities/Objects/Devices/Electronics/triggers.yml
Resources/Prototypes/Entities/Objects/Tools/cowtools.yml
Resources/Prototypes/Entities/Objects/Tools/tools.yml
Resources/Prototypes/Entities/Objects/Weapons/Bombs/plastic.yml
Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml
Resources/Prototypes/Entities/Structures/Doors/Shutter/shutters.yml
Resources/Prototypes/Entities/Structures/Doors/Windoors/base_structurewindoors.yml
Resources/Prototypes/Entities/Structures/Lighting/base_lighting.yml
Resources/Prototypes/Entities/Structures/Lighting/ground_lighting.yml
Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml
Resources/Prototypes/Entities/Structures/Machines/artifact_analyzer.yml
Resources/Prototypes/Entities/Structures/Machines/cloning_machine.yml
Resources/Prototypes/Entities/Structures/Machines/lathe.yml
Resources/Prototypes/Entities/Structures/Machines/medical_scanner.yml
Resources/Prototypes/Entities/Structures/Machines/recycler.yml
Resources/Prototypes/Entities/Structures/Piping/Atmospherics/binary.yml
Resources/Prototypes/Entities/Structures/Wallmounts/switch.yml
Resources/Prototypes/Entities/Structures/cargo_console.yml
Resources/Prototypes/Entities/Structures/conveyor.yml
Resources/Prototypes/Guidebook/engineering.yml
Resources/Prototypes/Recipes/Lathes/tools.yml
Resources/Prototypes/XenoArch/Effects/utility_effects.yml
Resources/Prototypes/tags.yml
Resources/ServerInfo/Guidebook/Network_Configurator.xml [new file with mode: 0644]
Resources/ServerInfo/Guidebook/Networking.xml [new file with mode: 0644]
Resources/Textures/Objects/Tools/network_configurator.rsi/equipped-BELT.png [new file with mode: 0644]
Resources/Textures/Objects/Tools/network_configurator.rsi/icon.png [new file with mode: 0644]
Resources/Textures/Objects/Tools/network_configurator.rsi/inhand-left.png [new file with mode: 0644]
Resources/Textures/Objects/Tools/network_configurator.rsi/inhand-right.png [new file with mode: 0644]
Resources/Textures/Objects/Tools/network_configurator.rsi/meta.json [new file with mode: 0644]
Resources/Textures/Objects/Tools/network_configurator.rsi/mode-link.png [new file with mode: 0644]
Resources/Textures/Objects/Tools/network_configurator.rsi/mode-list.png [new file with mode: 0644]

index e69ea43aa64a30fcc39e56c176b32ce16089c05e..7c50c150b2769854891f290a6ece45e1eb6f7fa2 100644 (file)
@@ -1,5 +1,5 @@
-ο»Ώusing Content.Shared.DeviceNetwork;
-using JetBrains.Annotations;
+ο»Ώusing Content.Client.NetworkConfigurator.Systems;
+using Content.Shared.DeviceNetwork;
 using Robust.Client.GameObjects;
 using Robust.Client.UserInterface.Controls;
 
@@ -8,17 +8,18 @@ namespace Content.Client.NetworkConfigurator;
 public sealed class NetworkConfiguratorBoundUserInterface : BoundUserInterface
 {
     [Dependency] private readonly IEntityManager _entityManager = default!;
+
     private NetworkConfiguratorListMenu? _listMenu;
     private NetworkConfiguratorConfigurationMenu? _configurationMenu;
+    private NetworkConfiguratorLinkMenu? _linkMenu;
 
     private NetworkConfiguratorSystem _netConfig;
-    private DeviceListSystem _deviceList;
 
     public NetworkConfiguratorBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
     {
         IoCManager.InjectDependencies(this);
+
         _netConfig = _entityManager.System<NetworkConfiguratorSystem>();
-        _deviceList = _entityManager.System<DeviceListSystem>();
     }
 
     public void OnRemoveButtonPressed(string address)
@@ -36,7 +37,7 @@ public sealed class NetworkConfiguratorBoundUserInterface : BoundUserInterface
                 _listMenu = new NetworkConfiguratorListMenu(this);
                 _listMenu.OnClose += Close;
                 _listMenu.ClearButton.OnPressed += _ => OnClearButtonPressed();
-                _listMenu.OpenCentered();
+                _listMenu.OpenCenteredRight();
                 break;
             case NetworkConfiguratorUiKey.Configure:
                 _configurationMenu = new NetworkConfiguratorConfigurationMenu();
@@ -50,6 +51,11 @@ public sealed class NetworkConfiguratorBoundUserInterface : BoundUserInterface
                 _configurationMenu.Show.Pressed = _netConfig.ConfiguredListIsTracked(Owner.Owner);
                 _configurationMenu.OpenCentered();
                 break;
+            case NetworkConfiguratorUiKey.Link:
+                _linkMenu = new NetworkConfiguratorLinkMenu(this);
+                _linkMenu.OnClose += Close;
+                _linkMenu.OpenCentered();
+                break;
         }
     }
 
@@ -70,6 +76,9 @@ public sealed class NetworkConfiguratorBoundUserInterface : BoundUserInterface
             case DeviceListUserInterfaceState listState:
                 _configurationMenu?.UpdateState(listState);
                 break;
+            case DeviceLinkUserInterfaceState linkState:
+                _linkMenu?.UpdateState(linkState);
+                break;
         }
     }
 
index 0f4efa56c8faa19d1a743e512667b7564f6f0633..469faf209d3301cad7940ddcf9876bad8b591a3b 100644 (file)
@@ -1,7 +1,7 @@
 <controls:FancyWindow xmlns="https://spacestation14.io"
                 xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
                 xmlns:networkConfigurator="clr-namespace:Content.Client.NetworkConfigurator"
-                Title="Network Configurator" MinSize="350 100">
+                Title="{Loc 'network-configurator-title-device-configuration'}" MinSize="350 100">
     <BoxContainer Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True">
         <networkConfigurator:NetworkConfiguratorDeviceList Name="DeviceList" MinHeight="500" />
         <BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="8 8 8 1">
@@ -15,6 +15,6 @@
             <Button Name="Copy" Text="Copy" Access="Public" ToolTip="{Loc 'network-configurator-tooltip-copy'}" HorizontalExpand="True" StyleClasses="OpenRight"/>
             <Button Name="Show" Text="Show" Access="Public" ToggleMode="True" ToolTip="{Loc 'network-configurator-tooltip-show'}" HorizontalExpand="True" StyleClasses="ButtonSquare"/>
         </BoxContainer>
-        <Label Name="Count" HorizontalAlignment="Right" />
+        <Label Name="Count" StyleClasses="LabelSubText" HorizontalAlignment="Right" Margin="0 0 12 8"/>
     </BoxContainer>
 </controls:FancyWindow>
diff --git a/Content.Client/NetworkConfigurator/NetworkConfiguratorLinkMenu.xaml b/Content.Client/NetworkConfigurator/NetworkConfiguratorLinkMenu.xaml
new file mode 100644 (file)
index 0000000..b048f45
--- /dev/null
@@ -0,0 +1,47 @@
+<controls:FancyWindow xmlns="https://spacestation14.io"
+                xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
+                xmlns:networkConfigurator="clr-namespace:Content.Client.NetworkConfigurator"
+                Title="Network Configurator" MinSize="620 400" RectClipContent="True">
+    <BoxContainer Orientation="Vertical" VerticalExpand="True">
+        <BoxContainer Orientation="Horizontal" HorizontalExpand="True">
+            <Label Margin="12 0" Text="{Loc signal-port-selector-help}"/>
+        </BoxContainer>
+        <Control VerticalExpand="True" HorizontalExpand="True" Margin="12 6 12 0">
+            <PanelContainer Name="MainPanel" HorizontalExpand="False" VerticalExpand="True" />
+            <ScrollContainer HorizontalExpand="True" VerticalExpand="True" HScrollEnabled="True">
+                <BoxContainer Orientation="Horizontal" HorizontalExpand="True" VerticalExpand="True" Margin="6">
+                    <BoxContainer Orientation="Vertical" HorizontalExpand="True" SizeFlagsStretchRatio="0.3">
+                        <RichTextLabel Name="HeaderLeft"/>
+                        <BoxContainer Name="ButtonContainerLeft" Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True"/>
+                    </BoxContainer>
+                    <BoxContainer Name="MiddleContainer" Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True" SizeFlagsStretchRatio="0.2"/>
+                    <BoxContainer Orientation="Vertical" HorizontalExpand="True" SizeFlagsStretchRatio="0.3">
+                        <RichTextLabel Name="HeaderRight"/>
+                        <BoxContainer Name="ButtonContainerRight" Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True"/>
+                    </BoxContainer>
+                </BoxContainer>
+            </ScrollContainer>
+        </Control>
+        <BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="12 4">
+            <BoxContainer Orientation="Horizontal">
+                <Button Name="ButtonClear" SetHeight="32" StyleClasses="OpenRight" Text="{Loc signal-port-selector-menu-clear}"/>
+                <Button Name="ButtonLinkDefault" SetHeight="32" StyleClasses="OpenLeft" Text="{Loc signal-port-selector-menu-link-defaults}"/>
+            </BoxContainer>
+            <Control HorizontalExpand="True"/>
+            <Control>
+                <Button Name="ButtonOk" MinHeight="26" StyleClasses="ButtonColorGreen" Text="{Loc signal-port-selector-menu-done}"></Button>
+            </Control>
+        </BoxContainer>
+        <Control SetHeight="28" Margin="1 0 2 1">
+            <PanelContainer Name="FooterPanel"></PanelContainer>
+            <BoxContainer Name="ContentFooter" HorizontalExpand="True" SetHeight="28">
+                <Label Text="Net#Link β„’" VerticalAlignment="Center" Margin="6 0" StyleClasses="PDAContentFooterText"/>
+                <Label Name="AddressLabel" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="6 0" StyleClasses="PDAContentFooterText"/>
+                <Control HorizontalExpand="True"/>
+                <Label Name="FromAddressLabel" Margin="6 0" StyleClasses="PDAContentFooterText"/>
+                <Label SetWidth="25" StyleClasses="PDAContentFooterText" Align="Center" Text="➝"/><!--Turn this into an arrow texture-->
+                <Label Name="ToAddressLabel" Margin="6 0" StyleClasses="PDAContentFooterText"/>
+            </BoxContainer>
+        </Control>
+    </BoxContainer>
+</controls:FancyWindow>
diff --git a/Content.Client/NetworkConfigurator/NetworkConfiguratorLinkMenu.xaml.cs b/Content.Client/NetworkConfigurator/NetworkConfiguratorLinkMenu.xaml.cs
new file mode 100644 (file)
index 0000000..a867a60
--- /dev/null
@@ -0,0 +1,221 @@
+ο»Ώusing Content.Client.UserInterface.Controls;
+using Content.Shared.DeviceLinking;
+using Content.Shared.DeviceNetwork;
+using Robust.Client.AutoGenerated;
+using Robust.Client.Graphics;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Vector2 = Robust.Shared.Maths.Vector2;
+
+namespace Content.Client.NetworkConfigurator;
+
+[GenerateTypedNameReferences]
+public sealed partial class NetworkConfiguratorLinkMenu : FancyWindow
+{
+    private const string PanelBgColor = "#202023";
+
+    private readonly LinksRender _links;
+
+
+    private readonly List<SourcePortPrototype> _sources = new();
+
+    private readonly List<SinkPortPrototype> _sinks = new();
+
+    private readonly NetworkConfiguratorBoundUserInterface _userInterface;
+
+    private (ButtonPosition position, string id, int index)? _selectedButton;
+
+    private List<(string left, string right)>? _defaults;
+
+    public NetworkConfiguratorLinkMenu(NetworkConfiguratorBoundUserInterface userInterface)
+    {
+        _userInterface = userInterface;
+        RobustXamlLoader.Load(this);
+
+        var footerStyleBox = new StyleBoxFlat()
+        {
+            BorderThickness = new Thickness(0, 2, 0, 0),
+            BorderColor = Color.FromHex("#5A5A5A")
+        };
+
+        FooterPanel.PanelOverride = footerStyleBox;
+        MainPanel.PanelOverride = new StyleBoxFlat(Color.FromHex(PanelBgColor));
+
+        ButtonClear.AddStyleClass("ButtonColorRed");
+        ButtonLinkDefault.Disabled = true;
+
+        _links = new LinksRender(ButtonContainerLeft, ButtonContainerRight);
+        _links.VerticalExpand = true;
+        MiddleContainer.AddChild(_links);
+
+        ButtonOk.OnPressed += _ => Close();
+        ButtonLinkDefault.OnPressed += _ => LinkDefaults();
+        ButtonClear.OnPressed += _ => _userInterface.SendMessage(new NetworkConfiguratorClearLinksMessage());
+    }
+
+    public void UpdateState(DeviceLinkUserInterfaceState linkState)
+    {
+        ButtonContainerLeft.RemoveAllChildren();
+        ButtonContainerRight.RemoveAllChildren();
+
+        _sources.Clear();
+        _sources.AddRange(linkState.Sources);
+        _links.SourceButtons.Clear();
+        var i = 0;
+        foreach (var source in _sources)
+        {
+            var button = CreateButton(ButtonPosition.Left, source.Name, source.Description, source.ID, i);
+            ButtonContainerLeft.AddChild(button);
+            _links.SourceButtons.Add(source.ID, button);
+            i++;
+        }
+
+        _sinks.Clear();
+        _sinks.AddRange(linkState.Sinks);
+        _links.SinkButtons.Clear();
+        i = 0;
+        foreach (var sink in _sinks)
+        {
+            var button = CreateButton(ButtonPosition.Right, sink.Name, sink.Description, sink.ID, i);
+            ButtonContainerRight.AddChild(button);
+            _links.SinkButtons.Add(sink.ID, button);
+            i++;
+        }
+
+        _links.Links.Clear();
+        _links.Links.AddRange(linkState.Links);
+        _defaults = linkState.Defaults;
+
+        ButtonLinkDefault.Disabled = _defaults == default;
+        FromAddressLabel.Text = linkState.SourceAddress;
+        ToAddressLabel.Text = linkState.SinkAddress;
+    }
+
+    private void LinkDefaults()
+    {
+        if (_defaults == default)
+            return;
+
+        _userInterface.SendMessage(new NetworkConfiguratorLinksSaveMessage(_defaults));
+    }
+
+    private Button CreateButton(ButtonPosition position, string name, string description, string id, int index)
+    {
+        var button = new Button();
+        button.AddStyleClass("OpenBoth");
+        button.Text = Loc.GetString(name);
+        button.ToolTip = Loc.GetString(description);
+        button.ToggleMode = true;
+        button.OnPressed += args => OnButtonPressed(args, position, id, index);
+        return button;
+    }
+
+    private void OnButtonPressed(BaseButton.ButtonEventArgs args, ButtonPosition position, string id, int index)
+    {
+        var key = (position, id, index);
+        if (_selectedButton == key)
+        {
+            args.Button.Pressed = false;
+            _selectedButton = null;
+            return;
+        }
+
+        if (!_selectedButton.HasValue)
+        {
+            args.Button.Pressed = true;
+            _selectedButton = key;
+            return;
+        }
+
+        if (_selectedButton.Value.position == position)
+        {
+            args.Button.Pressed = false;
+            return;
+        }
+
+        var left = _selectedButton.Value.position == ButtonPosition.Left ? _selectedButton.Value.id : id;
+        var right = _selectedButton.Value.position == ButtonPosition.Left ? id : _selectedButton.Value.id;
+
+        _userInterface.SendMessage(new NetworkConfiguratorToggleLinkMessage(left, right));
+
+        args.Button.Pressed = false;
+
+        var container = _selectedButton.Value.position == ButtonPosition.Left ? ButtonContainerLeft : ButtonContainerRight;
+        if (container.GetChild(_selectedButton.Value.index) is Button button)
+            button.Pressed = false;
+
+        _selectedButton = null;
+    }
+
+    private enum ButtonPosition
+    {
+        Left,
+        Right
+    }
+
+    /// <summary>
+    ///  Draws lines between linked ports using bezier curve calculated with polynomial coefficients
+    ///  See: https://youtu.be/jvPPXbo87ds?t=351
+    /// </summary>
+    private sealed class LinksRender : Control
+    {
+        public readonly List<(string, string)> Links = new();
+        public readonly Dictionary<string, Button> SourceButtons = new();
+        public readonly Dictionary<string, Button> SinkButtons = new();
+        private readonly BoxContainer _leftButtonContainer;
+        private readonly BoxContainer _rightButtonContainer;
+
+        public LinksRender(BoxContainer leftButtonContainer, BoxContainer rightButtonContainer)
+        {
+            _leftButtonContainer = leftButtonContainer;
+            _rightButtonContainer = rightButtonContainer;
+        }
+
+        protected override void Draw(DrawingHandleScreen handle)
+        {
+            foreach (var (left, right) in Links)
+            {
+                if (!SourceButtons.TryGetValue(left, out var leftChild) || !SinkButtons.TryGetValue(right, out var rightChild))
+                    continue;
+
+                var leftOffset = _leftButtonContainer.PixelPosition.Y;
+                var rightOffset = _rightButtonContainer.PixelPosition.Y;
+
+                var y1 = leftChild.PixelPosition.Y + leftChild.PixelHeight / 2 + leftOffset;
+                var y2 = rightChild.PixelPosition.Y + rightChild.PixelHeight / 2 + rightOffset;
+
+                if (left == right)
+                {
+                    handle.DrawLine((0, y1), (PixelWidth, y2), Color.Cyan);
+                    continue;
+                }
+
+                var controls = new List<Vector2>
+                {
+                    new(0, y1),
+                    new(30, y1),
+                    new(PixelWidth - 30, y2),
+                    new(PixelWidth, y2),
+                };
+
+                //Calculate coefficients
+                var c0 = controls[0];
+                var c1 = controls[0] * -3 + controls[1] * 3;
+                var c2 = controls[0] * 3 + controls[1] * -6 + controls[2] * 3;
+                var c3 = controls[0] * -1 + controls[1] * 3 + controls[2] * -3 + controls[3];
+
+                var points = new List<Vector2>();
+
+                //Calculate points using coefficients
+                for (float t = 0; t <= 1; t += 0.0001f)
+                {
+                    var point = c0 + c1 * t + c2 * (t * t) + c3 * (t * t * t);
+                    points.Add(point);
+                }
+
+                handle.DrawPrimitives(DrawPrimitiveTopology.LineStrip, points.ToArray(), Color.Cyan);
+            }
+        }
+    }
+}
index 23404b884b7248fb9019ad309c8577f32325b285..d61bb469b36e6ce241c542f9b0c5f7195a8b8c9c 100644 (file)
@@ -1,4 +1,6 @@
+using Content.Client.NetworkConfigurator.Systems;
 using Content.Shared.DeviceNetwork;
+using Content.Shared.DeviceNetwork.Components;
 using Robust.Client.Graphics;
 using Robust.Shared.Enums;
 using Robust.Shared.Random;
index 8d46ecfb2076541bff0ab83874574c196c5569ae..4f98e4ba8bb7d143275ab40ca0e1a6f4ebf9f973 100644 (file)
@@ -1,7 +1,7 @@
 <controls:FancyWindow xmlns="https://spacestation14.io"
                 xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
                 xmlns:networkConfigurator="clr-namespace:Content.Client.NetworkConfigurator"
-                Title="Network Configurator" MinSize="220 400">
+                Title="{Loc 'network-configurator-title-saved-devices'}" MinSize="220 400">
     <BoxContainer Orientation="Vertical" VerticalExpand="True">
         <networkConfigurator:NetworkConfiguratorDeviceList Name="DeviceList" />
         <BoxContainer Orientation="Horizontal" Margin="8 8 8 8">
index b171367e2343f895712982e0c6599a5e223c7e45..150361c0aebeefde422ee6f5e0187b758a9ccfc6 100644 (file)
@@ -1,8 +1,6 @@
-using System.Linq;
 using Content.Shared.DeviceNetwork;
-using Robust.Client.Graphics;
 
-namespace Content.Client.NetworkConfigurator;
+namespace Content.Client.NetworkConfigurator.Systems;
 
 public sealed class DeviceListSystem : SharedDeviceListSystem
 {
index e63365af1009d24719893d90e6bb6d9900522130..80d7b9381ce7f7c802f5a40e209f67ca1545df9a 100644 (file)
@@ -1,15 +1,22 @@
 using System.Linq;
 using Content.Client.Actions;
-using Content.Shared.Actions;
+using Content.Client.Items;
+using Content.Client.Message;
+using Content.Client.Stylesheets;
 using Content.Shared.Actions.ActionTypes;
-using Content.Shared.DeviceNetwork;
+using Content.Shared.DeviceNetwork.Components;
+using Content.Shared.DeviceNetwork.Systems;
+using Content.Shared.Input;
 using Robust.Client.Graphics;
+using Robust.Client.Input;
 using Robust.Client.Player;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
 using Robust.Shared.Console;
 using Robust.Shared.Prototypes;
-using Robust.Shared.Utility;
+using Robust.Shared.Timing;
 
-namespace Content.Client.NetworkConfigurator;
+namespace Content.Client.NetworkConfigurator.Systems;
 
 public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem
 {
@@ -17,6 +24,7 @@ public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem
     [Dependency] private readonly IOverlayManager _overlay = default!;
     [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
     [Dependency] private readonly ActionsSystem _actions = default!;
+    [Dependency] private readonly IInputManager _inputManager = default!;
 
     private const string Action = "ClearNetworkLinkOverlays";
 
@@ -25,6 +33,13 @@ public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem
         base.Initialize();
 
         SubscribeLocalEvent<ClearAllOverlaysEvent>(_ => ClearAllOverlays());
+        SubscribeLocalEvent<NetworkConfiguratorComponent, ItemStatusCollectMessage>(OnCollectItemStatus);
+    }
+
+    private void OnCollectItemStatus(EntityUid uid, NetworkConfiguratorComponent configurator, ItemStatusCollectMessage args)
+    {
+        _inputManager.TryGetKeyBinding((ContentKeyFunctions.AltUseItemInHand), out var binding);
+        args.Controls.Add(new StatusControl(configurator, binding?.GetKeyString() ?? ""));
     }
 
     public bool ConfiguredListIsTracked(EntityUid uid, NetworkConfiguratorComponent? component = null)
@@ -102,6 +117,41 @@ public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem
 
         component.ActiveDeviceList = list;
     }
+
+    private sealed class StatusControl : Control
+    {
+        private readonly RichTextLabel _label;
+        private readonly NetworkConfiguratorComponent _configurator;
+        private readonly string _keyBindingName;
+
+        private bool? _linkModeActive = null;
+
+        public StatusControl(NetworkConfiguratorComponent configurator, string keyBindingName)
+        {
+            _configurator = configurator;
+            _keyBindingName = keyBindingName;
+            _label = new RichTextLabel { StyleClasses = { StyleNano.StyleClassItemStatus } };
+            AddChild(_label);
+        }
+
+        protected override void FrameUpdate(FrameEventArgs args)
+        {
+            base.FrameUpdate(args);
+
+            if (_linkModeActive != null && _linkModeActive == _configurator.LinkModeActive)
+                return;
+
+            _linkModeActive = _configurator.LinkModeActive;
+
+            var modeLocString = _linkModeActive??false
+                ? "network-configurator-examine-mode-link"
+                : "network-configurator-examine-mode-list";
+
+            _label.SetMarkup(Loc.GetString("network-configurator-item-status-label",
+                ("mode", Loc.GetString(modeLocString)),
+                ("keybinding", _keyBindingName)));
+        }
+    }
 }
 
 public sealed class ClearAllNetworkLinkOverlays : IConsoleCommand
index 32042dbae5ba2ca0fb446f3e81e33a5a0f912cac..0c095b26147cdd550385b7ebae4b7272f59df065 100644 (file)
@@ -82,7 +82,6 @@ namespace Content.IntegrationTests.Tests
             await server.WaitPost(() =>
             {
                 entityMan = IoCManager.Resolve<IEntityManager>();
-                var mapManager = IoCManager.Resolve<IMapManager>();
 
                 var prototypeMan = IoCManager.Resolve<IPrototypeManager>();
                 var protoIds = prototypeMan
index 600671c4421f8c7b75865dfcfd8903f84491d451..2a3acda458cfe6b69cb7ada64374de460a283fd6 100644 (file)
@@ -1,12 +1,13 @@
 using Content.Server.Atmos.Piping.Binary.Components;
-using Content.Server.MachineLinking.Events;
+using Content.Server.DeviceLinking.Events;
+using Content.Server.DeviceLinking.Systems;
 using Content.Server.MachineLinking.System;
 
 namespace Content.Server.Atmos.Piping.Binary.EntitySystems;
 
 public sealed class SignalControlledValveSystem : EntitySystem
 {
-    [Dependency] private readonly SignalLinkerSystem _signal = default!;
+    [Dependency] private readonly DeviceLinkSystem _signal = default!;
     [Dependency] private readonly GasValveSystem _valve = default!;
 
     public override void Initialize()
@@ -19,10 +20,10 @@ public sealed class SignalControlledValveSystem : EntitySystem
 
     private void OnInit(EntityUid uid, SignalControlledValveComponent comp, ComponentInit args)
     {
-        _signal.EnsureReceiverPorts(uid, comp.OpenPort, comp.ClosePort, comp.TogglePort);
+        _signal.EnsureSinkPorts(uid, comp.OpenPort, comp.ClosePort, comp.TogglePort);
     }
 
-    private void OnSignalReceived(EntityUid uid, SignalControlledValveComponent comp, SignalReceivedEvent args)
+    private void OnSignalReceived(EntityUid uid, SignalControlledValveComponent comp, ref SignalReceivedEvent args)
     {
         if (!TryComp<GasValveComponent>(uid, out var valve))
             return;
index d14bc43d161dc590d370397a7bc19f991007840e..97d6b5a921873f1156047d1ebe74d97dd1d41967 100644 (file)
@@ -1,13 +1,12 @@
 using Content.Server.Atmos.EntitySystems;
-using Content.Server.Atmos.Monitor.Components;
 using Content.Server.Atmos.Monitor.Systems;
 using Content.Server.Atmos.Piping.Components;
 using Content.Server.Atmos.Piping.Unary.Components;
+using Content.Server.DeviceLinking.Events;
+using Content.Server.DeviceLinking.Systems;
 using Content.Server.DeviceNetwork;
 using Content.Server.DeviceNetwork.Components;
 using Content.Server.DeviceNetwork.Systems;
-using Content.Server.MachineLinking.Events;
-using Content.Server.MachineLinking.System;
 using Content.Server.NodeContainer;
 using Content.Server.NodeContainer.Nodes;
 using Content.Server.Power.Components;
@@ -28,7 +27,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
     {
         [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
         [Dependency] private readonly DeviceNetworkSystem _deviceNetSystem = default!;
-        [Dependency] private readonly SignalLinkerSystem _signalSystem = default!;
+        [Dependency] private readonly DeviceLinkSystem _signalSystem = default!;
         [Dependency] private readonly IGameTiming _gameTiming = default!;
         [Dependency] private readonly SharedAmbientSoundSystem _ambientSoundSystem = default!;
         [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
@@ -224,10 +223,10 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
         private void OnInit(EntityUid uid, GasVentPumpComponent component, ComponentInit args)
         {
             if (component.CanLink)
-                _signalSystem.EnsureReceiverPorts(uid, component.PressurizePort, component.DepressurizePort);
+                _signalSystem.EnsureSinkPorts(uid, component.PressurizePort, component.DepressurizePort);
         }
 
-        private void OnSignalReceived(EntityUid uid, GasVentPumpComponent component, SignalReceivedEvent args)
+        private void OnSignalReceived(EntityUid uid, GasVentPumpComponent component, ref SignalReceivedEvent args)
         {
             if (!component.CanLink)
                 return;
index 1ac6d52149e644810c744e8fead020e56fe95fde..0938052c070dc1404a66b7868234c37494fd5eda 100644 (file)
@@ -2,7 +2,7 @@ using System.Diagnostics.CodeAnalysis;
 using Content.Server.Access.Systems;
 using Content.Server.Cargo.Components;
 using Content.Server.Labels.Components;
-using Content.Server.MachineLinking.System;
+using Content.Server.DeviceLinking.Systems;
 using Content.Server.Popups;
 using Content.Server.Station.Systems;
 using Content.Shared.Access.Systems;
@@ -34,7 +34,7 @@ namespace Content.Server.Cargo.Systems
 
         [Dependency] private readonly IdCardSystem _idCardSystem = default!;
         [Dependency] private readonly AccessReaderSystem _accessReaderSystem = default!;
-        [Dependency] private readonly SignalLinkerSystem _linker = default!;
+        [Dependency] private readonly DeviceLinkSystem _linker = default!;
         [Dependency] private readonly PopupSystem _popup = default!;
         [Dependency] private readonly StationSystem _station = default!;
         [Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
index 02f31de606500666d1bf71eacad3b2e9ee03f27d..e1744c3e9aad7056a3da08f6ea64561c2f50f001 100644 (file)
@@ -74,7 +74,7 @@ public sealed partial class CargoSystem
 
     private void OnInit(EntityUid uid, CargoTelepadComponent telepad, ComponentInit args)
     {
-        _linker.EnsureReceiverPorts(uid, telepad.ReceiverPort);
+        _linker.EnsureSinkPorts(uid, telepad.ReceiverPort);
     }
 
     private void SetEnabled(CargoTelepadComponent component, ApcPowerReceiverComponent? receiver = null,
index eb8e2b80cb76af92d314d800e2d9d88e7fc67457..ceb642173f59ff082c363ee51e4c34d09bd43a07 100644 (file)
@@ -4,11 +4,10 @@ using Robust.Shared.Timing;
 using Content.Server.Administration.Logs;
 using Content.Server.Medical.Components;
 using Content.Server.Cloning.Components;
-using Content.Server.MachineLinking.Components;
+using Content.Server.DeviceLinking.Events;
+using Content.Server.DeviceLinking.Systems;
 using Content.Server.Power.Components;
 using Content.Server.Mind.Components;
-using Content.Server.MachineLinking.System;
-using Content.Server.MachineLinking.Events;
 using Content.Server.UserInterface;
 using Content.Server.Power.EntitySystems;
 using Robust.Server.GameObjects;
@@ -16,7 +15,8 @@ using Robust.Server.Player;
 using Content.Shared.Cloning.CloningConsole;
 using Content.Shared.Cloning;
 using Content.Shared.Database;
-using Content.Shared.MachineLinking.Events;
+using Content.Shared.DeviceLinking;
+using Content.Shared.DeviceLinking.Events;
 using Content.Shared.IdentityManagement;
 using Content.Shared.Mobs.Components;
 using Content.Shared.Mobs.Systems;
@@ -26,7 +26,7 @@ namespace Content.Server.Cloning
     [UsedImplicitly]
     public sealed class CloningConsoleSystem : EntitySystem
     {
-        [Dependency] private readonly SignalLinkerSystem _signalSystem = default!;
+        [Dependency] private readonly DeviceLinkSystem _signalSystem = default!;
         [Dependency] private readonly IAdminLogManager _adminLogger = default!;
         [Dependency] private readonly IPlayerManager _playerManager = default!;
         [Dependency] private readonly CloningSystem _cloningSystem = default!;
@@ -48,7 +48,7 @@ namespace Content.Server.Cloning
 
         private void OnInit(EntityUid uid, CloningConsoleComponent component, ComponentInit args)
         {
-            _signalSystem.EnsureTransmitterPorts(uid, CloningConsoleComponent.ScannerPort, CloningConsoleComponent.PodPort);
+            _signalSystem.EnsureSourcePorts(uid, CloningConsoleComponent.ScannerPort, CloningConsoleComponent.PodPort);
         }
         private void OnButtonPressed(EntityUid uid, CloningConsoleComponent consoleComponent, UiButtonPressedMessage args)
         {
@@ -72,20 +72,20 @@ namespace Content.Server.Cloning
 
         private void OnMapInit(EntityUid uid, CloningConsoleComponent component, MapInitEvent args)
         {
-            if (!TryComp<SignalTransmitterComponent>(uid, out var receiver))
+            if (!TryComp<DeviceLinkSourceComponent>(uid, out var receiver))
                 return;
 
             foreach (var port in receiver.Outputs.Values.SelectMany(ports => ports))
             {
-                if (TryComp<MedicalScannerComponent>(port.Uid, out var scanner))
+                if (TryComp<MedicalScannerComponent>(port, out var scanner))
                 {
-                    component.GeneticScanner = port.Uid;
+                    component.GeneticScanner = port;
                     scanner.ConnectedConsole = uid;
                 }
 
-                if (TryComp<CloningPodComponent>(port.Uid, out var pod))
+                if (TryComp<CloningPodComponent>(port, out var pod))
                 {
-                    component.CloningPod = port.Uid;
+                    component.CloningPod = port;
                     pod.ConnectedConsole = uid;
                 }
             }
@@ -93,15 +93,15 @@ namespace Content.Server.Cloning
 
         private void OnNewLink(EntityUid uid, CloningConsoleComponent component, NewLinkEvent args)
         {
-            if (TryComp<MedicalScannerComponent>(args.Receiver, out var scanner) && args.TransmitterPort == CloningConsoleComponent.ScannerPort)
+            if (TryComp<MedicalScannerComponent>(args.Sink, out var scanner) && args.SourcePort == CloningConsoleComponent.ScannerPort)
             {
-                component.GeneticScanner = args.Receiver;
+                component.GeneticScanner = args.Sink;
                 scanner.ConnectedConsole = uid;
             }
 
-            if (TryComp<CloningPodComponent>(args.Receiver, out var pod) && args.TransmitterPort == CloningConsoleComponent.PodPort)
+            if (TryComp<CloningPodComponent>(args.Sink, out var pod) && args.SourcePort == CloningConsoleComponent.PodPort)
             {
-                component.CloningPod = args.Receiver;
+                component.CloningPod = args.Sink;
                 pod.ConnectedConsole = uid;
             }
             RecheckConnections(uid, component.CloningPod, component.GeneticScanner, component);
index c98fa518036fdae2b4e10bcff80c42fcf175d6e5..f0077ff588e537afb4f34b24aa105816dfcf338c 100644 (file)
@@ -10,15 +10,15 @@ using Content.Server.Power.EntitySystems;
 using Content.Server.Atmos.EntitySystems;
 using Content.Server.EUI;
 using Content.Server.Humanoid;
-using Content.Server.MachineLinking.System;
-using Content.Server.MachineLinking.Events;
 using Content.Shared.Chemistry.Components;
 using Content.Server.Fluids.EntitySystems;
 using Content.Server.Chat.Systems;
 using Content.Server.Construction;
+using Content.Server.DeviceLinking.Events;
+using Content.Server.DeviceLinking.Systems;
 using Content.Server.Materials;
-using Content.Server.Stack;
 using Content.Server.Jobs;
+using Content.Shared.DeviceLinking.Events;
 using Content.Shared.Emag.Components;
 using Content.Shared.Humanoid;
 using Content.Shared.Humanoid.Prototypes;
@@ -43,7 +43,7 @@ namespace Content.Server.Cloning
 {
     public sealed class CloningSystem : EntitySystem
     {
-        [Dependency] private readonly SignalLinkerSystem _signalSystem = default!;
+        [Dependency] private readonly DeviceLinkSystem _signalSystem = default!;
         [Dependency] private readonly IPlayerManager _playerManager = null!;
         [Dependency] private readonly IPrototypeManager _prototype = default!;
         [Dependency] private readonly EuiManager _euiManager = null!;
@@ -84,7 +84,7 @@ namespace Content.Server.Cloning
         private void OnComponentInit(EntityUid uid, CloningPodComponent clonePod, ComponentInit args)
         {
             clonePod.BodyContainer = _containerSystem.EnsureContainer<ContainerSlot>(clonePod.Owner, "clonepod-bodyContainer");
-            _signalSystem.EnsureReceiverPorts(uid, CloningPodComponent.PodPort);
+            _signalSystem.EnsureSinkPorts(uid, CloningPodComponent.PodPort);
         }
 
         private void OnPartsRefreshed(EntityUid uid, CloningPodComponent component, RefreshPartsEvent args)
similarity index 84%
rename from Content.Server/MachineLinking/Components/AutoLinkReceiverComponent.cs
rename to Content.Server/DeviceLinking/Components/AutoLinkReceiverComponent.cs
index 7bb9f99a4e0bafea3e8ffb15c9875b3fe912cc13..9a98f81e31c3156c65cdba46291a939e7005ce83 100644 (file)
@@ -1,4 +1,4 @@
-namespace Content.Server.MachineLinking.Components;
+namespace Content.Server.DeviceLinking.Components;
 
 /// <summary>
 /// This is used for automatic linkage with buttons and other transmitters.
similarity index 85%
rename from Content.Server/MachineLinking/Components/AutoLinkTransmitterComponent.cs
rename to Content.Server/DeviceLinking/Components/AutoLinkTransmitterComponent.cs
index b3760a2f2c8de11acbe77c0a0269b1f093dd71d3..b013d9f578bfc0a5dc94c5cd8572339160b7ac63 100644 (file)
@@ -1,4 +1,4 @@
-namespace Content.Server.MachineLinking.Components;
+namespace Content.Server.DeviceLinking.Components;
 
 /// <summary>
 /// This is used for automatic linkage with various receivers, like shutters.
similarity index 93%
rename from Content.Server/MachineLinking/Components/DoorSignalControlComponent.cs
rename to Content.Server/DeviceLinking/Components/DoorSignalControlComponent.cs
index 1d5b92f2434732c9743db46785bcd3f0ceda077b..98c8792f7776f112a17db457c3fa135bbb813a40 100644 (file)
@@ -1,7 +1,7 @@
 using Content.Shared.MachineLinking;
 using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
 
-namespace Content.Server.MachineLinking.Components
+namespace Content.Server.DeviceLinking.Components
 {
     [RegisterComponent]
     public sealed class DoorSignalControlComponent : Component
similarity index 95%
rename from Content.Server/MachineLinking/Components/SignalSwitchComponent.cs
rename to Content.Server/DeviceLinking/Components/SignalSwitchComponent.cs
index 53723f82e435900eac7efe75c60bcc8cc22e0800..4259ba6c6c05141e7c774746052354a01eafb458 100644 (file)
@@ -2,7 +2,7 @@ using Content.Shared.MachineLinking;
 using Robust.Shared.Audio;
 using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
 
-namespace Content.Server.MachineLinking.Components
+namespace Content.Server.DeviceLinking.Components
 {
     /// <summary>
     ///     Simple switch that will fire ports when toggled on or off. A button is jsut a switch that signals on the
similarity index 91%
rename from Content.Server/MachineLinking/Components/SignallerComponent.cs
rename to Content.Server/DeviceLinking/Components/SignallerComponent.cs
index e4d71401364e44c52d14b34efb82005c82ae0821..8cc13b083f34881f0b44c2e6f621b72ad23795c5 100644 (file)
@@ -1,7 +1,7 @@
 using Content.Shared.MachineLinking;
 using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
 
-namespace Content.Server.MachineLinking.Components
+namespace Content.Server.DeviceLinking.Components
 {
     /// <summary>
     /// Sends out a signal to machine linked objects.
similarity index 90%
rename from Content.Server/MachineLinking/Components/TwoWayLeverComponent.cs
rename to Content.Server/DeviceLinking/Components/TwoWayLeverComponent.cs
index 7420fb191d1f22d70e27c0bcc5eb105386ba4a22..5ab02b679ab36320a8e34694650c3d19bfcf795d 100644 (file)
@@ -1,7 +1,8 @@
+using Content.Shared.DeviceLinking;
 using Content.Shared.MachineLinking;
 using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
 
-namespace Content.Server.MachineLinking.Components
+namespace Content.Server.DeviceLinking.Components
 {
     [RegisterComponent]
     public sealed class TwoWayLeverComponent : Component
diff --git a/Content.Server/DeviceLinking/Events/SignalReceivedEvent.cs b/Content.Server/DeviceLinking/Events/SignalReceivedEvent.cs
new file mode 100644 (file)
index 0000000..c192857
--- /dev/null
@@ -0,0 +1,6 @@
+using Content.Server.DeviceNetwork;
+
+namespace Content.Server.DeviceLinking.Events;
+
+[ByRefEvent]
+public readonly record struct SignalReceivedEvent(string Port, EntityUid? Trigger = null, NetworkPayload? Data = null);
similarity index 73%
rename from Content.Server/MachineLinking/System/AutoLinkSystem.cs
rename to Content.Server/DeviceLinking/Systems/AutoLinkSystem.cs
index c7590e564d87da71eb4a7159ca894196be6fc9a7..3c9ccb57b1e58ed801917ebbb3470a1124901d9c 100644 (file)
@@ -1,13 +1,14 @@
-using Content.Server.MachineLinking.Components;
+using Content.Server.DeviceLinking.Components;
+using Content.Server.MachineLinking.System;
 
-namespace Content.Server.MachineLinking.System;
+namespace Content.Server.DeviceLinking.Systems;
 
 /// <summary>
 /// This handles automatically linking autolinked entities at round-start.
 /// </summary>
 public sealed class AutoLinkSystem : EntitySystem
 {
-    [Dependency] private readonly SignalLinkerSystem _signalLinkerSystem = default!;
+    [Dependency] private readonly DeviceLinkSystem _deviceLinkSystem = default!;
 
     /// <inheritdoc/>
     public override void Initialize()
@@ -29,7 +30,7 @@ public sealed class AutoLinkSystem : EntitySystem
             if (rxXform.GridUid != xform.GridUid)
                 continue;
 
-            _signalLinkerSystem.TryLinkDefaults(receiver.Owner, uid, null);
+            _deviceLinkSystem.LinkDefaults(null, uid, receiver.Owner);
         }
     }
 }
diff --git a/Content.Server/DeviceLinking/Systems/DeviceLinkSystem.cs b/Content.Server/DeviceLinking/Systems/DeviceLinkSystem.cs
new file mode 100644 (file)
index 0000000..60c85fd
--- /dev/null
@@ -0,0 +1,114 @@
+ο»Ώusing Content.Server.DeviceLinking.Events;
+using Content.Server.DeviceNetwork;
+using Content.Server.DeviceNetwork.Components;
+using Content.Server.DeviceNetwork.Systems;
+using Content.Server.MachineLinking.Components;
+using Content.Shared.DeviceLinking;
+using Robust.Shared.Utility;
+
+namespace Content.Server.DeviceLinking.Systems;
+
+public sealed class DeviceLinkSystem : SharedDeviceLinkSystem
+{
+    [Dependency] private readonly DeviceNetworkSystem _deviceNetworkSystem = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+        SubscribeLocalEvent<SignalTransmitterComponent, MapInitEvent>(OnTransmitterStartup);
+        SubscribeLocalEvent<DeviceLinkSinkComponent, DeviceNetworkPacketEvent>(OnPacketReceived);
+    }
+
+    /// <summary>
+    /// Moves existing links from machine linking to device linking to ensure linked things still work even when the map wasn't updated yet
+    /// </summary>
+    private void OnTransmitterStartup(EntityUid sourceUid, SignalTransmitterComponent transmitterComponent, MapInitEvent args)
+    {
+        if (!TryComp<DeviceLinkSourceComponent?>(sourceUid, out var sourceComponent))
+            return;
+
+        Dictionary<EntityUid, List<(string, string)>> outputs = new();
+        foreach (var (transmitterPort, receiverPorts) in transmitterComponent.Outputs)
+        {
+
+            foreach (var receiverPort in receiverPorts)
+            {
+                outputs.GetOrNew(receiverPort.Uid).Add((transmitterPort, receiverPort.Port));
+            }
+        }
+
+        foreach (var (sinkUid, links) in outputs)
+        {
+            SaveLinks(null, sourceUid, sinkUid, links, sourceComponent);
+        }
+    }
+
+     #region Sending & Receiving
+    /// <summary>
+    /// Sends a network payload directed at the sink entity.
+    /// Just raises a <see cref="SignalReceivedEvent"/> without data if the source or the sink doesn't have a <see cref="DeviceNetworkComponent"/>
+    /// </summary>
+    /// <param name="uid">The source uid that invokes the port</param>
+    /// <param name="port">The port to invoke</param>
+    /// <param name="data">Optional data to send along</param>
+    /// <param name="sourceComponent"></param>
+    public void InvokePort(EntityUid uid, string port, NetworkPayload? data = null, DeviceLinkSourceComponent? sourceComponent = null)
+    {
+        if (!Resolve(uid, ref sourceComponent) || !sourceComponent.Outputs.TryGetValue(port, out var sinks))
+            return;
+
+        foreach (var sinkUid in sinks)
+        {
+            if (!sourceComponent.LinkedPorts.TryGetValue(sinkUid, out var links))
+                continue;
+
+            foreach (var (source, sink) in links)
+            {
+                if (source != port)
+                    continue;
+
+                //Just skip using device networking if the source or the sink doesn't support it
+                if (!HasComp<DeviceNetworkComponent>(uid) || !TryComp<DeviceNetworkComponent?>(sinkUid, out var sinkNetworkComponent))
+                {
+                    var eventArgs = new SignalReceivedEvent(sink, uid);
+
+                    RaiseLocalEvent(sinkUid, ref eventArgs);
+                    continue;
+                }
+
+                var payload = new NetworkPayload()
+                {
+                    [InvokedPort] = sink
+                };
+
+                if (data != null)
+                {
+                    //Prevent overriding the invoked port
+                    data.Remove(InvokedPort);
+                    foreach (var (key, value) in data)
+                    {
+                        payload.Add(key, value);
+                    }
+                }
+
+                _deviceNetworkSystem.QueuePacket(uid, sinkNetworkComponent.Address, payload, sinkNetworkComponent.ReceiveFrequency);
+            }
+        }
+    }
+
+    /// <summary>
+    /// Checks if the payload has a port defined and if the port is present on the sink.
+    /// Raises a <see cref="SignalReceivedEvent"/> containing the payload when the check passes
+    /// </summary>
+    private void OnPacketReceived(EntityUid uid, DeviceLinkSinkComponent component, DeviceNetworkPacketEvent args)
+    {
+        if (!args.Data.TryGetValue(InvokedPort, out string? port) || !(component.Ports?.Contains(port) ?? false))
+            return;
+
+        var eventArgs = new SignalReceivedEvent(port, args.Sender, args.Data);
+        RaiseLocalEvent(uid,  ref eventArgs);
+    }
+    #endregion
+
+
+}
similarity index 75%
rename from Content.Server/MachineLinking/System/DoorSignalControlSystem.cs
rename to Content.Server/DeviceLinking/Systems/DoorSignalControlSystem.cs
index e8b2f17c3c305c9ca430d7c9d5397fd4d4e903b2..930beee53be5baf6faa2d4d099dc01f1bc942e57 100644 (file)
@@ -1,16 +1,17 @@
-using Content.Server.MachineLinking.Components;
-using Content.Server.MachineLinking.Events;
+using Content.Server.DeviceLinking.Components;
+using Content.Server.DeviceLinking.Events;
 using Content.Server.Doors.Systems;
+using Content.Server.MachineLinking.System;
 using Content.Shared.Doors.Components;
 using JetBrains.Annotations;
 
-namespace Content.Server.MachineLinking.System
+namespace Content.Server.DeviceLinking.Systems
 {
     [UsedImplicitly]
     public sealed class DoorSignalControlSystem : EntitySystem
     {
         [Dependency] private readonly DoorSystem _doorSystem = default!;
-        [Dependency] private readonly SignalLinkerSystem _signalSystem = default!;
+        [Dependency] private readonly DeviceLinkSystem _signalSystem = default!;
 
         public override void Initialize()
         {
@@ -18,13 +19,13 @@ namespace Content.Server.MachineLinking.System
             SubscribeLocalEvent<DoorSignalControlComponent, ComponentInit>(OnInit);
             SubscribeLocalEvent<DoorSignalControlComponent, SignalReceivedEvent>(OnSignalReceived);
         }
-        
+
         private void OnInit(EntityUid uid, DoorSignalControlComponent component, ComponentInit args)
         {
-            _signalSystem.EnsureReceiverPorts(uid, component.OpenPort, component.ClosePort, component.TogglePort);
+            _signalSystem.EnsureSinkPorts(uid, component.OpenPort, component.ClosePort, component.TogglePort);
         }
 
-        private void OnSignalReceived(EntityUid uid, DoorSignalControlComponent component, SignalReceivedEvent args)
+        private void OnSignalReceived(EntityUid uid, DoorSignalControlComponent component, ref SignalReceivedEvent args)
         {
             if (!TryComp(uid, out DoorComponent? door))
                 return;
similarity index 78%
rename from Content.Server/MachineLinking/System/SignalSwitchSystem.cs
rename to Content.Server/DeviceLinking/Systems/SignalSwitchSystem.cs
index 78a0ef2ca4535bc8f63ed25925e7a34c7365f623..acb3acb4aa19b6d9e2e10c5c7af34cec45de2fe6 100644 (file)
@@ -1,14 +1,15 @@
-using Content.Server.MachineLinking.Components;
+using Content.Server.DeviceLinking.Components;
+using Content.Server.MachineLinking.System;
 using Content.Shared.Audio;
 using Content.Shared.Interaction;
 using Robust.Shared.Audio;
 using Robust.Shared.Player;
 
-namespace Content.Server.MachineLinking.System
+namespace Content.Server.DeviceLinking.Systems
 {
     public sealed class SignalSwitchSystem : EntitySystem
     {
-        [Dependency] private readonly SignalLinkerSystem _signalSystem = default!;
+        [Dependency] private readonly DeviceLinkSystem _signalSystem = default!;
 
         public override void Initialize()
         {
@@ -20,7 +21,7 @@ namespace Content.Server.MachineLinking.System
 
         private void OnInit(EntityUid uid, SignalSwitchComponent component, ComponentInit args)
         {
-            _signalSystem.EnsureTransmitterPorts(uid, component.OnPort, component.OffPort);
+            _signalSystem.EnsureSourcePorts(uid, component.OnPort, component.OffPort);
         }
 
         private void OnActivated(EntityUid uid, SignalSwitchComponent component, ActivateInWorldEvent args)
similarity index 79%
rename from Content.Server/MachineLinking/System/SignallerSystem.cs
rename to Content.Server/DeviceLinking/Systems/SignallerSystem.cs
index e83253cb65f4842d2b9d6255c94bf009bdae09f6..86f68bfb8fbb26728cfd9c6eece4517e62797b19 100644 (file)
@@ -1,13 +1,13 @@
+using Content.Server.DeviceLinking.Components;
 using Content.Server.Explosion.EntitySystems;
-using Content.Server.MachineLinking.Components;
 using Content.Shared.Interaction.Events;
 using Content.Shared.Timing;
 
-namespace Content.Server.MachineLinking.System;
+namespace Content.Server.DeviceLinking.Systems;
 
 public sealed class SignallerSystem : EntitySystem
 {
-    [Dependency] private readonly SignalLinkerSystem _signal = default!;
+    [Dependency] private readonly DeviceLinkSystem _link = default!;
     [Dependency] private readonly UseDelaySystem _useDelay = default!;
 
     public override void Initialize()
@@ -21,14 +21,14 @@ public sealed class SignallerSystem : EntitySystem
 
     private void OnInit(EntityUid uid, SignallerComponent component, ComponentInit args)
     {
-        _signal.EnsureTransmitterPorts(uid, component.Port);
+        _link.EnsureSourcePorts(uid, component.Port);
     }
 
     private void OnUseInHand(EntityUid uid, SignallerComponent component, UseInHandEvent args)
     {
         if (args.Handled)
             return;
-        _signal.InvokePort(uid, component.Port);
+        _link.InvokePort(uid, component.Port);
         args.Handled = true;
     }
 
@@ -43,7 +43,7 @@ public sealed class SignallerSystem : EntitySystem
         if (hasUseDelay)
             _useDelay.BeginDelay(uid, useDelay);
 
-        _signal.InvokePort(uid, component.Port);
+        _link.InvokePort(uid, component.Port);
         args.Handled = true;
     }
 }
similarity index 92%
rename from Content.Server/MachineLinking/System/TwoWayLeverSystem.cs
rename to Content.Server/DeviceLinking/Systems/TwoWayLeverSystem.cs
index f217ed7a42e9f061fbf9e6723b978dd4845d53f7..3251db2dc2c05c6fdfd974b27ae214f81d023cba 100644 (file)
@@ -1,15 +1,14 @@
-using Content.Server.MachineLinking.Components;
+using Content.Server.DeviceLinking.Components;
+using Content.Shared.DeviceLinking;
 using Content.Shared.Interaction;
-using Content.Shared.MachineLinking;
 using Content.Shared.Verbs;
-using Robust.Server.GameObjects;
 using Robust.Shared.Utility;
 
-namespace Content.Server.MachineLinking.System
+namespace Content.Server.DeviceLinking.Systems
 {
     public sealed class TwoWayLeverSystem : EntitySystem
     {
-        [Dependency] private readonly SignalLinkerSystem _signalSystem = default!;
+        [Dependency] private readonly DeviceLinkSystem _signalSystem = default!;
         [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
 
         const string _leftToggleImage = "rotate_ccw.svg.192dpi.png";
@@ -25,7 +24,7 @@ namespace Content.Server.MachineLinking.System
 
         private void OnInit(EntityUid uid, TwoWayLeverComponent component, ComponentInit args)
         {
-            _signalSystem.EnsureTransmitterPorts(uid, component.LeftPort, component.RightPort, component.MiddlePort);
+            _signalSystem.EnsureSourcePorts(uid, component.LeftPort, component.RightPort, component.MiddlePort);
         }
 
         private void OnActivated(EntityUid uid, TwoWayLeverComponent component, ActivateInWorldEvent args)
index a5ea2d5bf0ddb00af5c28be64db859dd09dfaf88..e91cce1e993e42782578a2e3df0442541fea8ee0 100644 (file)
@@ -1,6 +1,7 @@
 ο»Ώusing System.Linq;
 using Content.Server.DeviceNetwork.Components;
 using Content.Shared.DeviceNetwork;
+using Content.Shared.DeviceNetwork.Components;
 using Content.Shared.Interaction;
 using JetBrains.Annotations;
 
index 3ba10dcbb976a513db828eadd8b607424fd98b44..c6ef1bcbc71d7c28e84e1f49e4dfd3640e6fa2c7 100644 (file)
@@ -1,16 +1,22 @@
 using System.Linq;
+using Content.Server.DeviceLinking.Systems;
 using Content.Server.DeviceNetwork.Components;
+using Content.Server.UserInterface;
 using Content.Shared.Access.Components;
 using Content.Shared.Access.Systems;
 using Content.Shared.Database;
+using Content.Shared.DeviceLinking;
 using Content.Shared.DeviceNetwork;
+using Content.Shared.DeviceNetwork.Components;
+using Content.Shared.DeviceNetwork.Systems;
+using Content.Shared.Examine;
 using Content.Shared.Interaction;
 using Content.Shared.Popups;
 using Content.Shared.Verbs;
 using JetBrains.Annotations;
 using Robust.Server.GameObjects;
 using Robust.Shared.Audio;
-using Robust.Shared.Player;
+using Robust.Shared.Timing;
 using Robust.Shared.Utility;
 
 namespace Content.Server.DeviceNetwork.Systems;
@@ -19,11 +25,14 @@ namespace Content.Server.DeviceNetwork.Systems;
 public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem
 {
     [Dependency] private readonly DeviceListSystem _deviceListSystem = default!;
+    [Dependency] private readonly DeviceLinkSystem _deviceLinkSystem = default!;
     [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
     [Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
     [Dependency] private readonly AccessReaderSystem _accessSystem = default!;
     [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
-
+    [Dependency] private readonly AudioSystem _audioSystem = default!;
+    [Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
+    [Dependency] private readonly IGameTiming _gameTiming = default!;
 
     public override void Initialize()
     {
@@ -32,17 +41,23 @@ public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem
         SubscribeLocalEvent<NetworkConfiguratorComponent, MapInitEvent>(OnMapInit);
 
         //Interaction
-        SubscribeLocalEvent<NetworkConfiguratorComponent, AfterInteractEvent>((uid, component, args) => OnUsed(uid, component, args.Target, args.User, args.CanReach)); //TODO: Replace with utility verb?
+        SubscribeLocalEvent<NetworkConfiguratorComponent, AfterInteractEvent>(AfterInteract); //TODO: Replace with utility verb?
+        SubscribeLocalEvent<NetworkConfiguratorComponent, ExaminedEvent>(DoExamine);
 
         //Verbs
         SubscribeLocalEvent<NetworkConfiguratorComponent, GetVerbsEvent<UtilityVerb>>(OnAddInteractVerb);
         SubscribeLocalEvent<DeviceNetworkComponent, GetVerbsEvent<AlternativeVerb>>(OnAddAlternativeSaveDeviceVerb);
+        SubscribeLocalEvent<NetworkConfiguratorComponent, GetVerbsEvent<AlternativeVerb>>(OnAddSwitchModeVerb);
 
         //UI
         SubscribeLocalEvent<NetworkConfiguratorComponent, BoundUIClosedEvent>(OnUiClosed);
         SubscribeLocalEvent<NetworkConfiguratorComponent, NetworkConfiguratorRemoveDeviceMessage>(OnRemoveDevice);
         SubscribeLocalEvent<NetworkConfiguratorComponent, NetworkConfiguratorClearDevicesMessage>(OnClearDevice);
+        SubscribeLocalEvent<NetworkConfiguratorComponent, NetworkConfiguratorLinksSaveMessage>(OnSaveLinks);
+        SubscribeLocalEvent<NetworkConfiguratorComponent, NetworkConfiguratorClearLinksMessage>(OnClearLinks);
+        SubscribeLocalEvent<NetworkConfiguratorComponent, NetworkConfiguratorToggleLinkMessage>(OnToggleLinks);
         SubscribeLocalEvent<NetworkConfiguratorComponent, NetworkConfiguratorButtonPressedMessage>(OnConfigButtonPressed);
+        SubscribeLocalEvent<NetworkConfiguratorComponent, ActivatableUIOpenAttemptEvent>(OnUiOpenAttempt);
 
         SubscribeLocalEvent<DeviceListComponent, ComponentRemove>(OnComponentRemoved);
     }
@@ -62,14 +77,14 @@ public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem
             }
 
             //The network configurator is a handheld device. There can only ever be an ui session open for the player holding the device.
-            _uiSystem.GetUiOrNull(uid, NetworkConfiguratorUiKey.Configure)?.CloseAll();
+            _uiSystem.TryCloseAll(uid, NetworkConfiguratorUiKey.Configure);
         }
     }
 
     private void OnMapInit(EntityUid uid, NetworkConfiguratorComponent component, MapInitEvent args)
     {
         component.Devices.Clear();
-        UpdateUiState(uid, component);
+        UpdateListUiState(uid, component);
     }
 
     private void TryAddNetworkDevice(EntityUid? targetUid, EntityUid configuratorUid, EntityUid userUid,
@@ -118,18 +133,65 @@ public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem
         _popupSystem.PopupCursor(Loc.GetString("network-configurator-device-saved", ("address", device.Address), ("device", targetUid)),
             userUid, PopupType.Medium);
 
-        UpdateUiState(configurator.Owner, configurator);
+        UpdateListUiState(configurator.Owner, configurator);
+    }
+
+    private void TryLinkDevice(EntityUid uid, NetworkConfiguratorComponent configurator, EntityUid? target, EntityUid user)
+    {
+        if (!HasComp<DeviceLinkSourceComponent>(target) && !HasComp<DeviceLinkSinkComponent>(target))
+            return;
+
+        if (configurator.ActiveDeviceLink == target)
+        {
+            _popupSystem.PopupEntity(Loc.GetString("network-configurator-link-mode-stopped"), target.Value, user);
+            configurator.ActiveDeviceLink = null;
+            return;
+        }
+
+        if (HasComp<DeviceLinkSourceComponent>(target) && HasComp<DeviceLinkSourceComponent>(configurator.ActiveDeviceLink)
+            || HasComp<DeviceLinkSinkComponent>(target) && HasComp<DeviceLinkSinkComponent>(configurator.ActiveDeviceLink))
+        {
+            return;
+        }
+
+        if (configurator.ActiveDeviceLink.HasValue)
+        {
+            OpenDeviceLinkUi( uid, target, user, configurator);
+            return;
+        }
+
+        _popupSystem.PopupEntity(Loc.GetString("network-configurator-link-mode-started",  ("device", Name(target.Value))), target.Value, user);
+        configurator.ActiveDeviceLink = target;
+    }
+
+    private void TryLinkDefaults(EntityUid uid, NetworkConfiguratorComponent configurator, EntityUid? targetUid, EntityUid user)
+    {
+        if (!configurator.LinkModeActive || !configurator.ActiveDeviceLink.HasValue
+            || !targetUid.HasValue || configurator.ActiveDeviceLink == targetUid)
+            return;
+
+        if (!HasComp<DeviceLinkSourceComponent>(targetUid) && !HasComp<DeviceLinkSinkComponent>(targetUid))
+            return;
+
+        if (TryComp(configurator.ActiveDeviceLink, out DeviceLinkSourceComponent? activeSource) && TryComp(targetUid, out DeviceLinkSinkComponent? targetSink))
+        {
+            _deviceLinkSystem.LinkDefaults(user, configurator.ActiveDeviceLink.Value, targetUid.Value, activeSource, targetSink);
+        }
+        else if (TryComp(configurator.ActiveDeviceLink, out DeviceLinkSinkComponent? activeSink) && TryComp(targetUid, out DeviceLinkSourceComponent? targetSource))
+        {
+            _deviceLinkSystem.LinkDefaults(user, targetUid.Value, configurator.ActiveDeviceLink.Value, targetSource, activeSink);
+        }
     }
 
     private bool AccessCheck(EntityUid target, EntityUid? user, NetworkConfiguratorComponent component)
     {
         if (!TryComp(target, out AccessReaderComponent? reader) || user == null)
-            return false;
+            return true;
 
         if (_accessSystem.IsAllowed(user.Value, reader))
             return true;
 
-        SoundSystem.Play(component.SoundNoAccess.GetSound(), Filter.Pvs(user.Value), target, AudioParams.Default.WithVolume(-2f).WithPitchScale(1.2f));
+        _audioSystem.PlayPvs(component.SoundNoAccess, user.Value, AudioParams.Default.WithVolume(-2f).WithPitchScale(1.2f));
         _popupSystem.PopupEntity(Loc.GetString("network-configurator-device-access-denied"), target, user.Value);
 
         return false;
@@ -137,26 +199,80 @@ public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem
 
     private void OnComponentRemoved(EntityUid uid, DeviceListComponent component, ComponentRemove args)
     {
-        _uiSystem.GetUiOrNull(uid, NetworkConfiguratorUiKey.Configure)?.CloseAll();
+        _uiSystem.TryCloseAll(uid, NetworkConfiguratorUiKey.Configure);
+    }
+
+    private void SwitchMode(EntityUid? userUid, EntityUid configuratorUid, NetworkConfiguratorComponent configurator)
+    {
+        if (Delay(configurator))
+            return;
+
+        configurator.LinkModeActive = !configurator.LinkModeActive;
+
+        if (!userUid.HasValue)
+            return;
+
+        if (!configurator.LinkModeActive)
+            configurator.ActiveDeviceLink = null;
+
+        var locString = configurator.LinkModeActive ? "network-configurator-mode-link" : "network-configurator-mode-list";
+        _popupSystem.PopupEntity(Loc.GetString("network-configurator-switched-mode", ("mode", Loc.GetString(locString))),
+            configuratorUid, userUid.Value);
+
+        Dirty(configurator);
+        _appearanceSystem.SetData(configuratorUid, NetworkConfiguratorVisuals.Mode, configurator.LinkModeActive);
+
+        var pitch = configurator.LinkModeActive ? 1 : 0.8f;
+        _audioSystem.PlayPvs(configurator.SoundSwitchMode, userUid.Value, AudioParams.Default.WithVolume(1.5f).WithPitchScale(pitch));
+    }
+
+    /// <summary>
+    /// Returns true if the last time this method was called is earlier than the configurators use delay.
+    /// </summary>
+    private bool Delay(NetworkConfiguratorComponent configurator)
+    {
+        var currentTime = _gameTiming.CurTime;
+        if (currentTime < configurator.LastUseAttempt + configurator.UseDelay)
+            return true;
+
+        configurator.LastUseAttempt = currentTime;
+        return false;
     }
 
     #region Interactions
 
+    private void DoExamine(EntityUid uid, NetworkConfiguratorComponent component, ExaminedEvent args)
+    {
+        var mode = component.LinkModeActive ? "network-configurator-examine-mode-link" : "network-configurator-examine-mode-list";
+        args.PushMarkup(Loc.GetString("network-configurator-examine-current-mode", ("mode", Loc.GetString(mode))));
+    }
+
+    private void AfterInteract(EntityUid uid, NetworkConfiguratorComponent component, AfterInteractEvent args)
+    {
+        OnUsed(uid, component, args.Target, args.User, args.CanReach);
+    }
+
     /// <summary>
     /// Either adds a device to the device list or shows the config ui if the target is ant entity with a device list
     /// </summary>
-    private void OnUsed(EntityUid uid, NetworkConfiguratorComponent component, EntityUid? target, EntityUid user, bool canReach = true)
+    private void OnUsed(EntityUid uid, NetworkConfiguratorComponent configurator, EntityUid? target, EntityUid user, bool canReach = true)
     {
-        if (!canReach)
+        if (!canReach || !target.HasValue)
             return;
 
+        if (configurator.LinkModeActive)
+        {
+            TryLinkDevice(uid, configurator, target, user);
+            return;
+        }
+
         if (!HasComp<DeviceListComponent>(target))
         {
-            TryAddNetworkDevice(target, user, component);
+            TryAddNetworkDevice(target, user, configurator);
             return;
         }
 
-        OpenDeviceListUi(target, user, component);
+        OpenDeviceListUi(target, user, configurator);
     }
 
     #endregion
@@ -166,22 +282,32 @@ public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem
     /// <summary>
     /// Adds the interaction verb which is either configuring device lists or saving a device onto the configurator
     /// </summary>
-    private void OnAddInteractVerb(EntityUid uid, NetworkConfiguratorComponent component, GetVerbsEvent<UtilityVerb> args)
+    private void OnAddInteractVerb(EntityUid uid, NetworkConfiguratorComponent configurator, GetVerbsEvent<UtilityVerb> args)
     {
-        if (!args.CanAccess || !args.CanInteract || !args.Using.HasValue || !HasComp<DeviceNetworkComponent>(args.Target))
+        if (!args.CanAccess || !args.CanInteract || !args.Using.HasValue)
             return;
 
-        var isDeviceList = HasComp<DeviceListComponent>(args.Target);
-
-        UtilityVerb verb = new()
+        var verb = new UtilityVerb
         {
-            Text = Loc.GetString(isDeviceList ? "network-configurator-configure" : "network-configurator-save-device"),
-            Icon = isDeviceList ?
-                new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/settings.svg.192dpi.png")) :
-                new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/in.svg.192dpi.png")),
-            Act = () => OnUsed(uid, component, args.Target, args.User),
+            Act = () => OnUsed(uid, configurator, args.Target, args.User),
             Impact = LogImpact.Low
         };
+
+        if (configurator.LinkModeActive)
+        {
+            var linkStarted = configurator.ActiveDeviceLink.HasValue;
+            verb.Text = Loc.GetString(linkStarted ? "network-configurator-link" : "network-configurator-start-link");
+            verb.Icon = new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/in.svg.192dpi.png"));
+        }
+        else if (!HasComp<DeviceNetworkComponent>(args.Target))
+        {
+            var isDeviceList = HasComp<DeviceListComponent>(args.Target);
+            verb.Text = Loc.GetString(isDeviceList ? "network-configurator-configure" : "network-configurator-save-device");
+            verb.Icon = isDeviceList
+                ? new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/settings.svg.192dpi.png"))
+                : new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/in.svg.192dpi.png"));
+        }
+
         args.Verbs.Add(verb);
     }
 
@@ -193,8 +319,39 @@ public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem
     /// </summary>
     private void OnAddAlternativeSaveDeviceVerb(EntityUid uid, DeviceNetworkComponent component, GetVerbsEvent<AlternativeVerb> args)
     {
-        if (!args.CanAccess || !args.CanInteract || !args.Using.HasValue || !HasComp<NetworkConfiguratorComponent>(args.Using.Value)
-            || !HasComp<DeviceListComponent>(args.Target))
+        if (!args.CanAccess || !args.CanInteract || !args.Using.HasValue
+            || !TryComp<NetworkConfiguratorComponent>(args.Using.Value, out var configurator))
+            return;
+
+        if (!configurator.LinkModeActive && HasComp<DeviceListComponent>(args.Target))
+        {
+            AlternativeVerb verb = new()
+            {
+                Text = Loc.GetString("network-configurator-save-device"),
+                Icon = new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/in.svg.192dpi.png")),
+                Act = () => TryAddNetworkDevice(args.Target, args.Using.Value, args.User),
+                Impact = LogImpact.Low
+            };
+            args.Verbs.Add(verb);
+            return;
+        }
+
+        if (configurator is {LinkModeActive: true, ActiveDeviceLink: { }} && (HasComp<DeviceLinkSinkComponent>(args.Target) || HasComp<DeviceLinkSourceComponent>(args.Target)))
+        {
+            AlternativeVerb verb = new()
+            {
+                Text = Loc.GetString("network-configurator-link-defaults"),
+                Icon = new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/in.svg.192dpi.png")),
+                Act = () => TryLinkDefaults(args.Using.Value, configurator, args.Target, args.User),
+                Impact = LogImpact.Low
+            };
+            args.Verbs.Add(verb);
+        }
+    }
+
+    private void OnAddSwitchModeVerb(EntityUid uid, NetworkConfiguratorComponent configurator, GetVerbsEvent<AlternativeVerb> args)
+    {
+        if (!args.CanAccess || !args.CanInteract || !args.Using.HasValue || !HasComp<NetworkConfiguratorComponent>(args.Target))
             return;
 
         AlternativeVerb verb = new()
@@ -211,11 +368,57 @@ public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem
 
     #region UI
 
+    private void OpenDeviceLinkUi(EntityUid configuratorUid, EntityUid? targetUid, EntityUid userUid, NetworkConfiguratorComponent configurator)
+    {
+        if (Delay(configurator))
+            return;
+
+        if (!targetUid.HasValue || !configurator.ActiveDeviceLink.HasValue || !TryComp(userUid, out ActorComponent? actor) || !AccessCheck(targetUid.Value, userUid, configurator))
+            return;
+
+
+        _uiSystem.TryOpen(configuratorUid, NetworkConfiguratorUiKey.Link, actor.PlayerSession);
+        configurator.DeviceLinkTarget = targetUid;
+
+
+        if (TryComp(configurator.ActiveDeviceLink, out DeviceLinkSourceComponent? activeSource) && TryComp(targetUid, out DeviceLinkSinkComponent? targetSink))
+        {
+            UpdateLinkUiState(configuratorUid, configurator.ActiveDeviceLink.Value, targetUid.Value, activeSource, targetSink);
+        }
+        else if (TryComp(configurator.ActiveDeviceLink, out DeviceLinkSinkComponent? activeSink)
+                 && TryComp(targetUid, out DeviceLinkSourceComponent? targetSource))
+        {
+            UpdateLinkUiState(configuratorUid, targetUid.Value, configurator.ActiveDeviceLink.Value, targetSource, activeSink);
+        }
+    }
+
+    private void UpdateLinkUiState(EntityUid configuratorUid, EntityUid sourceUid, EntityUid sinkUid,
+        DeviceLinkSourceComponent? sourceComponent = null, DeviceLinkSinkComponent? sinkComponent = null,
+        DeviceNetworkComponent? sourceNetworkComponent = null, DeviceNetworkComponent? sinkNetworkComponent = null)
+    {
+        if (!Resolve(sourceUid, ref sourceComponent) || !Resolve(sinkUid, ref sinkComponent))
+            return;
+
+        var sources = _deviceLinkSystem.GetSourcePorts(sourceUid, sourceComponent);
+        var sinks = _deviceLinkSystem.GetSinkPorts(sinkUid, sinkComponent);
+        var links = _deviceLinkSystem.GetLinks(sourceUid, sinkUid, sourceComponent);
+        var defaults = _deviceLinkSystem.GetDefaults(sources);
+
+        var sourceAddress = Resolve(sourceUid, ref sourceNetworkComponent) ? sourceNetworkComponent.Address : "";
+        var sinkAddress = Resolve(sinkUid, ref sinkNetworkComponent) ? sinkNetworkComponent.Address : "";
+
+        var state = new DeviceLinkUserInterfaceState(sources, sinks, links, sourceAddress, sinkAddress, defaults);
+        _uiSystem.TrySetUiState(configuratorUid, NetworkConfiguratorUiKey.Link, state);
+    }
+
     /// <summary>
     /// Opens the config ui. It can be used to modify the devices in the targets device list.
     /// </summary>
     private void OpenDeviceListUi(EntityUid? targetUid, EntityUid userUid, NetworkConfiguratorComponent configurator)
     {
+        if (Delay(configurator))
+            return;
+
         if (!targetUid.HasValue || !TryComp(userUid, out ActorComponent? actor) || !AccessCheck(targetUid.Value, userUid, configurator))
             return;
 
@@ -233,7 +436,7 @@ public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem
     /// <summary>
     /// Sends the list of saved devices to the ui
     /// </summary>
-    private void UpdateUiState(EntityUid uid, NetworkConfiguratorComponent component)
+    private void UpdateListUiState(EntityUid uid, NetworkConfiguratorComponent component)
     {
         HashSet<(string address, string name)> devices = new();
         HashSet<string> invalidDevices = new();
@@ -264,6 +467,12 @@ public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem
     private void OnUiClosed(EntityUid uid, NetworkConfiguratorComponent component, BoundUIClosedEvent args)
     {
         component.ActiveDeviceList = null;
+
+        if (args.UiKey is NetworkConfiguratorUiKey.Link)
+        {
+            component.ActiveDeviceLink = null;
+            component.DeviceLinkTarget = null;
+        }
     }
 
     /// <summary>
@@ -272,7 +481,7 @@ public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem
     private void OnRemoveDevice(EntityUid uid, NetworkConfiguratorComponent component, NetworkConfiguratorRemoveDeviceMessage args)
     {
         component.Devices.Remove(args.Address);
-        UpdateUiState(uid, component);
+        UpdateListUiState(uid, component);
     }
 
     /// <summary>
@@ -281,7 +490,61 @@ public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem
     private void OnClearDevice(EntityUid uid, NetworkConfiguratorComponent component, NetworkConfiguratorClearDevicesMessage _)
     {
         component.Devices.Clear();
-        UpdateUiState(uid, component);
+        UpdateListUiState(uid, component);
+    }
+
+    private void OnClearLinks(EntityUid uid, NetworkConfiguratorComponent configurator, NetworkConfiguratorClearLinksMessage args)
+    {
+        if (!configurator.ActiveDeviceLink.HasValue || !configurator.DeviceLinkTarget.HasValue)
+            return;
+
+        if (HasComp<DeviceLinkSourceComponent>(configurator.ActiveDeviceLink))
+        {
+            _deviceLinkSystem.RemoveSinkFromSource(configurator.ActiveDeviceLink.Value, configurator.DeviceLinkTarget.Value);
+            UpdateLinkUiState(uid, configurator.ActiveDeviceLink.Value, configurator.DeviceLinkTarget.Value);
+        }
+        else if (HasComp<DeviceLinkSourceComponent>(configurator.DeviceLinkTarget))
+        {
+            _deviceLinkSystem.RemoveSinkFromSource(configurator.DeviceLinkTarget.Value, configurator.ActiveDeviceLink.Value);
+            UpdateLinkUiState(uid, configurator.DeviceLinkTarget.Value, configurator.ActiveDeviceLink.Value);
+        }
+    }
+
+    private void OnToggleLinks(EntityUid uid, NetworkConfiguratorComponent configurator, NetworkConfiguratorToggleLinkMessage args)
+    {
+        if (!configurator.ActiveDeviceLink.HasValue || !configurator.DeviceLinkTarget.HasValue)
+            return;
+
+        if (TryComp(configurator.ActiveDeviceLink, out DeviceLinkSourceComponent? activeSource))
+        {
+            _deviceLinkSystem.ToggleLink(args.Session.AttachedEntity, configurator.ActiveDeviceLink.Value, configurator.DeviceLinkTarget.Value, args.Source, args.Sink, activeSource);
+            UpdateLinkUiState(uid, configurator.ActiveDeviceLink.Value, configurator.DeviceLinkTarget.Value, activeSource);
+        }
+        else if (TryComp(configurator.DeviceLinkTarget, out DeviceLinkSourceComponent? targetSource))
+        {
+            _deviceLinkSystem.ToggleLink(args.Session.AttachedEntity, configurator.DeviceLinkTarget.Value, configurator.ActiveDeviceLink.Value, args.Source, args.Sink, targetSource);
+            UpdateLinkUiState(uid, configurator.DeviceLinkTarget.Value, configurator.ActiveDeviceLink.Value, targetSource);
+        }
+    }
+
+    /// <summary>
+    /// Saves links set by the device link UI
+    /// </summary>
+    private void OnSaveLinks(EntityUid uid, NetworkConfiguratorComponent configurator, NetworkConfiguratorLinksSaveMessage args)
+    {
+        if (!configurator.ActiveDeviceLink.HasValue || !configurator.DeviceLinkTarget.HasValue)
+            return;
+
+        if (TryComp(configurator.ActiveDeviceLink, out DeviceLinkSourceComponent? activeSource))
+        {
+            _deviceLinkSystem.SaveLinks(args.Session.AttachedEntity, configurator.ActiveDeviceLink.Value, configurator.DeviceLinkTarget.Value, args.Links, activeSource);
+            UpdateLinkUiState(uid, configurator.ActiveDeviceLink.Value, configurator.DeviceLinkTarget.Value, activeSource);
+        }
+        else if (TryComp(configurator.DeviceLinkTarget, out DeviceLinkSourceComponent? targetSource))
+        {
+            _deviceLinkSystem.SaveLinks(args.Session.AttachedEntity, configurator.DeviceLinkTarget.Value, configurator.ActiveDeviceLink.Value, args.Links, targetSource);
+            UpdateLinkUiState(uid, configurator.DeviceLinkTarget.Value, configurator.ActiveDeviceLink.Value, targetSource);
+        }
     }
 
     /// <summary>
@@ -307,11 +570,9 @@ public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem
                 break;
             case NetworkConfiguratorButtonKey.Copy:
                 component.Devices = _deviceListSystem.GetDeviceList(component.ActiveDeviceList.Value);
-                UpdateUiState(uid, component);
+                UpdateListUiState(uid, component);
                 return;
             case NetworkConfiguratorButtonKey.Show:
-                // This should be done client-side.
-                // _deviceListSystem.ToggleVisualization(component.ActiveDeviceList.Value);
                 break;
         }
 
@@ -330,5 +591,11 @@ public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem
                 _deviceListSystem.GetDeviceList(component.ActiveDeviceList.Value)
                     .Select(v => (v.Key, MetaData(v.Value).EntityName)).ToHashSet()));
     }
+
+    private void OnUiOpenAttempt(EntityUid uid, NetworkConfiguratorComponent configurator, ActivatableUIOpenAttemptEvent args)
+    {
+        if (configurator.LinkModeActive)
+            args.Cancel();
+    }
     #endregion
 }
index 4dc8a3dc99dc5ccd418fcd4a9b31239e52458ac3..5eed14fc2f34ca7282a0eb83f7f12990dfec4222 100644 (file)
@@ -1,3 +1,4 @@
+using Content.Server.DeviceLinking.Events;
 using Content.Server.Power.Components;
 using Content.Server.Power.EntitySystems;
 using Content.Shared.Tools.Components;
@@ -8,7 +9,6 @@ using Content.Shared.Doors.Systems;
 using Content.Shared.Interaction;
 using Robust.Server.GameObjects;
 using Content.Shared.Wires;
-using Content.Server.MachineLinking.Events;
 using Content.Server.MachineLinking.System;
 
 namespace Content.Server.Doors.Systems
@@ -44,7 +44,7 @@ namespace Content.Server.Doors.Systems
             }
         }
 
-        private void OnSignalReceived(EntityUid uid, AirlockComponent component, SignalReceivedEvent args)
+        private void OnSignalReceived(EntityUid uid, AirlockComponent component, ref SignalReceivedEvent args)
         {
             if (args.Port == component.AutoClosePort)
             {
index 0d719c4c5da1df82877ae1a6f0e6061c4e1c6abb..ffd47f0257b0bb6c4556b3b86cefd3a25da12888 100644 (file)
@@ -1,20 +1,19 @@
+using Content.Server.DeviceLinking.Events;
+using Content.Server.DeviceLinking.Systems;
 using Content.Server.Explosion.Components;
-using Content.Server.MachineLinking.Events;
-using Content.Server.MachineLinking.System;
 
 namespace Content.Server.Explosion.EntitySystems
 {
     public sealed partial class TriggerSystem
     {
-        [Dependency] private readonly SignalLinkerSystem _signalSystem = default!;
-
+        [Dependency] private readonly DeviceLinkSystem _signalSystem = default!;
         private void InitializeSignal()
         {
             SubscribeLocalEvent<TriggerOnSignalComponent,SignalReceivedEvent>(OnSignalReceived);
             SubscribeLocalEvent<TriggerOnSignalComponent,ComponentInit>(OnInit);
         }
 
-        private void OnSignalReceived(EntityUid uid, TriggerOnSignalComponent component, SignalReceivedEvent args)
+        private void OnSignalReceived(EntityUid uid, TriggerOnSignalComponent component, ref SignalReceivedEvent args)
         {
             if (args.Port != component.Port)
                 return;
@@ -23,7 +22,7 @@ namespace Content.Server.Explosion.EntitySystems
         }
         private void OnInit(EntityUid uid, TriggerOnSignalComponent component, ComponentInit args)
         {
-            _signalSystem.EnsureReceiverPorts(uid, component.Port);
+            _signalSystem.EnsureSinkPorts(uid, component.Port);
         }
     }
 }
index 81e4998507d24fac4064d4188b5cdd4ed7db1cff..bac13274ea76e8a92bede1d2b1d15a6716f58f07 100644 (file)
@@ -3,8 +3,6 @@ using Content.Server.DeviceNetwork;
 using Content.Server.DeviceNetwork.Systems;
 using Content.Server.Ghost;
 using Content.Server.Light.Components;
-using Content.Server.MachineLinking.Events;
-using Content.Server.MachineLinking.System;
 using Content.Server.Power.Components;
 using Content.Server.Temperature.Components;
 using Content.Shared.Audio;
@@ -22,6 +20,8 @@ using Robust.Shared.Player;
 using Robust.Shared.Timing;
 using Content.Shared.DoAfter;
 using Content.Server.Emp;
+using Content.Server.DeviceLinking.Events;
+using Content.Server.DeviceLinking.Systems;
 
 namespace Content.Server.Light.EntitySystems
 {
@@ -37,7 +37,7 @@ namespace Content.Server.Light.EntitySystems
         [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
         [Dependency] private readonly IAdminLogManager _adminLogger= default!;
         [Dependency] private readonly SharedHandsSystem _handsSystem = default!;
-        [Dependency] private readonly SignalLinkerSystem _signalSystem = default!;
+        [Dependency] private readonly DeviceLinkSystem _signalSystem = default!;
         [Dependency] private readonly SharedContainerSystem _containerSystem = default!;
         [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
         [Dependency] private readonly SharedAudioSystem _audio = default!;
@@ -69,7 +69,7 @@ namespace Content.Server.Light.EntitySystems
         private void OnInit(EntityUid uid, PoweredLightComponent light, ComponentInit args)
         {
             light.LightBulbContainer = _containerSystem.EnsureContainer<ContainerSlot>(uid, LightBulbContainer);
-            _signalSystem.EnsureReceiverPorts(uid, light.OnPort, light.OffPort, light.TogglePort);
+            _signalSystem.EnsureSinkPorts(uid, light.OnPort, light.OffPort, light.TogglePort);
         }
 
         private void OnMapInit(EntityUid uid, PoweredLightComponent light, MapInitEvent args)
@@ -354,7 +354,7 @@ namespace Content.Server.Light.EntitySystems
             _appearance.SetData(uid, PoweredLightVisuals.Blinking, isNowBlinking, appearance);
         }
 
-        private void OnSignalReceived(EntityUid uid, PoweredLightComponent component, SignalReceivedEvent args)
+        private void OnSignalReceived(EntityUid uid, PoweredLightComponent component, ref SignalReceivedEvent args)
         {
             if (args.Port == component.OffPort)
                 SetState(uid, false, component);
index b87564b6c1eb31b35ada0cf053db6a0aa9467c53..555fa76802704fd970e09978ae96a25d53457a12 100644 (file)
@@ -3,13 +3,13 @@ using Content.Server.MachineLinking.System;
 namespace Content.Server.MachineLinking.Components
 {
     [DataDefinition]
-    public struct PortIdentifier
+    public readonly struct PortIdentifier
     {
         [DataField("uid")]
-        public EntityUid Uid;
+        public readonly EntityUid Uid;
 
         [DataField("port")]
-        public string Port;
+        public readonly string Port;
 
         public PortIdentifier(EntityUid uid, string port)
         {
index d3d453d9c0dd6b5c1d02bf5da915148f0d1551d7..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 (file)
@@ -1,14 +0,0 @@
-namespace Content.Server.MachineLinking.Events
-{
-    public sealed class SignalReceivedEvent : EntityEventArgs
-    {
-        public readonly string Port;
-        public readonly EntityUid? Trigger;
-
-        public SignalReceivedEvent(string port, EntityUid? trigger)
-        {
-            Port = port;
-            Trigger = trigger;
-        }
-    }
-}
index 6d08c5dc57f20e9492adf47f0d7f955a0fd493a9..811a3f771acf84f46441b0a0ea20c9445ed3392a 100644 (file)
@@ -1,18 +1,16 @@
 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;
 using Content.Shared.Interaction;
 using Content.Shared.MachineLinking;
 using Content.Shared.Popups;
 using Robust.Server.GameObjects;
-using Robust.Shared.Player;
 using Content.Shared.Verbs;
 using Robust.Shared.Prototypes;
-using Content.Shared.MachineLinking.Events;
-using Content.Server.Database;
 
 namespace Content.Server.MachineLinking.System
 {
@@ -140,7 +138,10 @@ namespace Content.Server.MachineLinking.System
                 return;
 
             foreach (var receiver in receivers)
-                RaiseLocalEvent(receiver.Uid, new SignalReceivedEvent(receiver.Port, uid), false);
+            {
+                var eventArgs = new SignalReceivedEvent(receiver.Port, uid);
+                RaiseLocalEvent(receiver.Uid, ref eventArgs);
+            }
         }
 
         private void OnTransmitterStartup(EntityUid uid, SignalTransmitterComponent transmitter, ComponentStartup args)
index 73230c3a528a4acb5af04160175fa0052e8ad661..fff9b3a2cb4a2be3afdc3d496f976a56ad208319 100644 (file)
@@ -1,30 +1,28 @@
 using Content.Server.Climbing;
 using Content.Server.Cloning;
 using Content.Server.Medical.Components;
-using Content.Server.Power.Components;
 using Content.Shared.Destructible;
 using Content.Shared.ActionBlocker;
 using Content.Shared.DragDrop;
 using Content.Shared.Movement.Events;
 using Content.Shared.Verbs;
 using Robust.Shared.Containers;
-using Content.Server.MachineLinking.System;
-using Content.Server.MachineLinking.Events;
 using Content.Server.Cloning.Components;
 using Content.Server.Construction;
+using Content.Server.DeviceLinking.Systems;
+using Content.Shared.DeviceLinking.Events;
 using Content.Server.Power.EntitySystems;
 using Content.Shared.Body.Components;
 using Content.Shared.Mobs.Components;
 using Content.Shared.Mobs.Systems;
 using Robust.Server.Containers;
-using Robust.Server.GameObjects;
 using static Content.Shared.MedicalScanner.SharedMedicalScannerComponent; // Hmm...
 
 namespace Content.Server.Medical
 {
     public sealed class MedicalScannerSystem : EntitySystem
     {
-        [Dependency] private readonly SignalLinkerSystem _signalSystem = default!;
+        [Dependency] private readonly DeviceLinkSystem _signalSystem = default!;
         [Dependency] private readonly ActionBlockerSystem _blocker = default!;
         [Dependency] private readonly ClimbSystem _climbSystem = default!;
         [Dependency] private readonly CloningConsoleSystem _cloningConsoleSystem = default!;
@@ -70,7 +68,7 @@ namespace Content.Server.Medical
         {
             base.Initialize();
             scannerComponent.BodyContainer = _containerSystem.EnsureContainer<ContainerSlot>(uid, $"scanner-bodyContainer");
-            _signalSystem.EnsureReceiverPorts(uid, MedicalScannerComponent.ScannerPort);
+            _signalSystem.EnsureSinkPorts(uid, MedicalScannerComponent.ScannerPort);
         }
 
         private void OnRelayMovement(EntityUid uid, MedicalScannerComponent scannerComponent, ref ContainerRelayMovementEntityEvent args)
index 31779ed1a7efaf9ee07aee30bc4559bf965e71b4..e3777acbde13e6a51ee68765259b48b69735dee6 100644 (file)
@@ -1,4 +1,6 @@
-using Content.Server.MachineLinking.Events;
+
+using Content.Server.DeviceLinking.Events;
+using Content.Server.DeviceLinking.Systems;
 using Content.Server.MachineLinking.System;
 using Content.Server.Materials;
 using Content.Server.Power.Components;
@@ -9,7 +11,6 @@ using Content.Shared.Physics.Controllers;
 using Robust.Shared.Physics;
 using Robust.Shared.Physics.Collision.Shapes;
 using Robust.Shared.Physics.Components;
-using Robust.Shared.Physics.Dynamics;
 using Robust.Shared.Physics.Systems;
 
 namespace Content.Server.Physics.Controllers;
@@ -17,8 +18,8 @@ namespace Content.Server.Physics.Controllers;
 public sealed class ConveyorController : SharedConveyorController
 {
     [Dependency] private readonly FixtureSystem _fixtures = default!;
+    [Dependency] private readonly DeviceLinkSystem _signalSystem = default!;
     [Dependency] private readonly MaterialReclaimerSystem _materialReclaimer = default!;
-    [Dependency] private readonly SignalLinkerSystem _signalSystem = default!;
     [Dependency] private readonly SharedBroadphaseSystem _broadphase = default!;
     [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
 
@@ -37,7 +38,7 @@ public sealed class ConveyorController : SharedConveyorController
 
     private void OnInit(EntityUid uid, ConveyorComponent component, ComponentInit args)
     {
-        _signalSystem.EnsureReceiverPorts(uid, component.ReversePort, component.ForwardPort, component.OffPort);
+        _signalSystem.EnsureSinkPorts(uid, component.ReversePort, component.ForwardPort, component.OffPort);
 
         if (TryComp<PhysicsComponent>(uid, out var physics))
         {
@@ -76,7 +77,7 @@ public sealed class ConveyorController : SharedConveyorController
         _appearance.SetData(uid, ConveyorVisuals.State, component.Powered ? component.State : ConveyorState.Off);
     }
 
-    private void OnSignalReceived(EntityUid uid, ConveyorComponent component, SignalReceivedEvent args)
+    private void OnSignalReceived(EntityUid uid, ConveyorComponent component, ref SignalReceivedEvent args)
     {
         if (args.Port == component.OffPort)
             SetState(uid, ConveyorState.Off, component);
index 5bced250e2cf435e6ed3816b48994ee1bb967200..363c3f1e03863350da25f161b812cbd0f01d7e6f 100644 (file)
@@ -1,7 +1,7 @@
 using System.Linq;
 using Content.Server.Construction;
+using Content.Server.DeviceLinking.Events;
 using Content.Server.MachineLinking.Components;
-using Content.Server.MachineLinking.Events;
 using Content.Server.Paper;
 using Content.Server.Power.Components;
 using Content.Server.Research.Systems;
@@ -10,7 +10,7 @@ using Content.Server.Xenoarchaeology.Equipment.Components;
 using Content.Server.Xenoarchaeology.XenoArtifacts;
 using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
 using Content.Shared.Audio;
-using Content.Shared.MachineLinking.Events;
+using Content.Shared.DeviceLinking.Events;
 using Content.Shared.Popups;
 using Content.Shared.Research.Components;
 using Content.Shared.Xenoarchaeology.Equipment;
@@ -168,10 +168,10 @@ public sealed class ArtifactAnalyzerSystem : EntitySystem
 
     private void OnNewLink(EntityUid uid, AnalysisConsoleComponent component, NewLinkEvent args)
     {
-        if (!TryComp<ArtifactAnalyzerComponent>(args.Receiver, out var analyzer))
+        if (!TryComp<ArtifactAnalyzerComponent>(args.Sink, out var analyzer))
             return;
 
-        component.AnalyzerEntity = args.Receiver;
+        component.AnalyzerEntity = args.Sink;
         analyzer.Console = uid;
 
         UpdateUserInterface(uid, component);
index 5f1dfe2ea864075e9152d5bd1f9cbc7766177610..501ef590ec859b4845c73c71795f8151b3a0ef90 100644 (file)
@@ -5,9 +5,9 @@ using Content.Shared.Emag.Components;
 using Content.Shared.Emag.Systems;
 using Content.Shared.PDA;
 using Content.Shared.Access.Components;
+using Content.Shared.DeviceLinking.Events;
 using Robust.Shared.Prototypes;
 using Content.Shared.Hands.EntitySystems;
-using Content.Shared.MachineLinking.Events;
 using Content.Shared.StationRecords;
 using Robust.Shared.GameStates;
 
diff --git a/Content.Shared/DeviceLinking/DeviceLinkSinkComponent.cs b/Content.Shared/DeviceLinking/DeviceLinkSinkComponent.cs
new file mode 100644 (file)
index 0000000..3538b94
--- /dev/null
@@ -0,0 +1,22 @@
+ο»Ώusing Robust.Shared.GameStates;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set;
+
+namespace Content.Shared.DeviceLinking;
+
+[RegisterComponent]
+[NetworkedComponent] // for interactions. Actual state isn't currently synced.
+[Access(typeof(SharedDeviceLinkSystem))]
+public sealed class DeviceLinkSinkComponent : Component
+{
+    /// <summary>
+    /// The ports this sink has
+    /// </summary>
+    [DataField("ports", customTypeSerializer: typeof(PrototypeIdHashSetSerializer<SinkPortPrototype>))]
+    public HashSet<string>? Ports;
+
+    /// <summary>
+    /// Used for removing a sink from all linked sources when it gets removed
+    /// </summary>
+    [DataField("links")]
+    public HashSet<EntityUid> LinkedSources = new();
+}
diff --git a/Content.Shared/DeviceLinking/DeviceLinkSourceComponent.cs b/Content.Shared/DeviceLinking/DeviceLinkSourceComponent.cs
new file mode 100644 (file)
index 0000000..5059ec8
--- /dev/null
@@ -0,0 +1,35 @@
+ο»Ώusing Robust.Shared.GameStates;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set;
+
+namespace Content.Shared.DeviceLinking;
+
+[RegisterComponent]
+[NetworkedComponent] // for interactions. Actual state isn't currently synced.
+[Access(typeof(SharedDeviceLinkSystem))]
+public sealed class DeviceLinkSourceComponent : Component
+{
+    /// <summary>
+    /// The ports the device link source sends signals from
+    /// </summary>
+    [DataField("ports", customTypeSerializer: typeof(PrototypeIdHashSetSerializer<SourcePortPrototype>))]
+    public HashSet<string>? Ports;
+
+    /// <summary>
+    /// A list of sink uids that got linked for each port
+    /// </summary>
+    [DataField("registeredSinks")]
+    public Dictionary<string, HashSet<EntityUid>> Outputs = new();
+
+    /// <summary>
+    /// The list of source to sink ports for each linked sink entity for easier managing of links
+    /// </summary>
+    [DataField("linkedPorts")]
+    public Dictionary<EntityUid, HashSet<(string source, string sink)>> LinkedPorts = new();
+
+    /// <summary>
+    ///     Limits the range devices can be linked across.
+    /// </summary>
+    [DataField("range")]
+    [ViewVariables(VVAccess.ReadWrite)]
+    public float Range = 30f;
+}
diff --git a/Content.Shared/DeviceLinking/DevicePortPrototype.cs b/Content.Shared/DeviceLinking/DevicePortPrototype.cs
new file mode 100644 (file)
index 0000000..823f6f5
--- /dev/null
@@ -0,0 +1,46 @@
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set;
+
+namespace Content.Shared.DeviceLinking;
+
+/// <summary>
+///     A prototype for a device port, for use with device linking.
+/// </summary>
+[Serializable, NetSerializable]
+public abstract class DevicePortPrototype
+{
+    [IdDataField]
+    public string ID { get; } = default!;
+
+    /// <summary>
+    ///     Localization string for the port name. Displayed in the linking UI.
+    /// </summary>
+    [DataField("name", required:true)]
+    public string Name = default!;
+
+    /// <summary>
+    ///     Localization string for a description of the ports functionality. Should either indicate when a source
+    ///     port is fired, or what function a sink port serves. Displayed as a tooltip in the linking UI.
+    /// </summary>
+    [DataField("description", required: true)]
+    public string Description = default!;
+}
+
+[Prototype("sinkPort")]
+[Serializable, NetSerializable]
+public sealed class SinkPortPrototype : DevicePortPrototype, IPrototype
+{
+}
+
+[Prototype("sourcePort")]
+[Serializable, NetSerializable]
+public sealed class SourcePortPrototype : DevicePortPrototype, IPrototype
+{
+    /// <summary>
+    ///     This is a set of sink ports that this source port will attempt to link to when using the
+    ///     default-link functionality.
+    /// </summary>
+    [DataField("defaultLinks", customTypeSerializer: typeof(PrototypeIdHashSetSerializer<SinkPortPrototype>))]
+    public readonly HashSet<string>? DefaultLinks;
+}
diff --git a/Content.Shared/DeviceLinking/Events/LinkAttemptEvent.cs b/Content.Shared/DeviceLinking/Events/LinkAttemptEvent.cs
new file mode 100644 (file)
index 0000000..22af088
--- /dev/null
@@ -0,0 +1,19 @@
+namespace Content.Shared.DeviceLinking.Events;
+
+public sealed class LinkAttemptEvent : CancellableEntityEventArgs
+{
+    public readonly EntityUid Source;
+    public readonly EntityUid Sink;
+    public readonly EntityUid? User;
+    public readonly string SourcePort;
+    public readonly string SinkPort;
+
+    public LinkAttemptEvent(EntityUid? user, EntityUid source, string sourcePort, EntityUid sink, string sinkPort)
+    {
+        User = user;
+        Source = source;
+        SourcePort = sourcePort;
+        Sink = sink;
+        SinkPort = sinkPort;
+    }
+}
diff --git a/Content.Shared/DeviceLinking/Events/NewLinkEvent.cs b/Content.Shared/DeviceLinking/Events/NewLinkEvent.cs
new file mode 100644 (file)
index 0000000..3bf8f7d
--- /dev/null
@@ -0,0 +1,19 @@
+namespace Content.Shared.DeviceLinking.Events;
+
+public sealed class NewLinkEvent : EntityEventArgs
+{
+    public readonly EntityUid Source;
+    public readonly EntityUid Sink;
+    public readonly EntityUid? User;
+    public readonly string SourcePort;
+    public readonly string SinkPort;
+
+    public NewLinkEvent(EntityUid? user, EntityUid source, string sourcePort, EntityUid sink, string sinkPort)
+    {
+        User = user;
+        Source = source;
+        SourcePort = sourcePort;
+        Sink = sink;
+        SinkPort = sinkPort;
+    }
+}
similarity index 81%
rename from Content.Server/MachineLinking/Events/PortDisconnectedEvent.cs
rename to Content.Shared/DeviceLinking/Events/PortDisconnectedEvent.cs
index de5676e9905509a76608ac2e669ed5e8a08f71e7..680b0c8453e8bc4ec928eef50c4ac358cc89a97c 100644 (file)
@@ -1,4 +1,4 @@
-namespace Content.Server.MachineLinking.Events
+namespace Content.Shared.DeviceLinking.Events
 {
     public sealed class PortDisconnectedEvent : EntityEventArgs
     {
diff --git a/Content.Shared/DeviceLinking/SharedDeviceLinkSystem.cs b/Content.Shared/DeviceLinking/SharedDeviceLinkSystem.cs
new file mode 100644 (file)
index 0000000..b6b784b
--- /dev/null
@@ -0,0 +1,466 @@
+using Content.Shared.DeviceLinking.Events;
+using Content.Shared.Popups;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+
+namespace Content.Shared.DeviceLinking;
+
+public abstract class SharedDeviceLinkSystem : EntitySystem
+{
+    [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+    [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
+
+    public const string InvokedPort = "link_port";
+
+    /// <inheritdoc/>
+    public override void Initialize()
+    {
+        SubscribeLocalEvent<DeviceLinkSourceComponent, ComponentStartup>(OnSourceStartup);
+        SubscribeLocalEvent<DeviceLinkSinkComponent, ComponentStartup>(OnSinkStartup);
+        SubscribeLocalEvent<DeviceLinkSourceComponent, ComponentRemove>(OnSourceRemoved);
+        SubscribeLocalEvent<DeviceLinkSinkComponent, ComponentRemove>(OnSinkRemoved);
+    }
+
+    #region Link Validation
+    /// <summary>
+    /// Removes invalid links where the saved sink doesn't exist/have a sink component for example
+    /// </summary>
+    private void OnSourceStartup(EntityUid sourceUid, DeviceLinkSourceComponent sourceComponent, ComponentStartup args)
+    {
+        List<EntityUid> invalidSinks = new();
+        foreach (var sinkUid  in sourceComponent.LinkedPorts.Keys)
+        {
+            if (!TryComp<DeviceLinkSinkComponent?>(sinkUid, out var sinkComponent))
+            {
+                invalidSinks.Add(sinkUid);
+                foreach (var savedSinks in sourceComponent.Outputs.Values)
+                {
+                    savedSinks.Remove(sinkUid);
+                }
+
+                continue;
+            }
+
+            sinkComponent.LinkedSources.Add(sourceUid);
+        }
+
+        foreach (var invalidSink in invalidSinks)
+        {
+            sourceComponent.LinkedPorts.Remove(invalidSink);
+        }
+    }
+
+    /// <summary>
+    /// Same with <see cref="OnSourceStartup"/> but also checks that the saved ports are present on the sink
+    /// </summary>
+    private void OnSinkStartup(EntityUid sinkUid, DeviceLinkSinkComponent sinkComponent, ComponentStartup args)
+    {
+        List<EntityUid> invalidSources = new();
+        foreach (var sourceUid in sinkComponent.LinkedSources)
+        {
+            if (!TryComp<DeviceLinkSourceComponent>(sourceUid, out var sourceComponent))
+            {
+                invalidSources.Add(sourceUid);
+                continue;
+            }
+
+            if (!sourceComponent.LinkedPorts.TryGetValue(sinkUid, out var linkedPorts))
+            {
+                foreach (var savedSinks in sourceComponent.Outputs.Values)
+                {
+                    savedSinks.Remove(sinkUid);
+                }
+                continue;
+            }
+
+            if (sinkComponent.Ports == null)
+                continue;
+
+            List<(string, string)> invalidLinks = new();
+            foreach (var link in linkedPorts)
+            {
+                if (!sinkComponent.Ports.Contains(link.sink) || !(sourceComponent.Outputs.GetValueOrDefault(link.source)?.Contains(sinkUid) ?? false))
+                    invalidLinks.Add(link);
+            }
+
+            foreach (var invalidLink in invalidLinks)
+            {
+                linkedPorts.Remove(invalidLink);
+                sourceComponent.Outputs.GetValueOrDefault(invalidLink.Item1)?.Remove(sinkUid);
+            }
+        }
+
+        foreach (var invalidSource in invalidSources)
+        {
+            sinkComponent.LinkedSources.Remove(invalidSource);
+        }
+    }
+    #endregion
+
+    /// <summary>
+    /// Ensures that its links get deleted when a source gets removed
+    /// </summary>
+    private void OnSourceRemoved(EntityUid uid, DeviceLinkSourceComponent component, ComponentRemove args)
+    {
+        foreach (var sinkUid in component.LinkedPorts.Keys)
+        {
+            RemoveSinkFromSource(uid, sinkUid, component);
+        }
+    }
+
+    /// <summary>
+    /// Ensures that its links get deleted when a sink gets removed
+    /// </summary>
+    private void OnSinkRemoved(EntityUid sinkUid, DeviceLinkSinkComponent sinkComponent, ComponentRemove args)
+    {
+        foreach (var linkedSource in sinkComponent.LinkedSources)
+        {
+            RemoveSinkFromSource(linkedSource, sinkUid, null, sinkComponent);
+        }
+    }
+
+    #region Ports
+    /// <summary>
+    /// Convenience function to add several ports to an entity
+    /// </summary>
+    public void EnsureSourcePorts(EntityUid uid, params string[] ports)
+    {
+        var comp = EnsureComp<DeviceLinkSourceComponent>(uid);
+        comp.Ports ??= new HashSet<string>();
+
+        foreach (var port in ports)
+        {
+            comp.Ports?.Add(port);
+        }
+    }
+
+    /// <summary>
+    /// Convenience function to add several ports to an entity.
+    /// </summary>
+    public void EnsureSinkPorts(EntityUid uid, params string[] ports)
+    {
+        var comp = EnsureComp<DeviceLinkSinkComponent>(uid);
+        comp.Ports ??= new HashSet<string>();
+
+        foreach (var port in ports)
+        {
+            comp.Ports?.Add(port);
+        }
+    }
+
+    /// <summary>
+    /// Retrieves the available ports from a source
+    /// </summary>
+    /// <returns>A list of source port prototypes</returns>
+    public List<SourcePortPrototype> GetSourcePorts(EntityUid sourceUid, DeviceLinkSourceComponent? sourceComponent = null)
+    {
+        if (!Resolve(sourceUid, ref sourceComponent) || sourceComponent.Ports == null)
+            return new List<SourcePortPrototype>();
+
+        var sourcePorts = new List<SourcePortPrototype>();
+        foreach (var port in sourceComponent.Ports)
+        {
+            sourcePorts.Add(_prototypeManager.Index<SourcePortPrototype>(port));
+        }
+
+        return sourcePorts;
+    }
+
+    /// <summary>
+    /// Retrieves the available ports from a sink
+    /// </summary>
+    /// <returns>A list of sink port prototypes</returns>
+    public List<SinkPortPrototype> GetSinkPorts(EntityUid sinkUid, DeviceLinkSinkComponent? sinkComponent = null)
+    {
+        if (!Resolve(sinkUid, ref sinkComponent) || sinkComponent.Ports == null)
+            return new List<SinkPortPrototype>();
+
+        var sinkPorts = new List<SinkPortPrototype>();
+        foreach (var port in sinkComponent.Ports)
+        {
+            sinkPorts.Add(_prototypeManager.Index<SinkPortPrototype>(port));
+        }
+
+        return sinkPorts;
+    }
+
+    /// <summary>
+    /// Convenience function to retrieve the name of a port prototype
+    /// </summary>
+    public string PortName<TPort>(string port) where TPort : DevicePortPrototype, IPrototype
+    {
+        if (!_prototypeManager.TryIndex<TPort>(port, out var proto))
+            return port;
+
+        return Loc.GetString(proto.Name);
+    }
+    #endregion
+
+    #region Links
+    /// <summary>
+    /// Returns the links of a source
+    /// </summary>
+    /// <returns>A list of sink and source port ids that are linked together</returns>
+    public HashSet<(string source, string sink)> GetLinks(EntityUid sourceUid, EntityUid sinkUid, DeviceLinkSourceComponent? sourceComponent = null)
+    {
+        if (!Resolve(sourceUid, ref sourceComponent) || !sourceComponent.LinkedPorts.TryGetValue(sinkUid, out var links))
+            return new HashSet<(string source, string sink)>();
+
+        return links;
+    }
+
+    /// <summary>
+    /// Returns the default links for the given list of source port prototypes
+    /// </summary>
+    /// <param name="sources">The list of source port prototypes to get the default links for</param>
+    /// <returns>A list of sink and source port ids</returns>
+    public List<(string source, string sink)> GetDefaults(List<SourcePortPrototype> sources)
+    {
+        var defaults = new List<(string, string)>();
+        foreach (var source in sources)
+        {
+            if (source.DefaultLinks == null)
+                return new List<(string, string)>();
+
+            foreach (var defaultLink in source.DefaultLinks)
+            {
+                defaults.Add((source.ID, defaultLink));
+            }
+        }
+
+        return defaults;
+    }
+
+    /// <summary>
+    /// Links the given source and sink by their default links
+    /// </summary>
+    /// <param name="userId">Optinal user uid for displaying popups</param>
+    /// <param name="sourceUid">The source uid</param>
+    /// <param name="sinkUid">The sink uid</param>
+    /// <param name="sourceComponent"></param>
+    /// <param name="sinkComponent"></param>
+    public void LinkDefaults(
+        EntityUid? userId,
+        EntityUid sourceUid,
+        EntityUid sinkUid,
+        DeviceLinkSourceComponent? sourceComponent = null,
+        DeviceLinkSinkComponent? sinkComponent = null)
+    {
+        if (!Resolve(sourceUid, ref sourceComponent) || !Resolve(sinkUid, ref sinkComponent))
+            return;
+
+        var sourcePorts = GetSourcePorts(sourceUid, sourceComponent);
+        var defaults = GetDefaults(sourcePorts);
+        SaveLinks(userId, sourceUid, sinkUid, defaults, sourceComponent, sinkComponent);
+
+        if (userId != null)
+            _popupSystem.PopupCursor(Loc.GetString("signal-linking-verb-success", ("machine", sourceUid)), userId.Value);
+    }
+
+
+    /// <summary>
+    /// Saves multiple links between a source and a sink device.
+    /// Ignores links where either the source or sink port aren't present
+    /// </summary>
+    /// <param name="userId">Optinal user uid for displaying popups</param>
+    /// <param name="sourceUid">The source uid</param>
+    /// <param name="sinkUid">The sink uid</param>
+    /// <param name="links">List of source and sink ids to link</param>
+    /// <param name="sourceComponent"></param>
+    /// <param name="sinkComponent"></param>
+    public void SaveLinks(
+        EntityUid? userId,
+        EntityUid sourceUid,
+        EntityUid sinkUid,
+        List<(string source, string sink)> links,
+        DeviceLinkSourceComponent? sourceComponent = null,
+        DeviceLinkSinkComponent? sinkComponent = null)
+    {
+        if (!Resolve(sourceUid, ref sourceComponent) || !Resolve(sinkUid, ref sinkComponent))
+            return;
+
+        if (sourceComponent.Ports == null || sinkComponent.Ports == null)
+            return;
+
+        if (!InRange(sourceUid, sinkUid, sourceComponent.Range))
+        {
+            if (userId != null)
+                _popupSystem.PopupCursor(Loc.GetString("signal-linker-component-out-of-range"), userId.Value);
+
+            return;
+        }
+
+        RemoveSinkFromSource(sourceUid, sinkUid, sourceComponent);
+        foreach (var (source, sink) in links)
+        {
+            if (!sourceComponent.Ports.Contains(source) || !sinkComponent.Ports.Contains(sink))
+                continue;
+
+            if (!CanLink(userId, sourceUid, sinkUid, source, sink, false, sourceComponent))
+                continue;
+
+            sourceComponent.Outputs.GetOrNew(source).Add(sinkUid);
+            sourceComponent.LinkedPorts.GetOrNew(sinkUid).Add((source, sink));
+
+            SendNewLinkEvent(userId, sourceUid, source, sinkUid, sink);
+        }
+
+        if (links.Count > 0)
+            sinkComponent.LinkedSources.Add(sourceUid);
+    }
+
+    /// <summary>
+    /// Removes all links between a source and a sink
+    /// </summary>
+    public void RemoveSinkFromSource(
+        EntityUid sourceUid,
+        EntityUid sinkUid,
+        DeviceLinkSourceComponent? sourceComponent = null,
+        DeviceLinkSinkComponent? sinkComponent = null)
+    {
+        if (!Resolve(sourceUid, ref sourceComponent, false) || !Resolve(sinkUid, ref sinkComponent, false))
+            return;
+
+        if (sourceComponent.LinkedPorts.TryGetValue(sinkUid, out var ports))
+        {
+            foreach (var (sourcePort, sinkPort) in ports)
+            {
+                RaiseLocalEvent(sourceUid, new PortDisconnectedEvent(sourcePort));
+                RaiseLocalEvent(sinkUid, new PortDisconnectedEvent(sinkPort));
+            }
+        }
+
+        sinkComponent.LinkedSources.Remove(sourceUid);
+        sourceComponent.LinkedPorts.Remove(sinkUid);
+        var outputLists = sourceComponent.Outputs.Values;
+        foreach (var outputList in outputLists)
+        {
+            outputList.Remove(sinkUid);
+        }
+
+    }
+
+    /// <summary>
+    /// Adds or removes a link depending on if it's already present
+    /// </summary>
+    /// <returns>True if the link was successfully added or removed</returns>
+    public bool ToggleLink(
+        EntityUid? userId,
+        EntityUid sourceUid,
+        EntityUid sinkUid,
+        string source,
+        string sink,
+        DeviceLinkSourceComponent? sourceComponent = null,
+        DeviceLinkSinkComponent? sinkComponent = null)
+    {
+        if (!Resolve(sourceUid, ref sourceComponent) || !Resolve(sinkUid, ref sinkComponent))
+            return false;
+
+        if (sourceComponent.Ports == null || sinkComponent.Ports == null)
+            return false;
+
+        var outputs = sourceComponent.Outputs.GetOrNew(source);
+        var linkedPorts = sourceComponent.LinkedPorts.GetOrNew(sinkUid);
+
+        if (linkedPorts.Contains((source, sink)))
+        {
+            RaiseLocalEvent(sourceUid, new PortDisconnectedEvent(source));
+            RaiseLocalEvent(sinkUid, new PortDisconnectedEvent(sink));
+
+            outputs.Remove(sinkUid);
+            linkedPorts.Remove((source, sink));
+
+            if (linkedPorts.Count != 0)
+                return true;
+
+            sourceComponent.LinkedPorts.Remove(sinkUid);
+            sinkComponent.LinkedSources.Remove(sourceUid);
+            CreateLinkPopup(userId, sourceUid, source, sinkUid, sink, true);
+        }
+        else
+        {
+            if (!sourceComponent.Ports.Contains(source) || !sinkComponent.Ports.Contains(sink))
+                return false;
+
+            if (!CanLink(userId, sourceUid, sinkUid, source, sink, true, sourceComponent))
+                return false;
+
+            outputs.Add(sinkUid);
+            linkedPorts.Add((source, sink));
+            sinkComponent.LinkedSources.Add(sourceUid);
+
+            SendNewLinkEvent(userId, sourceUid, source, sinkUid, sink);
+            CreateLinkPopup(userId, sourceUid, source, sinkUid, sink, false);
+        }
+
+        return true;
+    }
+
+    /// <summary>
+    /// Checks if a source and a sink can be linked by allowing other systems to veto the link
+    /// and by optionally checking if they are in range of each other
+    /// </summary>
+    /// <returns></returns>
+    private bool CanLink(
+        EntityUid? userId,
+        EntityUid sourceUid,
+        EntityUid sinkUid,
+        string source,
+        string sink,
+        bool checkRange = true,
+        DeviceLinkSourceComponent? sourceComponent = null)
+    {
+        if (!Resolve(sourceUid, ref sourceComponent))
+            return false;
+
+        if (checkRange && !InRange(sourceUid, sinkUid, sourceComponent.Range))
+        {
+            if (userId.HasValue)
+                _popupSystem.PopupCursor(Loc.GetString("signal-linker-component-out-of-range"), userId.Value);
+
+            return false;
+        }
+
+        var linkAttemptEvent = new LinkAttemptEvent(userId, sourceUid, source, sinkUid, sink);
+
+        RaiseLocalEvent(sourceUid, linkAttemptEvent, true);
+        if (linkAttemptEvent.Cancelled && userId.HasValue)
+        {
+            _popupSystem.PopupCursor(Loc.GetString("signal-linker-component-connection-refused", ("machine", source)), userId.Value);
+            return false;
+        }
+
+        RaiseLocalEvent(sinkUid, linkAttemptEvent, true);
+        if (linkAttemptEvent.Cancelled && userId.HasValue)
+        {
+            _popupSystem.PopupCursor(Loc.GetString("signal-linker-component-connection-refused", ("machine", source)), userId.Value);
+            return false;
+        }
+
+        return !linkAttemptEvent.Cancelled;
+    }
+
+    private bool InRange(EntityUid sourceUid, EntityUid sinkUid, float range)
+    {
+        return Transform(sourceUid).MapPosition.InRange(Transform(sinkUid).MapPosition, range);
+    }
+
+    private void SendNewLinkEvent(EntityUid? user, EntityUid sourceUid, string source, EntityUid sinkUid, string sink)
+    {
+        var newLinkEvent = new NewLinkEvent(user, sourceUid, source, sinkUid, sink);
+        RaiseLocalEvent(sourceUid, newLinkEvent);
+        RaiseLocalEvent(sinkUid, newLinkEvent);
+    }
+
+    private void CreateLinkPopup(EntityUid? userId, EntityUid sourceUid, string source, EntityUid sinkUid, string sink, bool removed)
+    {
+        if (!userId.HasValue)
+            return;
+
+        var locString = removed ? "signal-linker-component-unlinked-port" : "signal-linker-component-linked-port";
+
+        _popupSystem.PopupCursor(Loc.GetString(locString, ("machine1", sourceUid), ("port1", PortName<SourcePortPrototype>(source)),
+                ("machine2", sinkUid), ("port2", PortName<SinkPortPrototype>(sink))), userId.Value, PopupType.Medium);
+    }
+    #endregion
+}
similarity index 87%
rename from Content.Shared/MachineLinking/TwoWayLeverSignal.cs
rename to Content.Shared/DeviceLinking/TwoWayLeverSignal.cs
index 02c88d4419b05d751b143496dfd5efd2f99cd095..a41e48556b9fffd76010f204ea1b1289e78fd386 100644 (file)
@@ -1,6 +1,6 @@
 ο»Ώusing Robust.Shared.Serialization;
 
-namespace Content.Shared.MachineLinking
+namespace Content.Shared.DeviceLinking
 {
     [Serializable, NetSerializable]
     public enum TwoWayLeverVisuals : byte
index f5c9160a0aa7e35b69fc134591bc22909ab37e54..a4b7ea92509304b13868c82b339dce3a26d2180b 100644 (file)
@@ -1,7 +1,7 @@
 ο»Ώusing Robust.Shared.GameStates;
 using Robust.Shared.Serialization;
 
-namespace Content.Shared.DeviceNetwork;
+namespace Content.Shared.DeviceNetwork.Components;
 
 [RegisterComponent]
 [NetworkedComponent]
index f443c50fdeefdcb41acb91848b66f5f23734dd9e..761cde436c307670a0d6307d723415fdeb862ab0 100644 (file)
@@ -1,14 +1,24 @@
+using Content.Shared.DeviceLinking;
+using Content.Shared.DeviceNetwork.Systems;
 using Robust.Shared.Audio;
 using Robust.Shared.GameStates;
 using Robust.Shared.Serialization;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
 
-namespace Content.Shared.DeviceNetwork;
+namespace Content.Shared.DeviceNetwork.Components;
 
 [RegisterComponent]
 [NetworkedComponent]
 [Access(typeof(SharedNetworkConfiguratorSystem))]
 public sealed class NetworkConfiguratorComponent : Component
 {
+    /// <summary>
+    /// Determines whether the configurator is in linking mode or list mode
+    /// </summary>
+    [DataField("linkModeActive")]
+    [ViewVariables(VVAccess.ReadWrite)]
+    public bool LinkModeActive = false;
+
     /// <summary>
     /// The entity containing a <see cref="DeviceListComponent"/> this configurator is currently interacting with
     /// </summary>
@@ -16,22 +26,48 @@ public sealed class NetworkConfiguratorComponent : Component
     public EntityUid? ActiveDeviceList = null;
 
     /// <summary>
-    /// The list of devices stored in the configurator-
+    /// The entity containing a <see cref="DeviceLinkSourceComponent"/> or <see cref="DeviceLinkSinkComponent"/> this configurator is currently interacting with.<br/>
+    /// If this is set the configurator is in linking mode.
+    /// </summary>
+    [DataField("activeDeviceLink")]
+    public EntityUid? ActiveDeviceLink = null;
+
+    /// <summary>
+    /// The target device this configurator is currently linking with the <see cref="ActiveDeviceLink"/>
+    /// </summary>
+    [DataField("deviceLinkTarget")]
+    public EntityUid? DeviceLinkTarget = null;
+
+    /// <summary>
+    /// The list of devices stored in the configurator
     /// </summary>
     [DataField("devices")]
     public Dictionary<string, EntityUid> Devices = new();
 
+    [DataField("useDelay")]
+    [ViewVariables(VVAccess.ReadWrite)]
+    public TimeSpan UseDelay = TimeSpan.FromSeconds(0.5);
+
+    [DataField("lastUseAttempt", customTypeSerializer:typeof(TimeOffsetSerializer))]
+    [ViewVariables(VVAccess.ReadWrite)]
+    public TimeSpan LastUseAttempt;
+
     [DataField("soundNoAccess")]
     public SoundSpecifier SoundNoAccess = new SoundPathSpecifier("/Audio/Machines/custom_deny.ogg");
+
+    [DataField("soundSwitchMode")]
+    public SoundSpecifier SoundSwitchMode = new SoundPathSpecifier("/Audio/Machines/beep.ogg");
 }
 
 [Serializable, NetSerializable]
 public sealed class NetworkConfiguratorComponentState : ComponentState
 {
     public readonly EntityUid? ActiveDeviceList;
+    public readonly bool LinkModeActive;
 
-    public NetworkConfiguratorComponentState(EntityUid? activeDeviceList)
+    public NetworkConfiguratorComponentState(EntityUid? activeDeviceList, bool linkModeActive)
     {
         ActiveDeviceList = activeDeviceList;
+        LinkModeActive = linkModeActive;
     }
 }
index cb8c8c8e6ee8a6ecee6c1913350a186705fd3168..e6e273b1fadbe9197f27ec466a71136a69833aba 100644 (file)
@@ -6,7 +6,8 @@ namespace Content.Shared.DeviceNetwork;
 public enum NetworkConfiguratorUiKey
 {
     List,
-    Configure
+    Configure,
+    Link
 }
 
 [Serializable, NetSerializable]
@@ -52,3 +53,33 @@ public sealed class NetworkConfiguratorButtonPressedMessage : BoundUserInterface
         ButtonKey = buttonKey;
     }
 }
+
+[Serializable, NetSerializable]
+public sealed class NetworkConfiguratorClearLinksMessage : BoundUserInterfaceMessage
+{
+
+}
+
+[Serializable, NetSerializable]
+public sealed class NetworkConfiguratorToggleLinkMessage : BoundUserInterfaceMessage
+{
+    public readonly string Source;
+    public readonly string Sink;
+
+    public NetworkConfiguratorToggleLinkMessage(string source, string sink)
+    {
+        Source = source;
+        Sink = sink;
+    }
+}
+
+[Serializable, NetSerializable]
+public sealed class NetworkConfiguratorLinksSaveMessage : BoundUserInterfaceMessage
+{
+    public readonly List<(string source, string sink)> Links;
+
+    public NetworkConfiguratorLinksSaveMessage(List<(string source, string sink)> links)
+    {
+        Links = links;
+    }
+}
index 483b65403604beb711b1e816525821bd14b67a0d..d12456dc52617396ce433cf58fcddecedc06d050 100644 (file)
@@ -1,4 +1,5 @@
-ο»Ώusing Robust.Shared.Serialization;
+ο»Ώusing Content.Shared.DeviceLinking;
+using Robust.Shared.Serialization;
 
 namespace Content.Shared.DeviceNetwork;
 
@@ -23,3 +24,24 @@ public sealed class DeviceListUserInterfaceState : BoundUserInterfaceState
         DeviceList = deviceList;
     }
 }
+
+[Serializable, NetSerializable]
+public sealed class DeviceLinkUserInterfaceState : BoundUserInterfaceState
+{
+    public readonly List<SourcePortPrototype> Sources;
+    public readonly List<SinkPortPrototype> Sinks;
+    public readonly HashSet<(string source, string sink)> Links;
+    public readonly List<(string source, string sink)>? Defaults;
+    public readonly string SourceAddress;
+    public readonly string SinkAddress;
+
+    public DeviceLinkUserInterfaceState(List<SourcePortPrototype> sources, List<SinkPortPrototype> sinks, HashSet<(string source, string sink)> links, string sourceAddress, string sinkAddress, List<(string source, string sink)>? defaults = default)
+    {
+        Links = links;
+        SourceAddress = sourceAddress;
+        SinkAddress = sinkAddress;
+        Defaults = defaults;
+        Sources = sources;
+        Sinks = sinks;
+    }
+}
index 56cc58307ce022b1a5c76b128a7e801d32dcaaf4..88de21b493380ac17bdd45f5841b8bc4cece6808 100644 (file)
@@ -1,4 +1,5 @@
 using System.Linq;
+using Content.Shared.DeviceNetwork.Components;
 using Robust.Shared.GameStates;
 
 namespace Content.Shared.DeviceNetwork;
index 29400e110c850f7074ab52690c07608006cc98bf..9a9d7e1885cbee409f72b2a3411b99b48452c02e 100644 (file)
@@ -1,8 +1,9 @@
 using Content.Shared.Actions;
+using Content.Shared.DeviceNetwork.Components;
 using Robust.Shared.GameStates;
 using Robust.Shared.Serialization;
 
-namespace Content.Shared.DeviceNetwork;
+namespace Content.Shared.DeviceNetwork.Systems;
 
 public abstract class SharedNetworkConfiguratorSystem : EntitySystem
 {
@@ -17,7 +18,7 @@ public abstract class SharedNetworkConfiguratorSystem : EntitySystem
     private void GetNetworkConfiguratorState(EntityUid uid, NetworkConfiguratorComponent comp,
         ref ComponentGetState args)
     {
-        args.State = new NetworkConfiguratorComponentState(comp.ActiveDeviceList);
+        args.State = new NetworkConfiguratorComponentState(comp.ActiveDeviceList, comp.LinkModeActive);
     }
 
     private void HandleNetworkConfiguratorState(EntityUid uid, NetworkConfiguratorComponent comp,
@@ -29,9 +30,22 @@ public abstract class SharedNetworkConfiguratorSystem : EntitySystem
         }
 
         comp.ActiveDeviceList = state.ActiveDeviceList;
+        comp.LinkModeActive = state.LinkModeActive;
     }
 }
 
 public sealed class ClearAllOverlaysEvent : InstantActionEvent
 {
 }
+
+[Serializable, NetSerializable]
+public enum NetworkConfiguratorVisuals
+{
+    Mode
+}
+
+[Serializable, NetSerializable]
+public enum NetworkConfiguratorLayers
+{
+    ModeLight
+}
diff --git a/Content.Shared/MachineLinking/Events/LinkAttemptEvent.cs b/Content.Shared/MachineLinking/Events/LinkAttemptEvent.cs
deleted file mode 100644 (file)
index 4eb75bd..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-namespace Content.Shared.MachineLinking.Events
-{
-    public sealed class LinkAttemptEvent : CancellableEntityEventArgs
-    {
-        public readonly EntityUid Transmitter;
-        public readonly EntityUid Receiver;
-        public readonly EntityUid? User;
-        public readonly string TransmitterPort;
-        public readonly string ReceiverPort;
-
-        public LinkAttemptEvent(EntityUid? user, EntityUid transmitter, string transmitterPort, EntityUid receiver, string receiverPort)
-        {
-            User = user;
-            Transmitter = transmitter;
-            TransmitterPort = transmitterPort;
-            Receiver = receiver;
-            ReceiverPort = receiverPort;
-        }
-    }
-}
diff --git a/Content.Shared/MachineLinking/Events/NewLinkEvent.cs b/Content.Shared/MachineLinking/Events/NewLinkEvent.cs
deleted file mode 100644 (file)
index 22dfce7..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-namespace Content.Shared.MachineLinking.Events
-{
-    public sealed class NewLinkEvent : EntityEventArgs
-    {
-        public readonly EntityUid Transmitter;
-        public readonly EntityUid Receiver;
-        public readonly EntityUid? User;
-        public readonly string TransmitterPort;
-        public readonly string ReceiverPort;
-
-        public NewLinkEvent(EntityUid? user, EntityUid transmitter, string transmitterPort, EntityUid receiver, string receiverPort)
-        {
-            User = user;
-            Transmitter = transmitter;
-            TransmitterPort = transmitterPort;
-            Receiver = receiver;
-            ReceiverPort = receiverPort;
-        }
-    }
-}
index 4f47ff53be59eab0d8fbceef75c20d7fa12aa416..0550a64f7c4cc4f9924cfe2a5360b579466fded5 100644 (file)
@@ -26,4 +26,4 @@
 - files: ["vessel_warning.ogg"]
   license: "CC-BY-4.0"
   copyright: "Created by AUDACITIER (freesound), converted to MONO and .ogg and edited by EmoGarbage404 (github)."
-  source: "https://freesound.org/people/AUDACITIER/sounds/629196/"
\ No newline at end of file
+  source: "https://freesound.org/people/AUDACITIER/sounds/629196/"
index fee17d9c877a737dbff798180c2efa5de3d3b4e5..a4133bc98452d54d986c2cf9dd87a23fcc777d2f 100644 (file)
@@ -6,6 +6,7 @@ device-frequency-prototype-name-lights = Smart Lights
 device-frequency-prototype-name-mailing-units = Mailing Units
 device-frequency-prototype-name-pdas = PDAs
 device-frequency-prototype-name-fax = Fax
+device-frequency-prototype-name-basic-device = Basic Devices
 
 ## camera frequencies
 device-frequency-prototype-name-surveillance-camera-test = Subnet Test
index f7d663a6d6221016c3f6555baa5842119b9f404e..38fad1ba7216650ecc706123eca90bab3199c8a3 100644 (file)
@@ -6,12 +6,23 @@ network-configurator-too-many-devices = Too many devices stored on this device!
 network-configurator-update-ok = Device storage updated.
 network-configurator-device-already-saved = network device: {$device} is already saved.
 network-configurator-device-access-denied = Access denied!
+network-configurator-link-mode-started = Started linking device: {$device}
+network-configurator-link-mode-stopped = Stopped linking.
+network-configurator-mode-link = Link
+network-configurator-mode-list = List
+network-configurator-switched-mode = Switched mode to: {$mode}
 
 # Verbs
 network-configurator-save-device = Save device
 network-configurator-configure = Configure
+network-configurator-switch-mode = Switch mode
+network-configurator-link-defaults = Link defaults
+network-configurator-start-link = Start link
+network-configurator-link = Link
 
 # ui
+network-configurator-title-saved-devices = Saved Devices
+network-configurator-title-device-configuration = Device Configuration
 network-configurator-ui-clear-button = Clear
 network-configurator-ui-count-label = {$count} Devices
 network-configurator-clear-network-link-overlays = Clear network link overlays
@@ -24,3 +35,13 @@ network-configurator-tooltip-edit = Edit targets device list
 network-configurator-tooltip-clear = Clear targets device list
 network-configurator-tooltip-copy = Copy targets device list to multitool
 network-configurator-tooltip-show = Show a holographic visualization of targets device list
+
+# examine
+network-configurator-examine-mode-link = [color=red]Link[/color]
+network-configurator-examine-mode-list = [color=green]List[/color]
+network-configurator-examine-current-mode = Current mode: {$mode}
+network-configurator-examine-switch-modes = Press {$key} to switch modes
+
+# item status
+network-configurator-item-status-label = Current mode: {$mode}
+{$keybinding} to switch mode
index 287e47f291b6a72e26534f56a01b7ef46a7be9c7..de0f6b4a4cf61cbb9d7b06fdf0faa9bb5bbcbb44 100644 (file)
@@ -4,6 +4,8 @@ guide-entry-atmospherics = Atmospherics
 guide-entry-botany = Botany
 guide-entry-fires = Fires & Space
 guide-entry-shuttle-craft = Shuttle-craft
+guide-entry-networking = Networking
+guide-entry-network-configurator = Network Configurator
 guide-entry-power = Power
 guide-entry-ame = Antimatter Engine (AME)
 guide-entry-singularity = Singularity
index 606ba33220e6c275f9977c86cb71f47c8b5da563..4e9d2620aee9bcf41818006d85eca7c3d07c8788 100644 (file)
@@ -1,3 +1,5 @@
 signal-port-selector-menu-title = Port Selector
 signal-port-selector-menu-clear = Clear
 signal-port-selector-menu-link-defaults = Link defaults
+signal-port-selector-help = Select the ports you want to link up
+signal-port-selector-menu-done = Done
index b86e711b17028759a82221d84f0d751b81201709..cbf2f537f9a5f95c881676ed4a21975814e82aa9 100644 (file)
@@ -1,3 +1,6 @@
+signal-port-name-toggle = Autoclose
+signal-port-description-toggle = Toggles whether the device should automatically close.
+
 signal-port-name-toggle = Toggle
 signal-port-description-toggle = Toggles the state of a device.
 
index c68f9fe8ab93e1de96af2a5c62cb3917a9a12b6b..62a0c71e425fe1d463c9e4a8e5f1075775ccdf11 100644 (file)
@@ -11,6 +11,7 @@
       - id: Wirecutter
       - id: Welder
       - id: Multitool
+      - id: NetworkConfigurator
 
 - type: entity
   id: ClothingBeltChiefEngineerFilled
@@ -24,6 +25,7 @@
       - id: WelderExperimental
       - id: Multitool
       - id: CableApcStack
+      - id: NetworkConfigurator
 
 - type: entity
   id: ClothingBeltSecurityFilled
index 3762129fdbace19e70706f095b66104e2d426a4d..0667cd0170f0efdcfca9647a24ae9d56e547bd32 100644 (file)
@@ -86,3 +86,8 @@
   id: Fax
   name: device-frequency-prototype-name-fax
   frequency: 2640
+
+- type: deviceFrequency
+  id: BasicDevice
+  name: device-frequency-prototype-name-basic-device
+  frequency: 1280
diff --git a/Resources/Prototypes/DeviceLinking/sink_ports.yml b/Resources/Prototypes/DeviceLinking/sink_ports.yml
new file mode 100644 (file)
index 0000000..19f3b4e
--- /dev/null
@@ -0,0 +1,74 @@
+- type: sinkPort
+  id: AutoClose
+  name: signal-port-name-autoclose
+  description: signal-port-description-autoclose
+
+- type: sinkPort
+  id: Toggle
+  name: signal-port-name-toggle
+  description: signal-port-description-toggle
+
+- type: sinkPort
+  id: On
+  name: signal-port-name-on-receiver
+  description: signal-port-description-on-receiver
+
+- type: sinkPort
+  id: Off
+  name: signal-port-name-off-receiver
+  description: signal-port-description-off-receiver
+
+- type: sinkPort
+  id: Forward
+  name: signal-port-name-forward
+  description: signal-port-description-forward
+
+- type: sinkPort
+  id: Reverse
+  name: signal-port-name-reverse
+  description: signal-port-description-reverse
+
+- type: sinkPort
+  id: Open
+  name: signal-port-name-open
+  description: signal-port-description-open
+
+- type: sinkPort
+  id: Close
+  name: signal-port-name-close
+  description: signal-port-description-close
+
+- type: sinkPort
+  id: Trigger
+  name: signal-port-name-trigger
+  description: signal-port-description-trigger
+
+- type: sinkPort
+  id: OrderReceiver
+  name: signal-port-name-order-receiver
+  description: signal-port-description-order-receiver
+
+- type: sinkPort
+  id: Pressurize
+  name: signal-port-name-pressurize
+  description: signal-port-description-pressurize
+
+- type: sinkPort
+  id: Depressurize
+  name: signal-port-name-depressurize
+  description: signal-port-description-depressurize
+
+- type: sinkPort
+  id: CloningPodReceiver
+  name: signal-port-name-pod-receiver
+  description: signal-port-description-pod-receiver
+
+- type: sinkPort
+  id: MedicalScannerReceiver
+  name: signal-port-name-med-scanner-receiver
+  description: signal-port-description-med-scanner-receiver
+
+- type: sinkPort
+  id: ArtifactAnalyzerReceiver
+  name: signal-port-name-artifact-analyzer-receiver
+  description: signal-port-description-artifact-analyzer-receiver
diff --git a/Resources/Prototypes/DeviceLinking/source_ports.yml b/Resources/Prototypes/DeviceLinking/source_ports.yml
new file mode 100644 (file)
index 0000000..d010892
--- /dev/null
@@ -0,0 +1,57 @@
+- type: sourcePort
+  id: Pressed
+  name: signal-port-name-pressed
+  description: signal-port-description-pressed
+  defaultLinks: [ Toggle, Trigger ]
+
+- type: sourcePort
+  id: On
+  name: signal-port-name-on-transmitter
+  description: signal-port-description-on-transmitter
+  defaultLinks: [ On, Open, Forward, Trigger ]
+
+- type: sourcePort
+  id: Off
+  name: signal-port-name-off-transmitter
+  description: signal-port-description-off-transmitter
+  defaultLinks: [ Off, Close ]
+
+- type: sourcePort
+  id: Left
+  name: signal-port-name-left
+  description: signal-port-description-left
+  defaultLinks: [ On, Open, Forward, Trigger ]
+
+- type: sourcePort
+  id: Right
+  name: signal-port-name-right
+  description: signal-port-description-right
+  defaultLinks: [ On, Open, Reverse, Trigger ]
+
+- type: sourcePort
+  id: Middle
+  name: signal-port-name-middle
+  description: signal-port-description-middle
+  defaultLinks: [ Off, Close ]
+
+- type: sourcePort
+  id: OrderSender
+  name: signal-port-name-order-sender
+  description: signal-port-description-order-sender
+  defaultLinks: [ OrderReceiver ]
+
+- type: sourcePort
+  id: CloningPodSender
+  name: signal-port-name-pod-receiver
+  description: signal-port-description-pod-sender
+
+- type: sourcePort
+  id: MedicalScannerSender
+  name: signal-port-name-med-scanner-sender
+  description: signal-port-description-med-scanner-sender
+
+- type: sourcePort
+  id: ArtifactAnalyzerSender
+  name: signal-port-name-artifact-analyzer-sender
+  description: signal-port-description-artifact-analyzer-sender
+  defaultLinks: [ ArtifactAnalyzerReceiver ]
index d4f9850f41335c4c21020c75a48bfc53f2cb37df..adc040e6565bd3ae8201f08b71163b7127897ee0 100644 (file)
@@ -11,6 +11,7 @@
   - type: Clothing
     sprite: Clothing/Belt/utility.rsi
   - type: Storage
+    capacity: 45
     # TODO: Fill this out more.
     whitelist:
       tags:
         - CigPack
         - Radio
         - HolofanProjector
+        - Multitool
         - AppraisalTool
       components:
         - AirlockPainter
-        - SignalLinker
+        - NetworkConfigurator
         - RCD
         - RCDAmmo
         - Welder
@@ -63,8 +65,8 @@
           - Wrench
       multitool:
         whitelist:
-          components:
-          - SignalLinker
+          tags:
+            - Multitool
     sprite: Clothing/Belt/belt_overlay.rsi
   - type: Appearance
 
@@ -79,7 +81,7 @@
   - type: Clothing
     sprite: Clothing/Belt/ce.rsi
   - type: Storage
-    capacity: 100
+    capacity: 105
     # TODO: Fill this out more.
     whitelist:
       tags:
         - CigPack
         - Radio
         - HolofanProjector
+        - Multitool
         - AppraisalTool
       components:
         - AirlockPainter
-        - SignalLinker
+        - NetworkConfigurator
         - RCD
         - RCDAmmo
         - Welder
           - Screwdriver
       multitool:
         whitelist:
-          components:
-          - SignalLinker
+          tags:
+            - Multitool
       wrench:
         whitelist:
           tags:
index 153e24818e6382d2b082d23319c80d729b72b11c..2a0b5ba1184941d0a6739dd05a7d542eab596ad0 100644 (file)
@@ -13,9 +13,9 @@
   - type: UseDelay
   - type: StaticPrice
     price: 40
-  - type: SignalTransmitter
-    outputs:
-      Pressed: []
+  - type: DeviceLinkSource
+    ports:
+      - Pressed
   - type: Tag
     tags:
     - Payload
index a2738e8be50b66affa1722e0e4d7a2d4224d427c..fe6c073e5d4c05faae8298e432fd5887dc08cdd2 100644 (file)
   - type: PayloadTrigger
     components:
     - type: TriggerOnSignal
-    - type: SignalReceiver
+    - type: DeviceNetwork
+      deviceNetId: Wireless
+      receiveFrequencyId: BasicDevice
+    - type: WirelessNetworkConnection
+      range: 200
+    - type: DeviceLinkSink
     - type: StaticPrice
       price: 40
 
index 4fa01ea6d32219f567b3ce66e977002cbe471823..211efabcadd6fcaffdf801b45c22ba28e75b027d 100644 (file)
   - type: Tool
     qualities:
     - Pulsing
-  - type: SignalLinker
+  - type: Tag
+    tags:
+      - Multitool
 
 - type: entity
   name: cowelding tool
index 34cb1cc3689e3933def9965987a95cc0f6e1a86c..7c0e8ed10e3b33eab0d67730a278f91e1717a4d1 100644 (file)
   - type: Tool
     qualities:
       - Pulsing
-  - type: SignalLinker
-  - type: NetworkConfigurator
-  - type: ActivatableUI
-    key: enum.NetworkConfiguratorUiKey.List
-    inHandsOnly: true
-  - type: UserInterface
-    interfaces:
-    - key: enum.SignalLinkerUiKey.Key
-      type: SignalPortSelectorBoundUserInterface
-    - key: enum.NetworkConfiguratorUiKey.List
-      type: NetworkConfiguratorBoundUserInterface
-    - key: enum.NetworkConfiguratorUiKey.Configure
-      type: NetworkConfiguratorBoundUserInterface
   - type: Tag
     tags:
+      - Multitool
       - DroneUsable
   - type: PhysicalComposition
     materialComposition:
   - type: StaticPrice
     price: 60
 
+- type: entity
+  name: network configurator
+  parent: BaseItem
+  id: NetworkConfigurator
+  description: A tool for linking devices together. Has two modes, a list mode for mass linking devices and a linking mode for advanced device linking.
+  components:
+    - type: EmitSoundOnLand
+      sound:
+        path: /Audio/Items/multitool_drop.ogg
+    - type: Sprite
+      sprite: Objects/Tools/network_configurator.rsi
+      layers:
+        - state: icon
+        - state: mode-list
+          map: ["enum.NetworkConfiguratorLayers.ModeLight"]
+          shader: unshaded
+    - type: Item
+      size: 5
+    - type: Clothing
+      sprite: Objects/Tools/network_configurator.rsi
+      quickEquip: false
+      slots:
+        - Belt
+    - type: Appearance
+    - type: GenericVisualizer
+      visuals:
+        enum.NetworkConfiguratorVisuals.Mode:
+          enum.NetworkConfiguratorLayers.ModeLight:
+            True: { state: mode-link }
+            False: { state: mode-list }
+    - type: NetworkConfigurator
+    - type: ActivatableUI
+      key: enum.NetworkConfiguratorUiKey.List
+      inHandsOnly: true
+    - type: UserInterface
+      interfaces:
+        - key: enum.NetworkConfiguratorUiKey.List
+          type: NetworkConfiguratorBoundUserInterface
+        - key: enum.NetworkConfiguratorUiKey.Configure
+          type: NetworkConfiguratorBoundUserInterface
+        - key: enum.NetworkConfiguratorUiKey.Link
+          type: NetworkConfiguratorBoundUserInterface
+    - type: Tag
+      tags:
+        - DroneUsable
+    - type: StaticPrice
+      price: 60
+
 - type: entity
   name: power drill
   parent: BaseItem
   - type: Item
     sprite: Objects/Tools/omnitool.rsi
     size: 20
+  - type: Tag
+    tags:
+    - Multitool
   - type: TilePrying
-  - type: SignalLinker
-    requiredQuality: Pulsing
-  - type: UserInterface
-    interfaces:
-      - key: enum.SignalLinkerUiKey.Key
-        type: SignalPortSelectorBoundUserInterface
   - type: Tool
     qualities:
       - Screwing
index e8cbe1c08e892d5b1583f8c80c0ec0a04c01bb36..13ab7309d545ca3404d626e1959b149fab9243db 100644 (file)
@@ -18,9 +18,9 @@
       startOnStick: true
       canToggleStartOnStick: true
     - type: TriggerOnSignal
-    - type: SignalReceiver
-      inputs:
-        Trigger: []
+    - type: DeviceLinkSink
+      ports:
+        - Trigger
     - type: Sticky
       stickDelay: 5
       unstickDelay: 5
index 833a5fa6da5f224400b7be9df288f95a5c8fcdd5..7e8100d10a60d83a0d6a19e98ee49d6cb85e6273 100644 (file)
     BoardName: "Airlock Control"
     LayoutId: Airlock
   - type: DoorSignalControl
-  - type: SignalReceiver
-    inputs:
-      Open: []
-      Close: []
-      Toggle: []
-      AutoClose: []
+  - type: DeviceNetwork
+    deviceNetId: Wireless
+    receiveFrequencyId: BasicDevice
+  - type: WirelessNetworkConnection
+    range: 200
+  - type: DeviceLinkSink
+    ports:
+      - Open
+      - Close
+      - Toggle
+      - AutoClose
   - type: UserInterface
     interfaces:
     - key: enum.WiresUiKey.Key
index dabe75a2eb8ea74b3cc0635a2981fa024eabeae3..5cdbfe848a77674f6acf23ea3304deb14443fce3 100644 (file)
     key: walls
     mode: NoSprite
   - type: DoorSignalControl
-  - type: SignalReceiver
-    inputs:
-      Open: []
-      Close: []
-      Toggle: []
+  - type: DeviceNetwork
+    deviceNetId: Wireless
+    receiveFrequencyId: BasicDevice
+  - type: WirelessNetworkConnection
+    range: 200
+  - type: DeviceLinkSink
+    ports:
+      - Open
+      - Close
+      - Toggle
   - type: InteractionPopup
     interactSuccessString: comp-window-knock
     messagePerceivedByOthers: comp-window-knock
index b6fb8f1295276f15689715fe55681369be076b1e..192f6202a103f593d7e0f63d376416c043c97af5 100644 (file)
   - type: ApcPowerReceiver
   - type: ExtensionCableReceiver
   - type: DoorSignalControl
-  - type: SignalReceiver
-    inputs:
-      Open: []
-      Close: []
-      Toggle: []
-      AutoClose: []
+  - type: DeviceNetwork
+    deviceNetId: Wireless
+    receiveFrequencyId: BasicDevice
+  - type: WirelessNetworkConnection
+    range: 200
+  - type: DeviceLinkSink
+    ports:
+      - Open
+      - Close
+      - Toggle
+      - AutoClose
   - type: Damageable
     damageContainer: Inorganic
     damageModifierSet: Glass
index 9abf8eef6003af660a049ee79753d5148cc4f14e..4f72c68b6a052c3678b7755f08542252ac74fc45 100644 (file)
   - type: ApcPowerReceiver
   - type: ExtensionCableReceiver
   - type: DeviceNetwork
-    deviceNetId: Apc
+    deviceNetId: Wireless
     receiveFrequencyId: SmartLight
-  - type: ApcNetworkConnection
+  - type: WirelessNetworkConnection
+    range: 200
+  - type: DeviceLinkSink
+    ports:
+      - On
+      - Off
+      - Toggle
   - type: Appearance
   - type: PoweredLightVisuals
     blinkingSound:
       path: "/Audio/Machines/light_tube_on.ogg"
-  - type: SignalReceiver
-    inputs:
-      On: []
-      Off: []
-      Toggle: []
 
 - type: entity
   id: Poweredlight
   - type: ApcPowerReceiver
   - type: ExtensionCableReceiver
   - type: DeviceNetwork
-    deviceNetId: Apc
+    deviceNetId: Wireless
     receiveFrequencyId: SmartLight
-  - type: ApcNetworkConnection
+  - type: WirelessNetworkConnection
+    range: 200
   - type: Appearance
   - type: PoweredLightVisuals
-  - type: SignalReceiver
-    inputs:
-      On: []
-      Off: []
-      Toggle: []
+  - type: DeviceLinkSink
+    ports:
+      - On
+      - Off
+      - Toggle
 
 - type: entity
   id: PoweredSmallLight
index eed05b2cc4171a3e813cd7fe8c60c4e41df20dea..3fd833ff5f8c9c90b95179912d8baadab23c3157 100644 (file)
   - type: ApcPowerReceiver
   - type: ExtensionCableReceiver
   - type: DeviceNetwork
-    deviceNetId: Apc
+    deviceNetId: Wireless
     receiveFrequencyId: SmartLight
-  - type: ApcNetworkConnection
+  - type: WirelessNetworkConnection
+    range: 200
+  - type: DeviceLinkSink
+    ports:
+      - On
+      - Off
+      - Toggle
   - type: Construction
     graph: LightFixture
     node: groundLight
   - type: PoweredLightVisuals
     blinkingSound:
       path: "/Audio/Machines/light_tube_on.ogg"
-  - type: SignalReceiver
-    inputs:
-      On: []
-      Off: []
-      Toggle: []
 
 - type: entity
   id: PoweredLightPostSmall
index 33eced8fca7b0aa1b649a2840f95d32740b4a0a2..6d80d44472d24dea70340cdfb0aa2819e08cf754 100644 (file)
   - type: DeviceList
   - type: DeviceNetwork
     deviceNetId: Wired
-  - type: SignalTransmitter
-    transmissionRange: 5
-    outputs:
-      ArtifactAnalyzerSender: []
+  - type: DeviceLinkSource
+    range: 5
+    ports:
+      - ArtifactAnalyzerSender
   - type: ActivatableUI
     key: enum.ArtifactAnalzyerUiKey.Key
   - type: UserInterface
     radius: 1.5
     energy: 1.6
     color: "#1f8c28"
-  - type: SignalTransmitter
-    transmissionRange: 4
-    outputs:
-      MedicalScannerSender: []
-      CloningPodSender: []
+  - type: DeviceLinkSource
+    range: 4
+    ports:
+      - MedicalScannerSender
+      - CloningPodSender
   - type: ActivatableUI
     key: enum.CloningConsoleUiKey.Key
   - type: UserInterface
index a51f7a3f49bc53ff05fac4cfe5a9eca773ed9372..ab05abda77f53fda3152e36e1c9cefde29dace99 100644 (file)
   - type: ArtifactAnalyzer
   - type: DeviceNetwork
     deviceNetId: Wired
+    receiveFrequencyId: BasicDevice
   - type: DeviceList
-  - type: SignalReceiver
-    inputs:
-      ArtifactAnalyzerReceiver: []
+  - type: DeviceLinkSink
+    ports:
+      - ArtifactAnalyzerReceiver
   - type: Machine
     board: ArtifactAnalyzerMachineCircuitboard
   - type: PointLight
index 0cdd0f477ccadd154ae48fd0cd4c40ebf1bcfdd3..b58b49fb98d2f0e6a7e1ca7d6b86ce2295632a9f 100644 (file)
@@ -8,6 +8,10 @@
   - type: DeviceList
   - type: DeviceNetwork
     deviceNetId: Wired
+    receiveFrequencyId: BasicDevice
+  - type: DeviceLinkSink
+    ports:
+      - CloningPodReceiver
   - type: Sprite
     netsync: false
     sprite: Structures/Machines/cloning.rsi
@@ -34,9 +38,6 @@
      - machine_board
      - machine_parts
      - clonepod-bodyContainer
-  - type: SignalReceiver
-    inputs:
-      CloningPodReceiver: []
   - type: EmptyOnMachineDeconstruct
     containers:
       - clonepod-bodyContainer
index 3fecef3304cf060b879752b3f4fd1d12f2475fd6..cd09dfc098c307666980f2b4bc6828c2e8f1f316 100644 (file)
@@ -74,6 +74,7 @@
       - Wrench
       - Crowbar
       - Multitool
+      - NetworkConfigurator
       - AirlockPainter
       - CableStack
       - HandheldGPSBasic
index 6edb724ca83a228f55d3ffd1cff637c6d143d418..ec6f631188347a3f4adc43086b503d6481f6e109 100644 (file)
@@ -7,7 +7,11 @@
   - type: MedicalScanner
   - type: DeviceNetwork
     deviceNetId: Wired
+    receiveFrequencyId: BasicDevice
   - type: DeviceList
+  - type: DeviceLinkSink
+    ports:
+      - MedicalScannerReceiver
   - type: Sprite
     netsync: false
     sprite: Structures/Machines/scanner.rsi
@@ -58,9 +62,6 @@
       scanner-bodyContainer: !type:ContainerSlot
       machine_board: !type:Container
       machine_parts: !type:Container
-  - type: SignalReceiver
-    inputs:
-      MedicalScannerReceiver: []
   - type: Damageable
     damageContainer: Inorganic
     damageModifierSet: StrongMetallic
index fc4374cb4eb4f1f45371d24be6337a6b356a2717..1a61ee9f2d9e19716ccd808976eede930f6301c5 100644 (file)
         - MidImpassable
         - LowImpassable
         hard: False
-  - type: SignalReceiver
-    inputs:
-      Reverse: []
-      Forward: []
-      Off: []
+  - type: DeviceLinkSink
+    ports:
+      - Reverse
+      - Forward
+      - Off
   - type: Transform
     anchored: true
     noRot: false
index 1ca4fce8118c9441a55c3e93f24b46da7dc954b5..e1ef0251c54d9a1e6cc60665156d24a2583834f4 100644 (file)
   - type: PipeColorVisuals
   - type: GasValve
   - type: SignalControlledValve
-  - type: SignalReceiver
-    inputs:
-      Open: []
-      Close: []
-      Toggle: []
+  - type: DeviceNetwork
+    deviceNetId: Wireless
+    receiveFrequencyId: BasicDevice
+  - type: WirelessNetworkConnection
+    range: 200
+  - type: DeviceLinkSink
+    ports:
+      - Open
+      - Close
+      - Toggle
   - type: NodeContainer
     nodes:
       inlet:
       inlet: inlet
       outlet: outlet
       canLink: true
-    - type: SignalReceiver
-      inputs:
-        Pressurize: []
-        Depressurize: []
+    - type: DeviceNetwork
+      deviceNetId: Wireless
+      receiveFrequencyId: BasicDevice
+    - type: WirelessNetworkConnection
+      range: 200
+    - type: DeviceLinkSink
+      ports:
+        - Pressurize
+        - Depressurize
     - type: Construction
       graph: GasBinary
       node: dualportventpump
index db25d97a8cc7e5434b49b9075f99d7a3ea5221b7..44c1f0e538e8cafdd48a99f85e17fa1d89116b31 100644 (file)
     graph: SignalSwitchGraph
     node: SignalSwitchNode
   - type: Fixtures
-  - type: SignalTransmitter
-    outputs:
-      On: []
-      Off: []
+  - type: DeviceNetwork
+    deviceNetId: Wireless
+  - type: WirelessNetworkConnection
+    range: 200
+  - type: DeviceLinkSource
+    ports:
+      - On
+      - Off
 
 - type: entity
   id: SignalButton
     graph: SignalButtonGraph
     node: SignalButtonNode
   - type: Fixtures
-  - type: SignalTransmitter
-    outputs:
-      Pressed: []
+  - type: DeviceNetwork
+    deviceNetId: Wireless
+  - type: WirelessNetworkConnection
+    range: 200
+  - type: DeviceLinkSource
+    ports:
+      - Pressed
   - type: Damageable
     damageContainer: Inorganic
     damageModifierSet: Metallic
     - type: Construction
       graph: LeverGraph
       node: LeverNode
-    - type: SignalTransmitter
-      outputs:
-        Left: []
-        Right: []
-        Middle: []
+    - type: DeviceNetwork
+      deviceNetId: Wireless
+    - type: WirelessNetworkConnection
+      range: 200
+    - type: DeviceLinkSource
+      ports:
+        - Left
+        - Right
+        - Middle
index f02bd60a1e132eebf648b47b499c0174f869525c..a920b450523785d39ea7d3a93640dd2cb45fc0b5 100644 (file)
   - type: Damageable
     damageContainer: Inorganic
     damageModifierSet: Metallic
-  - type: SignalReceiver
-    inputs:
-      OrderReceiver: []
+  - type: DeviceNetwork
+    deviceNetId: Wireless
+    receiveFrequencyId: BasicDevice
+  - type: WirelessNetworkConnection
+    range: 200
+  - type: DeviceLinkSink
+    ports:
+      - OrderReceiver
   - type: Destructible
     thresholds:
       - trigger:
index 42eb177d614559293b3188a95de771085ce4341d..608a3ca29adb6e9870f41d09a5540b19547556b8 100644 (file)
         - LowImpassable
         hard: False
   - type: Conveyor
-  - type: SignalReceiver
-    inputs:
-      Reverse: []
-      Forward: []
-      Off: []
+  - type: DeviceLinkSink
+    ports:
+      - Reverse
+      - Forward
+      - Off
   - type: Appearance
   - type: GenericVisualizer
     visuals:
index 964dfa6809d95a4e51f51c3087b8e92e3665751e..69a9c60e2e2e3737a6403b1add496ac97d4b748e 100644 (file)
@@ -7,6 +7,7 @@
   - Construction
   - Power
   - ShuttleCraft
+  - Networking
 
 - type: guideEntry
   id: Construction
   name: guide-entry-shuttle-craft
   text: "/ServerInfo/Guidebook/Shuttlecraft.xml"
 
+- type: guideEntry
+  id: Networking
+  name: guide-entry-networking
+  text: "/ServerInfo/Guidebook/Networking.xml"
+  children:
+  - NetworkConfigurator
+
+- type: guideEntry
+  id: NetworkConfigurator
+  name: guide-entry-network-configurator
+  text: "/ServerInfo/Guidebook/Network_Configurator.xml"
+
+
 - type: guideEntry
   id: Power
   name: guide-entry-power
index 47d44778ff926a6e6d3539d96123f3f9648ad4aa..ebd2ae8383405fb05b777bef9c41f4a26e4a564c 100644 (file)
     Steel: 200
     Plastic: 200
 
+- type: latheRecipe
+  id: NetworkConfigurator
+  result: NetworkConfigurator
+  completetime: 2
+  materials:
+    Steel: 200
+    Plastic: 200
+
 - type: latheRecipe
   id: PowerDrill
   result: PowerDrill
index 035c4334a5f567dc768ceb7eab99542c18aea09e..0f3a6f4e5a0777bc801eb7b454e09bd31f261914 100644 (file)
     - Item
   permanentComponents:
   - type: TilePrying
-  - type: SignalLinker
-    requiredQuality: Pulsing
   - type: UserInterface
     interfaces:
       - key: enum.SignalLinkerUiKey.Key
     - Screwing
     speed: 2 # Very powerful multitool to balance out the desire to sell or scrap for points
     useSound: /Audio/Items/drill_use.ogg
+  - type: Tag
+    tags:
+      - Multitool
   - type: MultipleTool
     statusShowBehavior: true
     entries:
index 9f989f04b437ea13f799144c53b456884d62c2af..3796775d66ab59895771474a8edc4d629a612f41 100644 (file)
 - type: Tag
   id: Mop
 
+- type: Tag
+  id: Multitool
+
 - type: Tag
   id: NoSpinOnThrow
 
diff --git a/Resources/ServerInfo/Guidebook/Network_Configurator.xml b/Resources/ServerInfo/Guidebook/Network_Configurator.xml
new file mode 100644 (file)
index 0000000..445d182
--- /dev/null
@@ -0,0 +1,39 @@
+<Document>
+# Network Configurator
+The network configurator allows you to manipulate device lists and link devices together.
+<Box>
+  <GuideEntityEmbed Entity="NetworkConfigurator"/>
+</Box>
+The configurator has two modes: List and Link. You can press [color=gray]Alt+Z[/color] or [color=gray]Alt+Y[/color] to switch between them.
+
+## List Mode
+In list mode you can click on network devices to save them on the configurator and then on a network device that has a device list like the [color=#a4885c]Air Alarm[/color].
+
+When clicking on a device like the Air Alarm, a UI will open displaying the list currently saved on the device and buttons to manipulate that list.
+
+You can:
+- Replace the current list with the one saved on the configurator
+- Add the list on the configurator to the current one
+- Clear the current list
+- Copy the current list to the configurator
+- Visualize the connections to the devices on the current list
+
+Pressing [color=gray]z[/color] or [color=gray]y[/color] opens the list saved on the configurator where you can remove saved devices.
+
+## Link Mode
+With link mode you can click on a device that is capable of device linking and click on any other device that is either
+a sink or source.
+
+For example, first clicking on a source like a [color=#a4885c]signal button[/color] and then on sink like a
+[color=#a4885c]small light[/color] opens a UI that displays the source ports on the left side and the sink ports on the right.
+
+Now you can eiter click [color=gray]link defaults[/color] to link the default ports for a source + sink combination or press on a source and then a sink port to connect them.
+
+An example of a default link for the aformentioned combinaton of devices would be:
+<Box>
+  [color=cyan]Pressed πŸ ’ Toggle[/color]
+</Box>
+When you're done connecting the ports you want you can click on [color=gray]ok[/color] to close the UI.
+
+You can quickly link multiple devices to their default port by first clicking on a device that can be linked and then using [color=gray]alt+left mouse button[/color] on the devices you want to link together.
+</Document>
diff --git a/Resources/ServerInfo/Guidebook/Networking.xml b/Resources/ServerInfo/Guidebook/Networking.xml
new file mode 100644 (file)
index 0000000..03576c7
--- /dev/null
@@ -0,0 +1,25 @@
+<Document>
+# Networking
+Some devices on the station need to communicate with each other, and they do this by utilizing device networking.
+With networking machines and devices can send arbitrary data between each other.
+There are multiple networks that get used, such as the wireless and wired network.
+Each network device has a frequency it receives on. PDAs for example, use the frequency: [color=green]220.2[/color]
+
+## Device Lists
+Some devices need to know what other devices to communicate with specifically.
+<Box>
+  <GuideEntityEmbed Entity="AirAlarm"/>
+</Box>
+Air alarms for example require you to tell it which vents, scrubbers, sensors, and firelocks to interact with.
+You do that by using the Network Configurator.
+
+## Linking
+If devices basic or still more advanced but need finer control of how and what connects to each other they will generally use device linking.
+  <Box>
+    <GuideEntityEmbed Entity="SignalSwitch"/>
+    <GuideEntityEmbed Entity="Airlock"/>
+  </Box>
+With linking you can connect the outputs of a device like [color=gray]On[/color] or [color=gray]Off[/color] with the inputs of a device like the airlocks
+[color=gray]Open[/color] or [color=gray]Close[/color] inputs.
+The Network Configurator is also used for linking devices together.
+</Document>
diff --git a/Resources/Textures/Objects/Tools/network_configurator.rsi/equipped-BELT.png b/Resources/Textures/Objects/Tools/network_configurator.rsi/equipped-BELT.png
new file mode 100644 (file)
index 0000000..503844c
Binary files /dev/null and b/Resources/Textures/Objects/Tools/network_configurator.rsi/equipped-BELT.png differ
diff --git a/Resources/Textures/Objects/Tools/network_configurator.rsi/icon.png b/Resources/Textures/Objects/Tools/network_configurator.rsi/icon.png
new file mode 100644 (file)
index 0000000..98f0f2a
Binary files /dev/null and b/Resources/Textures/Objects/Tools/network_configurator.rsi/icon.png differ
diff --git a/Resources/Textures/Objects/Tools/network_configurator.rsi/inhand-left.png b/Resources/Textures/Objects/Tools/network_configurator.rsi/inhand-left.png
new file mode 100644 (file)
index 0000000..469d9a9
Binary files /dev/null and b/Resources/Textures/Objects/Tools/network_configurator.rsi/inhand-left.png differ
diff --git a/Resources/Textures/Objects/Tools/network_configurator.rsi/inhand-right.png b/Resources/Textures/Objects/Tools/network_configurator.rsi/inhand-right.png
new file mode 100644 (file)
index 0000000..545ae54
Binary files /dev/null and b/Resources/Textures/Objects/Tools/network_configurator.rsi/inhand-right.png differ
diff --git a/Resources/Textures/Objects/Tools/network_configurator.rsi/meta.json b/Resources/Textures/Objects/Tools/network_configurator.rsi/meta.json
new file mode 100644 (file)
index 0000000..2730385
--- /dev/null
@@ -0,0 +1,32 @@
+{
+  "version": 1,
+  "license": "CC-BY-SA-3.0",
+  "copyright": "Made by Julian Giebel <juliangiebel@live.de> for ss14",
+  "size": {
+    "x": 32,
+    "y": 32
+  },
+  "states": [
+    {
+      "name": "icon"
+    },
+    {
+      "name": "mode-list"
+    },
+    {
+      "name": "mode-link"
+    },
+    {
+      "name": "inhand-left",
+      "directions": 4
+    },
+    {
+      "name": "inhand-right",
+      "directions": 4
+    },
+    {
+      "name": "equipped-BELT",
+      "directions": 4
+    }
+  ]
+}
diff --git a/Resources/Textures/Objects/Tools/network_configurator.rsi/mode-link.png b/Resources/Textures/Objects/Tools/network_configurator.rsi/mode-link.png
new file mode 100644 (file)
index 0000000..6eaa646
Binary files /dev/null and b/Resources/Textures/Objects/Tools/network_configurator.rsi/mode-link.png differ
diff --git a/Resources/Textures/Objects/Tools/network_configurator.rsi/mode-list.png b/Resources/Textures/Objects/Tools/network_configurator.rsi/mode-list.png
new file mode 100644 (file)
index 0000000..c817cb0
Binary files /dev/null and b/Resources/Textures/Objects/Tools/network_configurator.rsi/mode-list.png differ