diff --git a/Content.Client/NetworkConfigurator/NetworkConfiguratorActiveLinkOverlayComponent.cs b/Content.Client/NetworkConfigurator/NetworkConfiguratorActiveLinkOverlayComponent.cs
new file mode 100644
index 0000000000..b74f8f9af5
--- /dev/null
+++ b/Content.Client/NetworkConfigurator/NetworkConfiguratorActiveLinkOverlayComponent.cs
@@ -0,0 +1,15 @@
+namespace Content.Client.NetworkConfigurator;
+
+///
+/// This is used for...
+///
+[RegisterComponent]
+public sealed class NetworkConfiguratorActiveLinkOverlayComponent : Component
+{
+ ///
+ /// The entities linked to this network configurator.
+ /// This could just... couldn't this just be grabbed
+ /// if DeviceList was shared?
+ ///
+ public HashSet Devices = new();
+}
diff --git a/Content.Client/NetworkConfigurator/NetworkConfiguratorBoundUserInterface.cs b/Content.Client/NetworkConfigurator/NetworkConfiguratorBoundUserInterface.cs
index 86490b5147..168615ca0b 100644
--- a/Content.Client/NetworkConfigurator/NetworkConfiguratorBoundUserInterface.cs
+++ b/Content.Client/NetworkConfigurator/NetworkConfiguratorBoundUserInterface.cs
@@ -1,16 +1,24 @@
using Content.Shared.DeviceNetwork;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
+using Robust.Client.UserInterface.Controls;
namespace Content.Client.NetworkConfigurator;
public sealed class NetworkConfiguratorBoundUserInterface : BoundUserInterface
{
+ [Dependency] private readonly IEntityManager _entityManager = default!;
private NetworkConfiguratorListMenu? _listMenu;
private NetworkConfiguratorConfigurationMenu? _configurationMenu;
+ private NetworkConfiguratorSystem _netConfig;
+ private DeviceListSystem _deviceList;
+
public NetworkConfiguratorBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
{
+ IoCManager.InjectDependencies(this);
+ _netConfig = _entityManager.System();
+ _deviceList = _entityManager.System();
}
public void OnRemoveButtonPressed(string address)
@@ -38,12 +46,31 @@ public sealed class NetworkConfiguratorBoundUserInterface : BoundUserInterface
//_configurationMenu.Edit.OnPressed += _ => OnConfigButtonPressed(NetworkConfiguratorButtonKey.Edit);
_configurationMenu.Clear.OnPressed += _ => OnConfigButtonPressed(NetworkConfiguratorButtonKey.Clear);
_configurationMenu.Copy.OnPressed += _ => OnConfigButtonPressed(NetworkConfiguratorButtonKey.Copy);
- _configurationMenu.Show.OnPressed += _ => OnConfigButtonPressed(NetworkConfiguratorButtonKey.Show);
+ _configurationMenu.Show.OnPressed += OnShowPressed;
+ _configurationMenu.Show.Pressed = _netConfig.ConfiguredListIsTracked(Owner.Owner);
_configurationMenu.OpenCentered();
break;
}
}
+ private void OnShowPressed(BaseButton.ButtonEventArgs args)
+ {
+ if (!args.Button.Pressed)
+ {
+ _netConfig.ToggleVisualization(Owner.Owner, false);
+ return;
+ }
+
+ if (_entityManager.GetComponent(Owner.Owner).EntityLifeStage == EntityLifeStage.Initialized)
+ {
+ // We're in mapping mode. Do something hacky.
+ SendMessage(new ManualDeviceListSyncMessage(null, null));
+ return;
+ }
+
+ _netConfig.ToggleVisualization(Owner.Owner, true);
+ }
+
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
@@ -52,6 +79,25 @@ public sealed class NetworkConfiguratorBoundUserInterface : BoundUserInterface
_listMenu?.UpdateState(castState);
}
+ protected override void ReceiveMessage(BoundUserInterfaceMessage message)
+ {
+ base.ReceiveMessage(message);
+
+ if (_configurationMenu == null
+ || _entityManager.GetComponent(Owner.Owner).EntityLifeStage > EntityLifeStage.Initialized
+ || message is not ManualDeviceListSyncMessage cast
+ || cast.Device == null
+ || cast.Devices == null)
+ {
+ return;
+ }
+
+ _netConfig.SetActiveDeviceList(Owner.Owner, cast.Device.Value);
+ _deviceList.UpdateDeviceList(cast.Device.Value, cast.Devices);
+ _netConfig.ToggleVisualization(Owner.Owner, true);
+ _configurationMenu.Show.Pressed = true;
+ }
+
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
diff --git a/Content.Client/NetworkConfigurator/NetworkConfiguratorConfigurationMenu.xaml b/Content.Client/NetworkConfigurator/NetworkConfiguratorConfigurationMenu.xaml
index 0c1affe6a3..7be6c47b7a 100644
--- a/Content.Client/NetworkConfigurator/NetworkConfiguratorConfigurationMenu.xaml
+++ b/Content.Client/NetworkConfigurator/NetworkConfiguratorConfigurationMenu.xaml
@@ -11,7 +11,7 @@
-
+
diff --git a/Content.Client/NetworkConfigurator/NetworkConfiguratorLinkOverlay.cs b/Content.Client/NetworkConfigurator/NetworkConfiguratorLinkOverlay.cs
new file mode 100644
index 0000000000..3cd0df7218
--- /dev/null
+++ b/Content.Client/NetworkConfigurator/NetworkConfiguratorLinkOverlay.cs
@@ -0,0 +1,65 @@
+using Content.Shared.DeviceNetwork;
+using Robust.Client.Graphics;
+using Robust.Shared.Enums;
+using Robust.Shared.Random;
+using Robust.Shared.Utility;
+
+namespace Content.Client.NetworkConfigurator;
+
+public sealed class NetworkConfiguratorLinkOverlay : Overlay
+{
+ [Dependency] private readonly IEntityManager _entityManager = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+ private readonly DeviceListSystem _deviceListSystem;
+
+ private Dictionary _colors = new();
+
+ public override OverlaySpace Space => OverlaySpace.WorldSpace;
+
+ public NetworkConfiguratorLinkOverlay()
+ {
+ IoCManager.InjectDependencies(this);
+
+ _deviceListSystem = _entityManager.System();
+ }
+
+ public void ClearEntity(EntityUid uid)
+ {
+ _colors.Remove(uid);
+ }
+
+ protected override void Draw(in OverlayDrawArgs args)
+ {
+ foreach (var tracker in _entityManager.EntityQuery())
+ {
+ if (_entityManager.Deleted(tracker.Owner) || !_entityManager.TryGetComponent(tracker.Owner, out DeviceListComponent? deviceList))
+ {
+ _entityManager.RemoveComponentDeferred(tracker.Owner);
+ continue;
+ }
+
+ if (!_colors.TryGetValue(tracker.Owner, out var color))
+ {
+ color = new Color(
+ _random.Next(0, 255),
+ _random.Next(0, 255),
+ _random.Next(0, 255));
+ _colors.Add(tracker.Owner, color);
+ }
+
+ var sourceTransform = _entityManager.GetComponent(tracker.Owner);
+
+ foreach (var device in _deviceListSystem.GetAllDevices(tracker.Owner, deviceList))
+ {
+ if (_entityManager.Deleted(device))
+ {
+ continue;
+ }
+
+ var linkTransform = _entityManager.GetComponent(device);
+
+ args.WorldHandle.DrawLine(sourceTransform.WorldPosition, linkTransform.WorldPosition, _colors[tracker.Owner]);
+ }
+ }
+ }
+}
diff --git a/Content.Client/NetworkConfigurator/Systems/DeviceListSystem.cs b/Content.Client/NetworkConfigurator/Systems/DeviceListSystem.cs
new file mode 100644
index 0000000000..b171367e23
--- /dev/null
+++ b/Content.Client/NetworkConfigurator/Systems/DeviceListSystem.cs
@@ -0,0 +1,9 @@
+using System.Linq;
+using Content.Shared.DeviceNetwork;
+using Robust.Client.Graphics;
+
+namespace Content.Client.NetworkConfigurator;
+
+public sealed class DeviceListSystem : SharedDeviceListSystem
+{
+}
diff --git a/Content.Client/NetworkConfigurator/Systems/NetworkConfiguratorSystem.cs b/Content.Client/NetworkConfigurator/Systems/NetworkConfiguratorSystem.cs
new file mode 100644
index 0000000000..e63365af10
--- /dev/null
+++ b/Content.Client/NetworkConfigurator/Systems/NetworkConfiguratorSystem.cs
@@ -0,0 +1,116 @@
+using System.Linq;
+using Content.Client.Actions;
+using Content.Shared.Actions;
+using Content.Shared.Actions.ActionTypes;
+using Content.Shared.DeviceNetwork;
+using Robust.Client.Graphics;
+using Robust.Client.Player;
+using Robust.Shared.Console;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+
+namespace Content.Client.NetworkConfigurator;
+
+public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem
+{
+ [Dependency] private readonly IPlayerManager _playerManager = default!;
+ [Dependency] private readonly IOverlayManager _overlay = default!;
+ [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+ [Dependency] private readonly ActionsSystem _actions = default!;
+
+ private const string Action = "ClearNetworkLinkOverlays";
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(_ => ClearAllOverlays());
+ }
+
+ public bool ConfiguredListIsTracked(EntityUid uid, NetworkConfiguratorComponent? component = null)
+ {
+ return Resolve(uid, ref component)
+ && component.ActiveDeviceList != null
+ && HasComp(component.ActiveDeviceList.Value);
+ }
+
+ ///
+ /// Toggles a device list's (tied to this network configurator) connection visualisation on and off.
+ ///
+ public void ToggleVisualization(EntityUid uid, bool toggle, NetworkConfiguratorComponent? component = null)
+ {
+ if (_playerManager.LocalPlayer == null
+ || _playerManager.LocalPlayer.ControlledEntity == null
+ || !Resolve(uid, ref component)
+ || component.ActiveDeviceList == null)
+ return;
+
+ if (!toggle)
+ {
+ if (_overlay.HasOverlay())
+ {
+ _overlay.GetOverlay().ClearEntity(component.ActiveDeviceList.Value);
+ }
+
+ RemComp(component.ActiveDeviceList.Value);
+ if (!EntityQuery().Any())
+ {
+ _overlay.RemoveOverlay();
+ _actions.RemoveAction(_playerManager.LocalPlayer.ControlledEntity.Value, _prototypeManager.Index(Action));
+ }
+
+
+ return;
+ }
+
+ if (!_overlay.HasOverlay())
+ {
+ _overlay.AddOverlay(new NetworkConfiguratorLinkOverlay());
+ _actions.AddAction(_playerManager.LocalPlayer.ControlledEntity.Value, new InstantAction(_prototypeManager.Index(Action)), null);
+ }
+
+ EnsureComp(component.ActiveDeviceList.Value);
+ }
+
+ public void ClearAllOverlays()
+ {
+ if (!_overlay.HasOverlay())
+ {
+ return;
+ }
+
+ foreach (var tracker in EntityQuery())
+ {
+ RemCompDeferred(tracker.Owner);
+ }
+
+ _overlay.RemoveOverlay();
+
+ if (_playerManager.LocalPlayer?.ControlledEntity != null)
+ {
+ _actions.RemoveAction(_playerManager.LocalPlayer.ControlledEntity.Value, _prototypeManager.Index(Action));
+ }
+ }
+
+ // hacky solution related to mapping
+ public void SetActiveDeviceList(EntityUid tool, EntityUid list, NetworkConfiguratorComponent? component = null)
+ {
+ if (!Resolve(tool, ref component))
+ {
+ return;
+ }
+
+ component.ActiveDeviceList = list;
+ }
+}
+
+public sealed class ClearAllNetworkLinkOverlays : IConsoleCommand
+{
+ public string Command => "clearnetworklinkoverlays";
+ public string Description => "Clear all network link overlays.";
+ public string Help => Command;
+ public void Execute(IConsoleShell shell, string argStr, string[] args)
+ {
+ IoCManager.Resolve().System().ClearAllOverlays();
+ }
+}
diff --git a/Content.Server/Atmos/Monitor/Systems/AirAlarmSystem.cs b/Content.Server/Atmos/Monitor/Systems/AirAlarmSystem.cs
index 0df260c392..3cb6bc1eb7 100644
--- a/Content.Server/Atmos/Monitor/Systems/AirAlarmSystem.cs
+++ b/Content.Server/Atmos/Monitor/Systems/AirAlarmSystem.cs
@@ -14,6 +14,7 @@ using Content.Shared.Atmos;
using Content.Shared.Atmos.Monitor;
using Content.Shared.Atmos.Monitor.Components;
using Content.Shared.Atmos.Piping.Unary.Components;
+using Content.Shared.DeviceNetwork;
using Content.Shared.Interaction;
using Robust.Server.GameObjects;
using Robust.Shared.Player;
diff --git a/Content.Server/Atmos/Monitor/Systems/FireAlarmSystem.cs b/Content.Server/Atmos/Monitor/Systems/FireAlarmSystem.cs
index 190d8ea2c5..7a274e81ab 100644
--- a/Content.Server/Atmos/Monitor/Systems/FireAlarmSystem.cs
+++ b/Content.Server/Atmos/Monitor/Systems/FireAlarmSystem.cs
@@ -5,6 +5,7 @@ using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems;
using Content.Shared.AlertLevel;
using Content.Shared.Atmos.Monitor;
+using Content.Shared.DeviceNetwork;
using Content.Shared.Interaction;
using Content.Shared.Emag.Systems;
using Robust.Server.GameObjects;
diff --git a/Content.Server/DeviceNetwork/Systems/DeviceListSystem.cs b/Content.Server/DeviceNetwork/Systems/DeviceListSystem.cs
index 4c73774f0c..8041a5a88f 100644
--- a/Content.Server/DeviceNetwork/Systems/DeviceListSystem.cs
+++ b/Content.Server/DeviceNetwork/Systems/DeviceListSystem.cs
@@ -1,12 +1,13 @@
using System.Linq;
using Content.Server.DeviceNetwork.Components;
+using Content.Shared.DeviceNetwork;
using Content.Shared.Interaction;
using JetBrains.Annotations;
namespace Content.Server.DeviceNetwork.Systems;
[UsedImplicitly]
-public sealed class DeviceListSystem : EntitySystem
+public sealed class DeviceListSystem : SharedDeviceListSystem
{
public override void Initialize()
{
@@ -15,23 +16,6 @@ public sealed class DeviceListSystem : EntitySystem
SubscribeLocalEvent(OnBeforePacketSent);
}
- ///
- /// Replaces or merges the current device list with the given one
- ///
- public void UpdateDeviceList(EntityUid uid, IEnumerable devices, bool merge = false, DeviceListComponent? deviceList = null)
- {
- if (!Resolve(uid, ref deviceList))
- return;
-
- if (!merge)
- deviceList.Devices.Clear();
-
- var devicesList = devices.ToList();
- deviceList.Devices.UnionWith(devicesList);
-
- RaiseLocalEvent(uid, new DeviceListUpdateEvent(devicesList));
- }
-
///
/// Gets the given device list as a dictionary
///
@@ -54,16 +38,6 @@ public sealed class DeviceListSystem : EntitySystem
return devices;
}
- ///
- /// Toggles the given device lists connection visualisation on and off.
- /// TODO: Implement an overlay that draws a line between the given entity and the entities in the device list
- ///
- public void ToggleVisualization(EntityUid uid, bool ensureOff = false, DeviceListComponent? deviceList = null)
- {
- if (!Resolve(uid, ref deviceList))
- return;
- }
-
///
/// Filters the broadcasts recipient list against the device list as either an allow or deny list depending on the components IsAllowList field
///
@@ -95,13 +69,3 @@ public sealed class DeviceListSystem : EntitySystem
args.Cancel();
}
}
-
-public sealed class DeviceListUpdateEvent : EntityEventArgs
-{
- public DeviceListUpdateEvent(List devices)
- {
- Devices = devices;
- }
-
- public List Devices { get; }
-}
diff --git a/Content.Server/DeviceNetwork/Systems/NetworkConfiguratorSystem.cs b/Content.Server/DeviceNetwork/Systems/NetworkConfiguratorSystem.cs
index cb031526fb..1e01de5046 100644
--- a/Content.Server/DeviceNetwork/Systems/NetworkConfiguratorSystem.cs
+++ b/Content.Server/DeviceNetwork/Systems/NetworkConfiguratorSystem.cs
@@ -1,4 +1,5 @@
-using Content.Server.DeviceNetwork.Components;
+using System.Linq;
+using Content.Server.DeviceNetwork.Components;
using Content.Server.UserInterface;
using Content.Shared.Access.Components;
using Content.Shared.Access.Systems;
@@ -11,13 +12,14 @@ using Content.Shared.Popups;
using Content.Shared.Verbs;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
+using Robust.Server.Player;
using Robust.Shared.Audio;
using Robust.Shared.Player;
namespace Content.Server.DeviceNetwork.Systems;
[UsedImplicitly]
-public sealed class NetworkConfiguratorSystem : EntitySystem
+public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem
{
[Dependency] private readonly DeviceListSystem _deviceListSystem = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
@@ -44,6 +46,7 @@ public sealed class NetworkConfiguratorSystem : EntitySystem
SubscribeLocalEvent(OnRemoveDevice);
SubscribeLocalEvent(OnClearDevice);
SubscribeLocalEvent(OnConfigButtonPressed);
+ SubscribeLocalEvent(ManualDeviceListSync);
SubscribeLocalEvent(OnComponentRemoved);
}
@@ -217,6 +220,7 @@ public sealed class NetworkConfiguratorSystem : EntitySystem
return;
configurator.ActiveDeviceList = targetUid;
+ Dirty(configurator);
_uiSystem.GetUiOrNull(configurator.Owner, NetworkConfiguratorUiKey.Configure)?.Open(actor.PlayerSession);
}
@@ -299,10 +303,25 @@ public sealed class NetworkConfiguratorSystem : EntitySystem
UpdateUiState(uid, component);
break;
case NetworkConfiguratorButtonKey.Show:
- _deviceListSystem.ToggleVisualization(component.ActiveDeviceList.Value);
+ // This should be done client-side.
+ // _deviceListSystem.ToggleVisualization(component.ActiveDeviceList.Value);
break;
}
}
+ // hacky solution related to mapping
+ private void ManualDeviceListSync(EntityUid uid, NetworkConfiguratorComponent comp,
+ ManualDeviceListSyncMessage args)
+ {
+ if (comp.ActiveDeviceList == null || args.Session is not IPlayerSession player)
+ {
+ return;
+ }
+
+ var devices = _deviceListSystem.GetAllDevices(comp.ActiveDeviceList.Value).ToHashSet();
+
+ _uiSystem.TrySendUiMessage(uid, NetworkConfiguratorUiKey.Configure, new ManualDeviceListSyncMessage(comp.ActiveDeviceList.Value, devices), player);
+ }
+
#endregion
}
diff --git a/Content.Server/DeviceNetwork/Components/DeviceListComponent.cs b/Content.Shared/DeviceNetwork/Components/DeviceListComponent.cs
similarity index 53%
rename from Content.Server/DeviceNetwork/Components/DeviceListComponent.cs
rename to Content.Shared/DeviceNetwork/Components/DeviceListComponent.cs
index e233930374..6fba5e8145 100644
--- a/Content.Server/DeviceNetwork/Components/DeviceListComponent.cs
+++ b/Content.Shared/DeviceNetwork/Components/DeviceListComponent.cs
@@ -1,9 +1,11 @@
-using Content.Server.DeviceNetwork.Systems;
+using Robust.Shared.GameStates;
+using Robust.Shared.Serialization;
-namespace Content.Server.DeviceNetwork.Components;
+namespace Content.Shared.DeviceNetwork;
[RegisterComponent]
-[Access(typeof(DeviceListSystem))]
+[NetworkedComponent]
+[Access(typeof(SharedDeviceListSystem))]
public sealed class DeviceListComponent : Component
{
///
@@ -26,3 +28,18 @@ public sealed class DeviceListComponent : Component
[DataField("handleIncoming")]
public bool HandleIncomingPackets = false;
}
+
+[Serializable, NetSerializable]
+public sealed class DeviceListComponentState : ComponentState
+{
+ public readonly HashSet Devices;
+ public readonly bool IsAllowList;
+ public readonly bool HandleIncomingPackets;
+
+ public DeviceListComponentState(HashSet devices, bool isAllowList, bool handleIncomingPackets)
+ {
+ Devices = devices;
+ IsAllowList = isAllowList;
+ HandleIncomingPackets = handleIncomingPackets;
+ }
+}
diff --git a/Content.Server/DeviceNetwork/Components/NetworkConfiguratorComponent.cs b/Content.Shared/DeviceNetwork/Components/NetworkConfiguratorComponent.cs
similarity index 58%
rename from Content.Server/DeviceNetwork/Components/NetworkConfiguratorComponent.cs
rename to Content.Shared/DeviceNetwork/Components/NetworkConfiguratorComponent.cs
index 563d2684f1..f443c50fde 100644
--- a/Content.Server/DeviceNetwork/Components/NetworkConfiguratorComponent.cs
+++ b/Content.Shared/DeviceNetwork/Components/NetworkConfiguratorComponent.cs
@@ -1,24 +1,37 @@
-using Content.Server.DeviceNetwork.Systems;
using Robust.Shared.Audio;
+using Robust.Shared.GameStates;
+using Robust.Shared.Serialization;
-namespace Content.Server.DeviceNetwork.Components;
+namespace Content.Shared.DeviceNetwork;
[RegisterComponent]
-[Access(typeof(NetworkConfiguratorSystem))]
+[NetworkedComponent]
+[Access(typeof(SharedNetworkConfiguratorSystem))]
public sealed class NetworkConfiguratorComponent : Component
{
- ///
- /// The list of devices stored in the configurator-
- ///
- [DataField("devices")]
- public Dictionary Devices = new();
-
///
/// The entity containing a this configurator is currently interacting with
///
[DataField("activeDeviceList")]
public EntityUid? ActiveDeviceList = null;
+ ///
+ /// The list of devices stored in the configurator-
+ ///
+ [DataField("devices")]
+ public Dictionary Devices = new();
+
[DataField("soundNoAccess")]
public SoundSpecifier SoundNoAccess = new SoundPathSpecifier("/Audio/Machines/custom_deny.ogg");
}
+
+[Serializable, NetSerializable]
+public sealed class NetworkConfiguratorComponentState : ComponentState
+{
+ public readonly EntityUid? ActiveDeviceList;
+
+ public NetworkConfiguratorComponentState(EntityUid? activeDeviceList)
+ {
+ ActiveDeviceList = activeDeviceList;
+ }
+}
diff --git a/Content.Shared/DeviceNetwork/Systems/SharedDeviceListSystem.cs b/Content.Shared/DeviceNetwork/Systems/SharedDeviceListSystem.cs
new file mode 100644
index 0000000000..ada12eebef
--- /dev/null
+++ b/Content.Shared/DeviceNetwork/Systems/SharedDeviceListSystem.cs
@@ -0,0 +1,72 @@
+using System.Linq;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.DeviceNetwork;
+
+public abstract class SharedDeviceListSystem : EntitySystem
+{
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(GetDeviceListState);
+ SubscribeLocalEvent(HandleDeviceListState);
+ }
+
+ ///
+ /// Updates the device list stored on this entity.
+ ///
+ /// The entity to update.
+ /// The devices to store.
+ /// Whether to merge or replace the devices stored.
+ /// Device list component
+ public void UpdateDeviceList(EntityUid uid, IEnumerable devices, bool merge = false, DeviceListComponent? deviceList = null)
+ {
+ if (!Resolve(uid, ref deviceList))
+ return;
+
+ if (!merge)
+ deviceList.Devices.Clear();
+
+ var devicesList = devices.ToList();
+ deviceList.Devices.UnionWith(devicesList);
+
+ RaiseLocalEvent(uid, new DeviceListUpdateEvent(devicesList));
+
+ Dirty(deviceList);
+ }
+
+ public IEnumerable GetAllDevices(EntityUid uid, DeviceListComponent? component = null)
+ {
+ if (!Resolve(uid, ref component))
+ {
+ return new EntityUid[] { };
+ }
+ return component.Devices;
+ }
+
+ private void GetDeviceListState(EntityUid uid, DeviceListComponent comp, ref ComponentGetState args)
+ {
+ args.State = new DeviceListComponentState(comp.Devices, comp.IsAllowList, comp.HandleIncomingPackets);
+ }
+
+ private void HandleDeviceListState(EntityUid uid, DeviceListComponent comp, ref ComponentHandleState args)
+ {
+ if (args.Current is not DeviceListComponentState state)
+ {
+ return;
+ }
+
+ comp.Devices = state.Devices;
+ comp.HandleIncomingPackets = state.HandleIncomingPackets;
+ comp.IsAllowList = state.IsAllowList;
+ }
+}
+
+public sealed class DeviceListUpdateEvent : EntityEventArgs
+{
+ public DeviceListUpdateEvent(List devices)
+ {
+ Devices = devices;
+ }
+
+ public List Devices { get; }
+}
diff --git a/Content.Shared/DeviceNetwork/Systems/SharedNetworkConfiguratorSystem.cs b/Content.Shared/DeviceNetwork/Systems/SharedNetworkConfiguratorSystem.cs
new file mode 100644
index 0000000000..1af5393ae4
--- /dev/null
+++ b/Content.Shared/DeviceNetwork/Systems/SharedNetworkConfiguratorSystem.cs
@@ -0,0 +1,52 @@
+using Content.Shared.Actions;
+using Robust.Shared.GameStates;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.DeviceNetwork;
+
+public abstract class SharedNetworkConfiguratorSystem : EntitySystem
+{
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(GetNetworkConfiguratorState);
+ SubscribeLocalEvent(HandleNetworkConfiguratorState);
+ }
+
+
+
+ private void GetNetworkConfiguratorState(EntityUid uid, NetworkConfiguratorComponent comp,
+ ref ComponentGetState args)
+ {
+ args.State = new NetworkConfiguratorComponentState(comp.ActiveDeviceList);
+ }
+
+ private void HandleNetworkConfiguratorState(EntityUid uid, NetworkConfiguratorComponent comp,
+ ref ComponentHandleState args)
+ {
+ if (args.Current is not NetworkConfiguratorComponentState state)
+ {
+ return;
+ }
+
+ comp.ActiveDeviceList = state.ActiveDeviceList;
+ }
+}
+
+[Serializable, NetSerializable]
+public sealed class ManualDeviceListSyncMessage : BoundUserInterfaceMessage
+{
+ public ManualDeviceListSyncMessage(EntityUid? device, HashSet? devices)
+ {
+ Device = device;
+ Devices = devices;
+ }
+
+ public EntityUid? Device { get; }
+ public HashSet? Devices { get; }
+}
+
+public sealed class ClearAllOverlaysEvent : InstantActionEvent
+{
+}
diff --git a/Resources/Locale/en-US/devices/network-configurator.ftl b/Resources/Locale/en-US/devices/network-configurator.ftl
index d92bc07675..c3f210de0b 100644
--- a/Resources/Locale/en-US/devices/network-configurator.ftl
+++ b/Resources/Locale/en-US/devices/network-configurator.ftl
@@ -12,6 +12,8 @@ network-configurator-configure = Configure
# ui
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-clear-network-link-overlays-desc = Clear network link overlays.
# tooltips
network-configurator-tooltip-set = Sets targets device list
diff --git a/Resources/Prototypes/Actions/types.yml b/Resources/Prototypes/Actions/types.yml
index f8175841b7..d3fa15fd68 100644
--- a/Resources/Prototypes/Actions/types.yml
+++ b/Resources/Prototypes/Actions/types.yml
@@ -71,6 +71,13 @@
iconOn: Objects/Weapons/Melee/shields.rsi/teleriot-on.png
event: !type:ToggleActionEvent
+- type: instantAction
+ id: ClearNetworkLinkOverlays
+ name: network-configurator-clear-network-link-overlays
+ description: network-configurator-clear-network-link-overlays-desc
+ icon: Objects/Tools/multitool.rsi/icon.png
+ event: !type:ClearAllOverlaysEvent
+
- type: instantAction
id: AnimalLayEgg
name: action-name-lay-egg