diff --git a/Content.Client/Entry/IgnoredComponents.cs b/Content.Client/Entry/IgnoredComponents.cs index 6cee1d63a2..4d710806f9 100644 --- a/Content.Client/Entry/IgnoredComponents.cs +++ b/Content.Client/Entry/IgnoredComponents.cs @@ -281,7 +281,10 @@ namespace Content.Client.Entry "TabletopGame", "LitOnPowered", "TriggerOnSignalReceived", - "ToggleDoorOnTrigger" + "ToggleDoorOnTrigger", + "DeviceNetworkConnection", + "WiredNetworkConnection", + "WirelessNetworkConnection" }; } } diff --git a/Content.IntegrationTests/Tests/DeviceNetwork/DeviceNetworkTest.cs b/Content.IntegrationTests/Tests/DeviceNetwork/DeviceNetworkTest.cs new file mode 100644 index 0000000000..f098b516f3 --- /dev/null +++ b/Content.IntegrationTests/Tests/DeviceNetwork/DeviceNetworkTest.cs @@ -0,0 +1,272 @@ +using Content.Server.DeviceNetwork; +using Content.Server.DeviceNetwork.Components; +using Content.Server.DeviceNetwork.Systems; +using NUnit.Framework; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Map; +using System.Threading.Tasks; + +namespace Content.IntegrationTests.Tests.DeviceNetwork +{ + [TestFixture] + [TestOf(typeof(DeviceNetworkComponent))] + [TestOf(typeof(WiredNetworkComponent))] + [TestOf(typeof(WirelessNetworkComponent))] + public class DeviceNetworkTest : ContentIntegrationTest + { + private const string Prototypes = @" +- type: entity + name: DummyNetworkDevice + id: DummyNetworkDevice + components: + - type: DeviceNetworkComponent + frequency: 100 + +- type: entity + name: DummyWiredNetworkDevice + id: DummyWiredNetworkDevice + components: + - type: DeviceNetworkComponent + deviceNetId: 1 + - type: WiredNetworkConnection + - type: ApcPowerReceiver + +- type: entity + name: DummyWirelessNetworkDevice + id: DummyWirelessNetworkDevice + components: + - type: DeviceNetworkComponent + frequency: 100 + deviceNetId: 1 + - type: WirelessNetworkConnection + range: 100 + "; + + [Test] + public async Task NetworkDeviceSendAndReceive() + { + var options = new ServerContentIntegrationOption + { + ExtraPrototypes = Prototypes, + ContentBeforeIoC = () => { + IoCManager.Resolve().LoadExtraSystemType(); + } + }; + + var server = StartServerDummyTicker(options); + + await server.WaitIdleAsync(); + + var mapManager = server.ResolveDependency(); + var entityManager = server.ResolveDependency(); + var deviceNetSystem = entityManager.EntitySysManager.GetEntitySystem(); + var deviceNetTestSystem = entityManager.EntitySysManager.GetEntitySystem(); + + + IEntity device1 = null; + IEntity device2 = null; + DeviceNetworkComponent networkComponent1 = null; + DeviceNetworkComponent networkComponent2 = null; + + var testValue = "test"; + var payload = new NetworkPayload + { + ["Test"] = testValue, + ["testnumber"] = 1, + ["testbool"] = true + }; + + server.Assert(() => { + mapManager.CreateNewMapEntity(MapId.Nullspace); + + device1 = entityManager.SpawnEntity("DummyNetworkDevice", MapCoordinates.Nullspace); + + Assert.That(device1.TryGetComponent(out networkComponent1), Is.True); + Assert.That(networkComponent1.Open, Is.True); + Assert.That(networkComponent1.Address, Is.Not.EqualTo(string.Empty)); + + device2 = entityManager.SpawnEntity("DummyNetworkDevice", MapCoordinates.Nullspace); + + Assert.That(device2.TryGetComponent(out networkComponent2), Is.True); + Assert.That(networkComponent2.Open, Is.True); + Assert.That(networkComponent2.Address, Is.Not.EqualTo(string.Empty)); + + Assert.That(networkComponent1.Address, Is.Not.EqualTo(networkComponent2.Address)); + + deviceNetSystem.QueuePacket(device1.Uid, networkComponent2.Address, networkComponent2.Frequency, payload); + }); + + await server.WaitRunTicks(1); + await server.WaitIdleAsync(); + + server.Assert(() => { + CollectionAssert.AreEquivalent(deviceNetTestSystem.LastPayload, payload); + }); + } + + [Test] + public async Task WirelessNetworkDeviceSendAndReceive() + { + var options = new ServerContentIntegrationOption + { + ExtraPrototypes = Prototypes, + ContentBeforeIoC = () => { + IoCManager.Resolve().LoadExtraSystemType(); + } + }; + + var server = StartServerDummyTicker(options); + + await server.WaitIdleAsync(); + + var mapManager = server.ResolveDependency(); + var entityManager = server.ResolveDependency(); + var deviceNetSystem = entityManager.EntitySysManager.GetEntitySystem(); + var deviceNetTestSystem = entityManager.EntitySysManager.GetEntitySystem(); + + + IEntity device1 = null; + IEntity device2 = null; + DeviceNetworkComponent networkComponent1 = null; + DeviceNetworkComponent networkComponent2 = null; + WirelessNetworkComponent wirelessNetworkComponent = null; + + var testValue = "test"; + var payload = new NetworkPayload + { + ["Test"] = testValue, + ["testnumber"] = 1, + ["testbool"] = true + }; + + server.Assert(() => { + mapManager.CreateNewMapEntity(MapId.Nullspace); + + device1 = entityManager.SpawnEntity("DummyWirelessNetworkDevice", MapCoordinates.Nullspace); + + Assert.That(device1.TryGetComponent(out networkComponent1), Is.True); + Assert.That(device1.TryGetComponent(out wirelessNetworkComponent), Is.True); + Assert.That(networkComponent1.Open, Is.True); + Assert.That(networkComponent1.Address, Is.Not.EqualTo(string.Empty)); + + device2 = entityManager.SpawnEntity("DummyWirelessNetworkDevice", new MapCoordinates(new Robust.Shared.Maths.Vector2(0,50), MapId.Nullspace)); + + Assert.That(device2.TryGetComponent(out networkComponent2), Is.True); + Assert.That(networkComponent2.Open, Is.True); + Assert.That(networkComponent2.Address, Is.Not.EqualTo(string.Empty)); + + Assert.That(networkComponent1.Address, Is.Not.EqualTo(networkComponent2.Address)); + + deviceNetSystem.QueuePacket(device1.Uid, networkComponent2.Address, networkComponent2.Frequency, payload); + }); + + await server.WaitRunTicks(1); + await server.WaitIdleAsync(); + + server.Assert(() => { + CollectionAssert.AreEqual(deviceNetTestSystem.LastPayload, payload); + + payload = new NetworkPayload + { + ["Wirelesstest"] = 5 + }; + + wirelessNetworkComponent.Range = 0; + + deviceNetSystem.QueuePacket(device1.Uid, networkComponent2.Address, networkComponent2.Frequency, payload); + }); + + await server.WaitRunTicks(1); + await server.WaitIdleAsync(); + + server.Assert(() => { + CollectionAssert.AreNotEqual(deviceNetTestSystem.LastPayload, payload); + }); + + await server.WaitIdleAsync(); + } + + [Test] + public async Task WiredNetworkDeviceSendAndReceive() + { + var options = new ServerContentIntegrationOption + { + ExtraPrototypes = Prototypes, + ContentBeforeIoC = () => { + IoCManager.Resolve().LoadExtraSystemType(); + } + }; + + var server = StartServerDummyTicker(options); + + await server.WaitIdleAsync(); + + var mapManager = server.ResolveDependency(); + var entityManager = server.ResolveDependency(); + var deviceNetSystem = entityManager.EntitySysManager.GetEntitySystem(); + var deviceNetTestSystem = entityManager.EntitySysManager.GetEntitySystem(); + + + IEntity device1 = null; + IEntity device2 = null; + DeviceNetworkComponent networkComponent1 = null; + DeviceNetworkComponent networkComponent2 = null; + WiredNetworkComponent wiredNetworkComponent = null; + IMapGrid grid = null; + + var testValue = "test"; + var payload = new NetworkPayload + { + ["Test"] = testValue, + ["testnumber"] = 1, + ["testbool"] = true + }; + + await server.WaitRunTicks(1); + await server.WaitIdleAsync(); + + server.Assert(() => { + var map = mapManager.CreateNewMapEntity(MapId.Nullspace); + grid = mapManager.CreateGrid(MapId.Nullspace); + + device1 = entityManager.SpawnEntity("DummyWiredNetworkDevice", MapCoordinates.Nullspace); + + Assert.That(device1.TryGetComponent(out networkComponent1), Is.True); + Assert.That(device1.TryGetComponent(out wiredNetworkComponent), Is.True); + Assert.That(networkComponent1.Open, Is.True); + Assert.That(networkComponent1.Address, Is.Not.EqualTo(string.Empty)); + + device2 = entityManager.SpawnEntity("DummyWiredNetworkDevice", new MapCoordinates(new Robust.Shared.Maths.Vector2(0, 2), MapId.Nullspace)); + + Assert.That(device2.TryGetComponent(out networkComponent2), Is.True); + Assert.That(networkComponent2.Open, Is.True); + Assert.That(networkComponent2.Address, Is.Not.EqualTo(string.Empty)); + + Assert.That(networkComponent1.Address, Is.Not.EqualTo(networkComponent2.Address)); + + deviceNetSystem.QueuePacket(device1.Uid, networkComponent2.Address, networkComponent2.Frequency, payload); + }); + + await server.WaitRunTicks(1); + await server.WaitIdleAsync(); + + server.Assert(() => { + //CollectionAssert.AreNotEqual(deviceNetTestSystem.LastPayload, payload); + + entityManager.SpawnEntity("CableApcExtension", grid.MapToGrid(new MapCoordinates(new Robust.Shared.Maths.Vector2(0, 1), MapId.Nullspace))); + + deviceNetSystem.QueuePacket(device1.Uid, networkComponent2.Address, networkComponent2.Frequency, payload); + }); + + await server.WaitRunTicks(1); + await server.WaitIdleAsync(); + + server.Assert(() => { + CollectionAssert.AreEqual(deviceNetTestSystem.LastPayload, payload); + }); + + await server.WaitIdleAsync(); + } + } +} diff --git a/Content.IntegrationTests/Tests/DeviceNetwork/DeviceNetworkTestSystem.cs b/Content.IntegrationTests/Tests/DeviceNetwork/DeviceNetworkTestSystem.cs new file mode 100644 index 0000000000..369a63ebfb --- /dev/null +++ b/Content.IntegrationTests/Tests/DeviceNetwork/DeviceNetworkTestSystem.cs @@ -0,0 +1,27 @@ + +using Content.Server.DeviceNetwork; +using Content.Server.DeviceNetwork.Components; +using Content.Server.DeviceNetwork.Systems; +using Robust.Shared.GameObjects; +using Robust.Shared.Reflection; + +namespace Content.IntegrationTests.Tests.DeviceNetwork +{ + [Reflect(false)] + public class DeviceNetworkTestSystem : EntitySystem + { + public NetworkPayload LastPayload = default; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnPacketReceived); + } + + private void OnPacketReceived(EntityUid uid, DeviceNetworkComponent component, PacketSentEvent args) + { + LastPayload = args.Data; + } + } +} diff --git a/Content.Server/DeviceNetwork/Components/DeviceNetworkComponent.cs b/Content.Server/DeviceNetwork/Components/DeviceNetworkComponent.cs new file mode 100644 index 0000000000..1f4a671f70 --- /dev/null +++ b/Content.Server/DeviceNetwork/Components/DeviceNetworkComponent.cs @@ -0,0 +1,35 @@ +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.ViewVariables; + +namespace Content.Server.DeviceNetwork.Components +{ + [RegisterComponent] + public class DeviceNetworkComponent : Component + { + public override string Name => "DeviceNetworkComponent"; + + /// + /// The device networks netID this DeviceNetworkComponent connects to. + /// The netID is used to seperate device networks that shouldn't interact with each other e.g. wireless and wired. + /// The default netID's are_ + /// 0 -> Private + /// 1 -> Wired + /// 2 -> Wireless + /// + [DataField("deviceNetId")] + public int DeviceNetId { get; set; } = (int)DeviceNetworkConstants.ConnectionType.Private; + + [DataField("frequency")] + public int Frequency { get; set; } = 0; + + [ViewVariables] + public bool Open; + + [ViewVariables] + public string Address = string.Empty; + + [DataField("receiveAll")] + public bool ReceiveAll; + } +} diff --git a/Content.Server/DeviceNetwork/Components/WiredNetworkComponent.cs b/Content.Server/DeviceNetwork/Components/WiredNetworkComponent.cs new file mode 100644 index 0000000000..179c5e0599 --- /dev/null +++ b/Content.Server/DeviceNetwork/Components/WiredNetworkComponent.cs @@ -0,0 +1,10 @@ +using Robust.Shared.GameObjects; + +namespace Content.Server.DeviceNetwork.Components +{ + [RegisterComponent] + public class WiredNetworkComponent : Component + { + public override string Name => "WiredNetworkConnection"; + } +} diff --git a/Content.Server/DeviceNetwork/Components/WirelessNetworkComponent.cs b/Content.Server/DeviceNetwork/Components/WirelessNetworkComponent.cs new file mode 100644 index 0000000000..75d122d74d --- /dev/null +++ b/Content.Server/DeviceNetwork/Components/WirelessNetworkComponent.cs @@ -0,0 +1,17 @@ +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization.Manager.Attributes; + +namespace Content.Server.DeviceNetwork.Components +{ + /// + /// Sends and receives device network messages wirelessly. Devices sending and receiving need to be in range and on the same frequency. + /// + [RegisterComponent] + public class WirelessNetworkComponent : Component + { + public override string Name => "WirelessNetworkConnection"; + + [DataField("range")] + public int Range { get; set; } + } +} diff --git a/Content.Server/DeviceNetwork/Connections/BaseNetworkConnection.cs b/Content.Server/DeviceNetwork/Connections/BaseNetworkConnection.cs deleted file mode 100644 index e81d111cd8..0000000000 --- a/Content.Server/DeviceNetwork/Connections/BaseNetworkConnection.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System.Collections.Generic; -using Robust.Shared.IoC; -using Robust.Shared.ViewVariables; - -namespace Content.Server.DeviceNetwork.Connections -{ - public abstract class BaseNetworkConnection : IDeviceNetworkConnection - { - protected readonly DeviceNetworkConnection Connection; - - protected OnReceiveNetMessage MessageHandler; - - [ViewVariables] - public bool Open => Connection.Open; - [ViewVariables] - public string Address => Connection.Address; - [ViewVariables] - public int Frequency => Connection.Frequency; - - protected BaseNetworkConnection(int netId, int frequency, OnReceiveNetMessage onReceive, bool receiveAll) - { - var network = IoCManager.Resolve(); - Connection = network.Register(netId, frequency, OnReceiveNetMessage, receiveAll); - MessageHandler = onReceive; - - } - - public bool Send(int frequency, string address, Dictionary payload) - { - var data = ManipulatePayload(payload); - var metadata = GetMetadata(); - return Connection.Send(frequency, address, data, metadata); - } - - public bool Send(string address, Dictionary payload) - { - return Send(0, address, payload); - } - - public bool Broadcast(int frequency, Dictionary payload) - { - var data = ManipulatePayload(payload); - var metadata = GetMetadata(); - return Connection.Broadcast(frequency, data, metadata); - } - - public bool Broadcast(Dictionary payload) - { - return Broadcast(0, payload); - } - - public void Close() - { - Connection.Close(); - } - - private void OnReceiveNetMessage(int frequency, string sender, IReadOnlyDictionary payload, Metadata metadata, bool broadcast) - { - if (CanReceive(frequency, sender, payload, metadata, broadcast)) - { - MessageHandler(frequency, sender, payload, metadata, broadcast); - } - } - - protected abstract bool CanReceive(int frequency, string sender, IReadOnlyDictionary payload, Metadata metadata, bool broadcast); - protected abstract Dictionary ManipulatePayload(Dictionary payload); - protected abstract Metadata GetMetadata(); - } -} diff --git a/Content.Server/DeviceNetwork/Connections/DeviceNetworkConnection.cs b/Content.Server/DeviceNetwork/Connections/DeviceNetworkConnection.cs deleted file mode 100644 index a435fce031..0000000000 --- a/Content.Server/DeviceNetwork/Connections/DeviceNetworkConnection.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System.Collections.Generic; -using Robust.Shared.ViewVariables; - -namespace Content.Server.DeviceNetwork.Connections -{ - public class DeviceNetworkConnection : IDeviceNetworkConnection - { - private readonly DeviceNetwork _network; - [ViewVariables] - private readonly int _netId; - - [ViewVariables] - public bool Open { get; private set; } - [ViewVariables] - public string Address { get; private set; } - [ViewVariables] - public int Frequency { get; private set; } - - [ViewVariables] - public bool ReceiveAll - { - get => _network.GetDeviceReceiveAll(_netId, Frequency, Address); - set => _network.SetDeviceReceiveAll(_netId, Frequency, Address, value); - } - - public DeviceNetworkConnection(DeviceNetwork network, int netId, string address, int frequency) - { - _network = network; - _netId = netId; - Open = true; - Address = address; - Frequency = frequency; - } - - public bool Send(int frequency, string address, IReadOnlyDictionary payload, Metadata metadata) - { - return Open && _network.EnqueuePackage(_netId, frequency, address, payload, Address, metadata); - } - - public bool Send(int frequency, string address, Dictionary payload) - { - return Send(frequency, address, payload, new Metadata()); - } - - public bool Send(string address, Dictionary payload) - { - return Send(0, address, payload); - } - - public bool Broadcast(int frequency, IReadOnlyDictionary payload, Metadata metadata) - { - return Open && _network.EnqueuePackage(_netId, frequency, "", payload, Address, metadata, true); - } - - public bool Broadcast(int frequency, Dictionary payload) - { - return Broadcast(frequency, payload, new Metadata()); - } - - public bool Broadcast(Dictionary payload) - { - return Broadcast(0, payload); - } - - public void Close() - { - _network.RemoveDevice(_netId, Frequency, Address); - Open = false; - } - } -} diff --git a/Content.Server/DeviceNetwork/Connections/IDeviceNetworkConnection.cs b/Content.Server/DeviceNetwork/Connections/IDeviceNetworkConnection.cs deleted file mode 100644 index 3adfc52f27..0000000000 --- a/Content.Server/DeviceNetwork/Connections/IDeviceNetworkConnection.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Collections.Generic; - -namespace Content.Server.DeviceNetwork.Connections -{ - public interface IDeviceNetworkConnection - { - public int Frequency { get; } - /// - /// Sends a package to a specific device - /// - /// The frequency the package should be send on - /// The target devices address - /// - /// - public bool Send(int frequency, string address, Dictionary payload); - /// - public bool Send(string address, Dictionary payload); - /// - /// Sends a package to all devices - /// - /// The frequency the package should be send on - /// - /// - public bool Broadcast(int frequency, Dictionary payload); - /// - public bool Broadcast(Dictionary payload); - } -} diff --git a/Content.Server/DeviceNetwork/Connections/WiredNetworkConnection.cs b/Content.Server/DeviceNetwork/Connections/WiredNetworkConnection.cs deleted file mode 100644 index 710de4ea34..0000000000 --- a/Content.Server/DeviceNetwork/Connections/WiredNetworkConnection.cs +++ /dev/null @@ -1,87 +0,0 @@ -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using Content.Server.NodeContainer; -using Content.Server.NodeContainer.NodeGroups; -using Content.Server.Power.Components; -using Robust.Shared.GameObjects; - -namespace Content.Server.DeviceNetwork.Connections -{ - public class WiredNetworkConnection : BaseNetworkConnection - { - public const string WIRENET = "powernet"; - - private readonly IEntity _owner; - - public WiredNetworkConnection(OnReceiveNetMessage onReceive, bool receiveAll, IEntity owner) : base(NetworkUtils.WIRED, 0, onReceive, receiveAll) - { - _owner = owner; - } - - protected override bool CanReceive(int frequency, string sender, IReadOnlyDictionary payload, Metadata metadata, bool broadcast) - { - if (_owner.Deleted) - { - Connection.Close(); - return false; - } - - if (_owner.TryGetComponent(out var powerReceiver) - && TryGetWireNet(powerReceiver, out var ownNet) - && metadata.TryParseMetadata(WIRENET, out var senderNet)) - { - return ownNet.Equals(senderNet); - } - - return false; - } - - protected override Metadata GetMetadata() - { - if (_owner.Deleted) - { - Connection.Close(); - return new Metadata(); - } - - if (_owner.TryGetComponent(out var powerReceiver) - && TryGetWireNet(powerReceiver, out var net)) - { - var metadata = new Metadata - { - {WIRENET, net } - }; - - return metadata; - } - - return new Metadata(); - } - - protected override Dictionary ManipulatePayload(Dictionary payload) - { - return payload; - } - - private bool TryGetWireNet(ApcPowerReceiverComponent apcPowerReceiver, [NotNullWhen(true)] out INodeGroup? net) - { - var provider = apcPowerReceiver.Provider; - if (provider != null && provider.ProviderOwner.TryGetComponent(out var nodeContainer)) - { - var nodes = nodeContainer.Nodes; - - foreach (var node in nodes.Values) - { - if (node.NodeGroupID == NodeGroupID.WireNet && node.NodeGroup != null) - { - net = node.NodeGroup; - return true; - } - } - } - - net = default; - return false; - } - } -} diff --git a/Content.Server/DeviceNetwork/Connections/WirelessNetworkConnection.cs b/Content.Server/DeviceNetwork/Connections/WirelessNetworkConnection.cs deleted file mode 100644 index a205949ab6..0000000000 --- a/Content.Server/DeviceNetwork/Connections/WirelessNetworkConnection.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System; -using System.Collections.Generic; -using Robust.Shared.GameObjects; -using Robust.Shared.Maths; - -namespace Content.Server.DeviceNetwork.Connections -{ - public class WirelessNetworkConnection : BaseNetworkConnection - { - public const string WIRELESS_POSITION = "position"; - - private readonly IEntity _owner; - - private float _range; - public float Range { get => _range; set => _range = Math.Abs(value); } - - public WirelessNetworkConnection(int frequency, OnReceiveNetMessage onReceive, bool receiveAll, IEntity owner, float range) : base(NetworkUtils.WIRELESS, frequency, onReceive, receiveAll) - { - _owner = owner; - Range = range; - } - - protected override bool CanReceive(int frequency, string sender, IReadOnlyDictionary payload, Metadata metadata, bool broadcast) - { - if (_owner.Deleted) - { - Connection.Close(); - return false; - } - - if (metadata.TryParseMetadata(WIRELESS_POSITION, out var position)) - { - var ownPosition = _owner.Transform.WorldPosition; - var distance = (ownPosition - position).Length; - return distance <= Range; - } - //Only receive packages with the same frequency - return frequency == Frequency; - } - - protected override Metadata GetMetadata() - { - if (_owner.Deleted) - { - Connection.Close(); - return new Metadata(); - } - - var position = _owner.Transform.WorldPosition; - var metadata = new Metadata - { - {WIRELESS_POSITION, position} - }; - - return metadata; - } - - protected override Dictionary ManipulatePayload(Dictionary payload) - { - return payload; - } - } -} diff --git a/Content.Server/DeviceNetwork/DeviceNetwork.cs b/Content.Server/DeviceNetwork/DeviceNetwork.cs deleted file mode 100644 index a16c993bd0..0000000000 --- a/Content.Server/DeviceNetwork/DeviceNetwork.cs +++ /dev/null @@ -1,223 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using Content.Server.DeviceNetwork.Connections; -using Robust.Shared.IoC; -using Robust.Shared.Random; - -namespace Content.Server.DeviceNetwork -{ - public delegate void OnReceiveNetMessage(int frequency, string sender, IReadOnlyDictionary payload, Metadata metadata, bool broadcast); - - public class DeviceNetwork : IDeviceNetwork - { - private const int PACKAGES_PER_TICK = 30; - - [Dependency] private readonly IRobustRandom _random = default!; - - private readonly Dictionary> _devices = new(); - private readonly Queue _packages = new(); - - /// - public DeviceNetworkConnection Register(int netId, int frequency, OnReceiveNetMessage messageHandler, bool receiveAll = false) - { - var address = GenerateValidAddress(netId, frequency); - var device = new NetworkDevice(frequency, address, messageHandler, receiveAll); - - AddDevice(netId, device); - - return new DeviceNetworkConnection(this, netId, address, frequency); - } - - public DeviceNetworkConnection Register(int netId, OnReceiveNetMessage messageHandler, bool receiveAll = false) - { - return Register(netId, 0, messageHandler, receiveAll); - } - - public void Update() - { - var count = Math.Min(PACKAGES_PER_TICK, _packages.Count); - for (var i = 0; i < count; i++) - { - var package = _packages.Dequeue(); - - if (package.Broadcast) - { - BroadcastPackage(package); - continue; - } - - SendPackage(package); - } - } - - public bool EnqueuePackage(int netId, int frequency, string address, IReadOnlyDictionary data, string sender, Metadata metadata, bool broadcast = false) - { - if (!_devices.ContainsKey(netId)) - return false; - - var package = new NetworkPackage(netId, frequency, address, broadcast, data, metadata, sender); - - _packages.Enqueue(package); - return true; - } - - public void RemoveDevice(int netId, int frequency, string address) - { - if (TryDeviceWithAddress(netId, frequency, address, out var device)) - { - _devices[netId].Remove(device); - } - } - - public void SetDeviceReceiveAll(int netId, int frequency, string address, bool receiveAll) - { - if (TryDeviceWithAddress(netId, frequency, address, out var device)) - { - device.ReceiveAll = receiveAll; - } - } - - public bool GetDeviceReceiveAll(int netId, int frequency, string address) - { - if (TryDeviceWithAddress(netId, frequency, address, out var device)) - { - return device.ReceiveAll; - } - - return false; - } - - private string GenerateValidAddress(int netId, int frequency) - { - var unique = false; - var devices = DevicesForFrequency(netId, frequency); - var address = ""; - - while (!unique) - { - address = _random.Next().ToString("x"); - unique = !devices.Exists(device => device.Address == address); - } - - return address; - } - - private void AddDevice(int netId, NetworkDevice networkDevice) - { - if(!_devices.ContainsKey(netId)) - _devices[netId] = new List(); - - _devices[netId].Add(networkDevice); - } - - private List DevicesForFrequency(int netId, int frequency) - { - if (!_devices.ContainsKey(netId)) - return new List(); - - var result = _devices[netId].FindAll(device => device.Frequency == frequency); - - return result; - } - - private NetworkDevice? DeviceWithAddress(int netId, int frequency, string address) - { - var devices = DevicesForFrequency(netId, frequency); - - var device = devices.Find(dvc => dvc.Address == address); - - return device; - } - - private bool TryDeviceWithAddress(int netId, int frequency, string address, - [NotNullWhen(true)] out NetworkDevice? device) - { - return (device = DeviceWithAddress(netId, frequency, address)) != null; - } - - private List DevicesWithReceiveAll(int netId, int frequency) - { - if (!_devices.ContainsKey(netId)) - return new List(); - - var result = _devices[netId].FindAll(device => device.Frequency == frequency && device.ReceiveAll); - - return result; - } - - private void BroadcastPackage(NetworkPackage package) - { - var devices = DevicesForFrequency(package.NetId, package.Frequency); - SendToDevices(devices, package, true); - } - - private void SendPackage(NetworkPackage package) - { - var devices = DevicesWithReceiveAll(package.NetId, package.Frequency); - - if (TryDeviceWithAddress(package.NetId, package.Frequency, package.Address, out var device)) - { - devices.Add(device); - } - - SendToDevices(devices, package, false); - } - - private void SendToDevices(List devices, NetworkPackage package, bool broadcast) - { - foreach (var device in devices) - { - if (device.Address == package.Sender) - continue; - - device.ReceiveNetMessage(package.Frequency, package.Sender, package.Data, package.Metadata, broadcast); - } - } - - internal class NetworkDevice - { - internal NetworkDevice(int frequency, string address, OnReceiveNetMessage receiveNetMessage, bool receiveAll) - { - Frequency = frequency; - Address = address; - ReceiveNetMessage = receiveNetMessage; - ReceiveAll = receiveAll; - } - - public int Frequency; - public string Address; - public OnReceiveNetMessage ReceiveNetMessage; - public bool ReceiveAll; - } - - internal class NetworkPackage - { - internal NetworkPackage( - int netId, - int frequency, - string address, - bool broadcast, - IReadOnlyDictionary data, - Metadata metadata, - string sender) - { - NetId = netId; - Frequency = frequency; - Address = address; - Broadcast = broadcast; - Data = data; - Metadata = metadata; - Sender = sender; - } - - public int NetId; - public int Frequency; - public string Address; - public bool Broadcast; - public IReadOnlyDictionary Data { get; set; } - public Metadata Metadata; - public string Sender; - } - } -} diff --git a/Content.Server/DeviceNetwork/DeviceNetworkConstants.cs b/Content.Server/DeviceNetwork/DeviceNetworkConstants.cs new file mode 100644 index 0000000000..b8533fd5d2 --- /dev/null +++ b/Content.Server/DeviceNetwork/DeviceNetworkConstants.cs @@ -0,0 +1,21 @@ +namespace Content.Server.DeviceNetwork +{ + /// + /// A collection of utilities to help with using device networks + /// + public static class DeviceNetworkConstants + { + public enum ConnectionType + { + Private, + Wired, + Wireless + } + + /// + /// The key for command names + /// E.g. [DeviceNetworkConstants.Command] = "ping" + /// + public const string Command = "command"; + } +} diff --git a/Content.Server/DeviceNetwork/DeviceNetworkSystem.cs b/Content.Server/DeviceNetwork/DeviceNetworkSystem.cs deleted file mode 100644 index 0cec3813bf..0000000000 --- a/Content.Server/DeviceNetwork/DeviceNetworkSystem.cs +++ /dev/null @@ -1,18 +0,0 @@ -using JetBrains.Annotations; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; - -namespace Content.Server.DeviceNetwork -{ - [UsedImplicitly] - internal sealed class DeviceNetworkSystem : EntitySystem - { - [Dependency] private readonly IDeviceNetwork _network = default!; - - public override void Update(float frameTime) - { - base.Update(frameTime); - _network.Update(); - } - } -} diff --git a/Content.Server/DeviceNetwork/IDeviceNetwork.cs b/Content.Server/DeviceNetwork/IDeviceNetwork.cs deleted file mode 100644 index 10e77cd714..0000000000 --- a/Content.Server/DeviceNetwork/IDeviceNetwork.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Content.Server.DeviceNetwork.Connections; - -namespace Content.Server.DeviceNetwork -{ - /// - /// Package based device network allowing devices to communicate with eachother - /// - public interface IDeviceNetwork - { - /// - /// Registers a device with the device network - /// - /// The id of the network to register with - /// The frequency the device receives packages on. Wired networks use frequency 0 - /// The delegate that gets called when the device receives a message - /// If the device should receive all packages on its frequency or only ones addressed to itself - /// - public DeviceNetworkConnection Register(int netId, int frequency, OnReceiveNetMessage messageHandler, bool receiveAll = false); - /// - public DeviceNetworkConnection Register(int netId, OnReceiveNetMessage messageHandler, bool receiveAll = false); - - public void Update(); - } -} diff --git a/Content.Server/DeviceNetwork/Metadata.cs b/Content.Server/DeviceNetwork/Metadata.cs deleted file mode 100644 index 1f052eb34a..0000000000 --- a/Content.Server/DeviceNetwork/Metadata.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; - -namespace Content.Server.DeviceNetwork -{ - public class Metadata : Dictionary - { - public bool TryParseMetadata(string key, [NotNullWhen(true)] out T? data) - { - if (TryGetValue(key, out var value) && value is T typedValue) - { - data = typedValue; - return true; - } - - data = default; - return false; - } - } -} diff --git a/Content.Server/DeviceNetwork/NetworkPayload.cs b/Content.Server/DeviceNetwork/NetworkPayload.cs new file mode 100644 index 0000000000..79084935c7 --- /dev/null +++ b/Content.Server/DeviceNetwork/NetworkPayload.cs @@ -0,0 +1,28 @@ +using Robust.Shared.Log; +using System.Collections.Generic; +using Robust.Shared.Utility; +using System.Diagnostics.CodeAnalysis; + +namespace Content.Server.DeviceNetwork +{ + public class NetworkPayload : Dictionary + { + /// + /// Tries to get a value from the payload and checks if that value is of type T. + /// + /// The type that should be casted to + /// Whether the value was present in the payload and of the required type + public bool TryGetValue(string key, [NotNullWhen(true)] out T? value) + { + if (this.TryCastValue(key, out T? result)) + { + value = result; + return true; + } + + value = default; + return false; + } + } + +} diff --git a/Content.Server/DeviceNetwork/NetworkUtils.cs b/Content.Server/DeviceNetwork/NetworkUtils.cs deleted file mode 100644 index ff51c7b8ba..0000000000 --- a/Content.Server/DeviceNetwork/NetworkUtils.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Collections.Generic; -using Content.Server.DeviceNetwork.Connections; - -namespace Content.Server.DeviceNetwork -{ - /// - /// A collection of utilities to help with using device networks - /// - public static class NetworkUtils - { - public const int PRIVATE = 0; - public const int WIRED = 1; - public const int WIRELESS = 2; - - public const string COMMAND = "command"; - public const string MESSAGE = "message"; - public const string PING = "ping"; - - /// - /// Handles responding to pings. - /// - public static void PingResponse(T connection, string sender, IReadOnlyDictionary payload, string message = "") where T : IDeviceNetworkConnection - { - if (payload.TryGetValue(COMMAND, out var command) && command == PING) - { - var response = new Dictionary - { - {COMMAND, "ping_response"}, - {MESSAGE, message} - }; - - connection.Send(connection.Frequency, sender, response); - } - } - } -} diff --git a/Content.Server/DeviceNetwork/Systems/DeviceNetworkSystem.cs b/Content.Server/DeviceNetwork/Systems/DeviceNetworkSystem.cs new file mode 100644 index 0000000000..e5e5d25e4e --- /dev/null +++ b/Content.Server/DeviceNetwork/Systems/DeviceNetworkSystem.cs @@ -0,0 +1,286 @@ +using Content.Server.DeviceNetwork.Components; +using JetBrains.Annotations; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Random; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace Content.Server.DeviceNetwork.Systems +{ + /// + /// Entity system that handles everything device network related. + /// Device networking allows machines and devices to communicate with each other while adhering to restrictions like range or being connected to the same powernet. + /// + [UsedImplicitly] + public class DeviceNetworkSystem : EntitySystem + { + [Dependency] private readonly IRobustRandom _random = default!; + + private readonly Dictionary> _connections = new(); + private readonly Queue _packets = new(); + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnNetworkStarted); + SubscribeLocalEvent(OnNetworkShutdown); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + while (_packets.Count > 0) + { + var packet = _packets.Dequeue(); + + if(packet.Broadcast) + { + BroadcastPacket(packet); + continue; + } + SendPacket(packet); + } + } + + /// + /// Manually connect an entity with a DeviceNetworkComponent. + /// + /// The Entity containing a DeviceNetworkComponent + public void Connect(EntityUid uid) + { + if (EntityManager.GetEntity(uid).TryGetComponent(out var component)) + { + AddConnection(component); + } + } + + /// + /// Sends the given payload as a device network packet to the entity with the given address and frequency. + /// Addresses are given to the DeviceNetworkComponent of an entity when connecting. + /// + /// The EntityUid of the sending entity + /// The address of the entity that the packet gets sent to when not broadcasting + /// The frequency to send on + /// The data to be sent + /// Send to all devices on the same device network on the given frequency + public void QueuePacket(EntityUid uid, string address, int frequency, NetworkPayload data, bool broadcast = false) + { + if (EntityManager.TryGetComponent(uid, out var component)) + { + var packet = new NetworkPacket + { + NetId = component.DeviceNetId, + Address = address, + Frequency = frequency, + Broadcast = broadcast, + Data = data, + Sender = component + }; + + _packets.Enqueue(packet); + } + } + + /// + /// Manually disconnect an entity with a DeviceNetworkComponent. + /// + /// The Entity containing a DeviceNetworkComponent + public void Disconnect(EntityUid uid) + { + if (EntityManager.GetEntity(uid).TryGetComponent(out var component)) + { + RemoveConnection(component); + } + } + + /// + /// Automatically connect when an entity with a DeviceNetworkComponent starts up. + /// + private void OnNetworkStarted(EntityUid uid, DeviceNetworkComponent component, ComponentStartup args) + { + AddConnection(component); + } + + /// + /// Automatically disconnect when an entity with a DeviceNetworkComponent shuts down. + /// + private void OnNetworkShutdown(EntityUid uid, DeviceNetworkComponent component, ComponentShutdown args) + { + RemoveConnection(component); + } + + private bool AddConnection(DeviceNetworkComponent connection) + { + var netId = connection.DeviceNetId; + if (!_connections.ContainsKey(netId)) + _connections[netId] = new List(); + + if (!_connections[netId].Contains(connection)) + { + connection.Address = GenerateValidAddress(netId); + _connections[netId].Add(connection); + connection.Open = true; + return true; + } + + return false; + } + + private bool RemoveConnection(DeviceNetworkComponent connection) + { + connection.Address = ""; + connection.Open = false; + return _connections[connection.DeviceNetId].Remove(connection); + } + + /// + /// Generates a valid address by randomly generating one and checking if it already exists on the device network with the given device netId. + /// + private string GenerateValidAddress(int netId) + { + var unique = false; + var connections = _connections[netId]; + var address = ""; + + while (!unique) + { + address = _random.Next().ToString("x"); + unique = !connections.Exists(connection => connection.Address == address); + } + + return address; + } + + private List ConnectionsForFrequency(int netId, int frequency) + { + if (!_connections.ContainsKey(netId)) + return new List(); + + var result = _connections[netId].FindAll(connection => connection.Frequency == frequency); + + return result; + } + + private bool TryGetConnectionWithAddress(int netId, int frequency, string address, [NotNullWhen(true)] out DeviceNetworkComponent connection) + { + var connections = ConnectionsForFrequency(netId, frequency); + + var result = connections.Find(dvc => dvc.Address == address); + + if(result != null) + { + connection = result; + return true; + } + + connection = default!; + return false; + } + + private List ConnectionsWithReceiveAll(int netId, int frequency) + { + if (!_connections.ContainsKey(netId)) + return new List(); + + var result = _connections[netId].FindAll(device => device.Frequency == frequency && device.ReceiveAll); + + return result; + } + + private void SendPacket(NetworkPacket packet) + { + if (!TryGetConnectionWithAddress(packet.NetId, packet.Frequency, packet.Address, out var connection)) + return; + + var receivers = ConnectionsWithReceiveAll(packet.Frequency, packet.NetId); + receivers.Add(connection); + + SendToConnections(receivers, packet); + } + + private void BroadcastPacket(NetworkPacket packet) + { + var receivers = ConnectionsForFrequency(packet.Frequency, packet.NetId); + SendToConnections(receivers, packet); + } + + private void SendToConnections(List connections, NetworkPacket packet) + { + foreach (var connection in connections) + { + var beforeEvent = new BeforePacketSentEvent(packet.Sender.Owner.Uid); + RaiseLocalEvent(connection.Owner.Uid, beforeEvent, false); + + if (!beforeEvent.Cancelled) + { + RaiseLocalEvent(connection.Owner.Uid, new PacketSentEvent(connection.Frequency, packet.Sender.Address, packet.Data, packet.Broadcast) , false); + } + } + } + + internal struct NetworkPacket + { + public int NetId; + public int Frequency; + public string Address; + public bool Broadcast; + public NetworkPayload Data; + public DeviceNetworkComponent Sender; + } + } + + /// + /// Event raised before a device network packet is send. + /// Subscribed to by other systems to prevent the packet from being sent. + /// + public class BeforePacketSentEvent : CancellableEntityEventArgs + { + /// + /// The EntityUid of the entity the packet was sent from. + /// + public EntityUid Sender; + + public BeforePacketSentEvent(EntityUid sender) + { + Sender = sender; + } + } + + /// + /// Event raised when a device network packet gets sent. + /// + public class PacketSentEvent : EntityEventArgs + { + /// + /// The frequency the packet is sent on. + /// + public int Frequency; + + /// + /// The device network address of the sending entity. + /// + public string SenderAddress; + + /// + /// The data that is beeing sent. + /// + public NetworkPayload Data; + + /// + /// Whether the packet was broadcasted. + /// + public bool Broadcast; + + public PacketSentEvent(int frequency, string senderAddress, NetworkPayload data, bool broadcast) + { + Frequency = frequency; + SenderAddress = senderAddress; + Data = data; + Broadcast = broadcast; + } + + } +} diff --git a/Content.Server/DeviceNetwork/Systems/WiredNetworkSystem.cs b/Content.Server/DeviceNetwork/Systems/WiredNetworkSystem.cs new file mode 100644 index 0000000000..11eda63575 --- /dev/null +++ b/Content.Server/DeviceNetwork/Systems/WiredNetworkSystem.cs @@ -0,0 +1,39 @@ +using Content.Server.DeviceNetwork.Components; +using Content.Server.NodeContainer; +using Content.Server.NodeContainer.NodeGroups; +using Content.Server.Power.Components; +using JetBrains.Annotations; +using Robust.Shared.GameObjects; +using System.Diagnostics.CodeAnalysis; + +namespace Content.Server.DeviceNetwork.Systems +{ + [UsedImplicitly] + public class WiredNetworkSystem : EntitySystem + { + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnBeforePacketSent); + } + + /// + /// Checks if both devices are on the same grid + /// + private void OnBeforePacketSent(EntityUid uid, WiredNetworkComponent component, BeforePacketSentEvent args) + { + IEntity sender = EntityManager.GetEntity(args.Sender); + IEntity receiver = EntityManager.GetEntity(uid); + + if (receiver.Transform.GridID != sender.Transform.GridID) + { + args.Cancel(); + } + } + + //Things to do in a future PR: + //Abstract out the connection between the apcExtensionCable and the apcPowerReceiver + //Traverse the power cables using path traversal + //Cache an optimized representation of the traversed path (Probably just cache Devices) + } +} diff --git a/Content.Server/DeviceNetwork/Systems/WirelessNetworkSystem.cs b/Content.Server/DeviceNetwork/Systems/WirelessNetworkSystem.cs new file mode 100644 index 0000000000..be009b6543 --- /dev/null +++ b/Content.Server/DeviceNetwork/Systems/WirelessNetworkSystem.cs @@ -0,0 +1,33 @@ +using Content.Server.DeviceNetwork.Components; +using JetBrains.Annotations; +using Robust.Shared.GameObjects; + +namespace Content.Server.DeviceNetwork.Systems +{ + [UsedImplicitly] + public class WirelessNetworkSystem : EntitySystem + { + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnBeforePacketSent); + } + + /// + /// Gets the position of both the sending and receiving entity and checks if the receiver is in range of the sender. + /// + private void OnBeforePacketSent(EntityUid uid, WirelessNetworkComponent component, BeforePacketSentEvent args) + { + var sender = EntityManager.GetEntity(args.Sender); + + var ownPosition = component.Owner.Transform.WorldPosition; + var position = sender.Transform.WorldPosition; + var distance = (ownPosition - position).Length; + + if(sender.TryGetComponent(out var sendingComponent) && distance > sendingComponent.Range) + { + args.Cancel(); + } + } + } +} diff --git a/Content.Server/IoC/ServerContentIoC.cs b/Content.Server/IoC/ServerContentIoC.cs index a9de3aa77a..d2cecd0c59 100644 --- a/Content.Server/IoC/ServerContentIoC.cs +++ b/Content.Server/IoC/ServerContentIoC.cs @@ -47,7 +47,6 @@ namespace Content.Server.IoC IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); - IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); IoCManager.Register();