using Content.Server.DeviceNetwork.Components;
using Content.Shared.DeviceNetwork;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Utility;
using System.Buffers;
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 DeviceNet[] _networks = new DeviceNet[4]; // Number of ConnectionType enum values
private readonly Queue _packets = new();
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent(OnMapInit);
SubscribeLocalEvent(OnNetworkShutdown);
InitNetwork(ConnectionType.Private);
InitNetwork(ConnectionType.Wired);
InitNetwork(ConnectionType.Wireless);
InitNetwork(ConnectionType.Apc);
}
public override void Update(float frameTime)
{
while (_packets.TryDequeue(out var packet))
{
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));
}
private void InitNetwork(ConnectionType connectionType) =>
_networks[(int) connectionType] = new(connectionType, _random);
///
/// 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(ConnectionType connectionType) =>
_networks[(int) connectionType];
///
/// Automatically disconnect when an entity with a DeviceNetworkComponent shuts down.
///
private void OnNetworkShutdown(EntityUid uid, DeviceNetworkComponent component, ComponentShutdown args)
{
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);
}
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 == true) 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(ConnectionType 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)
{
if (network.ListeningDevices.TryGetValue(packet.Frequency, out var 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);
}
}
private void SendToConnections(ReadOnlySpan 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();
}
}
}
///
/// 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;
}
}
}