using Content.Server.DeviceNetwork.Components; using Content.Shared.DeviceNetwork; using JetBrains.Annotations; using Robust.Shared.Prototypes; using Robust.Shared.Random; using System.Diagnostics.CodeAnalysis; using static Content.Server.DeviceNetwork.Components.DeviceNetworkComponent; 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 sealed class DeviceNetworkSystem : EntitySystem { [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IPrototypeManager _protoMan = default!; [Dependency] private readonly SharedTransformSystem _transformSystem = default!; private readonly Dictionary _networks = new(); private readonly Queue _packets = new(); public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent(OnNetworkShutdown); } public override void Update(float frameTime) { base.Update(frameTime); while (_packets.Count > 0) { var packet = _packets.Dequeue(); SendPacket(packet); } } /// /// 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. If null, the message is broadcast to all devices on that frequency (except the sender) /// The frequency to send on /// The data to be sent public void QueuePacket(EntityUid uid, string? address, NetworkPayload data, uint? frequency = null, DeviceNetworkComponent? device = null) { if (!Resolve(uid, ref device, false)) return; if (device.Address == null) return; frequency ??= device.TransmitFrequency; if (frequency != null) _packets.Enqueue(new DeviceNetworkPacketEvent(device.DeviceNetId, address, frequency.Value, device.Address, uid, data)); } /// /// Automatically attempt to connect some devices when a map starts. /// private void OnMapInit(EntityUid uid, DeviceNetworkComponent device, MapInitEvent args) { if (device.ReceiveFrequency == null && device.ReceiveFrequencyId != null && _protoMan.TryIndex(device.ReceiveFrequencyId, out var receive)) { device.ReceiveFrequency = receive.Frequency; } if (device.TransmitFrequency == null && device.TransmitFrequencyId != null && _protoMan.TryIndex(device.TransmitFrequencyId, out var xmit)) { device.TransmitFrequency = xmit.Frequency; } if (device.AutoConnect) ConnectDevice(uid, device); } /// /// Automatically disconnect when an entity with a DeviceNetworkComponent shuts down. /// private void OnNetworkShutdown(EntityUid uid, DeviceNetworkComponent component, ComponentShutdown args) { DisconnectDevice(uid, component, false); } /// /// Connect an entity with a DeviceNetworkComponent. Note that this will re-use an existing address if the /// device already had one configured. If there is a clash, the device cannot join the network. /// public bool ConnectDevice(EntityUid uid, DeviceNetworkComponent? device = null) { if (!Resolve(uid, ref device, false)) return false; if (!_networks.TryGetValue(device.DeviceNetId, out var network)) { network = new(device.DeviceNetId, _random); _networks[device.DeviceNetId] = network; } return network.Add(device); } /// /// Disconnect an entity with a DeviceNetworkComponent. /// public bool DisconnectDevice(EntityUid uid, DeviceNetworkComponent? device, bool preventAutoConnect = true) { if (!Resolve(uid, ref device, false)) return false; // If manually disconnected, don't auto reconnect when a game state is loaded. if (preventAutoConnect) device.AutoConnect = false; if (!_networks.TryGetValue(device.DeviceNetId, out var network)) { return false; } if (!network.Remove(device)) return false; if (network.Devices.Count == 0) _networks.Remove(device.DeviceNetId); return true; } #region Get Device /// /// Get a list of devices listening on a given frequency on some network. /// private HashSet GetListeningDevices(ConnectionType netId, uint frequency) { if (_networks.TryGetValue(netId, out var network) && network.ListeningDevices.TryGetValue(frequency, out var devices)) return devices; return new(); } /// /// Get a list of devices listening for ANY transmission on a given frequency, rather than just broadcast & addressed events. /// private HashSet GetRecieveAllDevices(ConnectionType netId, uint frequency) { if (_networks.TryGetValue(netId, out var network) && network.ReceiveAllDevices.TryGetValue(frequency, out var devices)) return devices; return new(); } /// /// Try to find a device on a network using its address. /// private bool TryGetDevice(ConnectionType netId, string address, [NotNullWhen(true)] out DeviceNetworkComponent? device) { if (!_networks.TryGetValue(netId, out var network)) { device = null; return false; } return network.Devices.TryGetValue(address, out device); } #endregion #region Packet Sending private void SendPacket(DeviceNetworkPacketEvent packet) { HashSet recipients; if (packet.Address == null) { // Broadcast to all listening devices recipients = GetListeningDevices(packet.NetId, packet.Frequency); } else { // Add devices listening to all messages recipients = new(GetRecieveAllDevices(packet.NetId, packet.Frequency)); // add the intended recipient (if they are even listening). if (TryGetDevice(packet.NetId, packet.Address, out var device) && device.ReceiveFrequency == packet.Frequency) recipients.Add(device); } SendToConnections(recipients, packet); } private void SendToConnections(HashSet connections, DeviceNetworkPacketEvent packet) { var xform = Transform(packet.Sender); BeforePacketSentEvent beforeEv = new(packet.Sender, xform, _transformSystem.GetWorldPosition(xform)); foreach (var connection in connections) { if (connection.Owner == packet.Sender) continue; RaiseLocalEvent(connection.Owner, beforeEv, false); if (!beforeEv.Cancelled) RaiseLocalEvent(connection.Owner, packet, false); else beforeEv.Uncancel(); } } #endregion #region Component Setter Functions public void SetReceiveFrequency(EntityUid uid, uint? frequency, DeviceNetworkComponent? device = null) { if (!Resolve(uid, ref device, false)) return; if (device.ReceiveFrequency == frequency) return; if (!_networks.TryGetValue(device.DeviceNetId, out var deviceNet) || !deviceNet.UpdateReceiveFrequency(device.Address, frequency)) device.ReceiveFrequency = frequency; } public void SetTransmitFrequency(EntityUid uid, uint? frequency, DeviceNetworkComponent? device = null) { if (Resolve(uid, ref device, false)) device.TransmitFrequency = frequency; } public void SetReceiveAll(EntityUid uid, bool receiveAll, DeviceNetworkComponent? device = null) { if (!Resolve(uid, ref device, false)) return; if (device.ReceiveAll == receiveAll) return; if (!_networks.TryGetValue(device.DeviceNetId, out var deviceNet) || !deviceNet.UpdateReceiveAll(device.Address, receiveAll)) device.ReceiveAll = receiveAll; } public void SetAddress(EntityUid uid, string address, DeviceNetworkComponent? device = null) { if (!Resolve(uid, ref device, false)) return; if (device.Address == address) { device.CustomAddress = true; return; } if (!_networks.TryGetValue(device.DeviceNetId, out var deviceNet) || !deviceNet.UpdateAddress(device.Address, address)) { device.Address = address; device.CustomAddress = true; } } public void RandomizeAddress(EntityUid uid, string address, DeviceNetworkComponent? device = null) { if (!Resolve(uid, ref device, false)) return; if (!_networks.TryGetValue(device.DeviceNetId, out var deviceNet) || !deviceNet.RandomizeAddress(device.Address, address)) { var prefix = string.IsNullOrWhiteSpace(device.Prefix) ? null : Loc.GetString(device.Prefix); device.Address = $"{prefix}{_random.Next():x}"; device.CustomAddress = false; } } #endregion } /// /// Event raised before a device network packet is send. /// Subscribed to by other systems to prevent the packet from being sent. /// public sealed class BeforePacketSentEvent : CancellableEntityEventArgs { /// /// The EntityUid of the entity the packet was sent from. /// public readonly EntityUid Sender; public readonly TransformComponent SenderTransform; /// /// The senders current position in world coordinates. /// public readonly Vector2 SenderPosition; public BeforePacketSentEvent(EntityUid sender, TransformComponent xform, Vector2 senderPosition) { Sender = sender; SenderTransform = xform; SenderPosition = senderPosition; } } /// /// Event raised when a device network packet gets sent. /// public sealed class DeviceNetworkPacketEvent : EntityEventArgs { /// /// The type of network that this packet is being sent on. /// public ConnectionType NetId; /// /// The frequency the packet is sent on. /// public readonly uint Frequency; /// /// Address of the intended recipient. Null if the message was broadcast. /// public string? Address; /// /// The device network address of the sending entity. /// public readonly string SenderAddress; /// /// The entity that sent the packet. /// public EntityUid Sender; /// /// The data that is being sent. /// public readonly NetworkPayload Data; public DeviceNetworkPacketEvent(ConnectionType netId, string? address, uint frequency, string senderAddress, EntityUid sender, NetworkPayload data) { NetId = netId; Address = address; Frequency = frequency; SenderAddress = senderAddress; Sender = sender; Data = data; } } }