-ο»Ώ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;
public sealed class NetworkConfiguratorBoundUserInterface : BoundUserInterface
{
[Dependency] private readonly IEntityManager _entityManager = default!;
+
private NetworkConfiguratorListMenu? _listMenu;
private NetworkConfiguratorConfigurationMenu? _configurationMenu;
+ private NetworkConfiguratorLinkMenu? _linkMenu;
private NetworkConfiguratorSystem _netConfig;
- private DeviceListSystem _deviceList;
public NetworkConfiguratorBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
{
IoCManager.InjectDependencies(this);
+
_netConfig = _entityManager.System<NetworkConfiguratorSystem>();
- _deviceList = _entityManager.System<DeviceListSystem>();
}
public void OnRemoveButtonPressed(string address)
_listMenu = new NetworkConfiguratorListMenu(this);
_listMenu.OnClose += Close;
_listMenu.ClearButton.OnPressed += _ => OnClearButtonPressed();
- _listMenu.OpenCentered();
+ _listMenu.OpenCenteredRight();
break;
case NetworkConfiguratorUiKey.Configure:
_configurationMenu = new NetworkConfiguratorConfigurationMenu();
_configurationMenu.Show.Pressed = _netConfig.ConfiguredListIsTracked(Owner.Owner);
_configurationMenu.OpenCentered();
break;
+ case NetworkConfiguratorUiKey.Link:
+ _linkMenu = new NetworkConfiguratorLinkMenu(this);
+ _linkMenu.OnClose += Close;
+ _linkMenu.OpenCentered();
+ break;
}
}
case DeviceListUserInterfaceState listState:
_configurationMenu?.UpdateState(listState);
break;
+ case DeviceLinkUserInterfaceState linkState:
+ _linkMenu?.UpdateState(linkState);
+ break;
}
}
<controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:networkConfigurator="clr-namespace:Content.Client.NetworkConfigurator"
- Title="Network Configurator" MinSize="350 100">
+ Title="{Loc 'network-configurator-title-device-configuration'}" MinSize="350 100">
<BoxContainer Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True">
<networkConfigurator:NetworkConfiguratorDeviceList Name="DeviceList" MinHeight="500" />
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="8 8 8 1">
<Button Name="Copy" Text="Copy" Access="Public" ToolTip="{Loc 'network-configurator-tooltip-copy'}" HorizontalExpand="True" StyleClasses="OpenRight"/>
<Button Name="Show" Text="Show" Access="Public" ToggleMode="True" ToolTip="{Loc 'network-configurator-tooltip-show'}" HorizontalExpand="True" StyleClasses="ButtonSquare"/>
</BoxContainer>
- <Label Name="Count" HorizontalAlignment="Right" />
+ <Label Name="Count" StyleClasses="LabelSubText" HorizontalAlignment="Right" Margin="0 0 12 8"/>
</BoxContainer>
</controls:FancyWindow>
--- /dev/null
+<controls:FancyWindow xmlns="https://spacestation14.io"
+ xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
+ xmlns:networkConfigurator="clr-namespace:Content.Client.NetworkConfigurator"
+ Title="Network Configurator" MinSize="620 400" RectClipContent="True">
+ <BoxContainer Orientation="Vertical" VerticalExpand="True">
+ <BoxContainer Orientation="Horizontal" HorizontalExpand="True">
+ <Label Margin="12 0" Text="{Loc signal-port-selector-help}"/>
+ </BoxContainer>
+ <Control VerticalExpand="True" HorizontalExpand="True" Margin="12 6 12 0">
+ <PanelContainer Name="MainPanel" HorizontalExpand="False" VerticalExpand="True" />
+ <ScrollContainer HorizontalExpand="True" VerticalExpand="True" HScrollEnabled="True">
+ <BoxContainer Orientation="Horizontal" HorizontalExpand="True" VerticalExpand="True" Margin="6">
+ <BoxContainer Orientation="Vertical" HorizontalExpand="True" SizeFlagsStretchRatio="0.3">
+ <RichTextLabel Name="HeaderLeft"/>
+ <BoxContainer Name="ButtonContainerLeft" Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True"/>
+ </BoxContainer>
+ <BoxContainer Name="MiddleContainer" Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True" SizeFlagsStretchRatio="0.2"/>
+ <BoxContainer Orientation="Vertical" HorizontalExpand="True" SizeFlagsStretchRatio="0.3">
+ <RichTextLabel Name="HeaderRight"/>
+ <BoxContainer Name="ButtonContainerRight" Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True"/>
+ </BoxContainer>
+ </BoxContainer>
+ </ScrollContainer>
+ </Control>
+ <BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="12 4">
+ <BoxContainer Orientation="Horizontal">
+ <Button Name="ButtonClear" SetHeight="32" StyleClasses="OpenRight" Text="{Loc signal-port-selector-menu-clear}"/>
+ <Button Name="ButtonLinkDefault" SetHeight="32" StyleClasses="OpenLeft" Text="{Loc signal-port-selector-menu-link-defaults}"/>
+ </BoxContainer>
+ <Control HorizontalExpand="True"/>
+ <Control>
+ <Button Name="ButtonOk" MinHeight="26" StyleClasses="ButtonColorGreen" Text="{Loc signal-port-selector-menu-done}"></Button>
+ </Control>
+ </BoxContainer>
+ <Control SetHeight="28" Margin="1 0 2 1">
+ <PanelContainer Name="FooterPanel"></PanelContainer>
+ <BoxContainer Name="ContentFooter" HorizontalExpand="True" SetHeight="28">
+ <Label Text="Net#Link β’" VerticalAlignment="Center" Margin="6 0" StyleClasses="PDAContentFooterText"/>
+ <Label Name="AddressLabel" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="6 0" StyleClasses="PDAContentFooterText"/>
+ <Control HorizontalExpand="True"/>
+ <Label Name="FromAddressLabel" Margin="6 0" StyleClasses="PDAContentFooterText"/>
+ <Label SetWidth="25" StyleClasses="PDAContentFooterText" Align="Center" Text="β"/><!--Turn this into an arrow texture-->
+ <Label Name="ToAddressLabel" Margin="6 0" StyleClasses="PDAContentFooterText"/>
+ </BoxContainer>
+ </Control>
+ </BoxContainer>
+</controls:FancyWindow>
--- /dev/null
+ο»Ώusing Content.Client.UserInterface.Controls;
+using Content.Shared.DeviceLinking;
+using Content.Shared.DeviceNetwork;
+using Robust.Client.AutoGenerated;
+using Robust.Client.Graphics;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Vector2 = Robust.Shared.Maths.Vector2;
+
+namespace Content.Client.NetworkConfigurator;
+
+[GenerateTypedNameReferences]
+public sealed partial class NetworkConfiguratorLinkMenu : FancyWindow
+{
+ private const string PanelBgColor = "#202023";
+
+ private readonly LinksRender _links;
+
+
+ private readonly List<SourcePortPrototype> _sources = new();
+
+ private readonly List<SinkPortPrototype> _sinks = new();
+
+ private readonly NetworkConfiguratorBoundUserInterface _userInterface;
+
+ private (ButtonPosition position, string id, int index)? _selectedButton;
+
+ private List<(string left, string right)>? _defaults;
+
+ public NetworkConfiguratorLinkMenu(NetworkConfiguratorBoundUserInterface userInterface)
+ {
+ _userInterface = userInterface;
+ RobustXamlLoader.Load(this);
+
+ var footerStyleBox = new StyleBoxFlat()
+ {
+ BorderThickness = new Thickness(0, 2, 0, 0),
+ BorderColor = Color.FromHex("#5A5A5A")
+ };
+
+ FooterPanel.PanelOverride = footerStyleBox;
+ MainPanel.PanelOverride = new StyleBoxFlat(Color.FromHex(PanelBgColor));
+
+ ButtonClear.AddStyleClass("ButtonColorRed");
+ ButtonLinkDefault.Disabled = true;
+
+ _links = new LinksRender(ButtonContainerLeft, ButtonContainerRight);
+ _links.VerticalExpand = true;
+ MiddleContainer.AddChild(_links);
+
+ ButtonOk.OnPressed += _ => Close();
+ ButtonLinkDefault.OnPressed += _ => LinkDefaults();
+ ButtonClear.OnPressed += _ => _userInterface.SendMessage(new NetworkConfiguratorClearLinksMessage());
+ }
+
+ public void UpdateState(DeviceLinkUserInterfaceState linkState)
+ {
+ ButtonContainerLeft.RemoveAllChildren();
+ ButtonContainerRight.RemoveAllChildren();
+
+ _sources.Clear();
+ _sources.AddRange(linkState.Sources);
+ _links.SourceButtons.Clear();
+ var i = 0;
+ foreach (var source in _sources)
+ {
+ var button = CreateButton(ButtonPosition.Left, source.Name, source.Description, source.ID, i);
+ ButtonContainerLeft.AddChild(button);
+ _links.SourceButtons.Add(source.ID, button);
+ i++;
+ }
+
+ _sinks.Clear();
+ _sinks.AddRange(linkState.Sinks);
+ _links.SinkButtons.Clear();
+ i = 0;
+ foreach (var sink in _sinks)
+ {
+ var button = CreateButton(ButtonPosition.Right, sink.Name, sink.Description, sink.ID, i);
+ ButtonContainerRight.AddChild(button);
+ _links.SinkButtons.Add(sink.ID, button);
+ i++;
+ }
+
+ _links.Links.Clear();
+ _links.Links.AddRange(linkState.Links);
+ _defaults = linkState.Defaults;
+
+ ButtonLinkDefault.Disabled = _defaults == default;
+ FromAddressLabel.Text = linkState.SourceAddress;
+ ToAddressLabel.Text = linkState.SinkAddress;
+ }
+
+ private void LinkDefaults()
+ {
+ if (_defaults == default)
+ return;
+
+ _userInterface.SendMessage(new NetworkConfiguratorLinksSaveMessage(_defaults));
+ }
+
+ private Button CreateButton(ButtonPosition position, string name, string description, string id, int index)
+ {
+ var button = new Button();
+ button.AddStyleClass("OpenBoth");
+ button.Text = Loc.GetString(name);
+ button.ToolTip = Loc.GetString(description);
+ button.ToggleMode = true;
+ button.OnPressed += args => OnButtonPressed(args, position, id, index);
+ return button;
+ }
+
+ private void OnButtonPressed(BaseButton.ButtonEventArgs args, ButtonPosition position, string id, int index)
+ {
+ var key = (position, id, index);
+ if (_selectedButton == key)
+ {
+ args.Button.Pressed = false;
+ _selectedButton = null;
+ return;
+ }
+
+ if (!_selectedButton.HasValue)
+ {
+ args.Button.Pressed = true;
+ _selectedButton = key;
+ return;
+ }
+
+ if (_selectedButton.Value.position == position)
+ {
+ args.Button.Pressed = false;
+ return;
+ }
+
+ var left = _selectedButton.Value.position == ButtonPosition.Left ? _selectedButton.Value.id : id;
+ var right = _selectedButton.Value.position == ButtonPosition.Left ? id : _selectedButton.Value.id;
+
+ _userInterface.SendMessage(new NetworkConfiguratorToggleLinkMessage(left, right));
+
+ args.Button.Pressed = false;
+
+ var container = _selectedButton.Value.position == ButtonPosition.Left ? ButtonContainerLeft : ButtonContainerRight;
+ if (container.GetChild(_selectedButton.Value.index) is Button button)
+ button.Pressed = false;
+
+ _selectedButton = null;
+ }
+
+ private enum ButtonPosition
+ {
+ Left,
+ Right
+ }
+
+ /// <summary>
+ /// Draws lines between linked ports using bezier curve calculated with polynomial coefficients
+ /// See: https://youtu.be/jvPPXbo87ds?t=351
+ /// </summary>
+ private sealed class LinksRender : Control
+ {
+ public readonly List<(string, string)> Links = new();
+ public readonly Dictionary<string, Button> SourceButtons = new();
+ public readonly Dictionary<string, Button> SinkButtons = new();
+ private readonly BoxContainer _leftButtonContainer;
+ private readonly BoxContainer _rightButtonContainer;
+
+ public LinksRender(BoxContainer leftButtonContainer, BoxContainer rightButtonContainer)
+ {
+ _leftButtonContainer = leftButtonContainer;
+ _rightButtonContainer = rightButtonContainer;
+ }
+
+ protected override void Draw(DrawingHandleScreen handle)
+ {
+ foreach (var (left, right) in Links)
+ {
+ if (!SourceButtons.TryGetValue(left, out var leftChild) || !SinkButtons.TryGetValue(right, out var rightChild))
+ continue;
+
+ var leftOffset = _leftButtonContainer.PixelPosition.Y;
+ var rightOffset = _rightButtonContainer.PixelPosition.Y;
+
+ var y1 = leftChild.PixelPosition.Y + leftChild.PixelHeight / 2 + leftOffset;
+ var y2 = rightChild.PixelPosition.Y + rightChild.PixelHeight / 2 + rightOffset;
+
+ if (left == right)
+ {
+ handle.DrawLine((0, y1), (PixelWidth, y2), Color.Cyan);
+ continue;
+ }
+
+ var controls = new List<Vector2>
+ {
+ new(0, y1),
+ new(30, y1),
+ new(PixelWidth - 30, y2),
+ new(PixelWidth, y2),
+ };
+
+ //Calculate coefficients
+ var c0 = controls[0];
+ var c1 = controls[0] * -3 + controls[1] * 3;
+ var c2 = controls[0] * 3 + controls[1] * -6 + controls[2] * 3;
+ var c3 = controls[0] * -1 + controls[1] * 3 + controls[2] * -3 + controls[3];
+
+ var points = new List<Vector2>();
+
+ //Calculate points using coefficients
+ for (float t = 0; t <= 1; t += 0.0001f)
+ {
+ var point = c0 + c1 * t + c2 * (t * t) + c3 * (t * t * t);
+ points.Add(point);
+ }
+
+ handle.DrawPrimitives(DrawPrimitiveTopology.LineStrip, points.ToArray(), Color.Cyan);
+ }
+ }
+ }
+}
+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;
<controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:networkConfigurator="clr-namespace:Content.Client.NetworkConfigurator"
- Title="Network Configurator" MinSize="220 400">
+ Title="{Loc 'network-configurator-title-saved-devices'}" MinSize="220 400">
<BoxContainer Orientation="Vertical" VerticalExpand="True">
<networkConfigurator:NetworkConfiguratorDeviceList Name="DeviceList" />
<BoxContainer Orientation="Horizontal" Margin="8 8 8 8">
-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
{
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
{
[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";
base.Initialize();
SubscribeLocalEvent<ClearAllOverlaysEvent>(_ => ClearAllOverlays());
+ SubscribeLocalEvent<NetworkConfiguratorComponent, ItemStatusCollectMessage>(OnCollectItemStatus);
+ }
+
+ private void OnCollectItemStatus(EntityUid uid, NetworkConfiguratorComponent configurator, ItemStatusCollectMessage args)
+ {
+ _inputManager.TryGetKeyBinding((ContentKeyFunctions.AltUseItemInHand), out var binding);
+ args.Controls.Add(new StatusControl(configurator, binding?.GetKeyString() ?? ""));
}
public bool ConfiguredListIsTracked(EntityUid uid, NetworkConfiguratorComponent? component = null)
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
await server.WaitPost(() =>
{
entityMan = IoCManager.Resolve<IEntityManager>();
- var mapManager = IoCManager.Resolve<IMapManager>();
var prototypeMan = IoCManager.Resolve<IPrototypeManager>();
var protoIds = prototypeMan
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()
private void OnInit(EntityUid uid, SignalControlledValveComponent comp, ComponentInit args)
{
- _signal.EnsureReceiverPorts(uid, comp.OpenPort, comp.ClosePort, comp.TogglePort);
+ _signal.EnsureSinkPorts(uid, comp.OpenPort, comp.ClosePort, comp.TogglePort);
}
- private void OnSignalReceived(EntityUid uid, SignalControlledValveComponent comp, SignalReceivedEvent args)
+ private void OnSignalReceived(EntityUid uid, SignalControlledValveComponent comp, ref SignalReceivedEvent args)
{
if (!TryComp<GasValveComponent>(uid, out var valve))
return;
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;
{
[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!;
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;
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;
[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!;
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,
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;
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;
[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!;
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)
{
private void OnMapInit(EntityUid uid, CloningConsoleComponent component, MapInitEvent args)
{
- if (!TryComp<SignalTransmitterComponent>(uid, out var receiver))
+ if (!TryComp<DeviceLinkSourceComponent>(uid, out var receiver))
return;
foreach (var port in receiver.Outputs.Values.SelectMany(ports => ports))
{
- if (TryComp<MedicalScannerComponent>(port.Uid, out var scanner))
+ if (TryComp<MedicalScannerComponent>(port, out var scanner))
{
- component.GeneticScanner = port.Uid;
+ component.GeneticScanner = port;
scanner.ConnectedConsole = uid;
}
- if (TryComp<CloningPodComponent>(port.Uid, out var pod))
+ if (TryComp<CloningPodComponent>(port, out var pod))
{
- component.CloningPod = port.Uid;
+ component.CloningPod = port;
pod.ConnectedConsole = uid;
}
}
private void OnNewLink(EntityUid uid, CloningConsoleComponent component, NewLinkEvent args)
{
- if (TryComp<MedicalScannerComponent>(args.Receiver, out var scanner) && args.TransmitterPort == CloningConsoleComponent.ScannerPort)
+ if (TryComp<MedicalScannerComponent>(args.Sink, out var scanner) && args.SourcePort == CloningConsoleComponent.ScannerPort)
{
- component.GeneticScanner = args.Receiver;
+ component.GeneticScanner = args.Sink;
scanner.ConnectedConsole = uid;
}
- if (TryComp<CloningPodComponent>(args.Receiver, out var pod) && args.TransmitterPort == CloningConsoleComponent.PodPort)
+ if (TryComp<CloningPodComponent>(args.Sink, out var pod) && args.SourcePort == CloningConsoleComponent.PodPort)
{
- component.CloningPod = args.Receiver;
+ component.CloningPod = args.Sink;
pod.ConnectedConsole = uid;
}
RecheckConnections(uid, component.CloningPod, component.GeneticScanner, component);
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;
{
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!;
private void OnComponentInit(EntityUid uid, CloningPodComponent clonePod, ComponentInit args)
{
clonePod.BodyContainer = _containerSystem.EnsureContainer<ContainerSlot>(clonePod.Owner, "clonepod-bodyContainer");
- _signalSystem.EnsureReceiverPorts(uid, CloningPodComponent.PodPort);
+ _signalSystem.EnsureSinkPorts(uid, CloningPodComponent.PodPort);
}
private void OnPartsRefreshed(EntityUid uid, CloningPodComponent component, RefreshPartsEvent args)
-namespace Content.Server.MachineLinking.Components;
+namespace Content.Server.DeviceLinking.Components;
/// <summary>
/// This is used for automatic linkage with buttons and other transmitters.
-namespace Content.Server.MachineLinking.Components;
+namespace Content.Server.DeviceLinking.Components;
/// <summary>
/// This is used for automatic linkage with various receivers, like shutters.
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
using Robust.Shared.Audio;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
-namespace Content.Server.MachineLinking.Components
+namespace Content.Server.DeviceLinking.Components
{
/// <summary>
/// Simple switch that will fire ports when toggled on or off. A button is jsut a switch that signals on the
using Content.Shared.MachineLinking;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
-namespace Content.Server.MachineLinking.Components
+namespace Content.Server.DeviceLinking.Components
{
/// <summary>
/// Sends out a signal to machine linked objects.
+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
--- /dev/null
+using Content.Server.DeviceNetwork;
+
+namespace Content.Server.DeviceLinking.Events;
+
+[ByRefEvent]
+public readonly record struct SignalReceivedEvent(string Port, EntityUid? Trigger = null, NetworkPayload? Data = null);
-using Content.Server.MachineLinking.Components;
+using Content.Server.DeviceLinking.Components;
+using Content.Server.MachineLinking.System;
-namespace Content.Server.MachineLinking.System;
+namespace Content.Server.DeviceLinking.Systems;
/// <summary>
/// This handles automatically linking autolinked entities at round-start.
/// </summary>
public sealed class AutoLinkSystem : EntitySystem
{
- [Dependency] private readonly SignalLinkerSystem _signalLinkerSystem = default!;
+ [Dependency] private readonly DeviceLinkSystem _deviceLinkSystem = default!;
/// <inheritdoc/>
public override void Initialize()
if (rxXform.GridUid != xform.GridUid)
continue;
- _signalLinkerSystem.TryLinkDefaults(receiver.Owner, uid, null);
+ _deviceLinkSystem.LinkDefaults(null, uid, receiver.Owner);
}
}
}
--- /dev/null
+ο»Ώusing Content.Server.DeviceLinking.Events;
+using Content.Server.DeviceNetwork;
+using Content.Server.DeviceNetwork.Components;
+using Content.Server.DeviceNetwork.Systems;
+using Content.Server.MachineLinking.Components;
+using Content.Shared.DeviceLinking;
+using Robust.Shared.Utility;
+
+namespace Content.Server.DeviceLinking.Systems;
+
+public sealed class DeviceLinkSystem : SharedDeviceLinkSystem
+{
+ [Dependency] private readonly DeviceNetworkSystem _deviceNetworkSystem = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent<SignalTransmitterComponent, MapInitEvent>(OnTransmitterStartup);
+ SubscribeLocalEvent<DeviceLinkSinkComponent, DeviceNetworkPacketEvent>(OnPacketReceived);
+ }
+
+ /// <summary>
+ /// Moves existing links from machine linking to device linking to ensure linked things still work even when the map wasn't updated yet
+ /// </summary>
+ private void OnTransmitterStartup(EntityUid sourceUid, SignalTransmitterComponent transmitterComponent, MapInitEvent args)
+ {
+ if (!TryComp<DeviceLinkSourceComponent?>(sourceUid, out var sourceComponent))
+ return;
+
+ Dictionary<EntityUid, List<(string, string)>> outputs = new();
+ foreach (var (transmitterPort, receiverPorts) in transmitterComponent.Outputs)
+ {
+
+ foreach (var receiverPort in receiverPorts)
+ {
+ outputs.GetOrNew(receiverPort.Uid).Add((transmitterPort, receiverPort.Port));
+ }
+ }
+
+ foreach (var (sinkUid, links) in outputs)
+ {
+ SaveLinks(null, sourceUid, sinkUid, links, sourceComponent);
+ }
+ }
+
+ #region Sending & Receiving
+ /// <summary>
+ /// Sends a network payload directed at the sink entity.
+ /// Just raises a <see cref="SignalReceivedEvent"/> without data if the source or the sink doesn't have a <see cref="DeviceNetworkComponent"/>
+ /// </summary>
+ /// <param name="uid">The source uid that invokes the port</param>
+ /// <param name="port">The port to invoke</param>
+ /// <param name="data">Optional data to send along</param>
+ /// <param name="sourceComponent"></param>
+ public void InvokePort(EntityUid uid, string port, NetworkPayload? data = null, DeviceLinkSourceComponent? sourceComponent = null)
+ {
+ if (!Resolve(uid, ref sourceComponent) || !sourceComponent.Outputs.TryGetValue(port, out var sinks))
+ return;
+
+ foreach (var sinkUid in sinks)
+ {
+ if (!sourceComponent.LinkedPorts.TryGetValue(sinkUid, out var links))
+ continue;
+
+ foreach (var (source, sink) in links)
+ {
+ if (source != port)
+ continue;
+
+ //Just skip using device networking if the source or the sink doesn't support it
+ if (!HasComp<DeviceNetworkComponent>(uid) || !TryComp<DeviceNetworkComponent?>(sinkUid, out var sinkNetworkComponent))
+ {
+ var eventArgs = new SignalReceivedEvent(sink, uid);
+
+ RaiseLocalEvent(sinkUid, ref eventArgs);
+ continue;
+ }
+
+ var payload = new NetworkPayload()
+ {
+ [InvokedPort] = sink
+ };
+
+ if (data != null)
+ {
+ //Prevent overriding the invoked port
+ data.Remove(InvokedPort);
+ foreach (var (key, value) in data)
+ {
+ payload.Add(key, value);
+ }
+ }
+
+ _deviceNetworkSystem.QueuePacket(uid, sinkNetworkComponent.Address, payload, sinkNetworkComponent.ReceiveFrequency);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Checks if the payload has a port defined and if the port is present on the sink.
+ /// Raises a <see cref="SignalReceivedEvent"/> containing the payload when the check passes
+ /// </summary>
+ private void OnPacketReceived(EntityUid uid, DeviceLinkSinkComponent component, DeviceNetworkPacketEvent args)
+ {
+ if (!args.Data.TryGetValue(InvokedPort, out string? port) || !(component.Ports?.Contains(port) ?? false))
+ return;
+
+ var eventArgs = new SignalReceivedEvent(port, args.Sender, args.Data);
+ RaiseLocalEvent(uid, ref eventArgs);
+ }
+ #endregion
+
+
+}
-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()
{
SubscribeLocalEvent<DoorSignalControlComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<DoorSignalControlComponent, SignalReceivedEvent>(OnSignalReceived);
}
-
+
private void OnInit(EntityUid uid, DoorSignalControlComponent component, ComponentInit args)
{
- _signalSystem.EnsureReceiverPorts(uid, component.OpenPort, component.ClosePort, component.TogglePort);
+ _signalSystem.EnsureSinkPorts(uid, component.OpenPort, component.ClosePort, component.TogglePort);
}
- private void OnSignalReceived(EntityUid uid, DoorSignalControlComponent component, SignalReceivedEvent args)
+ private void OnSignalReceived(EntityUid uid, DoorSignalControlComponent component, ref SignalReceivedEvent args)
{
if (!TryComp(uid, out DoorComponent? door))
return;
-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()
{
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)
+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()
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;
}
if (hasUseDelay)
_useDelay.BeginDelay(uid, useDelay);
- _signal.InvokePort(uid, component.Port);
+ _link.InvokePort(uid, component.Port);
args.Handled = true;
}
}
-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";
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)
ο»Ώusing System.Linq;
using Content.Server.DeviceNetwork.Components;
using Content.Shared.DeviceNetwork;
+using Content.Shared.DeviceNetwork.Components;
using Content.Shared.Interaction;
using JetBrains.Annotations;
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;
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()
{
SubscribeLocalEvent<NetworkConfiguratorComponent, MapInitEvent>(OnMapInit);
//Interaction
- SubscribeLocalEvent<NetworkConfiguratorComponent, AfterInteractEvent>((uid, component, args) => OnUsed(uid, component, args.Target, args.User, args.CanReach)); //TODO: Replace with utility verb?
+ SubscribeLocalEvent<NetworkConfiguratorComponent, AfterInteractEvent>(AfterInteract); //TODO: Replace with utility verb?
+ SubscribeLocalEvent<NetworkConfiguratorComponent, ExaminedEvent>(DoExamine);
//Verbs
SubscribeLocalEvent<NetworkConfiguratorComponent, GetVerbsEvent<UtilityVerb>>(OnAddInteractVerb);
SubscribeLocalEvent<DeviceNetworkComponent, GetVerbsEvent<AlternativeVerb>>(OnAddAlternativeSaveDeviceVerb);
+ SubscribeLocalEvent<NetworkConfiguratorComponent, GetVerbsEvent<AlternativeVerb>>(OnAddSwitchModeVerb);
//UI
SubscribeLocalEvent<NetworkConfiguratorComponent, BoundUIClosedEvent>(OnUiClosed);
SubscribeLocalEvent<NetworkConfiguratorComponent, NetworkConfiguratorRemoveDeviceMessage>(OnRemoveDevice);
SubscribeLocalEvent<NetworkConfiguratorComponent, NetworkConfiguratorClearDevicesMessage>(OnClearDevice);
+ SubscribeLocalEvent<NetworkConfiguratorComponent, NetworkConfiguratorLinksSaveMessage>(OnSaveLinks);
+ SubscribeLocalEvent<NetworkConfiguratorComponent, NetworkConfiguratorClearLinksMessage>(OnClearLinks);
+ SubscribeLocalEvent<NetworkConfiguratorComponent, NetworkConfiguratorToggleLinkMessage>(OnToggleLinks);
SubscribeLocalEvent<NetworkConfiguratorComponent, NetworkConfiguratorButtonPressedMessage>(OnConfigButtonPressed);
+ SubscribeLocalEvent<NetworkConfiguratorComponent, ActivatableUIOpenAttemptEvent>(OnUiOpenAttempt);
SubscribeLocalEvent<DeviceListComponent, ComponentRemove>(OnComponentRemoved);
}
}
//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,
_popupSystem.PopupCursor(Loc.GetString("network-configurator-device-saved", ("address", device.Address), ("device", targetUid)),
userUid, PopupType.Medium);
- UpdateUiState(configurator.Owner, configurator);
+ UpdateListUiState(configurator.Owner, configurator);
+ }
+
+ private void TryLinkDevice(EntityUid uid, NetworkConfiguratorComponent configurator, EntityUid? target, EntityUid user)
+ {
+ if (!HasComp<DeviceLinkSourceComponent>(target) && !HasComp<DeviceLinkSinkComponent>(target))
+ return;
+
+ if (configurator.ActiveDeviceLink == target)
+ {
+ _popupSystem.PopupEntity(Loc.GetString("network-configurator-link-mode-stopped"), target.Value, user);
+ configurator.ActiveDeviceLink = null;
+ return;
+ }
+
+ if (HasComp<DeviceLinkSourceComponent>(target) && HasComp<DeviceLinkSourceComponent>(configurator.ActiveDeviceLink)
+ || HasComp<DeviceLinkSinkComponent>(target) && HasComp<DeviceLinkSinkComponent>(configurator.ActiveDeviceLink))
+ {
+ return;
+ }
+
+ if (configurator.ActiveDeviceLink.HasValue)
+ {
+ OpenDeviceLinkUi( uid, target, user, configurator);
+ return;
+ }
+
+ _popupSystem.PopupEntity(Loc.GetString("network-configurator-link-mode-started", ("device", Name(target.Value))), target.Value, user);
+ configurator.ActiveDeviceLink = target;
+ }
+
+ private void TryLinkDefaults(EntityUid uid, NetworkConfiguratorComponent configurator, EntityUid? targetUid, EntityUid user)
+ {
+ if (!configurator.LinkModeActive || !configurator.ActiveDeviceLink.HasValue
+ || !targetUid.HasValue || configurator.ActiveDeviceLink == targetUid)
+ return;
+
+ if (!HasComp<DeviceLinkSourceComponent>(targetUid) && !HasComp<DeviceLinkSinkComponent>(targetUid))
+ return;
+
+ if (TryComp(configurator.ActiveDeviceLink, out DeviceLinkSourceComponent? activeSource) && TryComp(targetUid, out DeviceLinkSinkComponent? targetSink))
+ {
+ _deviceLinkSystem.LinkDefaults(user, configurator.ActiveDeviceLink.Value, targetUid.Value, activeSource, targetSink);
+ }
+ else if (TryComp(configurator.ActiveDeviceLink, out DeviceLinkSinkComponent? activeSink) && TryComp(targetUid, out DeviceLinkSourceComponent? targetSource))
+ {
+ _deviceLinkSystem.LinkDefaults(user, targetUid.Value, configurator.ActiveDeviceLink.Value, targetSource, activeSink);
+ }
}
private bool AccessCheck(EntityUid target, EntityUid? user, NetworkConfiguratorComponent component)
{
if (!TryComp(target, out AccessReaderComponent? reader) || user == null)
- return false;
+ return true;
if (_accessSystem.IsAllowed(user.Value, reader))
return true;
- SoundSystem.Play(component.SoundNoAccess.GetSound(), Filter.Pvs(user.Value), target, AudioParams.Default.WithVolume(-2f).WithPitchScale(1.2f));
+ _audioSystem.PlayPvs(component.SoundNoAccess, user.Value, AudioParams.Default.WithVolume(-2f).WithPitchScale(1.2f));
_popupSystem.PopupEntity(Loc.GetString("network-configurator-device-access-denied"), target, user.Value);
return false;
private void OnComponentRemoved(EntityUid uid, DeviceListComponent component, ComponentRemove args)
{
- _uiSystem.GetUiOrNull(uid, NetworkConfiguratorUiKey.Configure)?.CloseAll();
+ _uiSystem.TryCloseAll(uid, NetworkConfiguratorUiKey.Configure);
+ }
+
+ private void SwitchMode(EntityUid? userUid, EntityUid configuratorUid, NetworkConfiguratorComponent configurator)
+ {
+ if (Delay(configurator))
+ return;
+
+ configurator.LinkModeActive = !configurator.LinkModeActive;
+
+ if (!userUid.HasValue)
+ return;
+
+ if (!configurator.LinkModeActive)
+ configurator.ActiveDeviceLink = null;
+
+ var locString = configurator.LinkModeActive ? "network-configurator-mode-link" : "network-configurator-mode-list";
+ _popupSystem.PopupEntity(Loc.GetString("network-configurator-switched-mode", ("mode", Loc.GetString(locString))),
+ configuratorUid, userUid.Value);
+
+ Dirty(configurator);
+ _appearanceSystem.SetData(configuratorUid, NetworkConfiguratorVisuals.Mode, configurator.LinkModeActive);
+
+ var pitch = configurator.LinkModeActive ? 1 : 0.8f;
+ _audioSystem.PlayPvs(configurator.SoundSwitchMode, userUid.Value, AudioParams.Default.WithVolume(1.5f).WithPitchScale(pitch));
+ }
+
+ /// <summary>
+ /// Returns true if the last time this method was called is earlier than the configurators use delay.
+ /// </summary>
+ private bool Delay(NetworkConfiguratorComponent configurator)
+ {
+ var currentTime = _gameTiming.CurTime;
+ if (currentTime < configurator.LastUseAttempt + configurator.UseDelay)
+ return true;
+
+ configurator.LastUseAttempt = currentTime;
+ return false;
}
#region Interactions
+ private void DoExamine(EntityUid uid, NetworkConfiguratorComponent component, ExaminedEvent args)
+ {
+ var mode = component.LinkModeActive ? "network-configurator-examine-mode-link" : "network-configurator-examine-mode-list";
+ args.PushMarkup(Loc.GetString("network-configurator-examine-current-mode", ("mode", Loc.GetString(mode))));
+ }
+
+ private void AfterInteract(EntityUid uid, NetworkConfiguratorComponent component, AfterInteractEvent args)
+ {
+ OnUsed(uid, component, args.Target, args.User, args.CanReach);
+ }
+
/// <summary>
/// Either adds a device to the device list or shows the config ui if the target is ant entity with a device list
/// </summary>
- private void OnUsed(EntityUid uid, NetworkConfiguratorComponent component, EntityUid? target, EntityUid user, bool canReach = true)
+ private void OnUsed(EntityUid uid, NetworkConfiguratorComponent configurator, EntityUid? target, EntityUid user, bool canReach = true)
{
- if (!canReach)
+ if (!canReach || !target.HasValue)
return;
+ if (configurator.LinkModeActive)
+ {
+ TryLinkDevice(uid, configurator, target, user);
+ return;
+ }
+
if (!HasComp<DeviceListComponent>(target))
{
- TryAddNetworkDevice(target, user, component);
+ TryAddNetworkDevice(target, user, configurator);
return;
}
- OpenDeviceListUi(target, user, component);
+ OpenDeviceListUi(target, user, configurator);
}
#endregion
/// <summary>
/// Adds the interaction verb which is either configuring device lists or saving a device onto the configurator
/// </summary>
- private void OnAddInteractVerb(EntityUid uid, NetworkConfiguratorComponent component, GetVerbsEvent<UtilityVerb> args)
+ private void OnAddInteractVerb(EntityUid uid, NetworkConfiguratorComponent configurator, GetVerbsEvent<UtilityVerb> args)
{
- if (!args.CanAccess || !args.CanInteract || !args.Using.HasValue || !HasComp<DeviceNetworkComponent>(args.Target))
+ if (!args.CanAccess || !args.CanInteract || !args.Using.HasValue)
return;
- var isDeviceList = HasComp<DeviceListComponent>(args.Target);
-
- UtilityVerb verb = new()
+ var verb = new UtilityVerb
{
- Text = Loc.GetString(isDeviceList ? "network-configurator-configure" : "network-configurator-save-device"),
- Icon = isDeviceList ?
- new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/settings.svg.192dpi.png")) :
- new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/in.svg.192dpi.png")),
- Act = () => OnUsed(uid, component, args.Target, args.User),
+ Act = () => OnUsed(uid, configurator, args.Target, args.User),
Impact = LogImpact.Low
};
+
+ if (configurator.LinkModeActive)
+ {
+ var linkStarted = configurator.ActiveDeviceLink.HasValue;
+ verb.Text = Loc.GetString(linkStarted ? "network-configurator-link" : "network-configurator-start-link");
+ verb.Icon = new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/in.svg.192dpi.png"));
+ }
+ else if (!HasComp<DeviceNetworkComponent>(args.Target))
+ {
+ var isDeviceList = HasComp<DeviceListComponent>(args.Target);
+ verb.Text = Loc.GetString(isDeviceList ? "network-configurator-configure" : "network-configurator-save-device");
+ verb.Icon = isDeviceList
+ ? new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/settings.svg.192dpi.png"))
+ : new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/in.svg.192dpi.png"));
+ }
+
args.Verbs.Add(verb);
}
/// </summary>
private void OnAddAlternativeSaveDeviceVerb(EntityUid uid, DeviceNetworkComponent component, GetVerbsEvent<AlternativeVerb> args)
{
- if (!args.CanAccess || !args.CanInteract || !args.Using.HasValue || !HasComp<NetworkConfiguratorComponent>(args.Using.Value)
- || !HasComp<DeviceListComponent>(args.Target))
+ if (!args.CanAccess || !args.CanInteract || !args.Using.HasValue
+ || !TryComp<NetworkConfiguratorComponent>(args.Using.Value, out var configurator))
+ return;
+
+ if (!configurator.LinkModeActive && HasComp<DeviceListComponent>(args.Target))
+ {
+ AlternativeVerb verb = new()
+ {
+ Text = Loc.GetString("network-configurator-save-device"),
+ Icon = new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/in.svg.192dpi.png")),
+ Act = () => TryAddNetworkDevice(args.Target, args.Using.Value, args.User),
+ Impact = LogImpact.Low
+ };
+ args.Verbs.Add(verb);
+ return;
+ }
+
+ if (configurator is {LinkModeActive: true, ActiveDeviceLink: { }} && (HasComp<DeviceLinkSinkComponent>(args.Target) || HasComp<DeviceLinkSourceComponent>(args.Target)))
+ {
+ AlternativeVerb verb = new()
+ {
+ Text = Loc.GetString("network-configurator-link-defaults"),
+ Icon = new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/in.svg.192dpi.png")),
+ Act = () => TryLinkDefaults(args.Using.Value, configurator, args.Target, args.User),
+ Impact = LogImpact.Low
+ };
+ args.Verbs.Add(verb);
+ }
+ }
+
+ private void OnAddSwitchModeVerb(EntityUid uid, NetworkConfiguratorComponent configurator, GetVerbsEvent<AlternativeVerb> args)
+ {
+ if (!args.CanAccess || !args.CanInteract || !args.Using.HasValue || !HasComp<NetworkConfiguratorComponent>(args.Target))
return;
AlternativeVerb verb = new()
#region UI
+ private void OpenDeviceLinkUi(EntityUid configuratorUid, EntityUid? targetUid, EntityUid userUid, NetworkConfiguratorComponent configurator)
+ {
+ if (Delay(configurator))
+ return;
+
+ if (!targetUid.HasValue || !configurator.ActiveDeviceLink.HasValue || !TryComp(userUid, out ActorComponent? actor) || !AccessCheck(targetUid.Value, userUid, configurator))
+ return;
+
+
+ _uiSystem.TryOpen(configuratorUid, NetworkConfiguratorUiKey.Link, actor.PlayerSession);
+ configurator.DeviceLinkTarget = targetUid;
+
+
+ if (TryComp(configurator.ActiveDeviceLink, out DeviceLinkSourceComponent? activeSource) && TryComp(targetUid, out DeviceLinkSinkComponent? targetSink))
+ {
+ UpdateLinkUiState(configuratorUid, configurator.ActiveDeviceLink.Value, targetUid.Value, activeSource, targetSink);
+ }
+ else if (TryComp(configurator.ActiveDeviceLink, out DeviceLinkSinkComponent? activeSink)
+ && TryComp(targetUid, out DeviceLinkSourceComponent? targetSource))
+ {
+ UpdateLinkUiState(configuratorUid, targetUid.Value, configurator.ActiveDeviceLink.Value, targetSource, activeSink);
+ }
+ }
+
+ private void UpdateLinkUiState(EntityUid configuratorUid, EntityUid sourceUid, EntityUid sinkUid,
+ DeviceLinkSourceComponent? sourceComponent = null, DeviceLinkSinkComponent? sinkComponent = null,
+ DeviceNetworkComponent? sourceNetworkComponent = null, DeviceNetworkComponent? sinkNetworkComponent = null)
+ {
+ if (!Resolve(sourceUid, ref sourceComponent) || !Resolve(sinkUid, ref sinkComponent))
+ return;
+
+ var sources = _deviceLinkSystem.GetSourcePorts(sourceUid, sourceComponent);
+ var sinks = _deviceLinkSystem.GetSinkPorts(sinkUid, sinkComponent);
+ var links = _deviceLinkSystem.GetLinks(sourceUid, sinkUid, sourceComponent);
+ var defaults = _deviceLinkSystem.GetDefaults(sources);
+
+ var sourceAddress = Resolve(sourceUid, ref sourceNetworkComponent) ? sourceNetworkComponent.Address : "";
+ var sinkAddress = Resolve(sinkUid, ref sinkNetworkComponent) ? sinkNetworkComponent.Address : "";
+
+ var state = new DeviceLinkUserInterfaceState(sources, sinks, links, sourceAddress, sinkAddress, defaults);
+ _uiSystem.TrySetUiState(configuratorUid, NetworkConfiguratorUiKey.Link, state);
+ }
+
/// <summary>
/// Opens the config ui. It can be used to modify the devices in the targets device list.
/// </summary>
private void OpenDeviceListUi(EntityUid? targetUid, EntityUid userUid, NetworkConfiguratorComponent configurator)
{
+ if (Delay(configurator))
+ return;
+
if (!targetUid.HasValue || !TryComp(userUid, out ActorComponent? actor) || !AccessCheck(targetUid.Value, userUid, configurator))
return;
/// <summary>
/// Sends the list of saved devices to the ui
/// </summary>
- private void UpdateUiState(EntityUid uid, NetworkConfiguratorComponent component)
+ private void UpdateListUiState(EntityUid uid, NetworkConfiguratorComponent component)
{
HashSet<(string address, string name)> devices = new();
HashSet<string> invalidDevices = new();
private void OnUiClosed(EntityUid uid, NetworkConfiguratorComponent component, BoundUIClosedEvent args)
{
component.ActiveDeviceList = null;
+
+ if (args.UiKey is NetworkConfiguratorUiKey.Link)
+ {
+ component.ActiveDeviceLink = null;
+ component.DeviceLinkTarget = null;
+ }
}
/// <summary>
private void OnRemoveDevice(EntityUid uid, NetworkConfiguratorComponent component, NetworkConfiguratorRemoveDeviceMessage args)
{
component.Devices.Remove(args.Address);
- UpdateUiState(uid, component);
+ UpdateListUiState(uid, component);
}
/// <summary>
private void OnClearDevice(EntityUid uid, NetworkConfiguratorComponent component, NetworkConfiguratorClearDevicesMessage _)
{
component.Devices.Clear();
- UpdateUiState(uid, component);
+ UpdateListUiState(uid, component);
+ }
+
+ private void OnClearLinks(EntityUid uid, NetworkConfiguratorComponent configurator, NetworkConfiguratorClearLinksMessage args)
+ {
+ if (!configurator.ActiveDeviceLink.HasValue || !configurator.DeviceLinkTarget.HasValue)
+ return;
+
+ if (HasComp<DeviceLinkSourceComponent>(configurator.ActiveDeviceLink))
+ {
+ _deviceLinkSystem.RemoveSinkFromSource(configurator.ActiveDeviceLink.Value, configurator.DeviceLinkTarget.Value);
+ UpdateLinkUiState(uid, configurator.ActiveDeviceLink.Value, configurator.DeviceLinkTarget.Value);
+ }
+ else if (HasComp<DeviceLinkSourceComponent>(configurator.DeviceLinkTarget))
+ {
+ _deviceLinkSystem.RemoveSinkFromSource(configurator.DeviceLinkTarget.Value, configurator.ActiveDeviceLink.Value);
+ UpdateLinkUiState(uid, configurator.DeviceLinkTarget.Value, configurator.ActiveDeviceLink.Value);
+ }
+ }
+
+ private void OnToggleLinks(EntityUid uid, NetworkConfiguratorComponent configurator, NetworkConfiguratorToggleLinkMessage args)
+ {
+ if (!configurator.ActiveDeviceLink.HasValue || !configurator.DeviceLinkTarget.HasValue)
+ return;
+
+ if (TryComp(configurator.ActiveDeviceLink, out DeviceLinkSourceComponent? activeSource))
+ {
+ _deviceLinkSystem.ToggleLink(args.Session.AttachedEntity, configurator.ActiveDeviceLink.Value, configurator.DeviceLinkTarget.Value, args.Source, args.Sink, activeSource);
+ UpdateLinkUiState(uid, configurator.ActiveDeviceLink.Value, configurator.DeviceLinkTarget.Value, activeSource);
+ }
+ else if (TryComp(configurator.DeviceLinkTarget, out DeviceLinkSourceComponent? targetSource))
+ {
+ _deviceLinkSystem.ToggleLink(args.Session.AttachedEntity, configurator.DeviceLinkTarget.Value, configurator.ActiveDeviceLink.Value, args.Source, args.Sink, targetSource);
+ UpdateLinkUiState(uid, configurator.DeviceLinkTarget.Value, configurator.ActiveDeviceLink.Value, targetSource);
+ }
+ }
+
+ /// <summary>
+ /// Saves links set by the device link UI
+ /// </summary>
+ private void OnSaveLinks(EntityUid uid, NetworkConfiguratorComponent configurator, NetworkConfiguratorLinksSaveMessage args)
+ {
+ if (!configurator.ActiveDeviceLink.HasValue || !configurator.DeviceLinkTarget.HasValue)
+ return;
+
+ if (TryComp(configurator.ActiveDeviceLink, out DeviceLinkSourceComponent? activeSource))
+ {
+ _deviceLinkSystem.SaveLinks(args.Session.AttachedEntity, configurator.ActiveDeviceLink.Value, configurator.DeviceLinkTarget.Value, args.Links, activeSource);
+ UpdateLinkUiState(uid, configurator.ActiveDeviceLink.Value, configurator.DeviceLinkTarget.Value, activeSource);
+ }
+ else if (TryComp(configurator.DeviceLinkTarget, out DeviceLinkSourceComponent? targetSource))
+ {
+ _deviceLinkSystem.SaveLinks(args.Session.AttachedEntity, configurator.DeviceLinkTarget.Value, configurator.ActiveDeviceLink.Value, args.Links, targetSource);
+ UpdateLinkUiState(uid, configurator.DeviceLinkTarget.Value, configurator.ActiveDeviceLink.Value, targetSource);
+ }
}
/// <summary>
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;
}
_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
}
+using Content.Server.DeviceLinking.Events;
using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems;
using Content.Shared.Tools.Components;
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
}
}
- private void OnSignalReceived(EntityUid uid, AirlockComponent component, SignalReceivedEvent args)
+ private void OnSignalReceived(EntityUid uid, AirlockComponent component, ref SignalReceivedEvent args)
{
if (args.Port == component.AutoClosePort)
{
+using Content.Server.DeviceLinking.Events;
+using Content.Server.DeviceLinking.Systems;
using Content.Server.Explosion.Components;
-using Content.Server.MachineLinking.Events;
-using Content.Server.MachineLinking.System;
namespace Content.Server.Explosion.EntitySystems
{
public sealed partial class TriggerSystem
{
- [Dependency] private readonly SignalLinkerSystem _signalSystem = default!;
-
+ [Dependency] private readonly DeviceLinkSystem _signalSystem = default!;
private void InitializeSignal()
{
SubscribeLocalEvent<TriggerOnSignalComponent,SignalReceivedEvent>(OnSignalReceived);
SubscribeLocalEvent<TriggerOnSignalComponent,ComponentInit>(OnInit);
}
- private void OnSignalReceived(EntityUid uid, TriggerOnSignalComponent component, SignalReceivedEvent args)
+ private void OnSignalReceived(EntityUid uid, TriggerOnSignalComponent component, ref SignalReceivedEvent args)
{
if (args.Port != component.Port)
return;
}
private void OnInit(EntityUid uid, TriggerOnSignalComponent component, ComponentInit args)
{
- _signalSystem.EnsureReceiverPorts(uid, component.Port);
+ _signalSystem.EnsureSinkPorts(uid, component.Port);
}
}
}
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;
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
{
[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!;
private void OnInit(EntityUid uid, PoweredLightComponent light, ComponentInit args)
{
light.LightBulbContainer = _containerSystem.EnsureContainer<ContainerSlot>(uid, LightBulbContainer);
- _signalSystem.EnsureReceiverPorts(uid, light.OnPort, light.OffPort, light.TogglePort);
+ _signalSystem.EnsureSinkPorts(uid, light.OnPort, light.OffPort, light.TogglePort);
}
private void OnMapInit(EntityUid uid, PoweredLightComponent light, MapInitEvent args)
_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);
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)
{
-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;
- }
- }
-}
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
{
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)
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!;
{
base.Initialize();
scannerComponent.BodyContainer = _containerSystem.EnsureContainer<ContainerSlot>(uid, $"scanner-bodyContainer");
- _signalSystem.EnsureReceiverPorts(uid, MedicalScannerComponent.ScannerPort);
+ _signalSystem.EnsureSinkPorts(uid, MedicalScannerComponent.ScannerPort);
}
private void OnRelayMovement(EntityUid uid, MedicalScannerComponent scannerComponent, ref ContainerRelayMovementEntityEvent args)
-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;
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;
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!;
private void OnInit(EntityUid uid, ConveyorComponent component, ComponentInit args)
{
- _signalSystem.EnsureReceiverPorts(uid, component.ReversePort, component.ForwardPort, component.OffPort);
+ _signalSystem.EnsureSinkPorts(uid, component.ReversePort, component.ForwardPort, component.OffPort);
if (TryComp<PhysicsComponent>(uid, out var physics))
{
_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);
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;
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;
private void OnNewLink(EntityUid uid, AnalysisConsoleComponent component, NewLinkEvent args)
{
- if (!TryComp<ArtifactAnalyzerComponent>(args.Receiver, out var analyzer))
+ if (!TryComp<ArtifactAnalyzerComponent>(args.Sink, out var analyzer))
return;
- component.AnalyzerEntity = args.Receiver;
+ component.AnalyzerEntity = args.Sink;
analyzer.Console = uid;
UpdateUserInterface(uid, component);
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;
--- /dev/null
+ο»Ώusing Robust.Shared.GameStates;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set;
+
+namespace Content.Shared.DeviceLinking;
+
+[RegisterComponent]
+[NetworkedComponent] // for interactions. Actual state isn't currently synced.
+[Access(typeof(SharedDeviceLinkSystem))]
+public sealed class DeviceLinkSinkComponent : Component
+{
+ /// <summary>
+ /// The ports this sink has
+ /// </summary>
+ [DataField("ports", customTypeSerializer: typeof(PrototypeIdHashSetSerializer<SinkPortPrototype>))]
+ public HashSet<string>? Ports;
+
+ /// <summary>
+ /// Used for removing a sink from all linked sources when it gets removed
+ /// </summary>
+ [DataField("links")]
+ public HashSet<EntityUid> LinkedSources = new();
+}
--- /dev/null
+ο»Ώusing Robust.Shared.GameStates;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set;
+
+namespace Content.Shared.DeviceLinking;
+
+[RegisterComponent]
+[NetworkedComponent] // for interactions. Actual state isn't currently synced.
+[Access(typeof(SharedDeviceLinkSystem))]
+public sealed class DeviceLinkSourceComponent : Component
+{
+ /// <summary>
+ /// The ports the device link source sends signals from
+ /// </summary>
+ [DataField("ports", customTypeSerializer: typeof(PrototypeIdHashSetSerializer<SourcePortPrototype>))]
+ public HashSet<string>? Ports;
+
+ /// <summary>
+ /// A list of sink uids that got linked for each port
+ /// </summary>
+ [DataField("registeredSinks")]
+ public Dictionary<string, HashSet<EntityUid>> Outputs = new();
+
+ /// <summary>
+ /// The list of source to sink ports for each linked sink entity for easier managing of links
+ /// </summary>
+ [DataField("linkedPorts")]
+ public Dictionary<EntityUid, HashSet<(string source, string sink)>> LinkedPorts = new();
+
+ /// <summary>
+ /// Limits the range devices can be linked across.
+ /// </summary>
+ [DataField("range")]
+ [ViewVariables(VVAccess.ReadWrite)]
+ public float Range = 30f;
+}
--- /dev/null
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set;
+
+namespace Content.Shared.DeviceLinking;
+
+/// <summary>
+/// A prototype for a device port, for use with device linking.
+/// </summary>
+[Serializable, NetSerializable]
+public abstract class DevicePortPrototype
+{
+ [IdDataField]
+ public string ID { get; } = default!;
+
+ /// <summary>
+ /// Localization string for the port name. Displayed in the linking UI.
+ /// </summary>
+ [DataField("name", required:true)]
+ public string Name = default!;
+
+ /// <summary>
+ /// Localization string for a description of the ports functionality. Should either indicate when a source
+ /// port is fired, or what function a sink port serves. Displayed as a tooltip in the linking UI.
+ /// </summary>
+ [DataField("description", required: true)]
+ public string Description = default!;
+}
+
+[Prototype("sinkPort")]
+[Serializable, NetSerializable]
+public sealed class SinkPortPrototype : DevicePortPrototype, IPrototype
+{
+}
+
+[Prototype("sourcePort")]
+[Serializable, NetSerializable]
+public sealed class SourcePortPrototype : DevicePortPrototype, IPrototype
+{
+ /// <summary>
+ /// This is a set of sink ports that this source port will attempt to link to when using the
+ /// default-link functionality.
+ /// </summary>
+ [DataField("defaultLinks", customTypeSerializer: typeof(PrototypeIdHashSetSerializer<SinkPortPrototype>))]
+ public readonly HashSet<string>? DefaultLinks;
+}
--- /dev/null
+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;
+ }
+}
--- /dev/null
+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;
+ }
+}
-namespace Content.Server.MachineLinking.Events
+namespace Content.Shared.DeviceLinking.Events
{
public sealed class PortDisconnectedEvent : EntityEventArgs
{
--- /dev/null
+using Content.Shared.DeviceLinking.Events;
+using Content.Shared.Popups;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+
+namespace Content.Shared.DeviceLinking;
+
+public abstract class SharedDeviceLinkSystem : EntitySystem
+{
+ [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+ [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
+
+ public const string InvokedPort = "link_port";
+
+ /// <inheritdoc/>
+ public override void Initialize()
+ {
+ SubscribeLocalEvent<DeviceLinkSourceComponent, ComponentStartup>(OnSourceStartup);
+ SubscribeLocalEvent<DeviceLinkSinkComponent, ComponentStartup>(OnSinkStartup);
+ SubscribeLocalEvent<DeviceLinkSourceComponent, ComponentRemove>(OnSourceRemoved);
+ SubscribeLocalEvent<DeviceLinkSinkComponent, ComponentRemove>(OnSinkRemoved);
+ }
+
+ #region Link Validation
+ /// <summary>
+ /// Removes invalid links where the saved sink doesn't exist/have a sink component for example
+ /// </summary>
+ private void OnSourceStartup(EntityUid sourceUid, DeviceLinkSourceComponent sourceComponent, ComponentStartup args)
+ {
+ List<EntityUid> invalidSinks = new();
+ foreach (var sinkUid in sourceComponent.LinkedPorts.Keys)
+ {
+ if (!TryComp<DeviceLinkSinkComponent?>(sinkUid, out var sinkComponent))
+ {
+ invalidSinks.Add(sinkUid);
+ foreach (var savedSinks in sourceComponent.Outputs.Values)
+ {
+ savedSinks.Remove(sinkUid);
+ }
+
+ continue;
+ }
+
+ sinkComponent.LinkedSources.Add(sourceUid);
+ }
+
+ foreach (var invalidSink in invalidSinks)
+ {
+ sourceComponent.LinkedPorts.Remove(invalidSink);
+ }
+ }
+
+ /// <summary>
+ /// Same with <see cref="OnSourceStartup"/> but also checks that the saved ports are present on the sink
+ /// </summary>
+ private void OnSinkStartup(EntityUid sinkUid, DeviceLinkSinkComponent sinkComponent, ComponentStartup args)
+ {
+ List<EntityUid> invalidSources = new();
+ foreach (var sourceUid in sinkComponent.LinkedSources)
+ {
+ if (!TryComp<DeviceLinkSourceComponent>(sourceUid, out var sourceComponent))
+ {
+ invalidSources.Add(sourceUid);
+ continue;
+ }
+
+ if (!sourceComponent.LinkedPorts.TryGetValue(sinkUid, out var linkedPorts))
+ {
+ foreach (var savedSinks in sourceComponent.Outputs.Values)
+ {
+ savedSinks.Remove(sinkUid);
+ }
+ continue;
+ }
+
+ if (sinkComponent.Ports == null)
+ continue;
+
+ List<(string, string)> invalidLinks = new();
+ foreach (var link in linkedPorts)
+ {
+ if (!sinkComponent.Ports.Contains(link.sink) || !(sourceComponent.Outputs.GetValueOrDefault(link.source)?.Contains(sinkUid) ?? false))
+ invalidLinks.Add(link);
+ }
+
+ foreach (var invalidLink in invalidLinks)
+ {
+ linkedPorts.Remove(invalidLink);
+ sourceComponent.Outputs.GetValueOrDefault(invalidLink.Item1)?.Remove(sinkUid);
+ }
+ }
+
+ foreach (var invalidSource in invalidSources)
+ {
+ sinkComponent.LinkedSources.Remove(invalidSource);
+ }
+ }
+ #endregion
+
+ /// <summary>
+ /// Ensures that its links get deleted when a source gets removed
+ /// </summary>
+ private void OnSourceRemoved(EntityUid uid, DeviceLinkSourceComponent component, ComponentRemove args)
+ {
+ foreach (var sinkUid in component.LinkedPorts.Keys)
+ {
+ RemoveSinkFromSource(uid, sinkUid, component);
+ }
+ }
+
+ /// <summary>
+ /// Ensures that its links get deleted when a sink gets removed
+ /// </summary>
+ private void OnSinkRemoved(EntityUid sinkUid, DeviceLinkSinkComponent sinkComponent, ComponentRemove args)
+ {
+ foreach (var linkedSource in sinkComponent.LinkedSources)
+ {
+ RemoveSinkFromSource(linkedSource, sinkUid, null, sinkComponent);
+ }
+ }
+
+ #region Ports
+ /// <summary>
+ /// Convenience function to add several ports to an entity
+ /// </summary>
+ public void EnsureSourcePorts(EntityUid uid, params string[] ports)
+ {
+ var comp = EnsureComp<DeviceLinkSourceComponent>(uid);
+ comp.Ports ??= new HashSet<string>();
+
+ foreach (var port in ports)
+ {
+ comp.Ports?.Add(port);
+ }
+ }
+
+ /// <summary>
+ /// Convenience function to add several ports to an entity.
+ /// </summary>
+ public void EnsureSinkPorts(EntityUid uid, params string[] ports)
+ {
+ var comp = EnsureComp<DeviceLinkSinkComponent>(uid);
+ comp.Ports ??= new HashSet<string>();
+
+ foreach (var port in ports)
+ {
+ comp.Ports?.Add(port);
+ }
+ }
+
+ /// <summary>
+ /// Retrieves the available ports from a source
+ /// </summary>
+ /// <returns>A list of source port prototypes</returns>
+ public List<SourcePortPrototype> GetSourcePorts(EntityUid sourceUid, DeviceLinkSourceComponent? sourceComponent = null)
+ {
+ if (!Resolve(sourceUid, ref sourceComponent) || sourceComponent.Ports == null)
+ return new List<SourcePortPrototype>();
+
+ var sourcePorts = new List<SourcePortPrototype>();
+ foreach (var port in sourceComponent.Ports)
+ {
+ sourcePorts.Add(_prototypeManager.Index<SourcePortPrototype>(port));
+ }
+
+ return sourcePorts;
+ }
+
+ /// <summary>
+ /// Retrieves the available ports from a sink
+ /// </summary>
+ /// <returns>A list of sink port prototypes</returns>
+ public List<SinkPortPrototype> GetSinkPorts(EntityUid sinkUid, DeviceLinkSinkComponent? sinkComponent = null)
+ {
+ if (!Resolve(sinkUid, ref sinkComponent) || sinkComponent.Ports == null)
+ return new List<SinkPortPrototype>();
+
+ var sinkPorts = new List<SinkPortPrototype>();
+ foreach (var port in sinkComponent.Ports)
+ {
+ sinkPorts.Add(_prototypeManager.Index<SinkPortPrototype>(port));
+ }
+
+ return sinkPorts;
+ }
+
+ /// <summary>
+ /// Convenience function to retrieve the name of a port prototype
+ /// </summary>
+ public string PortName<TPort>(string port) where TPort : DevicePortPrototype, IPrototype
+ {
+ if (!_prototypeManager.TryIndex<TPort>(port, out var proto))
+ return port;
+
+ return Loc.GetString(proto.Name);
+ }
+ #endregion
+
+ #region Links
+ /// <summary>
+ /// Returns the links of a source
+ /// </summary>
+ /// <returns>A list of sink and source port ids that are linked together</returns>
+ public HashSet<(string source, string sink)> GetLinks(EntityUid sourceUid, EntityUid sinkUid, DeviceLinkSourceComponent? sourceComponent = null)
+ {
+ if (!Resolve(sourceUid, ref sourceComponent) || !sourceComponent.LinkedPorts.TryGetValue(sinkUid, out var links))
+ return new HashSet<(string source, string sink)>();
+
+ return links;
+ }
+
+ /// <summary>
+ /// Returns the default links for the given list of source port prototypes
+ /// </summary>
+ /// <param name="sources">The list of source port prototypes to get the default links for</param>
+ /// <returns>A list of sink and source port ids</returns>
+ public List<(string source, string sink)> GetDefaults(List<SourcePortPrototype> sources)
+ {
+ var defaults = new List<(string, string)>();
+ foreach (var source in sources)
+ {
+ if (source.DefaultLinks == null)
+ return new List<(string, string)>();
+
+ foreach (var defaultLink in source.DefaultLinks)
+ {
+ defaults.Add((source.ID, defaultLink));
+ }
+ }
+
+ return defaults;
+ }
+
+ /// <summary>
+ /// Links the given source and sink by their default links
+ /// </summary>
+ /// <param name="userId">Optinal user uid for displaying popups</param>
+ /// <param name="sourceUid">The source uid</param>
+ /// <param name="sinkUid">The sink uid</param>
+ /// <param name="sourceComponent"></param>
+ /// <param name="sinkComponent"></param>
+ public void LinkDefaults(
+ EntityUid? userId,
+ EntityUid sourceUid,
+ EntityUid sinkUid,
+ DeviceLinkSourceComponent? sourceComponent = null,
+ DeviceLinkSinkComponent? sinkComponent = null)
+ {
+ if (!Resolve(sourceUid, ref sourceComponent) || !Resolve(sinkUid, ref sinkComponent))
+ return;
+
+ var sourcePorts = GetSourcePorts(sourceUid, sourceComponent);
+ var defaults = GetDefaults(sourcePorts);
+ SaveLinks(userId, sourceUid, sinkUid, defaults, sourceComponent, sinkComponent);
+
+ if (userId != null)
+ _popupSystem.PopupCursor(Loc.GetString("signal-linking-verb-success", ("machine", sourceUid)), userId.Value);
+ }
+
+
+ /// <summary>
+ /// Saves multiple links between a source and a sink device.
+ /// Ignores links where either the source or sink port aren't present
+ /// </summary>
+ /// <param name="userId">Optinal user uid for displaying popups</param>
+ /// <param name="sourceUid">The source uid</param>
+ /// <param name="sinkUid">The sink uid</param>
+ /// <param name="links">List of source and sink ids to link</param>
+ /// <param name="sourceComponent"></param>
+ /// <param name="sinkComponent"></param>
+ public void SaveLinks(
+ EntityUid? userId,
+ EntityUid sourceUid,
+ EntityUid sinkUid,
+ List<(string source, string sink)> links,
+ DeviceLinkSourceComponent? sourceComponent = null,
+ DeviceLinkSinkComponent? sinkComponent = null)
+ {
+ if (!Resolve(sourceUid, ref sourceComponent) || !Resolve(sinkUid, ref sinkComponent))
+ return;
+
+ if (sourceComponent.Ports == null || sinkComponent.Ports == null)
+ return;
+
+ if (!InRange(sourceUid, sinkUid, sourceComponent.Range))
+ {
+ if (userId != null)
+ _popupSystem.PopupCursor(Loc.GetString("signal-linker-component-out-of-range"), userId.Value);
+
+ return;
+ }
+
+ RemoveSinkFromSource(sourceUid, sinkUid, sourceComponent);
+ foreach (var (source, sink) in links)
+ {
+ if (!sourceComponent.Ports.Contains(source) || !sinkComponent.Ports.Contains(sink))
+ continue;
+
+ if (!CanLink(userId, sourceUid, sinkUid, source, sink, false, sourceComponent))
+ continue;
+
+ sourceComponent.Outputs.GetOrNew(source).Add(sinkUid);
+ sourceComponent.LinkedPorts.GetOrNew(sinkUid).Add((source, sink));
+
+ SendNewLinkEvent(userId, sourceUid, source, sinkUid, sink);
+ }
+
+ if (links.Count > 0)
+ sinkComponent.LinkedSources.Add(sourceUid);
+ }
+
+ /// <summary>
+ /// Removes all links between a source and a sink
+ /// </summary>
+ public void RemoveSinkFromSource(
+ EntityUid sourceUid,
+ EntityUid sinkUid,
+ DeviceLinkSourceComponent? sourceComponent = null,
+ DeviceLinkSinkComponent? sinkComponent = null)
+ {
+ if (!Resolve(sourceUid, ref sourceComponent, false) || !Resolve(sinkUid, ref sinkComponent, false))
+ return;
+
+ if (sourceComponent.LinkedPorts.TryGetValue(sinkUid, out var ports))
+ {
+ foreach (var (sourcePort, sinkPort) in ports)
+ {
+ RaiseLocalEvent(sourceUid, new PortDisconnectedEvent(sourcePort));
+ RaiseLocalEvent(sinkUid, new PortDisconnectedEvent(sinkPort));
+ }
+ }
+
+ sinkComponent.LinkedSources.Remove(sourceUid);
+ sourceComponent.LinkedPorts.Remove(sinkUid);
+ var outputLists = sourceComponent.Outputs.Values;
+ foreach (var outputList in outputLists)
+ {
+ outputList.Remove(sinkUid);
+ }
+
+ }
+
+ /// <summary>
+ /// Adds or removes a link depending on if it's already present
+ /// </summary>
+ /// <returns>True if the link was successfully added or removed</returns>
+ public bool ToggleLink(
+ EntityUid? userId,
+ EntityUid sourceUid,
+ EntityUid sinkUid,
+ string source,
+ string sink,
+ DeviceLinkSourceComponent? sourceComponent = null,
+ DeviceLinkSinkComponent? sinkComponent = null)
+ {
+ if (!Resolve(sourceUid, ref sourceComponent) || !Resolve(sinkUid, ref sinkComponent))
+ return false;
+
+ if (sourceComponent.Ports == null || sinkComponent.Ports == null)
+ return false;
+
+ var outputs = sourceComponent.Outputs.GetOrNew(source);
+ var linkedPorts = sourceComponent.LinkedPorts.GetOrNew(sinkUid);
+
+ if (linkedPorts.Contains((source, sink)))
+ {
+ RaiseLocalEvent(sourceUid, new PortDisconnectedEvent(source));
+ RaiseLocalEvent(sinkUid, new PortDisconnectedEvent(sink));
+
+ outputs.Remove(sinkUid);
+ linkedPorts.Remove((source, sink));
+
+ if (linkedPorts.Count != 0)
+ return true;
+
+ sourceComponent.LinkedPorts.Remove(sinkUid);
+ sinkComponent.LinkedSources.Remove(sourceUid);
+ CreateLinkPopup(userId, sourceUid, source, sinkUid, sink, true);
+ }
+ else
+ {
+ if (!sourceComponent.Ports.Contains(source) || !sinkComponent.Ports.Contains(sink))
+ return false;
+
+ if (!CanLink(userId, sourceUid, sinkUid, source, sink, true, sourceComponent))
+ return false;
+
+ outputs.Add(sinkUid);
+ linkedPorts.Add((source, sink));
+ sinkComponent.LinkedSources.Add(sourceUid);
+
+ SendNewLinkEvent(userId, sourceUid, source, sinkUid, sink);
+ CreateLinkPopup(userId, sourceUid, source, sinkUid, sink, false);
+ }
+
+ return true;
+ }
+
+ /// <summary>
+ /// Checks if a source and a sink can be linked by allowing other systems to veto the link
+ /// and by optionally checking if they are in range of each other
+ /// </summary>
+ /// <returns></returns>
+ private bool CanLink(
+ EntityUid? userId,
+ EntityUid sourceUid,
+ EntityUid sinkUid,
+ string source,
+ string sink,
+ bool checkRange = true,
+ DeviceLinkSourceComponent? sourceComponent = null)
+ {
+ if (!Resolve(sourceUid, ref sourceComponent))
+ return false;
+
+ if (checkRange && !InRange(sourceUid, sinkUid, sourceComponent.Range))
+ {
+ if (userId.HasValue)
+ _popupSystem.PopupCursor(Loc.GetString("signal-linker-component-out-of-range"), userId.Value);
+
+ return false;
+ }
+
+ var linkAttemptEvent = new LinkAttemptEvent(userId, sourceUid, source, sinkUid, sink);
+
+ RaiseLocalEvent(sourceUid, linkAttemptEvent, true);
+ if (linkAttemptEvent.Cancelled && userId.HasValue)
+ {
+ _popupSystem.PopupCursor(Loc.GetString("signal-linker-component-connection-refused", ("machine", source)), userId.Value);
+ return false;
+ }
+
+ RaiseLocalEvent(sinkUid, linkAttemptEvent, true);
+ if (linkAttemptEvent.Cancelled && userId.HasValue)
+ {
+ _popupSystem.PopupCursor(Loc.GetString("signal-linker-component-connection-refused", ("machine", source)), userId.Value);
+ return false;
+ }
+
+ return !linkAttemptEvent.Cancelled;
+ }
+
+ private bool InRange(EntityUid sourceUid, EntityUid sinkUid, float range)
+ {
+ return Transform(sourceUid).MapPosition.InRange(Transform(sinkUid).MapPosition, range);
+ }
+
+ private void SendNewLinkEvent(EntityUid? user, EntityUid sourceUid, string source, EntityUid sinkUid, string sink)
+ {
+ var newLinkEvent = new NewLinkEvent(user, sourceUid, source, sinkUid, sink);
+ RaiseLocalEvent(sourceUid, newLinkEvent);
+ RaiseLocalEvent(sinkUid, newLinkEvent);
+ }
+
+ private void CreateLinkPopup(EntityUid? userId, EntityUid sourceUid, string source, EntityUid sinkUid, string sink, bool removed)
+ {
+ if (!userId.HasValue)
+ return;
+
+ var locString = removed ? "signal-linker-component-unlinked-port" : "signal-linker-component-linked-port";
+
+ _popupSystem.PopupCursor(Loc.GetString(locString, ("machine1", sourceUid), ("port1", PortName<SourcePortPrototype>(source)),
+ ("machine2", sinkUid), ("port2", PortName<SinkPortPrototype>(sink))), userId.Value, PopupType.Medium);
+ }
+ #endregion
+}
ο»Ώusing Robust.Shared.Serialization;
-namespace Content.Shared.MachineLinking
+namespace Content.Shared.DeviceLinking
{
[Serializable, NetSerializable]
public enum TwoWayLeverVisuals : byte
ο»Ώusing Robust.Shared.GameStates;
using Robust.Shared.Serialization;
-namespace Content.Shared.DeviceNetwork;
+namespace Content.Shared.DeviceNetwork.Components;
[RegisterComponent]
[NetworkedComponent]
+using Content.Shared.DeviceLinking;
+using Content.Shared.DeviceNetwork.Systems;
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
-namespace Content.Shared.DeviceNetwork;
+namespace Content.Shared.DeviceNetwork.Components;
[RegisterComponent]
[NetworkedComponent]
[Access(typeof(SharedNetworkConfiguratorSystem))]
public sealed class NetworkConfiguratorComponent : Component
{
+ /// <summary>
+ /// Determines whether the configurator is in linking mode or list mode
+ /// </summary>
+ [DataField("linkModeActive")]
+ [ViewVariables(VVAccess.ReadWrite)]
+ public bool LinkModeActive = false;
+
/// <summary>
/// The entity containing a <see cref="DeviceListComponent"/> this configurator is currently interacting with
/// </summary>
public EntityUid? ActiveDeviceList = null;
/// <summary>
- /// The list of devices stored in the configurator-
+ /// The entity containing a <see cref="DeviceLinkSourceComponent"/> or <see cref="DeviceLinkSinkComponent"/> this configurator is currently interacting with.<br/>
+ /// If this is set the configurator is in linking mode.
+ /// </summary>
+ [DataField("activeDeviceLink")]
+ public EntityUid? ActiveDeviceLink = null;
+
+ /// <summary>
+ /// The target device this configurator is currently linking with the <see cref="ActiveDeviceLink"/>
+ /// </summary>
+ [DataField("deviceLinkTarget")]
+ public EntityUid? DeviceLinkTarget = null;
+
+ /// <summary>
+ /// The list of devices stored in the configurator
/// </summary>
[DataField("devices")]
public Dictionary<string, EntityUid> Devices = new();
+ [DataField("useDelay")]
+ [ViewVariables(VVAccess.ReadWrite)]
+ public TimeSpan UseDelay = TimeSpan.FromSeconds(0.5);
+
+ [DataField("lastUseAttempt", customTypeSerializer:typeof(TimeOffsetSerializer))]
+ [ViewVariables(VVAccess.ReadWrite)]
+ public TimeSpan LastUseAttempt;
+
[DataField("soundNoAccess")]
public SoundSpecifier SoundNoAccess = new SoundPathSpecifier("/Audio/Machines/custom_deny.ogg");
+
+ [DataField("soundSwitchMode")]
+ public SoundSpecifier SoundSwitchMode = new SoundPathSpecifier("/Audio/Machines/beep.ogg");
}
[Serializable, NetSerializable]
public sealed class NetworkConfiguratorComponentState : ComponentState
{
public readonly EntityUid? ActiveDeviceList;
+ public readonly bool LinkModeActive;
- public NetworkConfiguratorComponentState(EntityUid? activeDeviceList)
+ public NetworkConfiguratorComponentState(EntityUid? activeDeviceList, bool linkModeActive)
{
ActiveDeviceList = activeDeviceList;
+ LinkModeActive = linkModeActive;
}
}
public enum NetworkConfiguratorUiKey
{
List,
- Configure
+ Configure,
+ Link
}
[Serializable, NetSerializable]
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;
+ }
+}
-ο»Ώusing Robust.Shared.Serialization;
+ο»Ώusing Content.Shared.DeviceLinking;
+using Robust.Shared.Serialization;
namespace Content.Shared.DeviceNetwork;
DeviceList = deviceList;
}
}
+
+[Serializable, NetSerializable]
+public sealed class DeviceLinkUserInterfaceState : BoundUserInterfaceState
+{
+ public readonly List<SourcePortPrototype> Sources;
+ public readonly List<SinkPortPrototype> Sinks;
+ public readonly HashSet<(string source, string sink)> Links;
+ public readonly List<(string source, string sink)>? Defaults;
+ public readonly string SourceAddress;
+ public readonly string SinkAddress;
+
+ public DeviceLinkUserInterfaceState(List<SourcePortPrototype> sources, List<SinkPortPrototype> sinks, HashSet<(string source, string sink)> links, string sourceAddress, string sinkAddress, List<(string source, string sink)>? defaults = default)
+ {
+ Links = links;
+ SourceAddress = sourceAddress;
+ SinkAddress = sinkAddress;
+ Defaults = defaults;
+ Sources = sources;
+ Sinks = sinks;
+ }
+}
using System.Linq;
+using Content.Shared.DeviceNetwork.Components;
using Robust.Shared.GameStates;
namespace Content.Shared.DeviceNetwork;
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
{
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,
}
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
+}
+++ /dev/null
-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;
- }
- }
-}
+++ /dev/null
-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;
- }
- }
-}
- 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/"
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
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
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
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
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
+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.
- id: Wirecutter
- id: Welder
- id: Multitool
+ - id: NetworkConfigurator
- type: entity
id: ClothingBeltChiefEngineerFilled
- id: WelderExperimental
- id: Multitool
- id: CableApcStack
+ - id: NetworkConfigurator
- type: entity
id: ClothingBeltSecurityFilled
id: Fax
name: device-frequency-prototype-name-fax
frequency: 2640
+
+- type: deviceFrequency
+ id: BasicDevice
+ name: device-frequency-prototype-name-basic-device
+ frequency: 1280
--- /dev/null
+- 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
--- /dev/null
+- 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 ]
- type: Clothing
sprite: Clothing/Belt/utility.rsi
- type: Storage
+ capacity: 45
# TODO: Fill this out more.
whitelist:
tags:
- CigPack
- Radio
- HolofanProjector
+ - Multitool
- AppraisalTool
components:
- AirlockPainter
- - SignalLinker
+ - NetworkConfigurator
- RCD
- RCDAmmo
- Welder
- Wrench
multitool:
whitelist:
- components:
- - SignalLinker
+ tags:
+ - Multitool
sprite: Clothing/Belt/belt_overlay.rsi
- type: Appearance
- type: Clothing
sprite: Clothing/Belt/ce.rsi
- type: Storage
- capacity: 100
+ capacity: 105
# TODO: Fill this out more.
whitelist:
tags:
- CigPack
- Radio
- HolofanProjector
+ - Multitool
- AppraisalTool
components:
- AirlockPainter
- - SignalLinker
+ - NetworkConfigurator
- RCD
- RCDAmmo
- Welder
- Screwdriver
multitool:
whitelist:
- components:
- - SignalLinker
+ tags:
+ - Multitool
wrench:
whitelist:
tags:
- type: UseDelay
- type: StaticPrice
price: 40
- - type: SignalTransmitter
- outputs:
- Pressed: []
+ - type: DeviceLinkSource
+ ports:
+ - Pressed
- type: Tag
tags:
- Payload
- type: PayloadTrigger
components:
- type: TriggerOnSignal
- - type: SignalReceiver
+ - type: DeviceNetwork
+ deviceNetId: Wireless
+ receiveFrequencyId: BasicDevice
+ - type: WirelessNetworkConnection
+ range: 200
+ - type: DeviceLinkSink
- type: StaticPrice
price: 40
- type: Tool
qualities:
- Pulsing
- - type: SignalLinker
+ - type: Tag
+ tags:
+ - Multitool
- type: entity
name: cowelding tool
- type: Tool
qualities:
- Pulsing
- - type: SignalLinker
- - type: NetworkConfigurator
- - type: ActivatableUI
- key: enum.NetworkConfiguratorUiKey.List
- inHandsOnly: true
- - type: UserInterface
- interfaces:
- - key: enum.SignalLinkerUiKey.Key
- type: SignalPortSelectorBoundUserInterface
- - key: enum.NetworkConfiguratorUiKey.List
- type: NetworkConfiguratorBoundUserInterface
- - key: enum.NetworkConfiguratorUiKey.Configure
- type: NetworkConfiguratorBoundUserInterface
- type: Tag
tags:
+ - Multitool
- DroneUsable
- type: PhysicalComposition
materialComposition:
- type: StaticPrice
price: 60
+- type: entity
+ name: network configurator
+ parent: BaseItem
+ id: NetworkConfigurator
+ description: A tool for linking devices together. Has two modes, a list mode for mass linking devices and a linking mode for advanced device linking.
+ components:
+ - type: EmitSoundOnLand
+ sound:
+ path: /Audio/Items/multitool_drop.ogg
+ - type: Sprite
+ sprite: Objects/Tools/network_configurator.rsi
+ layers:
+ - state: icon
+ - state: mode-list
+ map: ["enum.NetworkConfiguratorLayers.ModeLight"]
+ shader: unshaded
+ - type: Item
+ size: 5
+ - type: Clothing
+ sprite: Objects/Tools/network_configurator.rsi
+ quickEquip: false
+ slots:
+ - Belt
+ - type: Appearance
+ - type: GenericVisualizer
+ visuals:
+ enum.NetworkConfiguratorVisuals.Mode:
+ enum.NetworkConfiguratorLayers.ModeLight:
+ True: { state: mode-link }
+ False: { state: mode-list }
+ - type: NetworkConfigurator
+ - type: ActivatableUI
+ key: enum.NetworkConfiguratorUiKey.List
+ inHandsOnly: true
+ - type: UserInterface
+ interfaces:
+ - key: enum.NetworkConfiguratorUiKey.List
+ type: NetworkConfiguratorBoundUserInterface
+ - key: enum.NetworkConfiguratorUiKey.Configure
+ type: NetworkConfiguratorBoundUserInterface
+ - key: enum.NetworkConfiguratorUiKey.Link
+ type: NetworkConfiguratorBoundUserInterface
+ - type: Tag
+ tags:
+ - DroneUsable
+ - type: StaticPrice
+ price: 60
+
- type: entity
name: power drill
parent: BaseItem
- type: Item
sprite: Objects/Tools/omnitool.rsi
size: 20
+ - type: Tag
+ tags:
+ - Multitool
- type: TilePrying
- - type: SignalLinker
- requiredQuality: Pulsing
- - type: UserInterface
- interfaces:
- - key: enum.SignalLinkerUiKey.Key
- type: SignalPortSelectorBoundUserInterface
- type: Tool
qualities:
- Screwing
startOnStick: true
canToggleStartOnStick: true
- type: TriggerOnSignal
- - type: SignalReceiver
- inputs:
- Trigger: []
+ - type: DeviceLinkSink
+ ports:
+ - Trigger
- type: Sticky
stickDelay: 5
unstickDelay: 5
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
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
- 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
- type: ApcPowerReceiver
- type: ExtensionCableReceiver
- type: DeviceNetwork
- deviceNetId: Apc
+ deviceNetId: Wireless
receiveFrequencyId: SmartLight
- - type: ApcNetworkConnection
+ - type: WirelessNetworkConnection
+ range: 200
+ - type: DeviceLinkSink
+ ports:
+ - On
+ - Off
+ - Toggle
- type: Appearance
- type: PoweredLightVisuals
blinkingSound:
path: "/Audio/Machines/light_tube_on.ogg"
- - type: SignalReceiver
- inputs:
- On: []
- Off: []
- Toggle: []
- type: entity
id: Poweredlight
- type: ApcPowerReceiver
- type: ExtensionCableReceiver
- type: DeviceNetwork
- deviceNetId: Apc
+ deviceNetId: Wireless
receiveFrequencyId: SmartLight
- - type: ApcNetworkConnection
+ - type: WirelessNetworkConnection
+ range: 200
- type: Appearance
- type: PoweredLightVisuals
- - type: SignalReceiver
- inputs:
- On: []
- Off: []
- Toggle: []
+ - type: DeviceLinkSink
+ ports:
+ - On
+ - Off
+ - Toggle
- type: entity
id: PoweredSmallLight
- type: ApcPowerReceiver
- type: ExtensionCableReceiver
- type: DeviceNetwork
- deviceNetId: Apc
+ deviceNetId: Wireless
receiveFrequencyId: SmartLight
- - type: ApcNetworkConnection
+ - type: WirelessNetworkConnection
+ range: 200
+ - type: DeviceLinkSink
+ ports:
+ - On
+ - Off
+ - Toggle
- type: Construction
graph: LightFixture
node: groundLight
- type: PoweredLightVisuals
blinkingSound:
path: "/Audio/Machines/light_tube_on.ogg"
- - type: SignalReceiver
- inputs:
- On: []
- Off: []
- Toggle: []
- type: entity
id: PoweredLightPostSmall
- type: DeviceList
- type: DeviceNetwork
deviceNetId: Wired
- - type: SignalTransmitter
- transmissionRange: 5
- outputs:
- ArtifactAnalyzerSender: []
+ - type: DeviceLinkSource
+ range: 5
+ ports:
+ - ArtifactAnalyzerSender
- type: ActivatableUI
key: enum.ArtifactAnalzyerUiKey.Key
- type: UserInterface
radius: 1.5
energy: 1.6
color: "#1f8c28"
- - type: SignalTransmitter
- transmissionRange: 4
- outputs:
- MedicalScannerSender: []
- CloningPodSender: []
+ - type: DeviceLinkSource
+ range: 4
+ ports:
+ - MedicalScannerSender
+ - CloningPodSender
- type: ActivatableUI
key: enum.CloningConsoleUiKey.Key
- type: UserInterface
- type: ArtifactAnalyzer
- type: DeviceNetwork
deviceNetId: Wired
+ receiveFrequencyId: BasicDevice
- type: DeviceList
- - type: SignalReceiver
- inputs:
- ArtifactAnalyzerReceiver: []
+ - type: DeviceLinkSink
+ ports:
+ - ArtifactAnalyzerReceiver
- type: Machine
board: ArtifactAnalyzerMachineCircuitboard
- type: PointLight
- type: DeviceList
- type: DeviceNetwork
deviceNetId: Wired
+ receiveFrequencyId: BasicDevice
+ - type: DeviceLinkSink
+ ports:
+ - CloningPodReceiver
- type: Sprite
netsync: false
sprite: Structures/Machines/cloning.rsi
- machine_board
- machine_parts
- clonepod-bodyContainer
- - type: SignalReceiver
- inputs:
- CloningPodReceiver: []
- type: EmptyOnMachineDeconstruct
containers:
- clonepod-bodyContainer
- Wrench
- Crowbar
- Multitool
+ - NetworkConfigurator
- AirlockPainter
- CableStack
- HandheldGPSBasic
- type: MedicalScanner
- type: DeviceNetwork
deviceNetId: Wired
+ receiveFrequencyId: BasicDevice
- type: DeviceList
+ - type: DeviceLinkSink
+ ports:
+ - MedicalScannerReceiver
- type: Sprite
netsync: false
sprite: Structures/Machines/scanner.rsi
scanner-bodyContainer: !type:ContainerSlot
machine_board: !type:Container
machine_parts: !type:Container
- - type: SignalReceiver
- inputs:
- MedicalScannerReceiver: []
- type: Damageable
damageContainer: Inorganic
damageModifierSet: StrongMetallic
- MidImpassable
- LowImpassable
hard: False
- - type: SignalReceiver
- inputs:
- Reverse: []
- Forward: []
- Off: []
+ - type: DeviceLinkSink
+ ports:
+ - Reverse
+ - Forward
+ - Off
- type: Transform
anchored: true
noRot: false
- type: PipeColorVisuals
- type: GasValve
- type: SignalControlledValve
- - type: SignalReceiver
- inputs:
- Open: []
- Close: []
- Toggle: []
+ - type: DeviceNetwork
+ deviceNetId: Wireless
+ receiveFrequencyId: BasicDevice
+ - type: WirelessNetworkConnection
+ range: 200
+ - type: DeviceLinkSink
+ ports:
+ - Open
+ - Close
+ - Toggle
- type: NodeContainer
nodes:
inlet:
inlet: inlet
outlet: outlet
canLink: true
- - type: SignalReceiver
- inputs:
- Pressurize: []
- Depressurize: []
+ - type: DeviceNetwork
+ deviceNetId: Wireless
+ receiveFrequencyId: BasicDevice
+ - type: WirelessNetworkConnection
+ range: 200
+ - type: DeviceLinkSink
+ ports:
+ - Pressurize
+ - Depressurize
- type: Construction
graph: GasBinary
node: dualportventpump
graph: SignalSwitchGraph
node: SignalSwitchNode
- type: Fixtures
- - type: SignalTransmitter
- outputs:
- On: []
- Off: []
+ - type: DeviceNetwork
+ deviceNetId: Wireless
+ - type: WirelessNetworkConnection
+ range: 200
+ - type: DeviceLinkSource
+ ports:
+ - On
+ - Off
- type: entity
id: SignalButton
graph: SignalButtonGraph
node: SignalButtonNode
- type: Fixtures
- - type: SignalTransmitter
- outputs:
- Pressed: []
+ - type: DeviceNetwork
+ deviceNetId: Wireless
+ - type: WirelessNetworkConnection
+ range: 200
+ - type: DeviceLinkSource
+ ports:
+ - Pressed
- type: Damageable
damageContainer: Inorganic
damageModifierSet: Metallic
- type: Construction
graph: LeverGraph
node: LeverNode
- - type: SignalTransmitter
- outputs:
- Left: []
- Right: []
- Middle: []
+ - type: DeviceNetwork
+ deviceNetId: Wireless
+ - type: WirelessNetworkConnection
+ range: 200
+ - type: DeviceLinkSource
+ ports:
+ - Left
+ - Right
+ - Middle
- 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:
- LowImpassable
hard: False
- type: Conveyor
- - type: SignalReceiver
- inputs:
- Reverse: []
- Forward: []
- Off: []
+ - type: DeviceLinkSink
+ ports:
+ - Reverse
+ - Forward
+ - Off
- type: Appearance
- type: GenericVisualizer
visuals:
- Construction
- Power
- ShuttleCraft
+ - Networking
- type: guideEntry
id: Construction
name: guide-entry-shuttle-craft
text: "/ServerInfo/Guidebook/Shuttlecraft.xml"
+- type: guideEntry
+ id: Networking
+ name: guide-entry-networking
+ text: "/ServerInfo/Guidebook/Networking.xml"
+ children:
+ - NetworkConfigurator
+
+- type: guideEntry
+ id: NetworkConfigurator
+ name: guide-entry-network-configurator
+ text: "/ServerInfo/Guidebook/Network_Configurator.xml"
+
+
- type: guideEntry
id: Power
name: guide-entry-power
Steel: 200
Plastic: 200
+- type: latheRecipe
+ id: NetworkConfigurator
+ result: NetworkConfigurator
+ completetime: 2
+ materials:
+ Steel: 200
+ Plastic: 200
+
- type: latheRecipe
id: PowerDrill
result: PowerDrill
- Item
permanentComponents:
- type: TilePrying
- - type: SignalLinker
- requiredQuality: Pulsing
- type: UserInterface
interfaces:
- key: enum.SignalLinkerUiKey.Key
- Screwing
speed: 2 # Very powerful multitool to balance out the desire to sell or scrap for points
useSound: /Audio/Items/drill_use.ogg
+ - type: Tag
+ tags:
+ - Multitool
- type: MultipleTool
statusShowBehavior: true
entries:
- type: Tag
id: Mop
+- type: Tag
+ id: Multitool
+
- type: Tag
id: NoSpinOnThrow
--- /dev/null
+<Document>
+# Network Configurator
+The network configurator allows you to manipulate device lists and link devices together.
+<Box>
+ <GuideEntityEmbed Entity="NetworkConfigurator"/>
+</Box>
+The configurator has two modes: List and Link. You can press [color=gray]Alt+Z[/color] or [color=gray]Alt+Y[/color] to switch between them.
+
+## List Mode
+In list mode you can click on network devices to save them on the configurator and then on a network device that has a device list like the [color=#a4885c]Air Alarm[/color].
+
+When clicking on a device like the Air Alarm, a UI will open displaying the list currently saved on the device and buttons to manipulate that list.
+
+You can:
+- Replace the current list with the one saved on the configurator
+- Add the list on the configurator to the current one
+- Clear the current list
+- Copy the current list to the configurator
+- Visualize the connections to the devices on the current list
+
+Pressing [color=gray]z[/color] or [color=gray]y[/color] opens the list saved on the configurator where you can remove saved devices.
+
+## Link Mode
+With link mode you can click on a device that is capable of device linking and click on any other device that is either
+a sink or source.
+
+For example, first clicking on a source like a [color=#a4885c]signal button[/color] and then on sink like a
+[color=#a4885c]small light[/color] opens a UI that displays the source ports on the left side and the sink ports on the right.
+
+Now you can eiter click [color=gray]link defaults[/color] to link the default ports for a source + sink combination or press on a source and then a sink port to connect them.
+
+An example of a default link for the aformentioned combinaton of devices would be:
+<Box>
+ [color=cyan]Pressed π Toggle[/color]
+</Box>
+When you're done connecting the ports you want you can click on [color=gray]ok[/color] to close the UI.
+
+You can quickly link multiple devices to their default port by first clicking on a device that can be linked and then using [color=gray]alt+left mouse button[/color] on the devices you want to link together.
+</Document>
--- /dev/null
+<Document>
+# Networking
+Some devices on the station need to communicate with each other, and they do this by utilizing device networking.
+With networking machines and devices can send arbitrary data between each other.
+There are multiple networks that get used, such as the wireless and wired network.
+Each network device has a frequency it receives on. PDAs for example, use the frequency: [color=green]220.2[/color]
+
+## Device Lists
+Some devices need to know what other devices to communicate with specifically.
+<Box>
+ <GuideEntityEmbed Entity="AirAlarm"/>
+</Box>
+Air alarms for example require you to tell it which vents, scrubbers, sensors, and firelocks to interact with.
+You do that by using the Network Configurator.
+
+## Linking
+If devices basic or still more advanced but need finer control of how and what connects to each other they will generally use device linking.
+ <Box>
+ <GuideEntityEmbed Entity="SignalSwitch"/>
+ <GuideEntityEmbed Entity="Airlock"/>
+ </Box>
+With linking you can connect the outputs of a device like [color=gray]On[/color] or [color=gray]Off[/color] with the inputs of a device like the airlocks
+[color=gray]Open[/color] or [color=gray]Close[/color] inputs.
+The Network Configurator is also used for linking devices together.
+</Document>
--- /dev/null
+{
+ "version": 1,
+ "license": "CC-BY-SA-3.0",
+ "copyright": "Made by Julian Giebel <juliangiebel@live.de> for ss14",
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "states": [
+ {
+ "name": "icon"
+ },
+ {
+ "name": "mode-list"
+ },
+ {
+ "name": "mode-link"
+ },
+ {
+ "name": "inhand-left",
+ "directions": 4
+ },
+ {
+ "name": "inhand-right",
+ "directions": 4
+ },
+ {
+ "name": "equipped-BELT",
+ "directions": 4
+ }
+ ]
+}