using Content.Shared.DeviceNetwork; using JetBrains.Annotations; using Robust.Shared.Prototypes; using Robust.Shared.Random; using System.Buffers; using System.Diagnostics.CodeAnalysis; using Content.Shared.DeviceNetwork.Components; using Content.Shared.DeviceNetwork.Events; using Content.Shared.DeviceNetwork.Systems; using Content.Shared.Examine; 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 : SharedDeviceNetworkSystem { [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IPrototypeManager _protoMan = default!; [Dependency] private readonly SharedTransformSystem _transformSystem = default!; [Dependency] private readonly DeviceListSystem _deviceLists = default!; [Dependency] private readonly NetworkConfiguratorSystem _configurator = default!; private readonly Dictionary _networks = new(4); private readonly Queue _queueA = new(); private readonly Queue _queueB = new(); /// /// The queue being processed in the current tick /// private Queue _activeQueue = null!; /// /// The queue that will be processed in the next tick /// private Queue _nextQueue = null!; public override void Initialize() { SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent(OnNetworkShutdown); SubscribeLocalEvent(OnExamine); _activeQueue = _queueA; _nextQueue = _queueB; } public override void Update(float frameTime) { while (_activeQueue.TryDequeue(out var packet)) { SendPacket(packet); } SwapQueues(); } public override bool QueuePacket(EntityUid uid, string? address, NetworkPayload data, uint? frequency = null, int? network = null, DeviceNetworkComponent? device = null) { if (!Resolve(uid, ref device, false)) return false; if (device.Address == string.Empty) return false; frequency ??= device.TransmitFrequency; if (frequency == null) return false; network ??= device.DeviceNetId; _nextQueue.Enqueue(new DeviceNetworkPacketEvent(network.Value, address, frequency.Value, device.Address, uid, data)); return true; } /// /// Swaps the active queue. /// Queues are swapped so that packets being sent in the current tick get processed in the next tick. /// /// /// This prevents infinite loops while sending packets /// private void SwapQueues() { _nextQueue = _activeQueue; _activeQueue = _activeQueue == _queueA ? _queueB : _queueA; } private void OnExamine(EntityUid uid, DeviceNetworkComponent device, ExaminedEvent args) { if (device.ExaminableAddress) { args.PushText(Loc.GetString("device-address-examine-message", ("address", device.Address))); } } /// /// 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); } private DeviceNet GetNetwork(int netId) { if (_networks.TryGetValue(netId, out var deviceNet)) return deviceNet; var newDeviceNet = new DeviceNet(netId, _random); _networks[netId] = newDeviceNet; return newDeviceNet; } /// /// Automatically disconnect when an entity with a DeviceNetworkComponent shuts down. /// private void OnNetworkShutdown(EntityUid uid, DeviceNetworkComponent component, ComponentShutdown args) { foreach (var list in component.DeviceLists) { _deviceLists.OnDeviceShutdown(list, (uid, component)); } foreach (var list in component.Configurators) { _configurator.OnDeviceShutdown(list, (uid, component)); } GetNetwork(component.DeviceNetId).Remove(component); } /// /// 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; return GetNetwork(device.DeviceNetId).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; return GetNetwork(device.DeviceNetId).Remove(device); } /// /// Checks if a device is already connected to its network /// /// True if the device was found in the network with its corresponding network id public bool IsDeviceConnected(EntityUid uid, DeviceNetworkComponent? device) { if (!Resolve(uid, ref device, false)) return false; if (!_networks.TryGetValue(device.DeviceNetId, out var deviceNet)) return false; return deviceNet.Devices.ContainsValue(device); } /// /// Checks if an address exists in the network with the given netId /// public bool IsAddressPresent(int netId, string? address) { if (address == null || !_networks.TryGetValue(netId, out var network)) return false; return network.Devices.ContainsKey(address); } public void SetReceiveFrequency(EntityUid uid, uint? frequency, DeviceNetworkComponent? device = null) { if (!Resolve(uid, ref device, false)) return; if (device.ReceiveFrequency == frequency) return; var deviceNet = GetNetwork(device.DeviceNetId); deviceNet.Remove(device); device.ReceiveFrequency = frequency; deviceNet.Add(device); } 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; var deviceNet = GetNetwork(device.DeviceNetId); deviceNet.Remove(device); device.ReceiveAll = receiveAll; deviceNet.Add(device); } public void SetAddress(EntityUid uid, string address, DeviceNetworkComponent? device = null) { if (!Resolve(uid, ref device, false)) return; if (device.Address == address && device.CustomAddress) return; var deviceNet = GetNetwork(device.DeviceNetId); deviceNet.Remove(device); device.CustomAddress = true; device.Address = address; deviceNet.Add(device); } public void RandomizeAddress(EntityUid uid, DeviceNetworkComponent? device = null) { if (!Resolve(uid, ref device, false)) return; var deviceNet = GetNetwork(device.DeviceNetId); deviceNet.Remove(device); device.CustomAddress = false; device.Address = ""; deviceNet.Add(device); } /// /// Try to find a device on a network using its address. /// private bool TryGetDevice(int netId, string address, [NotNullWhen(true)] out DeviceNetworkComponent? device) => GetNetwork(netId).Devices.TryGetValue(address, out device); private void SendPacket(DeviceNetworkPacketEvent packet) { var network = GetNetwork(packet.NetId); if (packet.Address == null) { // Broadcast to all listening devices if (network.ListeningDevices.TryGetValue(packet.Frequency, out var devices) && CheckRecipientsList(packet, ref devices)) { var deviceCopy = ArrayPool.Shared.Rent(devices.Count); devices.CopyTo(deviceCopy); SendToConnections(deviceCopy.AsSpan(0, devices.Count), packet); ArrayPool.Shared.Return(deviceCopy); } } else { var totalDevices = 0; var hasTargetedDevice = false; if (network.ReceiveAllDevices.TryGetValue(packet.Frequency, out var devices)) { totalDevices += devices.Count; } if (TryGetDevice(packet.NetId, packet.Address, out var device) && !device.ReceiveAll && device.ReceiveFrequency == packet.Frequency) { totalDevices += 1; hasTargetedDevice = true; } var deviceCopy = ArrayPool.Shared.Rent(totalDevices); if (devices != null) { devices.CopyTo(deviceCopy); } if (hasTargetedDevice) { deviceCopy[totalDevices - 1] = device!; } SendToConnections(deviceCopy.AsSpan(0, totalDevices), packet); ArrayPool.Shared.Return(deviceCopy); } } /// /// Sends the to the sending entity if the packets SendBeforeBroadcastAttemptEvent field is set to true. /// The recipients is set to the modified recipient list. /// /// false if the broadcast was canceled private bool CheckRecipientsList(DeviceNetworkPacketEvent packet, ref HashSet recipients) { if (!_networks.ContainsKey(packet.NetId) || !_networks[packet.NetId].Devices.ContainsKey(packet.SenderAddress)) return false; var sender = _networks[packet.NetId].Devices[packet.SenderAddress]; if (!sender.SendBroadcastAttemptEvent) return true; var beforeBroadcastAttemptEvent = new BeforeBroadcastAttemptEvent(recipients); RaiseLocalEvent(packet.Sender, beforeBroadcastAttemptEvent, true); if (beforeBroadcastAttemptEvent.Cancelled || beforeBroadcastAttemptEvent.ModifiedRecipients == null) return false; recipients = beforeBroadcastAttemptEvent.ModifiedRecipients; return true; } private void SendToConnections(ReadOnlySpan connections, DeviceNetworkPacketEvent packet) { if (Deleted(packet.Sender)) { return; } var xform = Transform(packet.Sender); var senderPos = _transformSystem.GetWorldPosition(xform); foreach (var connection in connections) { if (connection.Owner == packet.Sender) continue; BeforePacketSentEvent beforeEv = new(packet.Sender, xform, senderPos, connection.NetIdEnum.ToString(), packet.Frequency); RaiseLocalEvent(connection.Owner, beforeEv, false); if (!beforeEv.Cancelled) RaiseLocalEvent(connection.Owner, packet, false); else beforeEv.Uncancel(); } } } }