first commit
This commit is contained in:
commit
90b9eae405
43 changed files with 2245 additions and 0 deletions
50
Matchmaker/BaseServer/Attribute.cs
Executable file
50
Matchmaker/BaseServer/Attribute.cs
Executable file
|
@ -0,0 +1,50 @@
|
|||
namespace Matchmaker.Server.BaseServer;
|
||||
|
||||
public class ClientAttributes
|
||||
{
|
||||
private const uint MaxAttributes = 64;
|
||||
private class Attribute
|
||||
{
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Local
|
||||
public string AttributeName;
|
||||
public string AttributeValue;
|
||||
|
||||
public Attribute(string name, string value)
|
||||
{
|
||||
AttributeName = name;
|
||||
AttributeValue = value;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<Attribute> _attributes = new();
|
||||
|
||||
public void SetAttribute(string name, string value)
|
||||
{
|
||||
// Check if the attribute is already set
|
||||
foreach (var attr in _attributes.Where(attr => attr.AttributeName.Equals(name)))
|
||||
{
|
||||
attr.AttributeValue = value;
|
||||
return;
|
||||
}
|
||||
|
||||
// If it is not
|
||||
if (_attributes.Count < MaxAttributes)
|
||||
{
|
||||
_attributes.Add(new Attribute(name, value));
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Failed to add attribute: Reached limit!");
|
||||
}
|
||||
}
|
||||
|
||||
public string? GetAttribute(string name)
|
||||
{
|
||||
return _attributes.Where(attr => attr.AttributeName.Equals(name)).Select(attr => attr.AttributeValue).FirstOrDefault();
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_attributes.Clear();
|
||||
}
|
||||
}
|
306
Matchmaker/BaseServer/Client.cs
Executable file
306
Matchmaker/BaseServer/Client.cs
Executable file
|
@ -0,0 +1,306 @@
|
|||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace Matchmaker.Server.BaseServer;
|
||||
|
||||
public class Client
|
||||
{
|
||||
public ServerSend.StatusType LastStatus;
|
||||
|
||||
public readonly ClientAttributes Attributes = new();
|
||||
|
||||
public readonly TCP? Tcp;
|
||||
public readonly UDP Udp;
|
||||
public readonly Server ServerParent;
|
||||
|
||||
public Client(int clientId, Server server)
|
||||
{
|
||||
Tcp = new TCP(clientId, server);
|
||||
Udp = new UDP(clientId, server);
|
||||
ServerParent = server;
|
||||
}
|
||||
|
||||
public bool IsConnected
|
||||
{
|
||||
get
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Tcp?.Socket != null && Tcp.Socket.Client != null && Tcp.Socket.Client.Connected)
|
||||
{
|
||||
/* pear to the documentation on Poll:
|
||||
* When passing SelectMode.SelectRead as a parameter to the Poll method it will return
|
||||
* -either- true if Socket.Listen(Int32) has been called and a connection is pending;
|
||||
* -or- true if data is available for reading;
|
||||
* -or- true if the connection has been closed, reset, or terminated;
|
||||
* otherwise, returns false
|
||||
*/
|
||||
|
||||
// Detect if client disconnected
|
||||
if (Tcp.Socket.Client.Poll(0, SelectMode.SelectRead))
|
||||
{
|
||||
byte[] buff = new byte[1];
|
||||
if (Tcp.Socket.Client.Receive(buff, SocketFlags.Peek) == 0)
|
||||
{
|
||||
// Client disconnected
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public class TCP
|
||||
{
|
||||
private readonly Server _serv;
|
||||
public TcpClient? Socket;
|
||||
|
||||
private readonly int _id;
|
||||
private NetworkStream? _stream;
|
||||
private Packet? _receivedData;
|
||||
private byte[]? _receiveBuffer;
|
||||
|
||||
public TCP(int id, Server server)
|
||||
{
|
||||
_serv = server;
|
||||
_id = id;
|
||||
}
|
||||
|
||||
|
||||
public void Connect(TcpClient? socket)
|
||||
{
|
||||
Terminal.LogDebug("Connecting TCP...");
|
||||
Socket = socket;
|
||||
if (Socket != null)
|
||||
{
|
||||
Socket.ReceiveBufferSize = Constants.DataBufferSize;
|
||||
Socket.SendBufferSize = Constants.DataBufferSize;
|
||||
|
||||
_stream = Socket.GetStream();
|
||||
}
|
||||
|
||||
_receivedData = new Packet();
|
||||
_receiveBuffer = new byte[Constants.DataBufferSize];
|
||||
|
||||
_stream?.BeginRead(_receiveBuffer, 0, Constants.DataBufferSize, ReceiveCallback, null);
|
||||
|
||||
ServerSend.Welcome(_serv, _id, "Welcome to the server!");
|
||||
}
|
||||
|
||||
public void SendData(Packet packet)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Socket != null)
|
||||
{
|
||||
_stream?.BeginWrite(packet.ToArray(), 0, packet.Length(), null, null);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Terminal.LogError($"Error sending data to player {_id} via TCP: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
private void ReceiveCallback(IAsyncResult result)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Socket?.Client.RemoteEndPoint is not IPEndPoint remoteIpEndPoint)
|
||||
{
|
||||
Terminal.LogDebug($"[{_serv.DisplayName}]: RemoteIpEndPoint is NULL!");
|
||||
_serv.Clients[_id].Attributes.Clear();
|
||||
_serv.Clients[_id].Disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_stream != null)
|
||||
{
|
||||
var byteLength = _stream.EndRead(result);
|
||||
if (byteLength <= 0)
|
||||
{
|
||||
Terminal.LogDebug("Client has left. Disconnecting...");
|
||||
_serv.DisconnectClient(_id);
|
||||
return;
|
||||
}
|
||||
|
||||
var data = new byte[byteLength];
|
||||
if (_receiveBuffer != null) Array.Copy(_receiveBuffer, data, byteLength);
|
||||
|
||||
_receivedData?.Reset(HandleData(data));
|
||||
}
|
||||
|
||||
if (_receiveBuffer != null)
|
||||
{
|
||||
_stream?.BeginRead(_receiveBuffer, 0, Constants.DataBufferSize, ReceiveCallback, null);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Terminal.LogError($"[{_serv.DisplayName}] Error receiving TCP data: {ex}");
|
||||
_serv.DisconnectClient(_id);
|
||||
}
|
||||
}
|
||||
|
||||
private bool HandleData(IEnumerable<byte> data)
|
||||
{
|
||||
var packetLength = 0;
|
||||
|
||||
_receivedData?.SetBytes(data);
|
||||
|
||||
if (_receivedData?.UnreadLength() >= 4)
|
||||
{
|
||||
packetLength = _receivedData.ReadInt();
|
||||
if (packetLength <= 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
while (packetLength > 0 && packetLength <= _receivedData?.UnreadLength())
|
||||
{
|
||||
var packetBytes = _receivedData.ReadBytes(packetLength);
|
||||
ThreadManager.ExecuteOnMainThread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var packet = new Packet(packetBytes);
|
||||
|
||||
var packetId = packet.ReadInt();
|
||||
if ((packetId != int.MaxValue - 3) && Config.Sync)
|
||||
{
|
||||
ServerSend.Status(_serv, _id, ServerSend.StatusType.RECEIVED);
|
||||
}
|
||||
|
||||
Terminal.LogDebug($"[{_serv.DisplayName}] Received TCP Packet with ID: " + packetId);
|
||||
var x = _serv.Packets?.PacketHandlers.ContainsKey(packetId);
|
||||
if (x != null && x != false)
|
||||
{
|
||||
_serv.Packets?.PacketHandlers[packetId].DynamicInvoke(_serv, _id, packet);
|
||||
}
|
||||
else
|
||||
{
|
||||
Terminal.LogError(
|
||||
$"[{_serv.DisplayName}] Received unregistered TCP packet with ID: {packetId}");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Terminal.LogError($"[{_serv.DisplayName}] Error processing packet: {e}");
|
||||
throw;
|
||||
}
|
||||
});
|
||||
|
||||
packetLength = 0;
|
||||
if (_receivedData.UnreadLength() < 4) continue;
|
||||
packetLength = _receivedData.ReadInt();
|
||||
if (packetLength <= 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return packetLength <= 1;
|
||||
}
|
||||
|
||||
public void Disconnect()
|
||||
{
|
||||
Socket?.Close();
|
||||
_stream = null;
|
||||
_receivedData = null;
|
||||
_receiveBuffer = null;
|
||||
Socket = null;
|
||||
}
|
||||
}
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public class UDP
|
||||
{
|
||||
public IPEndPoint? EndPoint;
|
||||
private readonly Server _serv;
|
||||
private readonly int _id;
|
||||
|
||||
public UDP(int id, Server server)
|
||||
{
|
||||
_serv = server;
|
||||
_id = id;
|
||||
}
|
||||
|
||||
public void Connect(IPEndPoint endPoint)
|
||||
{
|
||||
EndPoint = endPoint;
|
||||
}
|
||||
|
||||
public void SendData(Packet packet)
|
||||
{
|
||||
if (EndPoint != null) _serv.SendUdpData(EndPoint, packet);
|
||||
}
|
||||
|
||||
public void HandleData(Packet packetData)
|
||||
{
|
||||
var packetLength = packetData.ReadInt();
|
||||
var packetBytes = packetData.ReadBytes(packetLength);
|
||||
|
||||
ThreadManager.ExecuteOnMainThread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
using var packet = new Packet(packetBytes);
|
||||
var packetId = packet.ReadInt();
|
||||
if ((packetId != int.MaxValue - 3) && Config.Sync)
|
||||
{
|
||||
ServerSend.Status(_serv, _id, ServerSend.StatusType.RECEIVED);
|
||||
}
|
||||
|
||||
Terminal.LogDebug($"[{_serv.DisplayName}] Received UDP Packet with ID: " + packetId);
|
||||
var x = _serv.Packets?.PacketHandlers.ContainsKey(packetId);
|
||||
if (x != null && x != false)
|
||||
{
|
||||
_serv.Packets?.PacketHandlers[packetId].DynamicInvoke(_serv, _id, packet);
|
||||
}
|
||||
else
|
||||
{
|
||||
Terminal.LogError(
|
||||
$"[{_serv.DisplayName}] Received unregistered UDP packet with ID: {packetId}");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Terminal.LogError($"[{_serv.DisplayName}] Error processing packet: {e}");
|
||||
throw;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void Disconnect()
|
||||
{
|
||||
EndPoint = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void Disconnect()
|
||||
{
|
||||
Terminal.LogInfo(
|
||||
$"[{ServerParent.DisplayName}] Disconnecting Client ({Tcp?.Socket?.Client.RemoteEndPoint})...");
|
||||
|
||||
Tcp?.Disconnect();
|
||||
Udp.Disconnect();
|
||||
Attributes.Clear();
|
||||
}
|
||||
}
|
12
Matchmaker/BaseServer/Constants.cs
Executable file
12
Matchmaker/BaseServer/Constants.cs
Executable file
|
@ -0,0 +1,12 @@
|
|||
|
||||
|
||||
namespace Matchmaker.Server.BaseServer;
|
||||
|
||||
internal static class Constants
|
||||
{
|
||||
public const int TicksPerSec = 30;
|
||||
public const float MsPerTick = 1000f / TicksPerSec;
|
||||
public const int DataBufferSize = 4096;
|
||||
|
||||
|
||||
}
|
12
Matchmaker/BaseServer/GameLogic.cs
Executable file
12
Matchmaker/BaseServer/GameLogic.cs
Executable file
|
@ -0,0 +1,12 @@
|
|||
|
||||
|
||||
namespace Matchmaker.Server.BaseServer;
|
||||
|
||||
internal static class GameLogic
|
||||
{
|
||||
public static void Update()
|
||||
{
|
||||
ThreadManager.UpdateMain();
|
||||
|
||||
}
|
||||
}
|
398
Matchmaker/BaseServer/Packet.cs
Executable file
398
Matchmaker/BaseServer/Packet.cs
Executable file
|
@ -0,0 +1,398 @@
|
|||
|
||||
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
|
||||
namespace Matchmaker.Server.BaseServer;
|
||||
|
||||
public sealed class Packet : IDisposable
|
||||
{
|
||||
private List<byte> _buffer;
|
||||
private byte[] _readableBuffer = null!;
|
||||
private int _readPos;
|
||||
|
||||
/// <summary>Creates a new empty packet (without an ID).</summary>
|
||||
public Packet()
|
||||
{
|
||||
_buffer = new List<byte>(); // Initialize buffer
|
||||
_readPos = 0; // Set readPos to 0
|
||||
}
|
||||
|
||||
/// <summary>Creates a new packet with a given ID. Used for sending.</summary>
|
||||
/// <param name="id">The packet ID.</param>
|
||||
public Packet(int id)
|
||||
{
|
||||
_buffer = new List<byte>(); // Initialize buffer
|
||||
_readPos = 0; // Set readPos to 0
|
||||
|
||||
Write(id); // Write packet id to the buffer
|
||||
}
|
||||
|
||||
/// <summary>Creates a packet from which data can be read. Used for receiving.</summary>
|
||||
/// <param name="data">The bytes to add to the packet.</param>
|
||||
public Packet(IEnumerable<byte> data)
|
||||
{
|
||||
_buffer = new List<byte>(); // Initialize buffer
|
||||
_readPos = 0; // Set readPos to 0
|
||||
|
||||
SetBytes(data);
|
||||
}
|
||||
|
||||
#region Functions
|
||||
/// <summary>Sets the packet's content and prepares it to be read.</summary>
|
||||
/// <param name="data">The bytes to add to the packet.</param>
|
||||
public void SetBytes(IEnumerable<byte> data)
|
||||
{
|
||||
Write(data);
|
||||
_readableBuffer = _buffer.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>Inserts the length of the packet's content at the start of the buffer.</summary>
|
||||
public void WriteLength()
|
||||
{
|
||||
_buffer.InsertRange(0, BitConverter.GetBytes(_buffer.Count)); // Insert the byte length of the packet at the very beginning
|
||||
}
|
||||
|
||||
/// <summary>Inserts the given int at the start of the buffer.</summary>
|
||||
/// <param name="value">The int to insert.</param>
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public void InsertInt(int value)
|
||||
{
|
||||
_buffer.InsertRange(0, BitConverter.GetBytes(value)); // Insert the int at the start of the buffer
|
||||
}
|
||||
|
||||
/// <summary>Gets the packet's content in array form.</summary>
|
||||
public byte[] ToArray()
|
||||
{
|
||||
_readableBuffer = _buffer.ToArray();
|
||||
return _readableBuffer;
|
||||
}
|
||||
|
||||
/// <summary>Gets the length of the packet's content.</summary>
|
||||
public int Length()
|
||||
{
|
||||
return _buffer.Count; // Return the length of buffer
|
||||
}
|
||||
|
||||
/// <summary>Gets the length of the unread data contained in the packet.</summary>
|
||||
public int UnreadLength()
|
||||
{
|
||||
return Length() - _readPos; // Return the remaining length (unread)
|
||||
}
|
||||
|
||||
/// <summary>Resets the packet instance to allow it to be reused.</summary>
|
||||
/// <param name="shouldReset">Whether or not to reset the packet.</param>
|
||||
public void Reset(bool shouldReset = true)
|
||||
{
|
||||
if (shouldReset)
|
||||
{
|
||||
_buffer.Clear(); // Clear buffer
|
||||
_readableBuffer = null!;
|
||||
_readPos = 0; // Reset readPos
|
||||
}
|
||||
else
|
||||
{
|
||||
_readPos -= 4; // "Unread" the last read int
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Write Data
|
||||
/// <summary>Adds a byte to the packet.</summary>
|
||||
/// <param name="value">The byte to add.</param>
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public void Write(byte value)
|
||||
{
|
||||
_buffer.Add(value);
|
||||
}
|
||||
/// <summary>Adds an array of bytes to the packet.</summary>
|
||||
/// <param name="value">The byte array to add.</param>
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
public void Write(IEnumerable<byte> value)
|
||||
{
|
||||
_buffer.AddRange(value);
|
||||
}
|
||||
/// <summary>Adds a short to the packet.</summary>
|
||||
/// <param name="value">The short to add.</param>
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public void Write(short value)
|
||||
{
|
||||
_buffer.AddRange(BitConverter.GetBytes(value));
|
||||
}
|
||||
/// <summary>Adds an int to the packet.</summary>
|
||||
/// <param name="value">The int to add.</param>
|
||||
public void Write(int value)
|
||||
{
|
||||
_buffer.AddRange(BitConverter.GetBytes(value));
|
||||
}
|
||||
/// <summary>Adds a long to the packet.</summary>
|
||||
/// <param name="value">The long to add.</param>
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public void Write(long value)
|
||||
{
|
||||
_buffer.AddRange(BitConverter.GetBytes(value));
|
||||
}
|
||||
/// <summary>Adds a float to the packet.</summary>
|
||||
/// <param name="value">The float to add.</param>
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public void Write(float value)
|
||||
{
|
||||
_buffer.AddRange(BitConverter.GetBytes(value));
|
||||
}
|
||||
/// <summary>Adds a bool to the packet.</summary>
|
||||
/// <param name="value">The bool to add.</param>
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public void Write(bool value)
|
||||
{
|
||||
_buffer.AddRange(BitConverter.GetBytes(value));
|
||||
}
|
||||
/// <summary>Adds a string to the packet.</summary>
|
||||
/// <param name="value">The string to add.</param>
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public void Write(string value)
|
||||
{
|
||||
Write(value.Length); // Add the length of the string to the packet
|
||||
_buffer.AddRange(Encoding.ASCII.GetBytes(value)); // Add the string itself
|
||||
}
|
||||
/// <summary>Adds a Vector3 to the packet.</summary>
|
||||
/// <param name="value">The Vector3 to add.</param>
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public void Write(Vector3 value)
|
||||
{
|
||||
Write(value.X);
|
||||
Write(value.Y);
|
||||
Write(value.Z);
|
||||
}
|
||||
/// <summary>Adds a Quaternion to the packet.</summary>
|
||||
/// <param name="value">The Quaternion to add.</param>
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public void Write(Quaternion value)
|
||||
{
|
||||
Write(value.X);
|
||||
Write(value.Y);
|
||||
Write(value.Z);
|
||||
Write(value.W);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Read Data
|
||||
/// <summary>Reads a byte from the packet.</summary>
|
||||
/// <param name="moveReadPos">Whether or not to move the buffer's read position.</param>
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public byte ReadByte(bool moveReadPos = true)
|
||||
{
|
||||
if (_buffer.Count > _readPos)
|
||||
{
|
||||
// If there are unread bytes
|
||||
var value = _readableBuffer[_readPos]; // Get the byte at readPos' position
|
||||
if (moveReadPos)
|
||||
{
|
||||
// If _moveReadPos is true
|
||||
_readPos += 1; // Increase readPos by 1
|
||||
}
|
||||
return value; // Return the byte
|
||||
}
|
||||
// ReSharper disable once RedundantIfElseBlock
|
||||
else
|
||||
{
|
||||
throw new Exception("Could not read value of type 'byte'!");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Reads an array of bytes from the packet.</summary>
|
||||
/// <param name="length">The length of the byte array.</param>
|
||||
/// <param name="moveReadPos">Whether or not to move the buffer's read position.</param>
|
||||
public byte[] ReadBytes(int length, bool moveReadPos = true)
|
||||
{
|
||||
if (_buffer.Count > _readPos)
|
||||
{
|
||||
// If there are unread bytes
|
||||
var value = _buffer.GetRange(_readPos, length).ToArray(); // Get the bytes at readPos' position with a range of _length
|
||||
if (moveReadPos)
|
||||
{
|
||||
// If _moveReadPos is true
|
||||
_readPos += length; // Increase readPos by _length
|
||||
}
|
||||
return value; // Return the bytes
|
||||
}
|
||||
|
||||
// ReSharper disable once RedundantIfElseBlock
|
||||
else
|
||||
{
|
||||
throw new Exception("Could not read value of type 'byte[]'!");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Reads a short from the packet.</summary>
|
||||
/// <param name="moveReadPos">Whether or not to move the buffer's read position.</param>
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public short ReadShort(bool moveReadPos = true)
|
||||
{
|
||||
if (_buffer.Count > _readPos)
|
||||
{
|
||||
// If there are unread bytes
|
||||
var value = BitConverter.ToInt16(_readableBuffer, _readPos); // Convert the bytes to a short
|
||||
if (moveReadPos)
|
||||
{
|
||||
// If _moveReadPos is true and there are unread bytes
|
||||
_readPos += 2; // Increase readPos by 2
|
||||
}
|
||||
return value; // Return the short
|
||||
}
|
||||
// ReSharper disable once RedundantIfElseBlock
|
||||
else
|
||||
{
|
||||
throw new Exception("Could not read value of type 'short'!");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Reads an int from the packet.</summary>
|
||||
/// <param name="moveReadPos">Whether or not to move the buffer's read position.</param>
|
||||
public int ReadInt(bool moveReadPos = true)
|
||||
{
|
||||
if (_buffer.Count > _readPos)
|
||||
{
|
||||
// If there are unread bytes
|
||||
var value = BitConverter.ToInt32(_readableBuffer, _readPos); // Convert the bytes to an int
|
||||
if (moveReadPos)
|
||||
{
|
||||
// If _moveReadPos is true
|
||||
_readPos += 4; // Increase readPos by 4
|
||||
}
|
||||
return value; // Return the int
|
||||
}
|
||||
// ReSharper disable once RedundantIfElseBlock
|
||||
else
|
||||
{
|
||||
throw new Exception("Could not read value of type 'int'!");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Reads a long from the packet.</summary>
|
||||
/// <param name="moveReadPos">Whether or not to move the buffer's read position.</param>
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public long ReadLong(bool moveReadPos = true)
|
||||
{
|
||||
if (_buffer.Count > _readPos)
|
||||
{
|
||||
// If there are unread bytes
|
||||
var value = BitConverter.ToInt64(_readableBuffer, _readPos); // Convert the bytes to a long
|
||||
if (moveReadPos)
|
||||
{
|
||||
// If _moveReadPos is true
|
||||
_readPos += 8; // Increase readPos by 8
|
||||
}
|
||||
return value; // Return the long
|
||||
}
|
||||
// ReSharper disable once RedundantIfElseBlock
|
||||
else
|
||||
{
|
||||
throw new Exception("Could not read value of type 'long'!");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Reads a float from the packet.</summary>
|
||||
/// <param name="moveReadPos">Whether or not to move the buffer's read position.</param>
|
||||
public float ReadFloat(bool moveReadPos = true)
|
||||
{
|
||||
if (_buffer.Count > _readPos)
|
||||
{
|
||||
// If there are unread bytes
|
||||
var value = BitConverter.ToSingle(_readableBuffer, _readPos); // Convert the bytes to a float
|
||||
if (moveReadPos)
|
||||
{
|
||||
// If _moveReadPos is true
|
||||
_readPos += 4; // Increase readPos by 4
|
||||
}
|
||||
return value; // Return the float
|
||||
}
|
||||
// ReSharper disable once RedundantIfElseBlock
|
||||
else
|
||||
{
|
||||
throw new Exception("Could not read value of type 'float'!");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Reads a bool from the packet.</summary>
|
||||
/// <param name="moveReadPos">Whether or not to move the buffer's read position.</param>
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public bool ReadBool(bool moveReadPos = true)
|
||||
{
|
||||
if (_buffer.Count > _readPos)
|
||||
{
|
||||
// If there are unread bytes
|
||||
var value = BitConverter.ToBoolean(_readableBuffer, _readPos); // Convert the bytes to a bool
|
||||
if (moveReadPos)
|
||||
{
|
||||
// If _moveReadPos is true
|
||||
_readPos += 1; // Increase readPos by 1
|
||||
}
|
||||
return value; // Return the bool
|
||||
}
|
||||
// ReSharper disable once RedundantIfElseBlock
|
||||
else
|
||||
{
|
||||
throw new Exception("Could not read value of type 'bool'!");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Reads a string from the packet.</summary>
|
||||
/// <param name="moveReadPos">Whether or not to move the buffer's read position.</param>
|
||||
public string ReadString(bool moveReadPos = true)
|
||||
{
|
||||
try
|
||||
{
|
||||
var length = ReadInt(); // Get the length of the string
|
||||
var value = Encoding.ASCII.GetString(_readableBuffer, _readPos, length); // Convert the bytes to a string
|
||||
if (moveReadPos && value.Length > 0)
|
||||
{
|
||||
// If _moveReadPos is true string is not empty
|
||||
_readPos += length; // Increase readPos by the length of the string
|
||||
}
|
||||
return value; // Return the string
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw new Exception("Could not read value of type 'string'!");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Reads a Vector3 from the packet.</summary>
|
||||
/// <param name="moveReadPos">Whether or not to move the buffer's read position.</param>
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public Vector3 ReadVector3(bool moveReadPos = true)
|
||||
{
|
||||
return new Vector3(ReadFloat(moveReadPos), ReadFloat(moveReadPos), ReadFloat(moveReadPos));
|
||||
}
|
||||
|
||||
/// <summary>Reads a Quaternion from the packet.</summary>
|
||||
/// <param name="moveReadPos">Whether or not to move the buffer's read position.</param>
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public Quaternion ReadQuaternion(bool moveReadPos = true)
|
||||
{
|
||||
return new Quaternion(ReadFloat(moveReadPos), ReadFloat(moveReadPos), ReadFloat(moveReadPos), ReadFloat(moveReadPos));
|
||||
}
|
||||
#endregion
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed) return;
|
||||
if (disposing)
|
||||
{
|
||||
_buffer = null!;
|
||||
_readableBuffer = null!;
|
||||
_readPos = 0;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
// ReSharper disable once GCSuppressFinalizeForTypeWithoutDestructor
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
159
Matchmaker/BaseServer/Server.cs
Executable file
159
Matchmaker/BaseServer/Server.cs
Executable file
|
@ -0,0 +1,159 @@
|
|||
|
||||
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace Matchmaker.Server.BaseServer;
|
||||
|
||||
public class Server
|
||||
{
|
||||
public int MaxPlayers;
|
||||
private int _port;
|
||||
public readonly Dictionary<int, Client> Clients = new();
|
||||
|
||||
public string DisplayName = "Unnamed Server";
|
||||
|
||||
public class ServerPackets
|
||||
{
|
||||
public delegate void PacketHandler(Server server, int fromClient, Packet packet);
|
||||
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Global
|
||||
public Dictionary<int, PacketHandler> PacketHandlers;
|
||||
|
||||
public ServerPackets(Dictionary<int, PacketHandler> packetHandlers)
|
||||
{
|
||||
PacketHandlers = packetHandlers;
|
||||
PacketHandlers.Add(int.MaxValue, ServerHandle.WelcomeReceived);
|
||||
PacketHandlers.Add(int.MaxValue - 1, ServerHandle.SetClientAttribute);
|
||||
PacketHandlers.Add(int.MaxValue - 2, ServerHandle.GetClientAttribute);
|
||||
}
|
||||
}
|
||||
|
||||
public ServerPackets? Packets;
|
||||
/*
|
||||
public Dictionary<int, Delegate> packetHandlers = new Dictionary<int, Delegate>()
|
||||
{
|
||||
{ (int)ClientPackets.welcomeReceived, ServerHandle.WelcomeReceived },
|
||||
{ (int)ClientPackets.setClientAttributes, ServerHandle.SetClientAttribute },
|
||||
{ (int)ClientPackets.getClientAttributes, ServerHandle.GetClientAttribute }
|
||||
};
|
||||
*/
|
||||
private TcpListener _tcpListener = null!;
|
||||
|
||||
private UdpClient _udpListener = null!;
|
||||
|
||||
// ReSharper disable once UnusedMethodReturnValue.Global
|
||||
public bool Start(ServerPackets packets, int maxPlayers, int port, string displayName)
|
||||
{
|
||||
try
|
||||
{
|
||||
DisplayName = displayName;
|
||||
|
||||
Packets = packets;
|
||||
|
||||
MaxPlayers = maxPlayers;
|
||||
_port = port;
|
||||
|
||||
InitializeServerData();
|
||||
|
||||
_tcpListener = new TcpListener(IPAddress.Any, _port);
|
||||
_tcpListener.Start();
|
||||
_tcpListener.BeginAcceptTcpClient(TcpConnectCallback, null);
|
||||
|
||||
_udpListener = new UdpClient(_port);
|
||||
_udpListener.BeginReceive(UdpReceiveCallback, null);
|
||||
|
||||
Terminal.LogSuccess($"[{DisplayName}] Server started on port {_port}.");
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Terminal.LogError($"[{DisplayName}] Failed to start server: {ex}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void DisconnectClient(int id)
|
||||
{
|
||||
Clients[id].Disconnect();
|
||||
}
|
||||
|
||||
|
||||
private void TcpConnectCallback(IAsyncResult result)
|
||||
{
|
||||
var client = _tcpListener.EndAcceptTcpClient(result);
|
||||
_tcpListener.BeginAcceptTcpClient(TcpConnectCallback, null);
|
||||
Terminal.LogInfo($"[{DisplayName}] Incoming connection from {client.Client.RemoteEndPoint}...");
|
||||
|
||||
for (var i = 1; i <= MaxPlayers; i++)
|
||||
{
|
||||
if (Clients[i].Tcp!.Socket != null) continue;
|
||||
Clients[i].Tcp!.Connect(client);
|
||||
return;
|
||||
}
|
||||
|
||||
Terminal.LogWarn($"[{DisplayName}] {client.Client.RemoteEndPoint} failed to connect: Server full!");
|
||||
}
|
||||
|
||||
private void UdpReceiveCallback(IAsyncResult result)
|
||||
{
|
||||
try
|
||||
{
|
||||
var clientEndPoint = new IPEndPoint(IPAddress.Any, 0);
|
||||
var data = _udpListener.EndReceive(result, ref clientEndPoint!);
|
||||
_udpListener.BeginReceive(UdpReceiveCallback, null);
|
||||
|
||||
if (data.Length < 4)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var packet = new Packet(data);
|
||||
var clientId = packet.ReadInt();
|
||||
|
||||
if (clientId == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
if (Clients[clientId].Udp.EndPoint == null)
|
||||
{
|
||||
Clients[clientId].Udp.Connect(clientEndPoint);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (Clients[clientId].Udp.EndPoint?.ToString() == clientEndPoint.ToString())
|
||||
{
|
||||
Clients[clientId].Udp.HandleData(packet);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Terminal.LogError($"[{DisplayName}] Error receiving UDP data: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
public void SendUdpData(IPEndPoint clientEndPoint, Packet packet)
|
||||
{
|
||||
try
|
||||
{
|
||||
_udpListener.BeginSend(packet.ToArray(), packet.Length(), clientEndPoint, null, null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Terminal.LogError($"[{DisplayName}] Error sending data to {clientEndPoint} via UDP: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeServerData()
|
||||
{
|
||||
for (var i = 1; i <= MaxPlayers; i++)
|
||||
{
|
||||
Clients.Add(i, new Client(i, this));
|
||||
}
|
||||
|
||||
Terminal.LogSuccess($"[{DisplayName}] Server data is now initialized.");
|
||||
}
|
||||
}
|
156
Matchmaker/BaseServer/ServerHandle.cs
Executable file
156
Matchmaker/BaseServer/ServerHandle.cs
Executable file
|
@ -0,0 +1,156 @@
|
|||
using System.Net;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace Matchmaker.Server.BaseServer;
|
||||
|
||||
internal static class ServerHandle
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the message received in response to the Welcome message
|
||||
/// </summary>
|
||||
/// <param name="server"></param>
|
||||
/// <param name="fromClient"></param>
|
||||
/// <param name="packet"></param>
|
||||
public static void WelcomeReceived(Server server, int fromClient, Packet packet)
|
||||
{
|
||||
try
|
||||
{
|
||||
var clientIdCheck = packet.ReadInt();
|
||||
var clientAPIVersion = packet.ReadString();
|
||||
var clientGameVersion = packet.ReadString();
|
||||
var clientGameId = packet.ReadString();
|
||||
|
||||
if (clientAPIVersion != Config.MatchmakerAPIVersion)
|
||||
{
|
||||
Terminal.LogError(
|
||||
$"[{server.DisplayName}] Client API is not the same version as the Server! \n Server API version: {Config.MatchmakerAPIVersion}. \n Client API version: {clientAPIVersion}");
|
||||
server.Clients[fromClient].Disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
if (clientGameId != Config.GameId)
|
||||
{
|
||||
Terminal.LogError($"Client Game ID and Server Game ID do not match: \n Server Game ID: {Config.GameId}. \n Client Game ID: {clientGameId}.");
|
||||
server.Clients[fromClient].Disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
if (clientGameVersion != Config.GameVersion)
|
||||
{
|
||||
Terminal.LogError($"Client Game Version and Server Game Version do not match: \n Server Game Ver: {Config.GameVersion}. \n Client Game Ver: {clientGameVersion}.");
|
||||
server.Clients[fromClient].Disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
var tcp = server.Clients[fromClient].Tcp;
|
||||
if (tcp == null)
|
||||
{
|
||||
Terminal.LogWarn("TCP is null!");
|
||||
}
|
||||
|
||||
if (tcp is { Socket: { } })
|
||||
Terminal.LogSuccess(
|
||||
$"[{server.DisplayName}] {tcp.Socket.Client.RemoteEndPoint} connected successfully and is now player {fromClient}.");
|
||||
if (fromClient != clientIdCheck)
|
||||
{
|
||||
Terminal.LogWarn(
|
||||
$"[{server.DisplayName}] Player (ID: {fromClient}) has assumed the wrong client ID ({clientIdCheck})!");
|
||||
}
|
||||
|
||||
|
||||
var remoteIpEndPoint = server.Clients[fromClient].Tcp?.Socket?.Client.RemoteEndPoint as IPEndPoint;
|
||||
if (remoteIpEndPoint == null)
|
||||
{
|
||||
Terminal.LogDebug($"[{server.DisplayName}] WelcomeReceived: RemoteIpEndPoint is NULL!");
|
||||
server.Clients[fromClient].Attributes.Clear();
|
||||
server.Clients[fromClient].Disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
var uuidList = new List<string>();
|
||||
for (var i = 1; i < server.Clients.Count; i++)
|
||||
{
|
||||
if (server.Clients[i].IsConnected)
|
||||
{
|
||||
var x = server.Clients[i].Attributes.GetAttribute("_auto_uuid");
|
||||
if (x != null)
|
||||
{
|
||||
uuidList.Add(x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
server.Clients[fromClient].Attributes.SetAttribute("_auto_ip", remoteIpEndPoint.Address.ToString());
|
||||
server.Clients[fromClient].Attributes.SetAttribute("_auto_port", remoteIpEndPoint.Port.ToString());
|
||||
server.Clients[fromClient].Attributes
|
||||
.SetAttribute("_auto_uuid", new UUID(Config.UuidLength, uuidList).GetValue());
|
||||
server.Clients[fromClient].Attributes
|
||||
.SetAttribute("_auto_game_version", clientGameVersion);
|
||||
|
||||
Terminal.LogInfo(
|
||||
$"[{server.DisplayName}] Automatically assigned _auto_ip for client {fromClient}. _auto_ip = {server.Clients[fromClient].Attributes.GetAttribute("_auto_ip")}");
|
||||
Terminal.LogInfo(
|
||||
$"[{server.DisplayName}] Automatically assigned _auto_port for client {fromClient}. _auto_port = {server.Clients[fromClient].Attributes.GetAttribute("_auto_port")}");
|
||||
Terminal.LogInfo(
|
||||
$"[{server.DisplayName}] Automatically assigned _auto_uuid for client {fromClient}. _auto_uuid = {server.Clients[fromClient].Attributes.GetAttribute("_auto_uuid")}");
|
||||
Terminal.LogInfo(
|
||||
$"[{server.DisplayName}] Automatically assigned _auto_game_version for client {fromClient} based on received Client data. _auto_game_version = {server.Clients[fromClient].Attributes.GetAttribute("_auto_game_version")}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Terminal.LogError($"[{server.DisplayName}] Error in WelcomeReceived packet handler: {ex}");
|
||||
ServerSend.Status(server, fromClient, ServerSend.StatusType.FAIL);
|
||||
server.Clients[fromClient].Disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetClientAttribute(Server server, int fromClient, Packet packet)
|
||||
{
|
||||
var clientIdCheck = packet.ReadInt();
|
||||
var name = packet.ReadString();
|
||||
var value = packet.ReadString();
|
||||
|
||||
var tcp = server.Clients[fromClient].Tcp;
|
||||
if (tcp is { Socket: { } })
|
||||
Terminal.LogInfo(
|
||||
$"[{server.DisplayName}] {tcp.Socket.Client.RemoteEndPoint} is changing Client Attribute {name} to {value}.");
|
||||
if (fromClient != clientIdCheck)
|
||||
{
|
||||
Terminal.LogWarn(
|
||||
$"[{server.DisplayName}] Player (ID: {fromClient}) has assumed the wrong client ID ({clientIdCheck})!");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
server.Clients[fromClient].Attributes.SetAttribute(name, value);
|
||||
}
|
||||
|
||||
public static void GetClientAttribute(Server server, int fromClient, Packet packet)
|
||||
{
|
||||
var clientIdCheck = packet.ReadInt();
|
||||
var requestedId = packet.ReadInt();
|
||||
var name = packet.ReadString();
|
||||
|
||||
try
|
||||
{
|
||||
var tcp = server.Clients[fromClient].Tcp;
|
||||
if (tcp is { Socket: { } })
|
||||
Terminal.LogInfo(
|
||||
$"[{server.DisplayName}] {tcp.Socket.Client.RemoteEndPoint} requests Client Attribute {name}.");
|
||||
if (fromClient != clientIdCheck)
|
||||
{
|
||||
Terminal.LogWarn(
|
||||
$"[{server.DisplayName}] Player (ID: {fromClient}) has assumed the wrong client ID ({clientIdCheck})!");
|
||||
}
|
||||
|
||||
var value = server.Clients[requestedId].Attributes.GetAttribute(name);
|
||||
ServerSend.GetClientAttributesReceived(server, fromClient, requestedId, name, value ?? "");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Terminal.LogError($"[{server.DisplayName}] Error in GetClientAttribute: {ex}");
|
||||
ServerSend.GetClientAttributesReceived(server, fromClient, requestedId, name, "ERR_HANDLED_EXCEPTION");
|
||||
}
|
||||
}
|
||||
}
|
277
Matchmaker/BaseServer/ServerSend.cs
Executable file
277
Matchmaker/BaseServer/ServerSend.cs
Executable file
|
@ -0,0 +1,277 @@
|
|||
|
||||
|
||||
|
||||
namespace Matchmaker.Server.BaseServer;
|
||||
|
||||
public static class ServerSend
|
||||
{
|
||||
|
||||
public enum StatusType
|
||||
{
|
||||
OK,
|
||||
RECEIVED,
|
||||
FAIL
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Sends TCP data to a Client
|
||||
/// </summary>
|
||||
/// <param name="server">Server the Client is in</param>
|
||||
/// <param name="toClient">ID for the recipient</param>
|
||||
/// <param name="packet">Data to send</param>
|
||||
public static void SendTcpData(Server server, int toClient, Packet packet)
|
||||
{
|
||||
packet.WriteLength();
|
||||
var tcp = server.Clients[toClient].Tcp;
|
||||
tcp?.SendData(packet);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends UDP data to a Client
|
||||
/// </summary>
|
||||
/// <param name="server">Server the Client is in</param>
|
||||
/// <param name="toClient">ID for the recipient</param>
|
||||
/// <param name="packet">Data to send</param>
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public static void SendUdpData(Server server, int toClient, Packet packet)
|
||||
{
|
||||
packet.WriteLength();
|
||||
var udp = server.Clients[toClient].Udp;
|
||||
udp.SendData(packet);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends TCP data to all Clients
|
||||
/// </summary>
|
||||
/// <param name="server">Server the Client is in</param>
|
||||
/// <param name="packet">Data to send</param>
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public static void SendTcpDataToAll(Server server, Packet packet)
|
||||
{
|
||||
packet.WriteLength();
|
||||
for (var i = 1; i <= server.MaxPlayers; i++)
|
||||
{
|
||||
var tcp = server.Clients[i].Tcp;
|
||||
tcp?.SendData(packet);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends TCP data to all Clients except one
|
||||
/// </summary>
|
||||
/// <param name="server">Server the Client is in</param>
|
||||
/// /// <param name="exceptClient">What Client to exclude</param>
|
||||
/// <param name="packet">Data to send</param>
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public static void SendTcpDataToAll(Server server, int exceptClient, Packet packet)
|
||||
{
|
||||
packet.WriteLength();
|
||||
for (var i = 1; i <= server.MaxPlayers; i++)
|
||||
{
|
||||
if (i == exceptClient) continue;
|
||||
var tcp = server.Clients[i].Tcp;
|
||||
tcp?.SendData(packet);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends UDP data to all Clients
|
||||
/// </summary>
|
||||
/// <param name="server">Server the Client is in</param>
|
||||
/// <param name="packet">Data to send</param>
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public static void SendUdpDataToAll(Server server, Packet packet)
|
||||
{
|
||||
packet.WriteLength();
|
||||
for (var i = 1; i <= server.MaxPlayers; i++)
|
||||
{
|
||||
var udp = server.Clients[i].Udp;
|
||||
udp.SendData(packet);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Sends UDP data to all Clients except one
|
||||
/// </summary>
|
||||
/// <param name="server">Server the Client is in</param>
|
||||
/// <param name="exceptClient">What Client to exclude</param>
|
||||
/// <param name="packet">Data to send</param>
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public static void SendUdpDataToAll(Server server, int exceptClient, Packet packet)
|
||||
{
|
||||
packet.WriteLength();
|
||||
for (var i = 1; i <= server.MaxPlayers; i++)
|
||||
{
|
||||
if (i == exceptClient) continue;
|
||||
var udp = server.Clients[i].Udp;
|
||||
udp.SendData(packet);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends TCP data to a Client
|
||||
/// </summary>
|
||||
/// <param name="server">Server the Client is in</param>
|
||||
/// <param name="toClient">ID for the recipient</param>
|
||||
/// <param name="packet">Data to send</param>
|
||||
public static void SendTcpDataNoSync(Server server, int toClient, Packet packet)
|
||||
{
|
||||
packet.WriteLength();
|
||||
var tcp = server.Clients[toClient].Tcp;
|
||||
tcp?.SendData(packet);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends UDP data to a Client
|
||||
/// </summary>
|
||||
/// <param name="server">Server the Client is in</param>
|
||||
/// <param name="toClient">ID for the recipient</param>
|
||||
/// <param name="packet">Data to send</param>
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public static void SendUdpDataNoSync(Server server, int toClient, Packet packet)
|
||||
{
|
||||
packet.WriteLength();
|
||||
var udp = server.Clients[toClient].Udp;
|
||||
udp.SendData(packet);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends TCP data to all Clients
|
||||
/// </summary>
|
||||
/// <param name="server">Server the Client is in</param>
|
||||
/// <param name="packet">Data to send</param>
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public static void SendTcpDataToAllNoSync(Server server, Packet packet)
|
||||
{
|
||||
packet.WriteLength();
|
||||
for (var i = 1; i <= server.MaxPlayers; i++)
|
||||
{
|
||||
var tcp = server.Clients[i].Tcp;
|
||||
tcp?.SendData(packet);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends TCP data to all Clients except one
|
||||
/// </summary>
|
||||
/// <param name="server">Server the Client is in</param>
|
||||
/// /// <param name="exceptClient">What Client to exclude</param>
|
||||
/// <param name="packet">Data to send</param>
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public static void SendTcpDataToAllNoSync(Server server, int exceptClient, Packet packet)
|
||||
{
|
||||
packet.WriteLength();
|
||||
for (var i = 1; i <= server.MaxPlayers; i++)
|
||||
{
|
||||
if (i == exceptClient) continue;
|
||||
var tcp = server.Clients[i].Tcp;
|
||||
tcp?.SendData(packet);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends UDP data to all Clients
|
||||
/// </summary>
|
||||
/// <param name="server">Server the Client is in</param>
|
||||
/// <param name="packet">Data to send</param>
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public static void SendUdpDataToAllNoSync(Server server, Packet packet)
|
||||
{
|
||||
packet.WriteLength();
|
||||
for (var i = 1; i <= server.MaxPlayers; i++)
|
||||
{
|
||||
var udp = server.Clients[i].Udp;
|
||||
udp.SendData(packet);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Sends UDP data to all Clients except one
|
||||
/// </summary>
|
||||
/// <param name="server">Server the Client is in</param>
|
||||
/// <param name="exceptClient">What Client to exclude</param>
|
||||
/// <param name="packet">Data to send</param>
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public static void SendUdpDataToAllNoSync(Server server, int exceptClient, Packet packet)
|
||||
{
|
||||
packet.WriteLength();
|
||||
for (var i = 1; i <= server.MaxPlayers; i++)
|
||||
{
|
||||
if (i == exceptClient) continue;
|
||||
var udp = server.Clients[i].Udp;
|
||||
udp.SendData(packet);
|
||||
}
|
||||
}
|
||||
|
||||
#region Built-in Packets
|
||||
|
||||
/// <summary>
|
||||
/// Welcome message for Client
|
||||
/// </summary>
|
||||
/// <param name="server">Server the Client is in</param>
|
||||
/// <param name="toClient">ID for the recipient</param>
|
||||
/// <param name="msg">Message to send</param>
|
||||
public static void Welcome(Server server, int toClient, string msg)
|
||||
{
|
||||
Terminal.LogDebug($"[{server.DisplayName}] Sending 'Welcome' message.");
|
||||
using var packet = new Packet(int.MaxValue);
|
||||
packet.Write(msg);
|
||||
packet.Write(toClient);
|
||||
packet.Write(Config.MatchmakerAPIVersion);
|
||||
packet.Write(Config.GameId);
|
||||
packet.Write(Config.GameVersion);
|
||||
|
||||
SendTcpData(server, toClient, packet);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnect a Client
|
||||
/// </summary>
|
||||
/// <param name="server">What server the Client is in</param>
|
||||
/// <param name="toClient">What Client to kick</param>
|
||||
public static void DisconnectClient(Server server, int toClient)
|
||||
{
|
||||
using var packet = new Packet(int.MaxValue-1);
|
||||
SendTcpData(server, toClient, packet);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send an Attribute to the Client
|
||||
/// </summary>
|
||||
/// <param name="server">What server the Client is in</param>
|
||||
/// <param name="toClient">ID for the recipient</param>
|
||||
/// <param name="requestedId">What Client the Attribute is from</param>
|
||||
/// <param name="name">The Attribute name</param>
|
||||
/// <param name="value">The Attribute value</param>
|
||||
public static void GetClientAttributesReceived(BaseServer.Server server, int toClient, int requestedId, string name, string value)
|
||||
{
|
||||
using var packet = new Packet(int.MaxValue-2);
|
||||
packet.Write(requestedId);
|
||||
packet.Write(name);
|
||||
packet.Write(value);
|
||||
|
||||
ServerSend.SendTcpData(server, toClient, packet);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Respond to the client with a status
|
||||
/// </summary>
|
||||
/// <param name="server">What server the Client is in</param>
|
||||
/// <param name="toClient">ID for the recipient</param>
|
||||
/// <param name="status">What status</param>
|
||||
public static void Status(BaseServer.Server server, int toClient, StatusType status)
|
||||
{
|
||||
Terminal.LogDebug($"[{server.DisplayName}] Sending status {status}...");
|
||||
using var packet = new Packet(int.MaxValue-3);
|
||||
packet.Write((int)status);
|
||||
|
||||
ServerSend.SendUdpDataNoSync(server, toClient, packet);
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endregion
|
||||
}
|
98
Matchmaker/BaseServer/Terminal.cs
Executable file
98
Matchmaker/BaseServer/Terminal.cs
Executable file
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2022 Vincent Dowling
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
namespace Matchmaker.Server.BaseServer;
|
||||
|
||||
public static class Terminal
|
||||
{
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public static void Log(string str)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
Console.Write(Config.LogText);
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
|
||||
Console.Write("({0:t}) " + str + "\n", DateTime.Now);
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
}
|
||||
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public static void LogError(string str)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.Write(Config.LogErrorText);
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
|
||||
Console.Write("({0:t}) " + str + "\n", DateTime.Now);
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
}
|
||||
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public static void LogDebug(string str)
|
||||
{
|
||||
|
||||
if (Config.ShowTerminalDebug)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Magenta;
|
||||
Console.Write(Config.LogDebugText);
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
|
||||
Console.Write("({0:t}) " + str + "\n", DateTime.Now);
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
}
|
||||
}
|
||||
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public static void LogWarn(string str)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.Write(Config.LogWarningText);
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
|
||||
Console.Write("({0:t}) " + str + "\n", DateTime.Now);
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
}
|
||||
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public static void LogSuccess(string str)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
Console.Write(Config.LogSuccessText);
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
|
||||
Console.Write("({0:t}) " + str + "\n", DateTime.Now);
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
}
|
||||
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public static void LogInfo(string str)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Blue;
|
||||
Console.Write(Config.LogInfoText);
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
|
||||
Console.Write("({0:t}) " + str + "\n", DateTime.Now);
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
}
|
||||
}
|
93
Matchmaker/BaseServer/ThreadManager.cs
Executable file
93
Matchmaker/BaseServer/ThreadManager.cs
Executable file
|
@ -0,0 +1,93 @@
|
|||
|
||||
namespace Matchmaker.Server.BaseServer;
|
||||
|
||||
internal static class ThreadManager
|
||||
{
|
||||
private static readonly List<Action> ExecuteOnMainThreadList = new();
|
||||
private static readonly List<Action> ExecuteCopiedOnMainThread = new();
|
||||
private static bool _actionToExecuteOnMainThread;
|
||||
|
||||
/// <summary>
|
||||
/// Controls whether or not the Main Thread should run. Set to FALSE to shut down Main Thread.
|
||||
/// </summary>
|
||||
private static bool _isRunning;
|
||||
private static Thread? _thread;
|
||||
|
||||
|
||||
/// <summary>Sets an action to be executed on the main thread.</summary>
|
||||
/// <param name="action">The action to be executed on the main thread.</param>
|
||||
public static void ExecuteOnMainThread(Action action)
|
||||
{
|
||||
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
if (action == null)
|
||||
{
|
||||
Terminal.LogWarn("No action to execute on main thread!");
|
||||
return;
|
||||
}
|
||||
|
||||
lock (ExecuteOnMainThreadList)
|
||||
{
|
||||
ExecuteOnMainThreadList.Add(action);
|
||||
_actionToExecuteOnMainThread = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start the Main Thread so Packets can be acted upon
|
||||
/// </summary>
|
||||
public static void Start()
|
||||
{
|
||||
_isRunning = true;
|
||||
_thread = new Thread(MainThread);
|
||||
_thread.Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stop the Main Thread
|
||||
/// </summary>
|
||||
public static void Stop()
|
||||
{
|
||||
_isRunning = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes all code meant to run on the main thread. NOTE: Call this ONLY from the main thread.
|
||||
/// </summary>
|
||||
public static void UpdateMain()
|
||||
{
|
||||
if (!_actionToExecuteOnMainThread) return;
|
||||
ExecuteCopiedOnMainThread.Clear();
|
||||
lock (ExecuteOnMainThreadList)
|
||||
{
|
||||
ExecuteCopiedOnMainThread.AddRange(ExecuteOnMainThreadList);
|
||||
ExecuteOnMainThreadList.Clear();
|
||||
_actionToExecuteOnMainThread = false;
|
||||
}
|
||||
|
||||
foreach (var t in ExecuteCopiedOnMainThread)
|
||||
{
|
||||
t();
|
||||
}
|
||||
}
|
||||
|
||||
private static void MainThread()
|
||||
{
|
||||
|
||||
var nextLoop = DateTime.Now;
|
||||
|
||||
while (_isRunning)
|
||||
{
|
||||
while (nextLoop < DateTime.Now)
|
||||
{
|
||||
GameLogic.Update();
|
||||
|
||||
nextLoop = nextLoop.AddMilliseconds(Constants.MsPerTick);
|
||||
|
||||
if (nextLoop > DateTime.Now)
|
||||
{
|
||||
Thread.Sleep(nextLoop - DateTime.Now);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
69
Matchmaker/BaseServer/UUID.cs
Executable file
69
Matchmaker/BaseServer/UUID.cs
Executable file
|
@ -0,0 +1,69 @@
|
|||
namespace Matchmaker.Server.BaseServer;
|
||||
|
||||
/// <summary>
|
||||
/// A UUID Object
|
||||
/// </summary>
|
||||
public class UUID
|
||||
{
|
||||
private string value = "";
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random string of specified length
|
||||
/// </summary>
|
||||
/// <param name="length">How long a random string to make</param>
|
||||
/// <returns>A random string</returns>
|
||||
public string GetRandomString(int length)
|
||||
{
|
||||
// Creating object of random class
|
||||
var rand = new Random();
|
||||
|
||||
|
||||
var str = "";
|
||||
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
|
||||
// Generating a random number.
|
||||
var randValue = rand.Next(0, 26);
|
||||
|
||||
// Generating random character by converting
|
||||
// the random number into character.
|
||||
var letter = Convert.ToChar(randValue + 65);
|
||||
|
||||
// Appending the letter to string.
|
||||
str = str + letter;
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the UUID value
|
||||
/// </summary>
|
||||
/// <returns>The UUID's value</returns>
|
||||
public string GetValue()
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
public UUID(int length, List<string> usedIDs)
|
||||
{
|
||||
var tempValue = "";
|
||||
var loop = true;
|
||||
|
||||
while (loop)
|
||||
{
|
||||
tempValue = GetRandomString(length);
|
||||
loop = false;
|
||||
foreach (var e in usedIDs)
|
||||
{
|
||||
if (e.Equals(tempValue))
|
||||
{
|
||||
loop = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
value = tempValue;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue