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;
}
}
}