First commit with everything

master
Benjamin Kraft 4 years ago
commit 71269033a3
  1. 13
      .idea/.idea.GameServer/.idea/.gitignore
  2. 4
      .idea/.idea.GameServer/.idea/encodings.xml
  3. 8
      .idea/.idea.GameServer/.idea/indexLayout.xml
  4. 7
      .idea/.idea.GameServer/.idea/riderModule.iml
  5. 6
      .idea/.idea.GameServer/.idea/vcs.xml
  6. 21
      Arch/DatePrefix.cs
  7. 338
      Arch/Packet.cs
  8. 210
      Arch/ProtocolManager.cs
  9. 81
      Arch/SendData.cs
  10. 69
      Arch/ThreadManager.cs
  11. 13
      Game/GameHandle.cs
  12. 32
      Game/GameManager.cs
  13. 14
      Game/GameSend.cs
  14. 20
      Game/Player.cs
  15. 8
      GameServer.csproj
  16. 16
      GameServer.sln
  17. 45
      Management/Client.cs
  18. 8
      Management/Constants.cs
  19. 125
      Management/Room.cs
  20. 121
      Management/RoomHandle.cs
  21. 106
      Management/RoomSend.cs
  22. 140
      Management/Server.cs
  23. 19
      Management/ServerHandle.cs
  24. 22
      Management/ServerSend.cs
  25. 5
      PacketTypes/ClientDefaultPacket.cs
  26. 5
      PacketTypes/ClientGamePacket.cs
  27. 13
      PacketTypes/ClientRoomPacket.cs
  28. 7
      PacketTypes/PacketType.cs
  29. 5
      PacketTypes/ServerDefaultPacket.cs
  30. 5
      PacketTypes/ServerGamePacket.cs
  31. 14
      PacketTypes/ServerRoomPacket.cs
  32. 21
      Program.cs

@ -0,0 +1,13 @@
# Default ignored files
/shelf/
/workspace.xml
# Rider ignored files
/contentModel.xml
/.idea.GameServer.iml
/modules.xml
/projectSettingsUpdater.xml
# Datasource local storage ignored files
/../../../../../../../../../:\Dateien\Documents\Programming\C#\GameServer\.idea\.idea.GameServer\.idea/dataSources/
/dataSources.local.xml
# Editor-based HTTP Client requests
/httpRequests/

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
</project>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ContentModelUserStore">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="RIDER_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$/../.." />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

@ -0,0 +1,21 @@
using System;
using System.IO;
using System.Text;
namespace GameServer.Arch {
public class DatePrefix : TextWriter {
private readonly TextWriter _originalOut;
public DatePrefix() {
_originalOut = Console.Out;
}
public override Encoding Encoding => new ASCIIEncoding();
public override void WriteLine(string value) {
_originalOut.WriteLine($"{DateTime.Now} {value}");
}
}
}

@ -0,0 +1,338 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using System.Text;
using GameServer.Management;
namespace GameServer.Arch {
public sealed class Packet : IDisposable {
private List<byte> _buffer;
private bool _disposed;
private byte[] _readableBuffer;
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="packetTypeId">The packetType ID.</param>
/// <param name="packetActionId">The packetAction ID.</param>
public Packet(int packetTypeId, int packetActionId) {
_buffer = new List<byte>(); // Initialize buffer
_readPos = 0; // Set readPos to 0
Write(packetTypeId); // Write packet ids to the buffer
Write(packetActionId);
}
/// <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(byte[] data) {
_buffer = new List<byte>(); // Initialize buffer
_readPos = 0; // Set readPos to 0
SetBytes(data);
}
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing) {
if (_disposed)
return;
if (disposing) {
_buffer = null;
_readableBuffer = null;
_readPos = 0;
}
_disposed = true;
}
#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(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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
public void Write(Quaternion value) {
Write(value.X);
Write(value.Y);
Write(value.Z);
Write(value.W);
}
/// <summary>Adds a Room to the packet.</summary>
/// <param name="value">The Room to add.</param>
public void Write(Room value) {
Write(value.Id);
Write(value.Name);
Write(value.IsLocked);
Write(value.Leader.Id);
Write(value.MaxPlayers);
Write(value.CurrentPlayers);
foreach (var client in value.Clients) {
Write(client.Id);
Write(client.Name);
}
}
/// <summary>Adds ClientProperties to the packet.</summary>
/// <param name="value">ClientProperties to add.</param>
public void Write(Dictionary<int, Room.ClientProperties> value) {
Write(value.Count);
foreach (var (key, clientProperties) in value) {
Write(key);
Write(clientProperties.IsLeader);
Write(clientProperties.IsReady);
Write(clientProperties.ColorId);
}
}
#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>
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
}
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
}
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>
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
}
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
}
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>
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
}
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
}
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>
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
}
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>
public Vector3 ReadVector3(bool moveReadPos = true) {
return new(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>
public Quaternion ReadQuaternion(bool moveReadPos = true) {
return new(ReadFloat(moveReadPos), ReadFloat(moveReadPos), ReadFloat(moveReadPos), ReadFloat(moveReadPos));
}
#endregion
}
}

@ -0,0 +1,210 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using GameServer.Management;
namespace GameServer.Arch {
public static class Listener {
private static TcpListener _tcpListener;
private static UdpClient _udpListener;
private static Dictionary<int, Client> Clients => Server.Clients;
public static void Start() {
_tcpListener = new TcpListener(IPAddress.Any, Server.Port);
_tcpListener.Start();
_tcpListener.BeginAcceptTcpClient(TcpConnectCallback, null);
_udpListener = new UdpClient(Server.Port);
_udpListener.BeginReceive(UdpReceiveCallback, null);
}
private static void TcpConnectCallback(IAsyncResult result) {
var client = _tcpListener.EndAcceptTcpClient(result);
_tcpListener.BeginAcceptTcpClient(TcpConnectCallback, null);
Console.WriteLine($"Incoming connection from {client.Client.RemoteEndPoint}...");
for (var i = 1; i <= Server.MaxPlayers; i++) {
if (Clients[i].Tcp.Socket != null)
continue;
Clients[i].Tcp.Connect(client);
return;
}
Console.WriteLine($"{client.Client.RemoteEndPoint} failed to connect: Server full!");
}
private static 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;
if (Clients[clientId].Tcp.Socket == null)
return;
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) {
Console.WriteLine($"Error receiving UDP data: {ex}");
}
}
public static void SendUdpData(IPEndPoint clientEndPoint, Packet packet) {
try {
if (clientEndPoint != null)
_udpListener.BeginSend(packet.ToArray(), packet.Length(), clientEndPoint, null, null);
} catch (Exception ex) {
Console.WriteLine($"Error sending data to {clientEndPoint} via UDP: {ex}");
}
}
}
public class TcpManager {
private readonly int _id;
private byte[] _receiveBuffer;
private Packet _receivedData;
private NetworkStream _stream;
public TcpClient Socket;
public TcpManager(int id) {
_id = id;
}
public void Connect(TcpClient socket) {
Socket = socket;
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);
Server.Clients[_id].OnConnect();
}
public void SendData(Packet packet) {
try {
if (Socket != null)
_stream.BeginWrite(packet.ToArray(), 0, packet.Length(), null, null);
} catch (Exception ex) {
Console.WriteLine($"Error sending data to player {_id} via TCP: {ex}");
}
}
private void ReceiveCallback(IAsyncResult result) {
try {
var byteLength = _stream.EndRead(result);
if (byteLength <= 0) {
Server.Clients[_id].Disconnect();
return;
}
var data = new byte[byteLength];
Array.Copy(_receiveBuffer, data, byteLength);
_receivedData.Reset(HandleData(data));
_stream.BeginRead(_receiveBuffer, 0, Constants.DataBufferSize, ReceiveCallback, null);
} catch (Exception ex) {
Console.WriteLine($"Error receiving TCP data: {ex}");
Server.Clients[_id].Disconnect();
}
}
private bool HandleData(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(() => ReadPacket.Read(packetBytes, _id));
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;
}
}
public class UdpManager {
private readonly int _id;
public IPEndPoint EndPoint;
public UdpManager(int id) {
_id = id;
}
public void Connect(IPEndPoint endPoint) {
EndPoint = endPoint;
}
public void SendData(Packet packet) {
Listener.SendUdpData(EndPoint, packet);
}
public void HandleData(Packet packetData) {
var packetLength = packetData.ReadInt();
var packetBytes = packetData.ReadBytes(packetLength);
ThreadManager.ExecuteOnMainThread(() => ReadPacket.Read(packetBytes, _id));
}
public void Disconnect() {
EndPoint = null;
}
}
internal static class ReadPacket {
public static void Read(byte[] packetBytes, int clientId) {
using var packet = new Packet(packetBytes);
var packetTypeId = packet.ReadInt();
var packetActionId = packet.ReadInt();
Server.PacketHandlers[packetTypeId][packetActionId](clientId, packet);
}
}
}

@ -0,0 +1,81 @@
using System;
using System.Linq;
using GameServer.Management;
namespace GameServer.Arch {
public static class SendData {
public static void SendTcpData(int toClient, Packet packet) {
packet.WriteLength();
Server.Clients[toClient].Tcp.SendData(packet);
}
public static void SendTcpDataToRoom(Room room, Packet packet) {
packet.WriteLength();
foreach (var client in room.Clients) {
client.Tcp.SendData(packet);
}
}
public static void SendTcpDataToRoom(Room room, Packet packet, int withClient) {
SendTcpDataToRoom(room, packet);
Server.Clients[withClient].Tcp.SendData(packet);
}
public static void SendTcpDataToRoom(Room room, int exceptClient, Packet packet) {
packet.WriteLength();
foreach (var client in room.Clients.Where(client => client.Id != exceptClient)) {
client.Tcp.SendData(packet);
}
}
public static void SendTcpDataToAll(Packet packet, Func<Client, bool> condition) {
packet.WriteLength();
foreach (var client in Server.Clients.Values.Where(condition)) {
client.Tcp.SendData(packet);
}
}
public static void SendTcpDataToAll(Packet packet) {
SendTcpDataToAll(packet, _ => true);
}
public static void SendTcpDataToAll(int exceptClient, Packet packet) {
packet.WriteLength();
for (var i = 1; i <= Server.MaxPlayers; i++)
if (i != exceptClient)
Server.Clients[i].Tcp.SendData(packet);
}
public static void SendUdpData(int toClient, Packet packet) {
packet.WriteLength();
Server.Clients[toClient].Udp.SendData(packet);
}
public static void SendUdpDataToAll(Room room, Packet packet) {
packet.WriteLength();
foreach (var client in room.Clients) {
client.Udp.SendData(packet);
}
}
public static void SendUdpDataToAll(Room room, int exceptClient, Packet packet) {
packet.WriteLength();
foreach (var client in room.Clients.Where(client => client.Id != exceptClient)) {
client.Udp.SendData(packet);
}
}
public static void SendUdpDataToAll(Packet packet) {
packet.WriteLength();
for (var i = 1; i <= Server.MaxPlayers; i++)
Server.Clients[i].Udp.SendData(packet);
}
public static void SendUdpDataToAll(int exceptClient, Packet packet) {
packet.WriteLength();
for (var i = 1; i <= Server.MaxPlayers; i++)
if (i != exceptClient)
Server.Clients[i].Udp.SendData(packet);
}
}
}

@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using GameServer.Management;
namespace GameServer.Arch {
internal static class ThreadManager {
private static readonly List<Action> ToExecuteOnMainThread = new();
private static readonly List<Action> ExecuteCopiedOnMainThread = new();
private static bool _actionToExecuteOnMainThread;
/// <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) {
if (action == null) {
Console.WriteLine("No action to execute on main thread!");
return;
}
lock (ToExecuteOnMainThread) {
ToExecuteOnMainThread.Add(action);
_actionToExecuteOnMainThread = true;
}
}
/// <summary>Executes all code meant to run on the main thread. NOTE: Call this ONLY from the main thread.</summary>
private static void UpdateMain() {
if (!_actionToExecuteOnMainThread)
return;
ExecuteCopiedOnMainThread.Clear();
lock (ToExecuteOnMainThread) {
ExecuteCopiedOnMainThread.AddRange(ToExecuteOnMainThread);
ToExecuteOnMainThread.Clear();
_actionToExecuteOnMainThread = false;
}
foreach (var t in ExecuteCopiedOnMainThread)
t();
}
public static bool IsRunning;
public static void MainThread() {
Console.WriteLine($"Main thread started. Running at {Constants.TicksPerSec} ticks per second.");
var nextLoop = DateTime.Now;
while (IsRunning)
while (nextLoop < DateTime.Now) {
Tick();
nextLoop = nextLoop.AddMilliseconds(Constants.MsPerTick);
if (nextLoop > DateTime.Now)
Thread.Sleep(nextLoop - DateTime.Now);
}
}
private static void Tick() {
foreach (var room in Server.Rooms.Values.Where(room => room.Game != null && room.Game.IsRunning)) {
room.Game.Update();
}
UpdateMain();
}
}
}

@ -0,0 +1,13 @@
using System.Collections.Generic;
using GameServer.Arch;
using GameServer.Management;
using GameServer.PacketTypes;
namespace GameServer.Game {
public static class GameHandle {
public static void Action(int fromClient, Packet packet) {
}
}
}

@ -0,0 +1,32 @@
using System.Collections.Generic;
using GameServer.Management;
namespace GameServer.Game {
public class GameManager {
public bool IsRunning { get; set; }
private Room Room { get; }
private Dictionary<int, Player> Players { get; } = new();
public GameManager(Room room) {
Room = room;
foreach (var client in room.Clients) {
Players.Add(client.Id, new Player(client.Id, client.Name));
}
}
public void Start() {
foreach (var player in Players.Values) {
player.Start();
}
}
public void Update() {
foreach (var player in Players.Values) {
player.Update();
}
}
}
}

@ -0,0 +1,14 @@
using GameServer.Arch;
using GameServer.Management;
using GameServer.PacketTypes;
using static GameServer.Arch.SendData;
namespace GameServer.Game {
public static class GameSend {
private static Packet CreatePacket(ServerGamePacket type) {
return new((int)PacketType.Game, (int)type);
}
}
}

@ -0,0 +1,20 @@
namespace GameServer.Game {
public class Player {
public int Id { get; }
public string Name { get; }
public Player(int id, string name) {
Id = id;
Name = name;
}
public void Start() {
}
public void Update() {
}
}
}

@ -0,0 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
</Project>

@ -0,0 +1,16 @@

Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameServer", "GameServer.csproj", "{A3BE7051-E90A-48ED-93E9-F7C46B05390B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{A3BE7051-E90A-48ED-93E9-F7C46B05390B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A3BE7051-E90A-48ED-93E9-F7C46B05390B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A3BE7051-E90A-48ED-93E9-F7C46B05390B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A3BE7051-E90A-48ED-93E9-F7C46B05390B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

@ -0,0 +1,45 @@
using System;
using GameServer.Arch;
using GameServer.Game;
namespace GameServer.Management {
public class Client {
public readonly int Id;
public readonly TcpManager Tcp;
public readonly UdpManager Udp;
public string Name { get; set; }
public Player Player;
public Room Room { get; set; }
public Client(int clientId) {
Id = clientId;
Tcp = new TcpManager(Id);
Udp = new UdpManager(Id);
}
public void OnConnect() {
ServerSend.Welcome(Id, "Welcome to the server!");
if (Tcp.Socket.Client.RemoteEndPoint != null)
_endpoint = Tcp.Socket.Client.RemoteEndPoint.ToString();
}
public void Disconnect() {
Tcp.Disconnect();
Udp.Disconnect();
if (Room != null)
Server.LeaveRoom(this);
Player = null;
Console.WriteLine($"{this} has disconnected.");
}
private string _endpoint = "Client";
public override string ToString() {
return $"{{\"{Name}\" | {_endpoint}}}";
}
}
}

@ -0,0 +1,8 @@
namespace GameServer.Management {
public static class Constants {
public const int TicksPerSec = 32;
public const int MsPerTick = 1000 / TicksPerSec;
public const int DataBufferSize = 4096;
public const int CountdownSeconds = 2;
}
}

@ -0,0 +1,125 @@
using System;
using System.Collections.Generic;
using System.Linq;
using GameServer.Game;
namespace GameServer.Management {
public class Room {
public class ClientProperties {
public bool IsLeader;
public bool IsReady;
public int ColorId;
}
public string Id { get; }
public string Name { get; }
public string Password { get; }
public int MaxPlayers { get; }
public int CurrentPlayers => Clients.Count;
public bool IsFull => CurrentPlayers == MaxPlayers;
public bool IsLocked { get; set; }
public GameManager Game { get; private set; }
public void StartGame() {
Game = new GameManager(this);
Game.Start();
}
public readonly Dictionary<int, ClientProperties> ClientPropertiesMap = new();
public Client Leader {
get {
return CurrentPlayers == 0 ? null
:
Server.Clients[ClientPropertiesMap.Single(pair => pair.Value.IsLeader).Key];
}
set {
foreach (var clientId in ClientPropertiesMap.Keys) {
ClientPropertiesMap[clientId].IsLeader = false;
}
ClientPropertiesMap[value.Id].IsLeader = true;
}
}
private readonly List<int> _clientIds = new();
public List<Client> Clients {
get {
var list = new List<Client>();
foreach (int clientId in _clientIds) {
list.Add(Server.Clients[clientId]);
}
return list;
}
}
public void SetReady(Client client, bool isReady) {
ClientPropertiesMap[client.Id].IsReady = isReady;
}
public void SetColor(Client client, int colorId) {
if (ClientPropertiesMap.Values.Any(properties => properties.ColorId.Equals(colorId)))
return;
ClientPropertiesMap[client.Id].ColorId = colorId;
}
public Room(string id, Client leader, string name, string password, int maxPlayers) {
Id = id;
AddClient(leader);
Leader = leader;
Name = name;
Password = password;
MaxPlayers = maxPlayers;
IsLocked = false;
}
public void AddClient(Client client) {
_clientIds.Add(client.Id);
client.Room = this;
for (int i = 0; i < 10; i++) {
if (ClientPropertiesMap.Values.Any(prop => prop.ColorId.Equals(i)))
continue;
ClientPropertiesMap.Add(client.Id, new ClientProperties {
IsReady = false,
ColorId = i,
IsLeader = false
});
break;
}
}
public bool RemoveClient(Client leftClient) {
var leader = Leader;
_clientIds.Remove(leftClient.Id);
leftClient.Room = null;
ClientPropertiesMap.Remove(leftClient.Id);
if (CurrentPlayers == 0) {
return false;
}
if (!leftClient.Equals(leader))
return true;
Leader = Clients.First();
Console.WriteLine($"{Leader} is the new leader of room {this}");
return true;
}
public void KickClient(Client kickClient) {
_clientIds.Remove(kickClient.Id);
kickClient.Room = null;
ClientPropertiesMap.Remove(kickClient.Id);
}
public override string ToString() {
return $"{{\"{Name}\" | \"{Id.Substring(0, 10)}...\" | ({CurrentPlayers}/{MaxPlayers})}}";
}
}
}

@ -0,0 +1,121 @@
using System;
using GameServer.Arch;
namespace GameServer.Management {
public static class RoomHandle {
public static void RoomList(int fromClientId, Packet packet) {
RoomSend.List(fromClientId);
Client client = Server.Clients[fromClientId];
Console.WriteLine($"{client} requested a list of rooms.");
}
public static void RoomCreate(int fromClientId, Packet packet) {
string roomName = packet.ReadString();
string roomPassword = packet.ReadString();
int maxPlayers = packet.ReadInt();
Server.CreateRoom(fromClientId, roomName, roomPassword, maxPlayers);
}
public static void RoomJoin(int fromClientId, Packet packet) {
string roomId = packet.ReadString();
string password = packet.ReadString();
Client client = Server.Clients[fromClientId];
Room room = Server.Rooms[roomId];
if (room == null)
return;
if (room.IsLocked)
return;
Server.JoinRoom(client, room, password);
}
public static void RoomLeave(int fromClientId, Packet packet) {
Client client = Server.Clients[fromClientId];
if (client.Room == null)
return;
if (client.Room.IsLocked)
return;
Server.LeaveRoom(client);
}
public static void RoomKick(int fromClientId, Packet packet) {
int kickId = packet.ReadInt();
Client leaderClient = Server.Clients[fromClientId];
if (leaderClient.Room == null)
return;
Client kickClient = Server.Clients[kickId];
if (kickClient.Room == null)
return;
if (!kickClient.Room.Equals(leaderClient.Room))
return;
Server.KickFromRoom(leaderClient, kickClient);
}
public static void RoomLeader(int fromClientId, Packet packet) {
var nextLeaderId = packet.ReadInt();
var nextLeader = Server.Clients[nextLeaderId];
var fromClient = Server.Clients[fromClientId];
if (fromClient.Room == null || nextLeader.Room == null || fromClient.Room != nextLeader.Room)
return;
if (!fromClient.Room.Leader.Id.Equals(fromClientId))
return;
fromClient.Room.Leader = nextLeader;
RoomSend.Properties(fromClient.Room);
}
public static void RoomReady(int fromClientId, Packet packet) {
Client client = Server.Clients[fromClientId];
if (client.Room == null)
return;
bool isReady = packet.ReadBool();
client.Room.SetReady(client, isReady);
RoomSend.Properties(client.Room);
}
public static void RoomColor(int fromClientId, Packet packet) {
var fromClient = Server.Clients[fromClientId];
var colorId = packet.ReadInt();
if (fromClient.Room == null)
return;
fromClient.Room.SetColor(fromClient, colorId);
RoomSend.Properties(fromClient.Room);
}
public static void RoomStart(int fromClientId, Packet packet) {
var fromClient = Server.Clients[fromClientId];
if (fromClient.Room == null)
return;
if (!fromClient.Room.Leader.Id.Equals(fromClientId))
return;
Server.StartRoom(fromClient.Room);
}
}
}

@ -0,0 +1,106 @@
using System;
using GameServer.Arch;
using GameServer.PacketTypes;
using static GameServer.Arch.SendData;
using static GameServer.PacketTypes.ServerRoomPacket;
namespace GameServer.Management {
public static class RoomSend {
private static Packet CreatePacket(ServerRoomPacket type) {
return new((int)PacketType.Room, (int)type);
}
public static void List(int toClient) {
using var packet = CreatePacket(RList);
packet.Write(Server.Rooms.Count);
foreach (var room in Server.Rooms.Values)
packet.Write(room);
SendTcpData(toClient, packet);
}
public static void ListUpdate() {
using var packet = CreatePacket(RList);
packet.Write(Server.Rooms.Count);
foreach (var room in Server.Rooms.Values)
packet.Write(room);
SendTcpDataToAll(packet, client => client.Room == null);
}
public static void Created(int toClient, Room room) {
using var packet = CreatePacket(RCreated);
packet.Write(room);
packet.Write(room.ClientPropertiesMap);
SendTcpData(toClient, packet);
}
public static void Joined(int joinedClient, Room room) {
using var packet = CreatePacket(RJoined);
packet.Write(room);
packet.Write(joinedClient);
packet.Write(room.ClientPropertiesMap);
SendTcpDataToRoom(room, packet);
}
public static void Left(int leftClient, Room room) {
using var packet = CreatePacket(RLeft);
packet.Write(leftClient);
packet.Write(room.ClientPropertiesMap);
SendTcpDataToRoom(room, packet, leftClient);
}
public static void CreateFailed(int toClient, string message) {
using var packet = CreatePacket(RCreateFailed);
packet.Write(message);
SendTcpData(toClient, packet);
}
public static void JoinFailed(int toClient, Room room, string message) {
using var packet = CreatePacket(RJoinFailed);
packet.Write(room.Id);
packet.Write(message);
SendTcpData(toClient, packet);
}
public static void KickFailed(int toClient, string message) {
using var packet = CreatePacket(RKickFailed);
packet.Write(message);
SendTcpData(toClient, packet);
}
public static void Properties(Room room) {
using var packet = CreatePacket(RProperties);
packet.Write(room.ClientPropertiesMap);
SendTcpDataToRoom(room, packet);
}
public static void Start(Room room) {
using var packet = CreatePacket(RStart);
DateTime startTime = DateTime.Now.AddSeconds(Constants.CountdownSeconds);
packet.Write(startTime.Ticks);
packet.Write(room);
packet.Write(room.ClientPropertiesMap);
SendTcpDataToRoom(room, packet);
}
}
}

@ -0,0 +1,140 @@
using System;
using System.Collections.Generic;
using GameServer.Arch;
using GameServer.Game;
using GameServer.PacketTypes;
namespace GameServer.Management {
internal static class Server {
public delegate void PacketHandler(int fromClient, Packet packet);
public static readonly Dictionary<int, Client> Clients = new();
public static readonly Dictionary<string, Room> Rooms = new();
public static Dictionary<int, Dictionary<int, PacketHandler>> PacketHandlers;
public static int MaxPlayers { get; private set; }
public static int Port { get; private set; }
public static void Start(int maxPlayers, int port){
MaxPlayers = maxPlayers;
Port = port;
Console.WriteLine("Starting server...");
InitializeServerData();
Listener.Start();
Console.WriteLine($"Server started on port {Port}.");
}
private static void InitializeServerData(){
for (var i = 1; i <= MaxPlayers; i++) {
Clients.Add(i, new Client(i));
}
PacketHandlers = new Dictionary<int, Dictionary<int, PacketHandler>> {
{(int)PacketType.Default, new Dictionary<int, PacketHandler> {
{(int)ClientDefaultPacket.DWelcomeReceived, ServerHandle.WelcomeReceived},
}},
{(int)PacketType.Room, new Dictionary<int, PacketHandler> {
{(int)ClientRoomPacket.RList, RoomHandle.RoomList},
{(int)ClientRoomPacket.RCreate, RoomHandle.RoomCreate},
{(int)ClientRoomPacket.RJoin, RoomHandle.RoomJoin},
{(int)ClientRoomPacket.RLeave, RoomHandle.RoomLeave},
{(int)ClientRoomPacket.RKick, RoomHandle.RoomKick},
{(int)ClientRoomPacket.RLeader, RoomHandle.RoomLeader},
{(int)ClientRoomPacket.RReady, RoomHandle.RoomReady},
{(int)ClientRoomPacket.RColor, RoomHandle.RoomColor},
{(int)ClientRoomPacket.RStart, RoomHandle.RoomStart},
}},
{(int)PacketType.Game, new Dictionary<int, PacketHandler> {
{(int)ClientGamePacket.Action, GameHandle.Action}
}}
};
Console.WriteLine("Initialized packets.");
}
public static void CreateRoom(int leaderId, string name, string password, int maxPlayers) {
var leader = Clients[leaderId];
if (leader.Room != null) {
RoomSend.CreateFailed(leaderId, "Failed to create room!");
Console.WriteLine($"{leader} tried to create a room while already being in one!");
return;
}
string id;
do {
id = Guid.NewGuid().ToString();
} while (Rooms.ContainsKey(id));
var room = new Room(id, leader, name, password, maxPlayers);
Rooms.Add(id, room);
RoomSend.Created(leaderId, room);
RoomSend.ListUpdate();
Console.WriteLine($"{leader} created a room {room}.");
}
public static void JoinRoom(Client client, Room room, string password) {
if (room.IsFull) {
RoomSend.JoinFailed(client.Id, room, "Room is full!");
Console.WriteLine($"{client} tried to join full room {room}.");
} else if (!room.Password.Equals(password)) {
RoomSend.JoinFailed(client.Id, room,"Wrong password!");
Console.WriteLine($"{client} entered wrong password for room {room}.");
} else {
room.AddClient(client);
RoomSend.Joined(client.Id, room);
RoomSend.ListUpdate();
Console.WriteLine($"{client} joined the room {room}.");
}
}
public static void LeaveRoom(Client client) {
Room room = client.Room;
bool isEmpty = !room.RemoveClient(client);
Console.WriteLine($"{client} has left room {room}");
if (isEmpty) {
Rooms.Remove(room.Id);
Console.WriteLine($"Room {room} was deleted because every client left.");
}
RoomSend.Left(client.Id, room);
RoomSend.ListUpdate();
}
public static void KickFromRoom(Client fromClient, Client kickClient) {
Room room = kickClient.Room;
if (!fromClient.Equals(room.Leader)) {
RoomSend.KickFailed(fromClient.Id, "Only the room lead can kick others!");
Console.WriteLine($"{fromClient} tried to kick {kickClient} while not being the leader!");
return;
}
room.KickClient(kickClient);
Console.WriteLine($"{kickClient} was kicked from room {room}");
RoomSend.Left(kickClient.Id, room);
RoomSend.ListUpdate();
}
public static void StartRoom(Room room) {
room.IsLocked = true;
room.StartGame();
RoomSend.Start(room);
Console.WriteLine($"Room {room} started the game.");
RoomSend.ListUpdate();
}
}
}

@ -0,0 +1,19 @@
using System;
using GameServer.Arch;
namespace GameServer.Management {
public static class ServerHandle {
public static void WelcomeReceived(int fromClientId, Packet packet) {
string username = packet.ReadString();
Client client = Server.Clients[fromClientId];
client.Name = username;
Console.WriteLine($"{client} connected successfully!");
}
}
}

@ -0,0 +1,22 @@
using GameServer.Arch;
using GameServer.PacketTypes;
using static GameServer.Arch.SendData;
using static GameServer.PacketTypes.ServerDefaultPacket;
namespace GameServer.Management {
internal static class ServerSend {
private static Packet CreatePacket(ServerDefaultPacket type) {
return new((int)PacketType.Default, (int)type);
}
public static void Welcome(int toClient, string msg) {
using var packet = CreatePacket(DWelcome);
packet.Write(msg);
packet.Write(toClient);
SendTcpData(toClient, packet);
}
}
}

@ -0,0 +1,5 @@
namespace GameServer.PacketTypes {
public enum ClientDefaultPacket {
DWelcomeReceived = 1,
}
}

@ -0,0 +1,5 @@
namespace GameServer.PacketTypes {
public enum ClientGamePacket {
Action,
}
}

@ -0,0 +1,13 @@
namespace GameServer.PacketTypes {
public enum ClientRoomPacket {
RList = 1,
RCreate,
RJoin,
RLeave,
RKick,
RReady,
RColor,
RLeader,
RStart,
}
}

@ -0,0 +1,7 @@
namespace GameServer.PacketTypes {
public enum PacketType {
Default = 1,
Room,
Game
}
}

@ -0,0 +1,5 @@
namespace GameServer.PacketTypes {
public enum ServerDefaultPacket {
DWelcome = 1,
}
}

@ -0,0 +1,5 @@
namespace GameServer.PacketTypes {
public enum ServerGamePacket {
}
}

@ -0,0 +1,14 @@
namespace GameServer.PacketTypes {
public enum ServerRoomPacket {
RList = 1,
RCreated,
RJoined,
RLeft,
RStart,
RCreateFailed,
RJoinFailed,
RLeaveFailed,
RKickFailed,
RProperties,
}
}

@ -0,0 +1,21 @@
using System;
using System.Threading;
using GameServer.Arch;
using GameServer.Management;
namespace GameServer {
internal static class Program {
private static void Main() {
Console.Title = "Game Server";
Console.SetOut(new DatePrefix());
Server.Start(50, 26950);
var mainThread = new Thread(ThreadManager.MainThread);
mainThread.Start();
ThreadManager.IsRunning = true;
}
}
}
Loading…
Cancel
Save