From 6ebd784cb6fc2ca69896bd6d791ad825bc32e1a3 Mon Sep 17 00:00:00 2001 From: Julian Giebel Date: Sun, 7 May 2023 08:07:24 +0200 Subject: [PATCH] Device Linking and better linking ui (#13645) 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 Co-authored-by: metalgearsloth --- .../NetworkConfiguratorBoundUserInterface.cs | 19 +- .../NetworkConfiguratorConfigurationMenu.xaml | 4 +- .../NetworkConfiguratorLinkMenu.xaml | 47 ++ .../NetworkConfiguratorLinkMenu.xaml.cs | 221 +++++++++ .../NetworkConfiguratorLinkOverlay.cs | 2 + .../NetworkConfiguratorListMenu.xaml | 2 +- .../Systems/DeviceListSystem.cs | 4 +- .../Systems/NetworkConfiguratorSystem.cs | 58 ++- Content.IntegrationTests/Tests/EntityTest.cs | 1 - .../SignalControlledValveSystem.cs | 9 +- .../Unary/EntitySystems/GasVentPumpSystem.cs | 11 +- .../Cargo/Systems/CargoSystem.Orders.cs | 4 +- .../Cargo/Systems/CargoSystem.Telepad.cs | 2 +- .../Cloning/CloningConsoleSystem.cs | 30 +- Content.Server/Cloning/CloningSystem.cs | 10 +- .../Components/AutoLinkReceiverComponent.cs | 2 +- .../AutoLinkTransmitterComponent.cs | 2 +- .../Components/DoorSignalControlComponent.cs | 2 +- .../Components/SignalSwitchComponent.cs | 2 +- .../Components/SignallerComponent.cs | 2 +- .../Components/TwoWayLeverComponent.cs | 3 +- .../Events/SignalReceivedEvent.cs | 6 + .../Systems}/AutoLinkSystem.cs | 9 +- .../DeviceLinking/Systems/DeviceLinkSystem.cs | 114 +++++ .../Systems}/DoorSignalControlSystem.cs | 15 +- .../Systems}/SignalSwitchSystem.cs | 9 +- .../Systems}/SignallerSystem.cs | 12 +- .../Systems}/TwoWayLeverSystem.cs | 11 +- .../DeviceNetwork/Systems/DeviceListSystem.cs | 1 + .../Systems/NetworkConfiguratorSystem.cs | 329 +++++++++++-- Content.Server/Doors/Systems/AirlockSystem.cs | 4 +- .../EntitySystems/TriggerSystem.Signal.cs | 11 +- .../Light/EntitySystems/PoweredLightSystem.cs | 10 +- .../Components/SignalTransmitterComponent.cs | 6 +- .../Events/SignalReceivedEvent.cs | 14 - .../System/SignalLinkerSystem.cs | 11 +- .../Medical/MedicalScannerSystem.cs | 10 +- .../Physics/Controllers/ConveyorController.cs | 11 +- .../Systems/ArtifactAnalyzerSystem.cs | 8 +- .../Access/Systems/AccessReaderSystem.cs | 2 +- .../DeviceLinking/DeviceLinkSinkComponent.cs | 22 + .../DeviceLinkSourceComponent.cs | 35 ++ .../DeviceLinking/DevicePortPrototype.cs | 46 ++ .../DeviceLinking/Events/LinkAttemptEvent.cs | 19 + .../DeviceLinking/Events/NewLinkEvent.cs | 19 + .../Events/PortDisconnectedEvent.cs | 2 +- .../DeviceLinking/SharedDeviceLinkSystem.cs | 466 ++++++++++++++++++ .../TwoWayLeverSignal.cs | 2 +- .../Components/DeviceListComponent.cs | 2 +- .../NetworkConfiguratorComponent.cs | 42 +- .../NetworkConfiguratorUIMessages.cs | 33 +- .../NetworkConfiguratorUserInterfaceState.cs | 24 +- .../Systems/SharedDeviceListSystem.cs | 1 + .../SharedNetworkConfiguratorSystem.cs | 18 +- .../MachineLinking/Events/LinkAttemptEvent.cs | 20 - .../MachineLinking/Events/NewLinkEvent.cs | 20 - Resources/Audio/Machines/attributions.yml | 2 +- .../Locale/en-US/devices/device-network.ftl | 1 + .../en-US/devices/network-configurator.ftl | 21 + Resources/Locale/en-US/guidebook/guides.ftl | 2 + .../en-US/machine-linking/port-selector.ftl | 2 + .../en-US/machine-linking/receiver_ports.ftl | 3 + .../Prototypes/Catalog/Fills/Items/belt.yml | 2 + .../Device/devicenet_frequencies.yml | 5 + .../Prototypes/DeviceLinking/sink_ports.yml | 74 +++ .../Prototypes/DeviceLinking/source_ports.yml | 57 +++ .../Entities/Clothing/Belt/belts.yml | 17 +- .../Objects/Devices/Electronics/signaller.yml | 6 +- .../Objects/Devices/Electronics/triggers.yml | 7 +- .../Entities/Objects/Tools/cowtools.yml | 4 +- .../Entities/Objects/Tools/tools.yml | 71 ++- .../Objects/Weapons/Bombs/plastic.yml | 6 +- .../Doors/Airlocks/base_structureairlocks.yml | 17 +- .../Structures/Doors/Shutter/shutters.yml | 15 +- .../Doors/Windoors/base_structurewindoors.yml | 17 +- .../Structures/Lighting/base_lighting.yml | 30 +- .../Structures/Lighting/ground_lighting.yml | 15 +- .../Machines/Computers/computers.yml | 18 +- .../Structures/Machines/artifact_analyzer.yml | 7 +- .../Structures/Machines/cloning_machine.yml | 7 +- .../Entities/Structures/Machines/lathe.yml | 1 + .../Structures/Machines/medical_scanner.yml | 7 +- .../Entities/Structures/Machines/recycler.yml | 10 +- .../Structures/Piping/Atmospherics/binary.yml | 28 +- .../Entities/Structures/Wallmounts/switch.yml | 36 +- .../Entities/Structures/cargo_console.yml | 11 +- .../Entities/Structures/conveyor.yml | 10 +- .../Prototypes/Guidebook/engineering.yml | 14 + Resources/Prototypes/Recipes/Lathes/tools.yml | 8 + .../XenoArch/Effects/utility_effects.yml | 5 +- Resources/Prototypes/tags.yml | 3 + .../Guidebook/Network_Configurator.xml | 39 ++ Resources/ServerInfo/Guidebook/Networking.xml | 25 + .../equipped-BELT.png | Bin 0 -> 145 bytes .../Tools/network_configurator.rsi/icon.png | Bin 0 -> 332 bytes .../network_configurator.rsi/inhand-left.png | Bin 0 -> 312 bytes .../network_configurator.rsi/inhand-right.png | Bin 0 -> 320 bytes .../Tools/network_configurator.rsi/meta.json | 32 ++ .../network_configurator.rsi/mode-link.png | Bin 0 -> 109 bytes .../network_configurator.rsi/mode-list.png | Bin 0 -> 104 bytes 100 files changed, 2095 insertions(+), 341 deletions(-) create mode 100644 Content.Client/NetworkConfigurator/NetworkConfiguratorLinkMenu.xaml create mode 100644 Content.Client/NetworkConfigurator/NetworkConfiguratorLinkMenu.xaml.cs rename Content.Server/{MachineLinking => DeviceLinking}/Components/AutoLinkReceiverComponent.cs (84%) rename Content.Server/{MachineLinking => DeviceLinking}/Components/AutoLinkTransmitterComponent.cs (85%) rename Content.Server/{MachineLinking => DeviceLinking}/Components/DoorSignalControlComponent.cs (93%) rename Content.Server/{MachineLinking => DeviceLinking}/Components/SignalSwitchComponent.cs (95%) rename Content.Server/{MachineLinking => DeviceLinking}/Components/SignallerComponent.cs (91%) rename Content.Server/{MachineLinking => DeviceLinking}/Components/TwoWayLeverComponent.cs (90%) create mode 100644 Content.Server/DeviceLinking/Events/SignalReceivedEvent.cs rename Content.Server/{MachineLinking/System => DeviceLinking/Systems}/AutoLinkSystem.cs (73%) create mode 100644 Content.Server/DeviceLinking/Systems/DeviceLinkSystem.cs rename Content.Server/{MachineLinking/System => DeviceLinking/Systems}/DoorSignalControlSystem.cs (75%) rename Content.Server/{MachineLinking/System => DeviceLinking/Systems}/SignalSwitchSystem.cs (78%) rename Content.Server/{MachineLinking/System => DeviceLinking/Systems}/SignallerSystem.cs (79%) rename Content.Server/{MachineLinking/System => DeviceLinking/Systems}/TwoWayLeverSystem.cs (92%) create mode 100644 Content.Shared/DeviceLinking/DeviceLinkSinkComponent.cs create mode 100644 Content.Shared/DeviceLinking/DeviceLinkSourceComponent.cs create mode 100644 Content.Shared/DeviceLinking/DevicePortPrototype.cs create mode 100644 Content.Shared/DeviceLinking/Events/LinkAttemptEvent.cs create mode 100644 Content.Shared/DeviceLinking/Events/NewLinkEvent.cs rename {Content.Server/MachineLinking => Content.Shared/DeviceLinking}/Events/PortDisconnectedEvent.cs (81%) create mode 100644 Content.Shared/DeviceLinking/SharedDeviceLinkSystem.cs rename Content.Shared/{MachineLinking => DeviceLinking}/TwoWayLeverSignal.cs (87%) delete mode 100644 Content.Shared/MachineLinking/Events/LinkAttemptEvent.cs delete mode 100644 Content.Shared/MachineLinking/Events/NewLinkEvent.cs create mode 100644 Resources/Prototypes/DeviceLinking/sink_ports.yml create mode 100644 Resources/Prototypes/DeviceLinking/source_ports.yml create mode 100644 Resources/ServerInfo/Guidebook/Network_Configurator.xml create mode 100644 Resources/ServerInfo/Guidebook/Networking.xml create mode 100644 Resources/Textures/Objects/Tools/network_configurator.rsi/equipped-BELT.png create mode 100644 Resources/Textures/Objects/Tools/network_configurator.rsi/icon.png create mode 100644 Resources/Textures/Objects/Tools/network_configurator.rsi/inhand-left.png create mode 100644 Resources/Textures/Objects/Tools/network_configurator.rsi/inhand-right.png create mode 100644 Resources/Textures/Objects/Tools/network_configurator.rsi/meta.json create mode 100644 Resources/Textures/Objects/Tools/network_configurator.rsi/mode-link.png create mode 100644 Resources/Textures/Objects/Tools/network_configurator.rsi/mode-list.png diff --git a/Content.Client/NetworkConfigurator/NetworkConfiguratorBoundUserInterface.cs b/Content.Client/NetworkConfigurator/NetworkConfiguratorBoundUserInterface.cs index e69ea43aa6..7c50c150b2 100644 --- a/Content.Client/NetworkConfigurator/NetworkConfiguratorBoundUserInterface.cs +++ b/Content.Client/NetworkConfigurator/NetworkConfiguratorBoundUserInterface.cs @@ -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(); - _deviceList = _entityManager.System(); } 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; } } diff --git a/Content.Client/NetworkConfigurator/NetworkConfiguratorConfigurationMenu.xaml b/Content.Client/NetworkConfigurator/NetworkConfiguratorConfigurationMenu.xaml index 0f4efa56c8..469faf209d 100644 --- a/Content.Client/NetworkConfigurator/NetworkConfiguratorConfigurationMenu.xaml +++ b/Content.Client/NetworkConfigurator/NetworkConfiguratorConfigurationMenu.xaml @@ -1,7 +1,7 @@ + Title="{Loc 'network-configurator-title-device-configuration'}" MinSize="350 100"> @@ -15,6 +15,6 @@ + + + + + + + + + diff --git a/Content.Client/NetworkConfigurator/NetworkConfiguratorLinkMenu.xaml.cs b/Content.Client/NetworkConfigurator/NetworkConfiguratorLinkMenu.xaml.cs new file mode 100644 index 0000000000..a867a608fb --- /dev/null +++ b/Content.Client/NetworkConfigurator/NetworkConfiguratorLinkMenu.xaml.cs @@ -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 _sources = new(); + + private readonly List _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 + } + + /// + /// Draws lines between linked ports using bezier curve calculated with polynomial coefficients + /// See: https://youtu.be/jvPPXbo87ds?t=351 + /// + private sealed class LinksRender : Control + { + public readonly List<(string, string)> Links = new(); + public readonly Dictionary SourceButtons = new(); + public readonly Dictionary 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 + { + 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(); + + //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); + } + } + } +} diff --git a/Content.Client/NetworkConfigurator/NetworkConfiguratorLinkOverlay.cs b/Content.Client/NetworkConfigurator/NetworkConfiguratorLinkOverlay.cs index 23404b884b..d61bb469b3 100644 --- a/Content.Client/NetworkConfigurator/NetworkConfiguratorLinkOverlay.cs +++ b/Content.Client/NetworkConfigurator/NetworkConfiguratorLinkOverlay.cs @@ -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; diff --git a/Content.Client/NetworkConfigurator/NetworkConfiguratorListMenu.xaml b/Content.Client/NetworkConfigurator/NetworkConfiguratorListMenu.xaml index 8d46ecfb20..4f98e4ba8b 100644 --- a/Content.Client/NetworkConfigurator/NetworkConfiguratorListMenu.xaml +++ b/Content.Client/NetworkConfigurator/NetworkConfiguratorListMenu.xaml @@ -1,7 +1,7 @@ + Title="{Loc 'network-configurator-title-saved-devices'}" MinSize="220 400"> diff --git a/Content.Client/NetworkConfigurator/Systems/DeviceListSystem.cs b/Content.Client/NetworkConfigurator/Systems/DeviceListSystem.cs index b171367e23..150361c0ae 100644 --- a/Content.Client/NetworkConfigurator/Systems/DeviceListSystem.cs +++ b/Content.Client/NetworkConfigurator/Systems/DeviceListSystem.cs @@ -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 { diff --git a/Content.Client/NetworkConfigurator/Systems/NetworkConfiguratorSystem.cs b/Content.Client/NetworkConfigurator/Systems/NetworkConfiguratorSystem.cs index e63365af10..80d7b9381c 100644 --- a/Content.Client/NetworkConfigurator/Systems/NetworkConfiguratorSystem.cs +++ b/Content.Client/NetworkConfigurator/Systems/NetworkConfiguratorSystem.cs @@ -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(_ => ClearAllOverlays()); + SubscribeLocalEvent(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 diff --git a/Content.IntegrationTests/Tests/EntityTest.cs b/Content.IntegrationTests/Tests/EntityTest.cs index 32042dbae5..0c095b2614 100644 --- a/Content.IntegrationTests/Tests/EntityTest.cs +++ b/Content.IntegrationTests/Tests/EntityTest.cs @@ -82,7 +82,6 @@ namespace Content.IntegrationTests.Tests await server.WaitPost(() => { entityMan = IoCManager.Resolve(); - var mapManager = IoCManager.Resolve(); var prototypeMan = IoCManager.Resolve(); var protoIds = prototypeMan diff --git a/Content.Server/Atmos/Piping/Binary/EntitySystems/SignalControlledValveSystem.cs b/Content.Server/Atmos/Piping/Binary/EntitySystems/SignalControlledValveSystem.cs index 600671c442..2a3acda458 100644 --- a/Content.Server/Atmos/Piping/Binary/EntitySystems/SignalControlledValveSystem.cs +++ b/Content.Server/Atmos/Piping/Binary/EntitySystems/SignalControlledValveSystem.cs @@ -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(uid, out var valve)) return; diff --git a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentPumpSystem.cs b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentPumpSystem.cs index d14bc43d16..97d6b5a921 100644 --- a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentPumpSystem.cs +++ b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentPumpSystem.cs @@ -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; diff --git a/Content.Server/Cargo/Systems/CargoSystem.Orders.cs b/Content.Server/Cargo/Systems/CargoSystem.Orders.cs index 1ac6d52149..0938052c07 100644 --- a/Content.Server/Cargo/Systems/CargoSystem.Orders.cs +++ b/Content.Server/Cargo/Systems/CargoSystem.Orders.cs @@ -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!; diff --git a/Content.Server/Cargo/Systems/CargoSystem.Telepad.cs b/Content.Server/Cargo/Systems/CargoSystem.Telepad.cs index 02f31de606..e1744c3e9a 100644 --- a/Content.Server/Cargo/Systems/CargoSystem.Telepad.cs +++ b/Content.Server/Cargo/Systems/CargoSystem.Telepad.cs @@ -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, diff --git a/Content.Server/Cloning/CloningConsoleSystem.cs b/Content.Server/Cloning/CloningConsoleSystem.cs index eb8e2b80cb..ceb642173f 100644 --- a/Content.Server/Cloning/CloningConsoleSystem.cs +++ b/Content.Server/Cloning/CloningConsoleSystem.cs @@ -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(uid, out var receiver)) + if (!TryComp(uid, out var receiver)) return; foreach (var port in receiver.Outputs.Values.SelectMany(ports => ports)) { - if (TryComp(port.Uid, out var scanner)) + if (TryComp(port, out var scanner)) { - component.GeneticScanner = port.Uid; + component.GeneticScanner = port; scanner.ConnectedConsole = uid; } - if (TryComp(port.Uid, out var pod)) + if (TryComp(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(args.Receiver, out var scanner) && args.TransmitterPort == CloningConsoleComponent.ScannerPort) + if (TryComp(args.Sink, out var scanner) && args.SourcePort == CloningConsoleComponent.ScannerPort) { - component.GeneticScanner = args.Receiver; + component.GeneticScanner = args.Sink; scanner.ConnectedConsole = uid; } - if (TryComp(args.Receiver, out var pod) && args.TransmitterPort == CloningConsoleComponent.PodPort) + if (TryComp(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); diff --git a/Content.Server/Cloning/CloningSystem.cs b/Content.Server/Cloning/CloningSystem.cs index c98fa51803..f0077ff588 100644 --- a/Content.Server/Cloning/CloningSystem.cs +++ b/Content.Server/Cloning/CloningSystem.cs @@ -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(clonePod.Owner, "clonepod-bodyContainer"); - _signalSystem.EnsureReceiverPorts(uid, CloningPodComponent.PodPort); + _signalSystem.EnsureSinkPorts(uid, CloningPodComponent.PodPort); } private void OnPartsRefreshed(EntityUid uid, CloningPodComponent component, RefreshPartsEvent args) diff --git a/Content.Server/MachineLinking/Components/AutoLinkReceiverComponent.cs b/Content.Server/DeviceLinking/Components/AutoLinkReceiverComponent.cs similarity index 84% rename from Content.Server/MachineLinking/Components/AutoLinkReceiverComponent.cs rename to Content.Server/DeviceLinking/Components/AutoLinkReceiverComponent.cs index 7bb9f99a4e..9a98f81e31 100644 --- a/Content.Server/MachineLinking/Components/AutoLinkReceiverComponent.cs +++ b/Content.Server/DeviceLinking/Components/AutoLinkReceiverComponent.cs @@ -1,4 +1,4 @@ -namespace Content.Server.MachineLinking.Components; +namespace Content.Server.DeviceLinking.Components; /// /// This is used for automatic linkage with buttons and other transmitters. diff --git a/Content.Server/MachineLinking/Components/AutoLinkTransmitterComponent.cs b/Content.Server/DeviceLinking/Components/AutoLinkTransmitterComponent.cs similarity index 85% rename from Content.Server/MachineLinking/Components/AutoLinkTransmitterComponent.cs rename to Content.Server/DeviceLinking/Components/AutoLinkTransmitterComponent.cs index b3760a2f2c..b013d9f578 100644 --- a/Content.Server/MachineLinking/Components/AutoLinkTransmitterComponent.cs +++ b/Content.Server/DeviceLinking/Components/AutoLinkTransmitterComponent.cs @@ -1,4 +1,4 @@ -namespace Content.Server.MachineLinking.Components; +namespace Content.Server.DeviceLinking.Components; /// /// This is used for automatic linkage with various receivers, like shutters. diff --git a/Content.Server/MachineLinking/Components/DoorSignalControlComponent.cs b/Content.Server/DeviceLinking/Components/DoorSignalControlComponent.cs similarity index 93% rename from Content.Server/MachineLinking/Components/DoorSignalControlComponent.cs rename to Content.Server/DeviceLinking/Components/DoorSignalControlComponent.cs index 1d5b92f243..98c8792f77 100644 --- a/Content.Server/MachineLinking/Components/DoorSignalControlComponent.cs +++ b/Content.Server/DeviceLinking/Components/DoorSignalControlComponent.cs @@ -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 diff --git a/Content.Server/MachineLinking/Components/SignalSwitchComponent.cs b/Content.Server/DeviceLinking/Components/SignalSwitchComponent.cs similarity index 95% rename from Content.Server/MachineLinking/Components/SignalSwitchComponent.cs rename to Content.Server/DeviceLinking/Components/SignalSwitchComponent.cs index 53723f82e4..4259ba6c6c 100644 --- a/Content.Server/MachineLinking/Components/SignalSwitchComponent.cs +++ b/Content.Server/DeviceLinking/Components/SignalSwitchComponent.cs @@ -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 { /// /// Simple switch that will fire ports when toggled on or off. A button is jsut a switch that signals on the diff --git a/Content.Server/MachineLinking/Components/SignallerComponent.cs b/Content.Server/DeviceLinking/Components/SignallerComponent.cs similarity index 91% rename from Content.Server/MachineLinking/Components/SignallerComponent.cs rename to Content.Server/DeviceLinking/Components/SignallerComponent.cs index e4d7140136..8cc13b083f 100644 --- a/Content.Server/MachineLinking/Components/SignallerComponent.cs +++ b/Content.Server/DeviceLinking/Components/SignallerComponent.cs @@ -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 { /// /// Sends out a signal to machine linked objects. diff --git a/Content.Server/MachineLinking/Components/TwoWayLeverComponent.cs b/Content.Server/DeviceLinking/Components/TwoWayLeverComponent.cs similarity index 90% rename from Content.Server/MachineLinking/Components/TwoWayLeverComponent.cs rename to Content.Server/DeviceLinking/Components/TwoWayLeverComponent.cs index 7420fb191d..5ab02b679a 100644 --- a/Content.Server/MachineLinking/Components/TwoWayLeverComponent.cs +++ b/Content.Server/DeviceLinking/Components/TwoWayLeverComponent.cs @@ -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 index 0000000000..c1928575fe --- /dev/null +++ b/Content.Server/DeviceLinking/Events/SignalReceivedEvent.cs @@ -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); diff --git a/Content.Server/MachineLinking/System/AutoLinkSystem.cs b/Content.Server/DeviceLinking/Systems/AutoLinkSystem.cs similarity index 73% rename from Content.Server/MachineLinking/System/AutoLinkSystem.cs rename to Content.Server/DeviceLinking/Systems/AutoLinkSystem.cs index c7590e564d..3c9ccb57b1 100644 --- a/Content.Server/MachineLinking/System/AutoLinkSystem.cs +++ b/Content.Server/DeviceLinking/Systems/AutoLinkSystem.cs @@ -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; /// /// This handles automatically linking autolinked entities at round-start. /// public sealed class AutoLinkSystem : EntitySystem { - [Dependency] private readonly SignalLinkerSystem _signalLinkerSystem = default!; + [Dependency] private readonly DeviceLinkSystem _deviceLinkSystem = default!; /// 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 index 0000000000..60c85fde7b --- /dev/null +++ b/Content.Server/DeviceLinking/Systems/DeviceLinkSystem.cs @@ -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(OnTransmitterStartup); + SubscribeLocalEvent(OnPacketReceived); + } + + /// + /// Moves existing links from machine linking to device linking to ensure linked things still work even when the map wasn't updated yet + /// + private void OnTransmitterStartup(EntityUid sourceUid, SignalTransmitterComponent transmitterComponent, MapInitEvent args) + { + if (!TryComp(sourceUid, out var sourceComponent)) + return; + + Dictionary> 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 + /// + /// Sends a network payload directed at the sink entity. + /// Just raises a without data if the source or the sink doesn't have a + /// + /// The source uid that invokes the port + /// The port to invoke + /// Optional data to send along + /// + 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(uid) || !TryComp(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); + } + } + } + + /// + /// Checks if the payload has a port defined and if the port is present on the sink. + /// Raises a containing the payload when the check passes + /// + 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 + + +} diff --git a/Content.Server/MachineLinking/System/DoorSignalControlSystem.cs b/Content.Server/DeviceLinking/Systems/DoorSignalControlSystem.cs similarity index 75% rename from Content.Server/MachineLinking/System/DoorSignalControlSystem.cs rename to Content.Server/DeviceLinking/Systems/DoorSignalControlSystem.cs index e8b2f17c3c..930beee53b 100644 --- a/Content.Server/MachineLinking/System/DoorSignalControlSystem.cs +++ b/Content.Server/DeviceLinking/Systems/DoorSignalControlSystem.cs @@ -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(OnInit); SubscribeLocalEvent(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; diff --git a/Content.Server/MachineLinking/System/SignalSwitchSystem.cs b/Content.Server/DeviceLinking/Systems/SignalSwitchSystem.cs similarity index 78% rename from Content.Server/MachineLinking/System/SignalSwitchSystem.cs rename to Content.Server/DeviceLinking/Systems/SignalSwitchSystem.cs index 78a0ef2ca4..acb3acb4aa 100644 --- a/Content.Server/MachineLinking/System/SignalSwitchSystem.cs +++ b/Content.Server/DeviceLinking/Systems/SignalSwitchSystem.cs @@ -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) diff --git a/Content.Server/MachineLinking/System/SignallerSystem.cs b/Content.Server/DeviceLinking/Systems/SignallerSystem.cs similarity index 79% rename from Content.Server/MachineLinking/System/SignallerSystem.cs rename to Content.Server/DeviceLinking/Systems/SignallerSystem.cs index e83253cb65..86f68bfb8f 100644 --- a/Content.Server/MachineLinking/System/SignallerSystem.cs +++ b/Content.Server/DeviceLinking/Systems/SignallerSystem.cs @@ -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; } } diff --git a/Content.Server/MachineLinking/System/TwoWayLeverSystem.cs b/Content.Server/DeviceLinking/Systems/TwoWayLeverSystem.cs similarity index 92% rename from Content.Server/MachineLinking/System/TwoWayLeverSystem.cs rename to Content.Server/DeviceLinking/Systems/TwoWayLeverSystem.cs index f217ed7a42..3251db2dc2 100644 --- a/Content.Server/MachineLinking/System/TwoWayLeverSystem.cs +++ b/Content.Server/DeviceLinking/Systems/TwoWayLeverSystem.cs @@ -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) diff --git a/Content.Server/DeviceNetwork/Systems/DeviceListSystem.cs b/Content.Server/DeviceNetwork/Systems/DeviceListSystem.cs index a5ea2d5bf0..e91cce1e99 100644 --- a/Content.Server/DeviceNetwork/Systems/DeviceListSystem.cs +++ b/Content.Server/DeviceNetwork/Systems/DeviceListSystem.cs @@ -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; diff --git a/Content.Server/DeviceNetwork/Systems/NetworkConfiguratorSystem.cs b/Content.Server/DeviceNetwork/Systems/NetworkConfiguratorSystem.cs index 3ba10dcbb9..c6ef1bcbc7 100644 --- a/Content.Server/DeviceNetwork/Systems/NetworkConfiguratorSystem.cs +++ b/Content.Server/DeviceNetwork/Systems/NetworkConfiguratorSystem.cs @@ -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(OnMapInit); //Interaction - SubscribeLocalEvent((uid, component, args) => OnUsed(uid, component, args.Target, args.User, args.CanReach)); //TODO: Replace with utility verb? + SubscribeLocalEvent(AfterInteract); //TODO: Replace with utility verb? + SubscribeLocalEvent(DoExamine); //Verbs SubscribeLocalEvent>(OnAddInteractVerb); SubscribeLocalEvent>(OnAddAlternativeSaveDeviceVerb); + SubscribeLocalEvent>(OnAddSwitchModeVerb); //UI SubscribeLocalEvent(OnUiClosed); SubscribeLocalEvent(OnRemoveDevice); SubscribeLocalEvent(OnClearDevice); + SubscribeLocalEvent(OnSaveLinks); + SubscribeLocalEvent(OnClearLinks); + SubscribeLocalEvent(OnToggleLinks); SubscribeLocalEvent(OnConfigButtonPressed); + SubscribeLocalEvent(OnUiOpenAttempt); SubscribeLocalEvent(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(target) && !HasComp(target)) + return; + + if (configurator.ActiveDeviceLink == target) + { + _popupSystem.PopupEntity(Loc.GetString("network-configurator-link-mode-stopped"), target.Value, user); + configurator.ActiveDeviceLink = null; + return; + } + + if (HasComp(target) && HasComp(configurator.ActiveDeviceLink) + || HasComp(target) && HasComp(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(targetUid) && !HasComp(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)); + } + + /// + /// Returns true if the last time this method was called is earlier than the configurators use delay. + /// + 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); + } + /// /// Either adds a device to the device list or shows the config ui if the target is ant entity with a device list /// - 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(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 /// /// Adds the interaction verb which is either configuring device lists or saving a device onto the configurator /// - private void OnAddInteractVerb(EntityUid uid, NetworkConfiguratorComponent component, GetVerbsEvent args) + private void OnAddInteractVerb(EntityUid uid, NetworkConfiguratorComponent configurator, GetVerbsEvent args) { - if (!args.CanAccess || !args.CanInteract || !args.Using.HasValue || !HasComp(args.Target)) + if (!args.CanAccess || !args.CanInteract || !args.Using.HasValue) return; - var isDeviceList = HasComp(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(args.Target)) + { + var isDeviceList = HasComp(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 /// private void OnAddAlternativeSaveDeviceVerb(EntityUid uid, DeviceNetworkComponent component, GetVerbsEvent args) { - if (!args.CanAccess || !args.CanInteract || !args.Using.HasValue || !HasComp(args.Using.Value) - || !HasComp(args.Target)) + if (!args.CanAccess || !args.CanInteract || !args.Using.HasValue + || !TryComp(args.Using.Value, out var configurator)) + return; + + if (!configurator.LinkModeActive && HasComp(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(args.Target) || HasComp(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 args) + { + if (!args.CanAccess || !args.CanInteract || !args.Using.HasValue || !HasComp(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); + } + /// /// Opens the config ui. It can be used to modify the devices in the targets device list. /// 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 /// /// Sends the list of saved devices to the ui /// - private void UpdateUiState(EntityUid uid, NetworkConfiguratorComponent component) + private void UpdateListUiState(EntityUid uid, NetworkConfiguratorComponent component) { HashSet<(string address, string name)> devices = new(); HashSet 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; + } } /// @@ -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); } /// @@ -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(configurator.ActiveDeviceLink)) + { + _deviceLinkSystem.RemoveSinkFromSource(configurator.ActiveDeviceLink.Value, configurator.DeviceLinkTarget.Value); + UpdateLinkUiState(uid, configurator.ActiveDeviceLink.Value, configurator.DeviceLinkTarget.Value); + } + else if (HasComp(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); + } + } + + /// + /// Saves links set by the device link UI + /// + 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); + } } /// @@ -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 } diff --git a/Content.Server/Doors/Systems/AirlockSystem.cs b/Content.Server/Doors/Systems/AirlockSystem.cs index 4dc8a3dc99..5eed14fc2f 100644 --- a/Content.Server/Doors/Systems/AirlockSystem.cs +++ b/Content.Server/Doors/Systems/AirlockSystem.cs @@ -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) { diff --git a/Content.Server/Explosion/EntitySystems/TriggerSystem.Signal.cs b/Content.Server/Explosion/EntitySystems/TriggerSystem.Signal.cs index 0d719c4c5d..ffd47f0257 100644 --- a/Content.Server/Explosion/EntitySystems/TriggerSystem.Signal.cs +++ b/Content.Server/Explosion/EntitySystems/TriggerSystem.Signal.cs @@ -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(OnSignalReceived); SubscribeLocalEvent(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); } } } diff --git a/Content.Server/Light/EntitySystems/PoweredLightSystem.cs b/Content.Server/Light/EntitySystems/PoweredLightSystem.cs index 81e4998507..bac13274ea 100644 --- a/Content.Server/Light/EntitySystems/PoweredLightSystem.cs +++ b/Content.Server/Light/EntitySystems/PoweredLightSystem.cs @@ -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(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); diff --git a/Content.Server/MachineLinking/Components/SignalTransmitterComponent.cs b/Content.Server/MachineLinking/Components/SignalTransmitterComponent.cs index b87564b6c1..555fa76802 100644 --- a/Content.Server/MachineLinking/Components/SignalTransmitterComponent.cs +++ b/Content.Server/MachineLinking/Components/SignalTransmitterComponent.cs @@ -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) { diff --git a/Content.Server/MachineLinking/Events/SignalReceivedEvent.cs b/Content.Server/MachineLinking/Events/SignalReceivedEvent.cs index d3d453d9c0..e69de29bb2 100644 --- a/Content.Server/MachineLinking/Events/SignalReceivedEvent.cs +++ b/Content.Server/MachineLinking/Events/SignalReceivedEvent.cs @@ -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; - } - } -} diff --git a/Content.Server/MachineLinking/System/SignalLinkerSystem.cs b/Content.Server/MachineLinking/System/SignalLinkerSystem.cs index 6d08c5dc57..811a3f771a 100644 --- a/Content.Server/MachineLinking/System/SignalLinkerSystem.cs +++ b/Content.Server/MachineLinking/System/SignalLinkerSystem.cs @@ -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) diff --git a/Content.Server/Medical/MedicalScannerSystem.cs b/Content.Server/Medical/MedicalScannerSystem.cs index 73230c3a52..fff9b3a2cb 100644 --- a/Content.Server/Medical/MedicalScannerSystem.cs +++ b/Content.Server/Medical/MedicalScannerSystem.cs @@ -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(uid, $"scanner-bodyContainer"); - _signalSystem.EnsureReceiverPorts(uid, MedicalScannerComponent.ScannerPort); + _signalSystem.EnsureSinkPorts(uid, MedicalScannerComponent.ScannerPort); } private void OnRelayMovement(EntityUid uid, MedicalScannerComponent scannerComponent, ref ContainerRelayMovementEntityEvent args) diff --git a/Content.Server/Physics/Controllers/ConveyorController.cs b/Content.Server/Physics/Controllers/ConveyorController.cs index 31779ed1a7..e3777acbde 100644 --- a/Content.Server/Physics/Controllers/ConveyorController.cs +++ b/Content.Server/Physics/Controllers/ConveyorController.cs @@ -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(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); diff --git a/Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactAnalyzerSystem.cs b/Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactAnalyzerSystem.cs index 5bced250e2..363c3f1e03 100644 --- a/Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactAnalyzerSystem.cs +++ b/Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactAnalyzerSystem.cs @@ -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(args.Receiver, out var analyzer)) + if (!TryComp(args.Sink, out var analyzer)) return; - component.AnalyzerEntity = args.Receiver; + component.AnalyzerEntity = args.Sink; analyzer.Console = uid; UpdateUserInterface(uid, component); diff --git a/Content.Shared/Access/Systems/AccessReaderSystem.cs b/Content.Shared/Access/Systems/AccessReaderSystem.cs index 5f1dfe2ea8..501ef590ec 100644 --- a/Content.Shared/Access/Systems/AccessReaderSystem.cs +++ b/Content.Shared/Access/Systems/AccessReaderSystem.cs @@ -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 index 0000000000..3538b9474c --- /dev/null +++ b/Content.Shared/DeviceLinking/DeviceLinkSinkComponent.cs @@ -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 +{ + /// + /// The ports this sink has + /// + [DataField("ports", customTypeSerializer: typeof(PrototypeIdHashSetSerializer))] + public HashSet? Ports; + + /// + /// Used for removing a sink from all linked sources when it gets removed + /// + [DataField("links")] + public HashSet LinkedSources = new(); +} diff --git a/Content.Shared/DeviceLinking/DeviceLinkSourceComponent.cs b/Content.Shared/DeviceLinking/DeviceLinkSourceComponent.cs new file mode 100644 index 0000000000..5059ec8448 --- /dev/null +++ b/Content.Shared/DeviceLinking/DeviceLinkSourceComponent.cs @@ -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 +{ + /// + /// The ports the device link source sends signals from + /// + [DataField("ports", customTypeSerializer: typeof(PrototypeIdHashSetSerializer))] + public HashSet? Ports; + + /// + /// A list of sink uids that got linked for each port + /// + [DataField("registeredSinks")] + public Dictionary> Outputs = new(); + + /// + /// The list of source to sink ports for each linked sink entity for easier managing of links + /// + [DataField("linkedPorts")] + public Dictionary> LinkedPorts = new(); + + /// + /// Limits the range devices can be linked across. + /// + [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 index 0000000000..823f6f5748 --- /dev/null +++ b/Content.Shared/DeviceLinking/DevicePortPrototype.cs @@ -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; + +/// +/// A prototype for a device port, for use with device linking. +/// +[Serializable, NetSerializable] +public abstract class DevicePortPrototype +{ + [IdDataField] + public string ID { get; } = default!; + + /// + /// Localization string for the port name. Displayed in the linking UI. + /// + [DataField("name", required:true)] + public string Name = default!; + + /// + /// 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. + /// + [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 +{ + /// + /// This is a set of sink ports that this source port will attempt to link to when using the + /// default-link functionality. + /// + [DataField("defaultLinks", customTypeSerializer: typeof(PrototypeIdHashSetSerializer))] + public readonly HashSet? DefaultLinks; +} diff --git a/Content.Shared/DeviceLinking/Events/LinkAttemptEvent.cs b/Content.Shared/DeviceLinking/Events/LinkAttemptEvent.cs new file mode 100644 index 0000000000..22af0880e9 --- /dev/null +++ b/Content.Shared/DeviceLinking/Events/LinkAttemptEvent.cs @@ -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 index 0000000000..3bf8f7defe --- /dev/null +++ b/Content.Shared/DeviceLinking/Events/NewLinkEvent.cs @@ -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; + } +} diff --git a/Content.Server/MachineLinking/Events/PortDisconnectedEvent.cs b/Content.Shared/DeviceLinking/Events/PortDisconnectedEvent.cs similarity index 81% rename from Content.Server/MachineLinking/Events/PortDisconnectedEvent.cs rename to Content.Shared/DeviceLinking/Events/PortDisconnectedEvent.cs index de5676e990..680b0c8453 100644 --- a/Content.Server/MachineLinking/Events/PortDisconnectedEvent.cs +++ b/Content.Shared/DeviceLinking/Events/PortDisconnectedEvent.cs @@ -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 index 0000000000..b6b784b63c --- /dev/null +++ b/Content.Shared/DeviceLinking/SharedDeviceLinkSystem.cs @@ -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"; + + /// + public override void Initialize() + { + SubscribeLocalEvent(OnSourceStartup); + SubscribeLocalEvent(OnSinkStartup); + SubscribeLocalEvent(OnSourceRemoved); + SubscribeLocalEvent(OnSinkRemoved); + } + + #region Link Validation + /// + /// Removes invalid links where the saved sink doesn't exist/have a sink component for example + /// + private void OnSourceStartup(EntityUid sourceUid, DeviceLinkSourceComponent sourceComponent, ComponentStartup args) + { + List invalidSinks = new(); + foreach (var sinkUid in sourceComponent.LinkedPorts.Keys) + { + if (!TryComp(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); + } + } + + /// + /// Same with but also checks that the saved ports are present on the sink + /// + private void OnSinkStartup(EntityUid sinkUid, DeviceLinkSinkComponent sinkComponent, ComponentStartup args) + { + List invalidSources = new(); + foreach (var sourceUid in sinkComponent.LinkedSources) + { + if (!TryComp(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 + + /// + /// Ensures that its links get deleted when a source gets removed + /// + private void OnSourceRemoved(EntityUid uid, DeviceLinkSourceComponent component, ComponentRemove args) + { + foreach (var sinkUid in component.LinkedPorts.Keys) + { + RemoveSinkFromSource(uid, sinkUid, component); + } + } + + /// + /// Ensures that its links get deleted when a sink gets removed + /// + private void OnSinkRemoved(EntityUid sinkUid, DeviceLinkSinkComponent sinkComponent, ComponentRemove args) + { + foreach (var linkedSource in sinkComponent.LinkedSources) + { + RemoveSinkFromSource(linkedSource, sinkUid, null, sinkComponent); + } + } + + #region Ports + /// + /// Convenience function to add several ports to an entity + /// + public void EnsureSourcePorts(EntityUid uid, params string[] ports) + { + var comp = EnsureComp(uid); + comp.Ports ??= new HashSet(); + + foreach (var port in ports) + { + comp.Ports?.Add(port); + } + } + + /// + /// Convenience function to add several ports to an entity. + /// + public void EnsureSinkPorts(EntityUid uid, params string[] ports) + { + var comp = EnsureComp(uid); + comp.Ports ??= new HashSet(); + + foreach (var port in ports) + { + comp.Ports?.Add(port); + } + } + + /// + /// Retrieves the available ports from a source + /// + /// A list of source port prototypes + public List GetSourcePorts(EntityUid sourceUid, DeviceLinkSourceComponent? sourceComponent = null) + { + if (!Resolve(sourceUid, ref sourceComponent) || sourceComponent.Ports == null) + return new List(); + + var sourcePorts = new List(); + foreach (var port in sourceComponent.Ports) + { + sourcePorts.Add(_prototypeManager.Index(port)); + } + + return sourcePorts; + } + + /// + /// Retrieves the available ports from a sink + /// + /// A list of sink port prototypes + public List GetSinkPorts(EntityUid sinkUid, DeviceLinkSinkComponent? sinkComponent = null) + { + if (!Resolve(sinkUid, ref sinkComponent) || sinkComponent.Ports == null) + return new List(); + + var sinkPorts = new List(); + foreach (var port in sinkComponent.Ports) + { + sinkPorts.Add(_prototypeManager.Index(port)); + } + + return sinkPorts; + } + + /// + /// Convenience function to retrieve the name of a port prototype + /// + public string PortName(string port) where TPort : DevicePortPrototype, IPrototype + { + if (!_prototypeManager.TryIndex(port, out var proto)) + return port; + + return Loc.GetString(proto.Name); + } + #endregion + + #region Links + /// + /// Returns the links of a source + /// + /// A list of sink and source port ids that are linked together + 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; + } + + /// + /// Returns the default links for the given list of source port prototypes + /// + /// The list of source port prototypes to get the default links for + /// A list of sink and source port ids + public List<(string source, string sink)> GetDefaults(List 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; + } + + /// + /// Links the given source and sink by their default links + /// + /// Optinal user uid for displaying popups + /// The source uid + /// The sink uid + /// + /// + 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); + } + + + /// + /// Saves multiple links between a source and a sink device. + /// Ignores links where either the source or sink port aren't present + /// + /// Optinal user uid for displaying popups + /// The source uid + /// The sink uid + /// List of source and sink ids to link + /// + /// + 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); + } + + /// + /// Removes all links between a source and a sink + /// + 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); + } + + } + + /// + /// Adds or removes a link depending on if it's already present + /// + /// True if the link was successfully added or removed + 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; + } + + /// + /// 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 + /// + /// + 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(source)), + ("machine2", sinkUid), ("port2", PortName(sink))), userId.Value, PopupType.Medium); + } + #endregion +} diff --git a/Content.Shared/MachineLinking/TwoWayLeverSignal.cs b/Content.Shared/DeviceLinking/TwoWayLeverSignal.cs similarity index 87% rename from Content.Shared/MachineLinking/TwoWayLeverSignal.cs rename to Content.Shared/DeviceLinking/TwoWayLeverSignal.cs index 02c88d4419..a41e48556b 100644 --- a/Content.Shared/MachineLinking/TwoWayLeverSignal.cs +++ b/Content.Shared/DeviceLinking/TwoWayLeverSignal.cs @@ -1,6 +1,6 @@ using Robust.Shared.Serialization; -namespace Content.Shared.MachineLinking +namespace Content.Shared.DeviceLinking { [Serializable, NetSerializable] public enum TwoWayLeverVisuals : byte diff --git a/Content.Shared/DeviceNetwork/Components/DeviceListComponent.cs b/Content.Shared/DeviceNetwork/Components/DeviceListComponent.cs index f5c9160a0a..a4b7ea9250 100644 --- a/Content.Shared/DeviceNetwork/Components/DeviceListComponent.cs +++ b/Content.Shared/DeviceNetwork/Components/DeviceListComponent.cs @@ -1,7 +1,7 @@ using Robust.Shared.GameStates; using Robust.Shared.Serialization; -namespace Content.Shared.DeviceNetwork; +namespace Content.Shared.DeviceNetwork.Components; [RegisterComponent] [NetworkedComponent] diff --git a/Content.Shared/DeviceNetwork/Components/NetworkConfiguratorComponent.cs b/Content.Shared/DeviceNetwork/Components/NetworkConfiguratorComponent.cs index f443c50fde..761cde436c 100644 --- a/Content.Shared/DeviceNetwork/Components/NetworkConfiguratorComponent.cs +++ b/Content.Shared/DeviceNetwork/Components/NetworkConfiguratorComponent.cs @@ -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 { + /// + /// Determines whether the configurator is in linking mode or list mode + /// + [DataField("linkModeActive")] + [ViewVariables(VVAccess.ReadWrite)] + public bool LinkModeActive = false; + /// /// The entity containing a this configurator is currently interacting with /// @@ -16,22 +26,48 @@ public sealed class NetworkConfiguratorComponent : Component public EntityUid? ActiveDeviceList = null; /// - /// The list of devices stored in the configurator- + /// The entity containing a or this configurator is currently interacting with.
+ /// If this is set the configurator is in linking mode. + ///
+ [DataField("activeDeviceLink")] + public EntityUid? ActiveDeviceLink = null; + + /// + /// The target device this configurator is currently linking with the + /// + [DataField("deviceLinkTarget")] + public EntityUid? DeviceLinkTarget = null; + + /// + /// The list of devices stored in the configurator /// [DataField("devices")] public Dictionary 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; } } diff --git a/Content.Shared/DeviceNetwork/NetworkConfiguratorUIMessages.cs b/Content.Shared/DeviceNetwork/NetworkConfiguratorUIMessages.cs index cb8c8c8e6e..e6e273b1fa 100644 --- a/Content.Shared/DeviceNetwork/NetworkConfiguratorUIMessages.cs +++ b/Content.Shared/DeviceNetwork/NetworkConfiguratorUIMessages.cs @@ -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; + } +} diff --git a/Content.Shared/DeviceNetwork/NetworkConfiguratorUserInterfaceState.cs b/Content.Shared/DeviceNetwork/NetworkConfiguratorUserInterfaceState.cs index 483b654036..d12456dc52 100644 --- a/Content.Shared/DeviceNetwork/NetworkConfiguratorUserInterfaceState.cs +++ b/Content.Shared/DeviceNetwork/NetworkConfiguratorUserInterfaceState.cs @@ -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 Sources; + public readonly List 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 sources, List 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; + } +} diff --git a/Content.Shared/DeviceNetwork/Systems/SharedDeviceListSystem.cs b/Content.Shared/DeviceNetwork/Systems/SharedDeviceListSystem.cs index 56cc58307c..88de21b493 100644 --- a/Content.Shared/DeviceNetwork/Systems/SharedDeviceListSystem.cs +++ b/Content.Shared/DeviceNetwork/Systems/SharedDeviceListSystem.cs @@ -1,4 +1,5 @@ using System.Linq; +using Content.Shared.DeviceNetwork.Components; using Robust.Shared.GameStates; namespace Content.Shared.DeviceNetwork; diff --git a/Content.Shared/DeviceNetwork/Systems/SharedNetworkConfiguratorSystem.cs b/Content.Shared/DeviceNetwork/Systems/SharedNetworkConfiguratorSystem.cs index 29400e110c..9a9d7e1885 100644 --- a/Content.Shared/DeviceNetwork/Systems/SharedNetworkConfiguratorSystem.cs +++ b/Content.Shared/DeviceNetwork/Systems/SharedNetworkConfiguratorSystem.cs @@ -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 index 4eb75bdc7c..0000000000 --- a/Content.Shared/MachineLinking/Events/LinkAttemptEvent.cs +++ /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 index 22dfce7b8f..0000000000 --- a/Content.Shared/MachineLinking/Events/NewLinkEvent.cs +++ /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; - } - } -} diff --git a/Resources/Audio/Machines/attributions.yml b/Resources/Audio/Machines/attributions.yml index 4f47ff53be..0550a64f7c 100644 --- a/Resources/Audio/Machines/attributions.yml +++ b/Resources/Audio/Machines/attributions.yml @@ -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/" diff --git a/Resources/Locale/en-US/devices/device-network.ftl b/Resources/Locale/en-US/devices/device-network.ftl index fee17d9c87..a4133bc984 100644 --- a/Resources/Locale/en-US/devices/device-network.ftl +++ b/Resources/Locale/en-US/devices/device-network.ftl @@ -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 diff --git a/Resources/Locale/en-US/devices/network-configurator.ftl b/Resources/Locale/en-US/devices/network-configurator.ftl index f7d663a6d6..38fad1ba72 100644 --- a/Resources/Locale/en-US/devices/network-configurator.ftl +++ b/Resources/Locale/en-US/devices/network-configurator.ftl @@ -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 diff --git a/Resources/Locale/en-US/guidebook/guides.ftl b/Resources/Locale/en-US/guidebook/guides.ftl index 287e47f291..de0f6b4a4c 100644 --- a/Resources/Locale/en-US/guidebook/guides.ftl +++ b/Resources/Locale/en-US/guidebook/guides.ftl @@ -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 diff --git a/Resources/Locale/en-US/machine-linking/port-selector.ftl b/Resources/Locale/en-US/machine-linking/port-selector.ftl index 606ba33220..4e9d2620ae 100644 --- a/Resources/Locale/en-US/machine-linking/port-selector.ftl +++ b/Resources/Locale/en-US/machine-linking/port-selector.ftl @@ -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 diff --git a/Resources/Locale/en-US/machine-linking/receiver_ports.ftl b/Resources/Locale/en-US/machine-linking/receiver_ports.ftl index b86e711b17..cbf2f537f9 100644 --- a/Resources/Locale/en-US/machine-linking/receiver_ports.ftl +++ b/Resources/Locale/en-US/machine-linking/receiver_ports.ftl @@ -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. diff --git a/Resources/Prototypes/Catalog/Fills/Items/belt.yml b/Resources/Prototypes/Catalog/Fills/Items/belt.yml index c68f9fe8ab..62a0c71e42 100644 --- a/Resources/Prototypes/Catalog/Fills/Items/belt.yml +++ b/Resources/Prototypes/Catalog/Fills/Items/belt.yml @@ -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 diff --git a/Resources/Prototypes/Device/devicenet_frequencies.yml b/Resources/Prototypes/Device/devicenet_frequencies.yml index 3762129fdb..0667cd0170 100644 --- a/Resources/Prototypes/Device/devicenet_frequencies.yml +++ b/Resources/Prototypes/Device/devicenet_frequencies.yml @@ -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 index 0000000000..19f3b4e1c7 --- /dev/null +++ b/Resources/Prototypes/DeviceLinking/sink_ports.yml @@ -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 index 0000000000..d01089205c --- /dev/null +++ b/Resources/Prototypes/DeviceLinking/source_ports.yml @@ -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 ] diff --git a/Resources/Prototypes/Entities/Clothing/Belt/belts.yml b/Resources/Prototypes/Entities/Clothing/Belt/belts.yml index d4f9850f41..adc040e656 100644 --- a/Resources/Prototypes/Entities/Clothing/Belt/belts.yml +++ b/Resources/Prototypes/Entities/Clothing/Belt/belts.yml @@ -11,6 +11,7 @@ - type: Clothing sprite: Clothing/Belt/utility.rsi - type: Storage + capacity: 45 # TODO: Fill this out more. whitelist: tags: @@ -26,10 +27,11 @@ - 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: @@ -96,10 +98,11 @@ - CigPack - Radio - HolofanProjector + - Multitool - AppraisalTool components: - AirlockPainter - - SignalLinker + - NetworkConfigurator - RCD - RCDAmmo - Welder @@ -135,8 +138,8 @@ - Screwdriver multitool: whitelist: - components: - - SignalLinker + tags: + - Multitool wrench: whitelist: tags: diff --git a/Resources/Prototypes/Entities/Objects/Devices/Electronics/signaller.yml b/Resources/Prototypes/Entities/Objects/Devices/Electronics/signaller.yml index 153e24818e..2a0b5ba118 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Electronics/signaller.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Electronics/signaller.yml @@ -13,9 +13,9 @@ - type: UseDelay - type: StaticPrice price: 40 - - type: SignalTransmitter - outputs: - Pressed: [] + - type: DeviceLinkSource + ports: + - Pressed - type: Tag tags: - Payload diff --git a/Resources/Prototypes/Entities/Objects/Devices/Electronics/triggers.yml b/Resources/Prototypes/Entities/Objects/Devices/Electronics/triggers.yml index a2738e8be5..fe6c073e5d 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Electronics/triggers.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Electronics/triggers.yml @@ -42,7 +42,12 @@ - type: PayloadTrigger components: - type: TriggerOnSignal - - type: SignalReceiver + - type: DeviceNetwork + deviceNetId: Wireless + receiveFrequencyId: BasicDevice + - type: WirelessNetworkConnection + range: 200 + - type: DeviceLinkSink - type: StaticPrice price: 40 diff --git a/Resources/Prototypes/Entities/Objects/Tools/cowtools.yml b/Resources/Prototypes/Entities/Objects/Tools/cowtools.yml index 4fa01ea6d3..211efabcad 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/cowtools.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/cowtools.yml @@ -108,7 +108,9 @@ - type: Tool qualities: - Pulsing - - type: SignalLinker + - type: Tag + tags: + - Multitool - type: entity name: cowelding tool diff --git a/Resources/Prototypes/Entities/Objects/Tools/tools.yml b/Resources/Prototypes/Entities/Objects/Tools/tools.yml index 34cb1cc368..7c0e8ed10e 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/tools.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/tools.yml @@ -196,21 +196,9 @@ - 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: @@ -219,6 +207,54 @@ - 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 @@ -371,13 +407,10 @@ - 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 diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Bombs/plastic.yml b/Resources/Prototypes/Entities/Objects/Weapons/Bombs/plastic.yml index e8cbe1c08e..13ab7309d5 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Bombs/plastic.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Bombs/plastic.yml @@ -18,9 +18,9 @@ startOnStick: true canToggleStartOnStick: true - type: TriggerOnSignal - - type: SignalReceiver - inputs: - Trigger: [] + - type: DeviceLinkSink + ports: + - Trigger - type: Sticky stickDelay: 5 unstickDelay: 5 diff --git a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml index 833a5fa6da..7e8100d10a 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml @@ -78,12 +78,17 @@ 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 diff --git a/Resources/Prototypes/Entities/Structures/Doors/Shutter/shutters.yml b/Resources/Prototypes/Entities/Structures/Doors/Shutter/shutters.yml index dabe75a2eb..5cdbfe848a 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Shutter/shutters.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Shutter/shutters.yml @@ -76,11 +76,16 @@ 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 diff --git a/Resources/Prototypes/Entities/Structures/Doors/Windoors/base_structurewindoors.yml b/Resources/Prototypes/Entities/Structures/Doors/Windoors/base_structurewindoors.yml index b6fb8f1295..192f6202a1 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Windoors/base_structurewindoors.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Windoors/base_structurewindoors.yml @@ -46,12 +46,17 @@ - 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 diff --git a/Resources/Prototypes/Entities/Structures/Lighting/base_lighting.yml b/Resources/Prototypes/Entities/Structures/Lighting/base_lighting.yml index 9abf8eef60..4f72c68b6a 100644 --- a/Resources/Prototypes/Entities/Structures/Lighting/base_lighting.yml +++ b/Resources/Prototypes/Entities/Structures/Lighting/base_lighting.yml @@ -86,18 +86,19 @@ - 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 @@ -266,16 +267,17 @@ - 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 diff --git a/Resources/Prototypes/Entities/Structures/Lighting/ground_lighting.yml b/Resources/Prototypes/Entities/Structures/Lighting/ground_lighting.yml index eed05b2cc4..3fd833ff5f 100644 --- a/Resources/Prototypes/Entities/Structures/Lighting/ground_lighting.yml +++ b/Resources/Prototypes/Entities/Structures/Lighting/ground_lighting.yml @@ -91,9 +91,15 @@ - 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 @@ -101,11 +107,6 @@ - type: PoweredLightVisuals blinkingSound: path: "/Audio/Machines/light_tube_on.ogg" - - type: SignalReceiver - inputs: - On: [] - Off: [] - Toggle: [] - type: entity id: PoweredLightPostSmall diff --git a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml index 33eced8fca..6d80d44472 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml @@ -378,10 +378,10 @@ - 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 @@ -698,11 +698,11 @@ 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 diff --git a/Resources/Prototypes/Entities/Structures/Machines/artifact_analyzer.yml b/Resources/Prototypes/Entities/Structures/Machines/artifact_analyzer.yml index a51f7a3f49..ab05abda77 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/artifact_analyzer.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/artifact_analyzer.yml @@ -49,10 +49,11 @@ - type: ArtifactAnalyzer - type: DeviceNetwork deviceNetId: Wired + receiveFrequencyId: BasicDevice - type: DeviceList - - type: SignalReceiver - inputs: - ArtifactAnalyzerReceiver: [] + - type: DeviceLinkSink + ports: + - ArtifactAnalyzerReceiver - type: Machine board: ArtifactAnalyzerMachineCircuitboard - type: PointLight diff --git a/Resources/Prototypes/Entities/Structures/Machines/cloning_machine.yml b/Resources/Prototypes/Entities/Structures/Machines/cloning_machine.yml index 0cdd0f477c..b58b49fb98 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/cloning_machine.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/cloning_machine.yml @@ -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 diff --git a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml index 3fecef3304..cd09dfc098 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml @@ -74,6 +74,7 @@ - Wrench - Crowbar - Multitool + - NetworkConfigurator - AirlockPainter - CableStack - HandheldGPSBasic diff --git a/Resources/Prototypes/Entities/Structures/Machines/medical_scanner.yml b/Resources/Prototypes/Entities/Structures/Machines/medical_scanner.yml index 6edb724ca8..ec6f631188 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/medical_scanner.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/medical_scanner.yml @@ -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 diff --git a/Resources/Prototypes/Entities/Structures/Machines/recycler.yml b/Resources/Prototypes/Entities/Structures/Machines/recycler.yml index fc4374cb4e..1a61ee9f2d 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/recycler.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/recycler.yml @@ -42,11 +42,11 @@ - MidImpassable - LowImpassable hard: False - - type: SignalReceiver - inputs: - Reverse: [] - Forward: [] - Off: [] + - type: DeviceLinkSink + ports: + - Reverse + - Forward + - Off - type: Transform anchored: true noRot: false diff --git a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/binary.yml b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/binary.yml index 1ca4fce811..e1ef0251c5 100644 --- a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/binary.yml +++ b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/binary.yml @@ -208,11 +208,16 @@ - 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: @@ -296,10 +301,15 @@ 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 diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/switch.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/switch.yml index db25d97a8c..44c1f0e538 100644 --- a/Resources/Prototypes/Entities/Structures/Wallmounts/switch.yml +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/switch.yml @@ -24,10 +24,14 @@ 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 @@ -57,9 +61,13 @@ 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 @@ -153,8 +161,12 @@ - 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 diff --git a/Resources/Prototypes/Entities/Structures/cargo_console.yml b/Resources/Prototypes/Entities/Structures/cargo_console.yml index f02bd60a1e..a920b45052 100644 --- a/Resources/Prototypes/Entities/Structures/cargo_console.yml +++ b/Resources/Prototypes/Entities/Structures/cargo_console.yml @@ -32,9 +32,14 @@ - 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: diff --git a/Resources/Prototypes/Entities/Structures/conveyor.yml b/Resources/Prototypes/Entities/Structures/conveyor.yml index 42eb177d61..608a3ca29a 100644 --- a/Resources/Prototypes/Entities/Structures/conveyor.yml +++ b/Resources/Prototypes/Entities/Structures/conveyor.yml @@ -35,11 +35,11 @@ - LowImpassable hard: False - type: Conveyor - - type: SignalReceiver - inputs: - Reverse: [] - Forward: [] - Off: [] + - type: DeviceLinkSink + ports: + - Reverse + - Forward + - Off - type: Appearance - type: GenericVisualizer visuals: diff --git a/Resources/Prototypes/Guidebook/engineering.yml b/Resources/Prototypes/Guidebook/engineering.yml index 964dfa6809..69a9c60e2e 100644 --- a/Resources/Prototypes/Guidebook/engineering.yml +++ b/Resources/Prototypes/Guidebook/engineering.yml @@ -7,6 +7,7 @@ - Construction - Power - ShuttleCraft + - Networking - type: guideEntry id: Construction @@ -30,6 +31,19 @@ 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 diff --git a/Resources/Prototypes/Recipes/Lathes/tools.yml b/Resources/Prototypes/Recipes/Lathes/tools.yml index 47d44778ff..ebd2ae8383 100644 --- a/Resources/Prototypes/Recipes/Lathes/tools.yml +++ b/Resources/Prototypes/Recipes/Lathes/tools.yml @@ -86,6 +86,14 @@ Steel: 200 Plastic: 200 +- type: latheRecipe + id: NetworkConfigurator + result: NetworkConfigurator + completetime: 2 + materials: + Steel: 200 + Plastic: 200 + - type: latheRecipe id: PowerDrill result: PowerDrill diff --git a/Resources/Prototypes/XenoArch/Effects/utility_effects.yml b/Resources/Prototypes/XenoArch/Effects/utility_effects.yml index 035c4334a5..0f3a6f4e5a 100644 --- a/Resources/Prototypes/XenoArch/Effects/utility_effects.yml +++ b/Resources/Prototypes/XenoArch/Effects/utility_effects.yml @@ -194,8 +194,6 @@ - Item permanentComponents: - type: TilePrying - - type: SignalLinker - requiredQuality: Pulsing - type: UserInterface interfaces: - key: enum.SignalLinkerUiKey.Key @@ -206,6 +204,9 @@ - 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: diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml index 9f989f04b4..3796775d66 100644 --- a/Resources/Prototypes/tags.yml +++ b/Resources/Prototypes/tags.yml @@ -498,6 +498,9 @@ - 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 index 0000000000..445d182ab8 --- /dev/null +++ b/Resources/ServerInfo/Guidebook/Network_Configurator.xml @@ -0,0 +1,39 @@ + +# Network Configurator +The network configurator allows you to manipulate device lists and link devices together. + + + +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: + + [color=cyan]Pressed 🠒 Toggle[/color] + +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. + diff --git a/Resources/ServerInfo/Guidebook/Networking.xml b/Resources/ServerInfo/Guidebook/Networking.xml new file mode 100644 index 0000000000..03576c789a --- /dev/null +++ b/Resources/ServerInfo/Guidebook/Networking.xml @@ -0,0 +1,25 @@ + +# 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. + + + +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. + + + + +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. + 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 index 0000000000000000000000000000000000000000..503844c59af66ff6fee34bce71135e8548a5f299 GIT binary patch literal 145 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=jKx9jP7LeL$-D$|yggkULn`LH zz2L}sz(By|qUx$Foxlsu?+qozJKGK&+V){y!+8TiphAWZxBi}q-McWX|GwM>BcPx$21!IgR9J;$U>F6XU=)mWz(@^4S~@2GzjEO!gN}hVLrq04NoJDe029~H|M=w3 z>|RZ@xgkOy6dAs8@M2d>bqACYRze7nl?Y+! z09zU$CH@&0C`bdymcjt3mNPI=BMndp2HXKuF9%4C2C_ro-}lc9T{+s-xA#ciwr0000#Z3RZ>qqqj*H?$Xzl{M~2qGRFfBK4V%5OP+jr8&d`s@l%U-MmgDR{s9 zMs8Z~t`$>5X1WGl%NOU1`>=zrK5&6X>pPI9d*$psO&*%B%uY*!xSpIfEI5cs&&? zH!S_Xeyg&r{mHz29zcVDApF!SzH2`}yprkPaISsRD)!Ck+r@xFm5EnohOL~MFqeOB z=2USvXVp784wLqExn(`${cdvB@_&Qt{YOjp?lG`>Q~t{C*1f-mK;1bzFWrCNuk!Nu z=h}H^UY|Al_5FaA{I%I{|EI`&UT{QV>KurTK+@yf4a=G}RmUGYzngjZcvcYOB)9Vq zlc)Osw1{38vhV7ov^j_EZ#uKa)HzwaDL*M!vnk}g4NzM_5zBQacjps_Z@mT?<>~6@ Jvd$@?2>=y 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 index 0000000000000000000000000000000000000000..6eaa64652ec9c2123854412623b65f412e99cdd2 GIT binary patch literal 109 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}>Ygr+ArY-_ z&l&PE7;rEcY)N4IfBgxMB+DhO3>F6Qy{GJJ85k1!*@QPTIn-R!18HIKboFyt=akR{ E09a2P^8f$< literal 0 HcmV?d00001 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 index 0000000000000000000000000000000000000000..c817cb0e850a172404555bdf9fb7f284a44b9f9f GIT binary patch literal 104 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}N}eu`ArY-_ z&oS~cFmNy{6i5F%R$%4fAj+V>baN~N!-GD?sO3zRR)1D70aY`2y85}Sb4q9e0JQrW A<^TWy literal 0 HcmV?d00001 -- 2.51.2