]> git.deb.at Git - rhonda/impostor.git/commitdiff
Refactor code
authorAeonLucid <aeonlucid@gmail.com>
Fri, 25 Sep 2020 20:02:03 +0000 (22:02 +0200)
committerAeonLucid <aeonlucid@gmail.com>
Fri, 25 Sep 2020 20:02:03 +0000 (22:02 +0200)
26 files changed:
src/Impostor.Server/Extensions/ConnectionExtensions.cs [deleted file]
src/Impostor.Server/Net/Client.cs
src/Impostor.Server/Net/ClientManager.cs [deleted file]
src/Impostor.Server/Net/ClientPlayer.cs [deleted file]
src/Impostor.Server/Net/Game.cs [deleted file]
src/Impostor.Server/Net/GameManager.cs [deleted file]
src/Impostor.Server/Net/GameStates.cs [deleted file]
src/Impostor.Server/Net/Manager/ClientManager.cs [new file with mode: 0644]
src/Impostor.Server/Net/Manager/GameManager.cs [new file with mode: 0644]
src/Impostor.Server/Net/Matchmaker.cs
src/Impostor.Server/Net/Messages/Message00HostGame.cs [new file with mode: 0644]
src/Impostor.Server/Net/Messages/Message01JoinGame.cs [new file with mode: 0644]
src/Impostor.Server/Net/Messages/Message04RemovePlayer.cs [new file with mode: 0644]
src/Impostor.Server/Net/Messages/Message07JoinedGame.cs [new file with mode: 0644]
src/Impostor.Server/Net/Messages/Message10AlterGame.cs [new file with mode: 0644]
src/Impostor.Server/Net/Messages/Message11KickPlayer.cs [new file with mode: 0644]
src/Impostor.Server/Net/Messages/Message12WaitForHost.cs [new file with mode: 0644]
src/Impostor.Server/Net/Messages/MessageFlags.cs [new file with mode: 0644]
src/Impostor.Server/Net/Response/Message1DisconnectReason.cs [deleted file]
src/Impostor.Server/Net/Response/MessageBase.cs [deleted file]
src/Impostor.Server/Net/Response/MessageFlag.cs [deleted file]
src/Impostor.Server/Net/State/ClientPlayer.cs [new file with mode: 0644]
src/Impostor.Server/Net/State/Game.Incoming.cs [new file with mode: 0644]
src/Impostor.Server/Net/State/Game.Outgoing.cs [new file with mode: 0644]
src/Impostor.Server/Net/State/Game.cs [new file with mode: 0644]
src/Impostor.Shared/Innersloth/Data/GameStates.cs

diff --git a/src/Impostor.Server/Extensions/ConnectionExtensions.cs b/src/Impostor.Server/Extensions/ConnectionExtensions.cs
deleted file mode 100644 (file)
index 21ef19e..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-using Hazel;
-using Impostor.Server.Net.Response;
-
-namespace Impostor.Server.Extensions
-{
-    internal static class ConnectionExtensions
-    {
-        public static void Send(this Connection connection, MessageBase message)
-        {
-            using (message)
-            {
-                connection.Send(message.Write());
-            }
-        }
-    }
-}
\ No newline at end of file
index b3a041eecc077bb2008d536a4250ad52566e064b..72e35581db8b04552fafbc39152e15673254661a 100644 (file)
@@ -1,9 +1,9 @@
 using System;
 using Hazel;
 using Impostor.Server.Data;
-using Impostor.Server.Extensions;
-using Impostor.Server.Net.Response;
-using Impostor.Shared.Innersloth;
+using Impostor.Server.Net.Manager;
+using Impostor.Server.Net.Messages;
+using Impostor.Server.Net.State;
 using Impostor.Shared.Innersloth.Data;
 using Serilog;
 using ILogger = Serilog.ILogger;
@@ -85,7 +85,7 @@ namespace Impostor.Server.Net
             catch (Exception ex)
             {
                 Logger.Error(ex, "Exception caught in client data handler.");
-                Connection.Send(new Message1DisconnectReason(DisconnectReason.Custom, DisconnectMessages.Error));
+                Player.SendDisconnectReason(DisconnectReason.Custom, DisconnectMessages.Error);
             }
         }
 
@@ -100,23 +100,20 @@ namespace Impostor.Server.Net
                 case RequestFlag.HostGame:
                 {
                     // Read game settings.
-                    var gameInfoBytes = message.ReadBytesAndSize();
-                    var gameInfo = GameOptionsData.Deserialize(gameInfoBytes);
+                    var gameInfo = Message00HostGame.Deserialize(message);
                     
                     // Create game.
                     var game = _gameManager.Create(this, gameInfo);
                     if (game == null)
                     {
-                        Connection.Send(new Message1DisconnectReason(DisconnectReason.ServerFull));
+                        Player.SendDisconnectReason(DisconnectReason.ServerFull);
                         return;
                     }
 
                     // Code in the packet below will be used in JoinGame.
                     using (var writer = MessageWriter.Get(SendOption.Reliable))
                     {
-                        writer.StartMessage(0);
-                        writer.Write(game.Code);
-                        writer.EndMessage();
+                        Message00HostGame.Serialize(writer, game.Code);
                 
                         Connection.Send(writer);
                     }
@@ -130,7 +127,7 @@ namespace Impostor.Server.Net
                     var game = _gameManager.Find(gameCode);
                     if (game == null)
                     {
-                        Connection.Send(new Message1DisconnectReason(DisconnectReason.GameMissing));
+                        Player.SendDisconnectReason(DisconnectReason.GameMissing);
                         return;
                     }
 
@@ -240,17 +237,19 @@ namespace Impostor.Server.Net
                     Logger.Warning("Server received unknown flag {0}.", flag);
                     break;
             }
-
+            
+#if DEBUG
             if (flag != RequestFlag.GameData &&
                 flag != RequestFlag.GameDataTo &&
                 flag != RequestFlag.EndGame &&
                 message.Position < message.Length)
             {
-                Logger.Warning("Server did not consume all bytes from {0} ({1} < {2}).", 
-                    flag, 
-                    message.Position, 
+                Logger.Warning("Server did not consume all bytes from {0} ({1} < {2}).",
+                    flag,
+                    message.Position,
                     message.Length);
             }
+#endif
         }
         
         private void OnDisconnected(object sender, DisconnectedEventArgs e)
diff --git a/src/Impostor.Server/Net/ClientManager.cs b/src/Impostor.Server/Net/ClientManager.cs
deleted file mode 100644 (file)
index 2a43d14..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-using System.Collections.Concurrent;
-using Impostor.Server.Exceptions;
-using Serilog;
-
-namespace Impostor.Server.Net
-{
-    public class ClientManager
-    {
-        private static readonly ILogger Logger = Log.ForContext<ClientManager>();
-        
-        private readonly ConcurrentDictionary<int, Client> _clients;
-        private readonly object _idLock;
-        private int _idLast;
-        
-        public ClientManager()
-        {
-            _clients = new ConcurrentDictionary<int, Client>();
-            _idLock = new object();
-            _idLast = 0;
-        }
-
-        // No idea what a good way for this is.
-        public int NextId()
-        {
-            lock (_idLock)
-            {
-                // 3 Attempts.
-                for (var i = 0; i < 3; i++)
-                {
-                    // It is important that ids start from 1, a 0 id causes issues.
-                    var result = ++_idLast;
-
-                    if (_idLast == int.MaxValue)
-                    {
-                        _idLast = 0;
-                    }
-
-                    if (_clients.ContainsKey(_idLast))
-                    {
-                        continue;
-                    }
-            
-                    return result;
-                }
-                
-                throw new AmongUsException("Unable to generate a client id.");
-            }
-        }
-        
-        public void Add(Client client)
-        {
-            Logger.Information("Client connected.");
-
-            _clients.TryAdd(client.Id, client);
-        }
-
-        public void Remove(Client client)
-        {
-            Logger.Information("Client disconnected.");
-
-            _clients.TryRemove(client.Id, out _);
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/Impostor.Server/Net/ClientPlayer.cs b/src/Impostor.Server/Net/ClientPlayer.cs
deleted file mode 100644 (file)
index a0b7866..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-using Impostor.Shared.Innersloth.Data;
-
-namespace Impostor.Server.Net
-{
-    public class ClientPlayer
-    {
-        public ClientPlayer(Client client)
-        {
-            Client = client;
-        }
-        
-        public Client Client { get; }
-        public Game Game { get; set; }
-        public LimboStates LimboState { get; set; }
-    }
-}
\ No newline at end of file
diff --git a/src/Impostor.Server/Net/Game.cs b/src/Impostor.Server/Net/Game.cs
deleted file mode 100644 (file)
index be982d7..0000000
+++ /dev/null
@@ -1,387 +0,0 @@
-using System;
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Linq;
-using System.Net;
-using Hazel;
-using Impostor.Server.Data;
-using Impostor.Server.Exceptions;
-using Impostor.Server.Extensions;
-using Impostor.Server.Net.Response;
-using Impostor.Shared.Innersloth;
-using Impostor.Shared.Innersloth.Data;
-using Serilog;
-using ILogger = Serilog.ILogger;
-
-namespace Impostor.Server.Net
-{
-    public class Game
-    {
-        private static readonly ILogger Logger = Log.ForContext<Game>();
-        
-        private readonly GameManager _gameManager;
-        private readonly ConcurrentDictionary<int, ClientPlayer> _players;
-        private readonly HashSet<IPAddress> _bannedIps;
-
-        public Game(GameManager gameManager, int code, GameOptionsData options)
-        {
-            _gameManager = gameManager;
-            _players = new ConcurrentDictionary<int, ClientPlayer>();
-            _bannedIps = new HashSet<IPAddress>();
-            
-            Code = code;
-            CodeStr = GameCode.IntToGameName(code);
-            HostId = -1;
-            GameState = GameStates.NotStarted;
-            Options = options;
-        }
-        
-        public int Code { get; }
-        public string CodeStr { get; }
-        public bool IsPublic { get; private set; }
-        public int HostId { get; private set; }
-        public GameStates GameState { get; private set; }
-        public GameOptionsData Options { get; }
-
-        public void SendToAllExcept(MessageWriter message, ClientPlayer sender)
-        {
-            foreach (var (_, player) in _players.Where(x => x.Value != sender))
-            {
-                if (player.Client.Connection.State != ConnectionState.Connected)
-                {
-                    Logger.Warning("[{0}] Tried to sent data to a disconnected player ({1}).", sender?.Client.Id, player.Client.Id);
-                    continue;
-                }
-                
-                player.Client.Send(message);
-            }
-        }
-
-        public void SendTo(MessageWriter message, int playerId)
-        {
-            if (_players.TryGetValue(playerId, out var player))
-            {
-                if (player.Client.Connection.State != ConnectionState.Connected)
-                {
-                    Logger.Warning("[{0}] Sending data to {1} failed, player is not connected.", CodeStr, player.Client.Id);
-                    return;
-                }
-                
-                player.Client.Send(message);
-            }
-            else
-            {
-                Logger.Warning("[{0}] Sending data to {1} failed, player does not exist.", CodeStr, playerId);
-            }
-        }
-
-        public void HandleStartGame(MessageReader message)
-        {
-            GameState = GameStates.Started;
-            
-            using (var packet = MessageWriter.Get(SendOption.Reliable))
-            {
-                packet.CopyFrom(message);
-                SendToAllExcept(packet, null);
-            }
-        }
-
-        public void HandleJoinGame(ClientPlayer sender)
-        {
-            if (_bannedIps.Contains(sender.Client.Connection.EndPoint.Address))
-            {
-                sender.Client.Connection.Send(new Message1DisconnectReason(DisconnectReason.Banned));
-                return;
-            }
-            
-            switch (GameState)
-            {
-                case GameStates.NotStarted:
-                    HandleJoinGameNew(sender);
-                    break;
-                case GameStates.Ended:
-                    HandleJoinGameNext(sender);
-                    break;
-                case GameStates.Started:
-                    sender.Client.Connection.Send(new Message1DisconnectReason(DisconnectReason.GameStarted));
-                    return;
-                case GameStates.Destroyed:
-                    sender.Client.Connection.Send(new Message1DisconnectReason(DisconnectReason.Custom, DisconnectMessages.Destroyed));
-                    return;
-                default:
-                    throw new ArgumentOutOfRangeException();
-            }
-        }
-
-        public void HandleEndGame(MessageReader message)
-        {
-            GameState = GameStates.Ended;
-            
-            // Broadcast end of the game.
-            using (var packet = MessageWriter.Get(SendOption.Reliable))
-            {
-                packet.CopyFrom(message);
-                SendToAllExcept(packet, null);
-            }
-            
-            // Remove all players from this game.
-            foreach (var player in _players)
-            {
-                player.Value.Game = null;
-            }
-            
-            _players.Clear();
-        }
-
-        public void HandleAlterGame(MessageReader message, ClientPlayer sender, bool isPublic)
-        {
-            IsPublic = isPublic;
-            
-            using (var packet = MessageWriter.Get(SendOption.Reliable))
-            {
-                packet.CopyFrom(message);
-                SendToAllExcept(packet, sender);
-            }
-        }
-        
-        public void HandleRemovePlayer(int playerId, DisconnectReason reason)
-        {
-            if (_players.TryRemove(playerId, out var player))
-            {
-                player.Game = null;
-            }
-            
-            Logger.Information("{0} - Player {1} ({2}) has left.", CodeStr, player?.Client.Name, playerId);
-
-            // Game is empty, remove it.
-            if (_players.Count == 0)
-            {
-                GameState = GameStates.Destroyed;
-
-                // Remove instance reference.
-                _gameManager.Remove(Code);
-                return;
-            }
-
-            // Host migration.
-            if (HostId == playerId)
-            {
-                var newHost = _players.First().Value;
-                HostId = newHost.Client.Id;
-                Logger.Information("{0} - Assigned {1} ({2}) as new host.", CodeStr, newHost.Client.Name, newHost.Client.Id);
-            }
-
-            using (var packet = MessageWriter.Get(SendOption.Reliable))
-            {
-                WriteRemovePlayerMessage(packet, false, playerId, reason);
-                SendToAllExcept(packet, player);
-            }
-        }
-
-        public void HandleKickPlayer(int playerId, bool isBan)
-        {
-            _players.TryGetValue(playerId, out var p);
-            Logger.Information("{0} - Player {1} ({2}) has left.", CodeStr, p?.Client.Name, playerId);
-            
-            using (var message = MessageWriter.Get(SendOption.Reliable))
-            {
-                WriteKickPlayerMessage(message, false, playerId, isBan);
-                SendToAllExcept(message, null);
-                
-                if (_players.TryRemove(playerId, out var player))
-                {
-                    player.Game = null;
-
-                    if (isBan)
-                    {
-                        _bannedIps.Add(player.Client.Connection.EndPoint.Address);
-                    }
-                }
-                
-                WriteRemovePlayerMessage(message, true, playerId, isBan 
-                    ? DisconnectReason.Banned 
-                    : DisconnectReason.Kicked);
-                SendToAllExcept(message, player);
-            }
-        }
-        
-        private void HandleJoinGameNew(ClientPlayer sender)
-        {
-            Logger.Information("{0} - Player {1} ({2}) is joining.", CodeStr, sender.Client.Name, sender.Client.Id);
-            
-            // Store player.
-            if (!_players.TryAdd(sender.Client.Id, sender))
-            {
-                throw new AmongUsException("Failed to add player to game.");
-            }
-            
-            // Assign player to this game for future packets.
-            sender.Game = this;
-
-            // Assign hostId if none is set.
-            if (HostId == -1)
-            {
-                HostId = sender.Client.Id;
-            }
-
-            if (HostId == sender.Client.Id)
-            {
-                sender.LimboState = LimboStates.NotLimbo;
-            }
-
-            using (var message = MessageWriter.Get(SendOption.Reliable))
-            {
-                WriteJoinedGameMessage(message, false, sender);
-                WriteAlterGameMessage(message, false);
-                
-                sender.Client.Send(message);
-
-                BroadcastJoinMessage(message, true, sender);
-            }
-        }
-
-        private void HandleJoinGameNext(ClientPlayer sender)
-        {
-            Logger.Information("{0} - Player {1} ({2}) is rejoining.", CodeStr, sender.Client.Name, sender.Client.Id);
-            
-            if (sender.Client.Id == HostId)
-            {
-                GameState = GameStates.NotStarted;
-                HandleJoinGameNew(sender);
-
-                using (var message = MessageWriter.Get(SendOption.Reliable))
-                {
-                    foreach (var (_, player) in _players.Where(x => x.Value != sender))
-                    {
-                        WriteJoinedGameMessage(message, true, player);
-                        WriteAlterGameMessage(message, false);
-                        player.Client.Send(message);
-                    }
-                }
-                
-                return;
-            }
-
-            if (_players.Count >= 9)
-            {
-                sender.Client.Connection.Send(new Message1DisconnectReason(DisconnectReason.GameFull));
-                return;
-            }
-
-            // Store player.
-            if (!_players.TryAdd(sender.Client.Id, sender))
-            {
-                throw new AmongUsException("Failed to add player to game.");
-            }
-            
-            // Assign player to this game for future packets.
-            sender.Game = this;
-            
-            // Limbo, yes.
-            sender.LimboState = LimboStates.WaitingForHost;
-
-            using (var packet = MessageWriter.Get(SendOption.Reliable))
-            {
-                WriteWaitForHostMessage(packet, false, sender);
-                sender.Client.Send(packet);
-
-                BroadcastJoinMessage(packet, true, sender);
-            }
-        }
-
-        private void WriteRemovePlayerMessage(MessageWriter message, bool clear, int playerId, DisconnectReason reason)
-        {
-            // Only a subset of DisconnectReason shows an unique message.
-            // ExitGame, Banned and Kicked.
-            if (clear)
-            {
-                message.Clear(SendOption.Reliable);
-            }
-            
-            message.StartMessage((byte) RequestFlag.RemovePlayer);
-            message.Write(Code);
-            message.Write(playerId);
-            message.Write(HostId);
-            message.Write((byte) reason);
-            message.EndMessage();
-        }
-        
-        private void WriteJoinedGameMessage(MessageWriter message, bool clear, ClientPlayer player)
-        {
-            if (clear)
-            {
-                message.Clear(SendOption.Reliable);
-            }
-            
-            message.StartMessage((byte) RequestFlag.JoinedGame);
-            message.Write(Code);
-            message.Write(player.Client.Id);
-            message.Write(HostId);
-            message.WritePacked(_players.Count - 1);
-            
-            foreach (var (_, p) in _players.Where(x => x.Value != player))
-            {
-                message.WritePacked(p.Client.Id);
-            }
-            
-            message.EndMessage();
-        }
-
-        private void WriteAlterGameMessage(MessageWriter message, bool clear)
-        {
-            if (clear)
-            {
-                message.Clear(SendOption.Reliable);
-            }
-            
-            message.StartMessage((byte) RequestFlag.AlterGame);
-            message.Write(Code);
-            message.Write((byte) AlterGameTags.ChangePrivacy);
-            message.Write(IsPublic);
-            message.EndMessage();
-        }
-
-        private void WriteKickPlayerMessage(MessageWriter message, bool clear, int playerId, bool isBan)
-        {
-            if (clear)
-            {
-                message.Clear(SendOption.Reliable);
-            }
-            
-            message.StartMessage((byte) RequestFlag.KickPlayer);
-            message.Write(Code);
-            message.WritePacked(playerId);
-            message.Write(isBan);
-            message.EndMessage();
-        }
-        
-        private void WriteWaitForHostMessage(MessageWriter message, bool clear, ClientPlayer player)
-        {
-            if (clear)
-            {
-                message.Clear(SendOption.Reliable);
-            }
-            
-            message.StartMessage((byte) RequestFlag.WaitForHost);
-            message.Write(Code);
-            message.Write(player.Client.Id);
-            message.EndMessage();
-        }
-        
-        private void BroadcastJoinMessage(MessageWriter message, bool clear, ClientPlayer player)
-        {
-            if (clear)
-            {
-                message.Clear(SendOption.Reliable);
-            }
-            
-            message.StartMessage((byte) RequestFlag.JoinGame);
-            message.Write(Code);
-            message.Write(player.Client.Id);
-            message.Write(HostId);
-            message.EndMessage();
-            
-            SendToAllExcept(message, player);
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/Impostor.Server/Net/GameManager.cs b/src/Impostor.Server/Net/GameManager.cs
deleted file mode 100644 (file)
index 664f640..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-using System.Collections.Concurrent;
-using Impostor.Shared.Innersloth;
-using Serilog;
-
-namespace Impostor.Server.Net
-{
-    public class GameManager
-    {
-        private static readonly ILogger Logger = Log.ForContext<GameManager>();
-        
-        private readonly ConcurrentDictionary<int, Game> _games;
-
-        public GameManager()
-        {
-            _games = new ConcurrentDictionary<int, Game>();
-        }
-        
-        public Game Create(Client owner, GameOptionsData options)
-        {
-            var gameCode = GameCode.GenerateCode(6);
-            var game = new Game(this, gameCode, options);
-
-            if (_games.TryAdd(gameCode, game))
-            {
-                Logger.Debug("Created game with code {0} ({1}).", GameCode.IntToGameName(gameCode), gameCode);        
-                return game;
-            }
-
-            Logger.Warning("Failed to create game.");
-            return null;
-        }
-
-        public Game Find(int gameCode)
-        {
-            _games.TryGetValue(gameCode, out var game);
-            return game;
-        }
-
-        public void Remove(int gameCode)
-        {
-            Logger.Debug("Remove game with code {0} ({1}).", GameCode.IntToGameName(gameCode), gameCode);   
-            _games.TryRemove(gameCode, out _);
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/Impostor.Server/Net/GameStates.cs b/src/Impostor.Server/Net/GameStates.cs
deleted file mode 100644 (file)
index f24ecc5..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace Impostor.Server.Net
-{
-    public enum GameStates : byte
-    {
-        NotStarted = 0,
-        Started = 1,
-        Ended = 2,
-        Destroyed = 3
-    }
-}
\ No newline at end of file
diff --git a/src/Impostor.Server/Net/Manager/ClientManager.cs b/src/Impostor.Server/Net/Manager/ClientManager.cs
new file mode 100644 (file)
index 0000000..b4f7684
--- /dev/null
@@ -0,0 +1,64 @@
+using System.Collections.Concurrent;
+using Impostor.Server.Exceptions;
+using Serilog;
+
+namespace Impostor.Server.Net.Manager
+{
+    public class ClientManager
+    {
+        private static readonly ILogger Logger = Log.ForContext<ClientManager>();
+        
+        private readonly ConcurrentDictionary<int, Client> _clients;
+        private readonly object _idLock;
+        private int _idLast;
+        
+        public ClientManager()
+        {
+            _clients = new ConcurrentDictionary<int, Client>();
+            _idLock = new object();
+            _idLast = 0;
+        }
+
+        // No idea what a good way for this is.
+        public int NextId()
+        {
+            lock (_idLock)
+            {
+                // 3 Attempts.
+                for (var i = 0; i < 3; i++)
+                {
+                    // It is important that ids start from 1, a 0 id causes issues.
+                    var result = ++_idLast;
+
+                    if (_idLast == int.MaxValue)
+                    {
+                        _idLast = 0;
+                    }
+
+                    if (_clients.ContainsKey(_idLast))
+                    {
+                        continue;
+                    }
+            
+                    return result;
+                }
+                
+                throw new AmongUsException("Unable to generate a client id.");
+            }
+        }
+        
+        public void Add(Client client)
+        {
+            Logger.Information("Client connected.");
+
+            _clients.TryAdd(client.Id, client);
+        }
+
+        public void Remove(Client client)
+        {
+            Logger.Information("Client disconnected.");
+
+            _clients.TryRemove(client.Id, out _);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Impostor.Server/Net/Manager/GameManager.cs b/src/Impostor.Server/Net/Manager/GameManager.cs
new file mode 100644 (file)
index 0000000..0b2b251
--- /dev/null
@@ -0,0 +1,46 @@
+using System.Collections.Concurrent;
+using Impostor.Server.Net.State;
+using Impostor.Shared.Innersloth;
+using Serilog;
+
+namespace Impostor.Server.Net.Manager
+{
+    public class GameManager
+    {
+        private static readonly ILogger Logger = Log.ForContext<GameManager>();
+        
+        private readonly ConcurrentDictionary<int, Game> _games;
+
+        public GameManager()
+        {
+            _games = new ConcurrentDictionary<int, Game>();
+        }
+        
+        public Game Create(Client owner, GameOptionsData options)
+        {
+            var gameCode = GameCode.GenerateCode(6);
+            var game = new Game(this, gameCode, options);
+
+            if (_games.TryAdd(gameCode, game))
+            {
+                Logger.Debug("Created game with code {0} ({1}).", game.CodeStr, gameCode);        
+                return game;
+            }
+
+            Logger.Warning("Failed to create game.");
+            return null;
+        }
+
+        public Game Find(int gameCode)
+        {
+            _games.TryGetValue(gameCode, out var game);
+            return game;
+        }
+
+        public void Remove(int gameCode)
+        {
+            Logger.Debug("Remove game with code {0} ({1}).", GameCode.IntToGameName(gameCode), gameCode);   
+            _games.TryRemove(gameCode, out _);
+        }
+    }
+}
\ No newline at end of file
index 8acc9d603678c2d7e8a2430af9b0044bbde90e24..5a1bec707ab5695a1aab85b1f07f7874fcf5bf2d 100644 (file)
@@ -1,8 +1,8 @@
 using System.Net;
 using Hazel;
 using Hazel.Udp;
-using Impostor.Server.Extensions;
-using Impostor.Server.Net.Response;
+using Impostor.Server.Net.Manager;
+using Impostor.Server.Net.Messages;
 using Impostor.Shared.Innersloth.Data;
 using Serilog;
 using ILogger = Serilog.ILogger;
@@ -39,7 +39,11 @@ namespace Impostor.Server.Net
                 
             if (clientVersion != 50516550)
             {
-                e.Connection.Send(new Message1DisconnectReason(DisconnectReason.IncorrectVersion));
+                using (var packet = MessageWriter.Get(SendOption.Reliable))
+                {
+                    Message01JoinGame.SerializeError(packet, false, DisconnectReason.IncorrectVersion);
+                    e.Connection.Send(packet);
+                }
                 return;
             }
             
diff --git a/src/Impostor.Server/Net/Messages/Message00HostGame.cs b/src/Impostor.Server/Net/Messages/Message00HostGame.cs
new file mode 100644 (file)
index 0000000..392bc38
--- /dev/null
@@ -0,0 +1,60 @@
+using System.IO;
+using Hazel;
+using Impostor.Shared.Innersloth;
+using Impostor.Shared.Innersloth.Data;
+
+namespace Impostor.Server.Net.Messages
+{
+    internal static class Message00HostGame
+    {
+        public static void Serialize(MessageWriter writer, int gameCode)
+        {
+            writer.StartMessage(MessageFlags.HostGame);
+            writer.Write(gameCode);
+            writer.EndMessage();
+        }
+
+        public static GameOptionsData Deserialize(MessageReader reader)
+        {
+            var bytes = reader.ReadBytesAndSize();
+            
+            using (var stream = new MemoryStream(bytes))
+            using (var binary = new BinaryReader(stream))
+            {
+                var result = new GameOptionsData
+                {
+                    Version = binary.ReadByte(),
+                    MaxPlayers = binary.ReadByte(),
+                    Keywords = (GameKeywords) binary.ReadUInt32(),
+                    MapId = binary.ReadByte(),
+                    PlayerSpeedMod = binary.ReadSingle(),
+                    CrewLightMod = binary.ReadSingle(),
+                    ImpostorLightMod = binary.ReadSingle(),
+                    KillCooldown = binary.ReadSingle(),
+                    NumCommonTasks = binary.ReadByte(),
+                    NumLongTasks = binary.ReadByte(),
+                    NumShortTasks = binary.ReadByte(),
+                    NumEmergencyMeetings = binary.ReadInt32(),
+                    NumImpostors = binary.ReadByte(),
+                    KillDistance = binary.ReadByte(),
+                    DiscussionTime = binary.ReadInt32(),
+                    VotingTime = binary.ReadInt32(),
+                    IsDefaults = binary.ReadBoolean()
+                };
+
+                if (result.Version > 1)
+                {
+                    result.EmergencyCooldown = binary.ReadByte();
+                }
+
+                if (result.Version > 2)
+                {
+                    result.ConfirmImpostor = binary.ReadBoolean();
+                    result.VisualTasks = binary.ReadBoolean();
+                }
+                
+                return result;
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Impostor.Server/Net/Messages/Message01JoinGame.cs b/src/Impostor.Server/Net/Messages/Message01JoinGame.cs
new file mode 100644 (file)
index 0000000..badee4f
--- /dev/null
@@ -0,0 +1,46 @@
+using System;
+using Hazel;
+using Impostor.Shared.Innersloth.Data;
+
+namespace Impostor.Server.Net.Messages
+{
+    internal static class Message01JoinGame
+    {
+        public static void SerializeJoin(MessageWriter writer, bool clear, int gameCode, int playerId, int hostId)
+        {
+            if (clear)
+            {
+                writer.Clear(SendOption.Reliable);
+            }
+            
+            writer.StartMessage(MessageFlags.JoinGame);
+            writer.Write(gameCode);
+            writer.Write(playerId);
+            writer.Write(hostId);
+            writer.EndMessage();
+        }
+        
+        public static void SerializeError(MessageWriter writer, bool clear, DisconnectReason reason, string message = null)
+        {
+            if (clear)
+            {
+                writer.Clear(SendOption.Reliable);
+            }
+            
+            writer.StartMessage(MessageFlags.JoinGame);
+            writer.Write((int) reason);
+
+            if (reason == DisconnectReason.Custom)
+            {
+                if (message == null)
+                {
+                    throw new ArgumentNullException(nameof(message));
+                }
+                
+                writer.Write(message);
+            }
+            
+            writer.EndMessage();
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Impostor.Server/Net/Messages/Message04RemovePlayer.cs b/src/Impostor.Server/Net/Messages/Message04RemovePlayer.cs
new file mode 100644 (file)
index 0000000..b572c21
--- /dev/null
@@ -0,0 +1,25 @@
+using Hazel;
+using Impostor.Shared.Innersloth.Data;
+
+namespace Impostor.Server.Net.Messages
+{
+    internal static class Message04RemovePlayer
+    {
+        public static void Serialize(MessageWriter writer, bool clear, int gameCode, int playerId, int hostId, DisconnectReason reason)
+        {
+            // Only a subset of DisconnectReason shows an unique message.
+            // ExitGame, Banned and Kicked.
+            if (clear)
+            {
+                writer.Clear(SendOption.Reliable);
+            }
+            
+            writer.StartMessage(MessageFlags.RemovePlayer);
+            writer.Write(gameCode);
+            writer.Write(playerId);
+            writer.Write(hostId);
+            writer.Write((byte) reason);
+            writer.EndMessage();
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Impostor.Server/Net/Messages/Message07JoinedGame.cs b/src/Impostor.Server/Net/Messages/Message07JoinedGame.cs
new file mode 100644 (file)
index 0000000..9fa08e7
--- /dev/null
@@ -0,0 +1,28 @@
+using Hazel;
+
+namespace Impostor.Server.Net.Messages
+{
+    internal static class Message07JoinedGame
+    {
+        public static void Serialize(MessageWriter writer, bool clear, int gameCode, int playerId, int hostId, int[] otherPlayerIds)
+        {
+            if (clear)
+            {
+                writer.Clear(SendOption.Reliable);
+            }
+            
+            writer.StartMessage(MessageFlags.JoinedGame);
+            writer.Write(gameCode);
+            writer.Write(playerId);
+            writer.Write(hostId);
+            writer.WritePacked(otherPlayerIds.Length);
+
+            foreach (var id in otherPlayerIds)
+            {
+                writer.WritePacked(id);
+            }
+            
+            writer.EndMessage();
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Impostor.Server/Net/Messages/Message10AlterGame.cs b/src/Impostor.Server/Net/Messages/Message10AlterGame.cs
new file mode 100644 (file)
index 0000000..3b77548
--- /dev/null
@@ -0,0 +1,19 @@
+using Hazel;
+
+namespace Impostor.Server.Net.Messages
+{
+    internal static class Message10AlterGame
+    {
+        public static void Serialize(MessageWriter writer, bool clear, int gameCode)
+        {
+            if (clear)
+            {
+                writer.Clear(SendOption.Reliable);
+            }
+            
+            writer.StartMessage(MessageFlags.HostGame);
+            writer.Write(gameCode);
+            writer.EndMessage();
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Impostor.Server/Net/Messages/Message11KickPlayer.cs b/src/Impostor.Server/Net/Messages/Message11KickPlayer.cs
new file mode 100644 (file)
index 0000000..dde4726
--- /dev/null
@@ -0,0 +1,21 @@
+using Hazel;
+
+namespace Impostor.Server.Net.Messages
+{
+    internal static class Message11KickPlayer
+    {
+        public static void Serialize(MessageWriter writer, bool clear, int gameCode, int playerId, bool isBan)
+        {
+            if (clear)
+            {
+                writer.Clear(SendOption.Reliable);
+            }
+            
+            writer.StartMessage(MessageFlags.KickPlayer);
+            writer.Write(gameCode);
+            writer.WritePacked(playerId);
+            writer.Write(isBan);
+            writer.EndMessage();
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Impostor.Server/Net/Messages/Message12WaitForHost.cs b/src/Impostor.Server/Net/Messages/Message12WaitForHost.cs
new file mode 100644 (file)
index 0000000..e09f4b4
--- /dev/null
@@ -0,0 +1,20 @@
+using Hazel;
+
+namespace Impostor.Server.Net.Messages
+{
+    internal static class Message12WaitForHost
+    {
+        public static void Serialize(MessageWriter writer, bool clear, int gameCode, int playerId)
+        {
+            if (clear)
+            {
+                writer.Clear(SendOption.Reliable);
+            }
+            
+            writer.StartMessage(MessageFlags.WaitForHost);
+            writer.Write(gameCode);
+            writer.Write(playerId);
+            writer.EndMessage();
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Impostor.Server/Net/Messages/MessageFlags.cs b/src/Impostor.Server/Net/Messages/MessageFlags.cs
new file mode 100644 (file)
index 0000000..595c02e
--- /dev/null
@@ -0,0 +1,22 @@
+namespace Impostor.Server.Net.Messages
+{
+    internal static class MessageFlags
+    {
+        public const byte HostGame = 0;
+        public const byte JoinGame = 1;
+        public const byte StartGame = 2;
+        public const byte RemoveGame = 3;
+        public const byte RemovePlayer = 4;
+        public const byte GameData = 5;
+        public const byte GameDataTo = 6;
+        public const byte JoinedGame = 7;
+        public const byte EndGame = 8;
+        public const byte AlterGame = 10;
+        public const byte KickPlayer = 11;
+        public const byte WaitForHost = 12;
+        public const byte Redirect = 13;
+        public const byte ReselectServer = 14;
+        public const byte GetGameList = 9;
+        public const byte GetGameListV2 = 16;
+    }
+}
\ No newline at end of file
diff --git a/src/Impostor.Server/Net/Response/Message1DisconnectReason.cs b/src/Impostor.Server/Net/Response/Message1DisconnectReason.cs
deleted file mode 100644 (file)
index 0ab5be4..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-using Hazel;
-using Impostor.Shared.Innersloth.Data;
-
-namespace Impostor.Server.Net.Response
-{
-    public class Message1DisconnectReason : MessageBase
-    {
-        private readonly DisconnectReason _reason;
-        private readonly string _message;
-
-        // Notes:
-        // - Specifying no reason does something with ban minutes left.
-        // - (?) You were disconnected because Among Us was suspended by another app.
-        public Message1DisconnectReason(DisconnectReason reason, string message = null) : base(SendOption.Reliable, MessageFlag.DisconnectReason)
-        {
-            _reason = reason;
-            _message = message;
-        }
-
-        protected override void WriteMessage(MessageWriter writer)
-        {
-            writer.Write((int) _reason);
-
-            if (_reason == DisconnectReason.Custom)
-            {
-                writer.Write(_message);
-            }
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/Impostor.Server/Net/Response/MessageBase.cs b/src/Impostor.Server/Net/Response/MessageBase.cs
deleted file mode 100644 (file)
index d9fceab..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-using System;
-using Hazel;
-
-namespace Impostor.Server.Net.Response
-{
-    public abstract class MessageBase : IDisposable
-    {
-        private readonly MessageWriter _writer;
-        private readonly MessageFlag _flag;
-
-        protected MessageBase(SendOption option, MessageFlag flag)
-        {
-            _writer = MessageWriter.Get(option);
-            _flag = flag;
-        }
-
-        public MessageWriter Write()
-        {
-            _writer.StartMessage((byte) _flag);
-            WriteMessage(_writer);
-            _writer.EndMessage();
-            return _writer;
-        }
-
-        protected abstract void WriteMessage(MessageWriter writer);
-
-        public void Dispose()
-        {
-            _writer.Dispose();
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/Impostor.Server/Net/Response/MessageFlag.cs b/src/Impostor.Server/Net/Response/MessageFlag.cs
deleted file mode 100644 (file)
index a29135d..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace Impostor.Server.Net.Response
-{
-    public enum MessageFlag : byte
-    {
-        DisconnectReason = 1
-    }
-}
\ No newline at end of file
diff --git a/src/Impostor.Server/Net/State/ClientPlayer.cs b/src/Impostor.Server/Net/State/ClientPlayer.cs
new file mode 100644 (file)
index 0000000..242d6b9
--- /dev/null
@@ -0,0 +1,27 @@
+using Hazel;
+using Impostor.Server.Net.Messages;
+using Impostor.Shared.Innersloth.Data;
+
+namespace Impostor.Server.Net.State
+{
+    public class ClientPlayer
+    {
+        public ClientPlayer(Client client)
+        {
+            Client = client;
+        }
+        
+        public Client Client { get; }
+        public Game Game { get; set; }
+        public LimboStates LimboState { get; set; }
+
+        public void SendDisconnectReason(DisconnectReason reason, string message = null)
+        {
+            using (var packet = MessageWriter.Get(SendOption.Reliable))
+            {
+                Message01JoinGame.SerializeError(packet, false, reason, message);
+                Client.Connection.Send(packet);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Impostor.Server/Net/State/Game.Incoming.cs b/src/Impostor.Server/Net/State/Game.Incoming.cs
new file mode 100644 (file)
index 0000000..1e7155f
--- /dev/null
@@ -0,0 +1,226 @@
+using System;
+using System.Linq;
+using Hazel;
+using Impostor.Server.Data;
+using Impostor.Server.Exceptions;
+using Impostor.Shared.Innersloth.Data;
+
+namespace Impostor.Server.Net.State
+{
+    public partial class Game
+    {
+        public void HandleStartGame(MessageReader message)
+        {
+            GameState = GameStates.Started;
+            
+            using (var packet = MessageWriter.Get(SendOption.Reliable))
+            {
+                packet.CopyFrom(message);
+                SendToAllExcept(packet, null);
+            }
+        }
+
+        public void HandleJoinGame(ClientPlayer sender)
+        {
+            if (_bannedIps.Contains(sender.Client.Connection.EndPoint.Address))
+            {
+                sender.SendDisconnectReason(DisconnectReason.Banned);
+                return;
+            }
+            
+            switch (GameState)
+            {
+                case GameStates.NotStarted:
+                    HandleJoinGameNew(sender);
+                    break;
+                case GameStates.Ended:
+                    HandleJoinGameNext(sender);
+                    break;
+                case GameStates.Started:
+                    sender.SendDisconnectReason(DisconnectReason.GameStarted);
+                    return;
+                case GameStates.Destroyed:
+                    sender.SendDisconnectReason(DisconnectReason.Custom, DisconnectMessages.Destroyed);
+                    return;
+                default:
+                    throw new ArgumentOutOfRangeException();
+            }
+        }
+
+        public void HandleEndGame(MessageReader message)
+        {
+            GameState = GameStates.Ended;
+            
+            // Broadcast end of the game.
+            using (var packet = MessageWriter.Get(SendOption.Reliable))
+            {
+                packet.CopyFrom(message);
+                SendToAllExcept(packet, null);
+            }
+            
+            // Remove all players from this game.
+            foreach (var player in _players)
+            {
+                player.Value.Game = null;
+            }
+            
+            _players.Clear();
+        }
+
+        public void HandleAlterGame(MessageReader message, ClientPlayer sender, bool isPublic)
+        {
+            IsPublic = isPublic;
+            
+            using (var packet = MessageWriter.Get(SendOption.Reliable))
+            {
+                packet.CopyFrom(message);
+                SendToAllExcept(packet, sender);
+            }
+        }
+        
+        public void HandleRemovePlayer(int playerId, DisconnectReason reason)
+        {
+            if (_players.TryRemove(playerId, out var player))
+            {
+                player.Game = null;
+            }
+            
+            Logger.Information("{0} - Player {1} ({2}) has left.", CodeStr, player?.Client.Name, playerId);
+
+            // Game is empty, remove it.
+            if (_players.Count == 0)
+            {
+                GameState = GameStates.Destroyed;
+
+                // Remove instance reference.
+                _gameManager.Remove(Code);
+                return;
+            }
+
+            // Host migration.
+            if (HostId == playerId)
+            {
+                var newHost = _players.First().Value;
+                HostId = newHost.Client.Id;
+                Logger.Information("{0} - Assigned {1} ({2}) as new host.", CodeStr, newHost.Client.Name, newHost.Client.Id);
+            }
+
+            using (var packet = MessageWriter.Get(SendOption.Reliable))
+            {
+                WriteRemovePlayerMessage(packet, false, playerId, reason);
+                SendToAllExcept(packet, player);
+            }
+        }
+
+        public void HandleKickPlayer(int playerId, bool isBan)
+        {
+            _players.TryGetValue(playerId, out var p);
+            Logger.Information("{0} - Player {1} ({2}) has left.", CodeStr, p?.Client.Name, playerId);
+            
+            using (var message = MessageWriter.Get(SendOption.Reliable))
+            {
+                WriteKickPlayerMessage(message, false, playerId, isBan);
+                SendToAllExcept(message, null);
+                
+                if (_players.TryRemove(playerId, out var player))
+                {
+                    player.Game = null;
+
+                    if (isBan)
+                    {
+                        _bannedIps.Add(player.Client.Connection.EndPoint.Address);
+                    }
+                }
+                
+                WriteRemovePlayerMessage(message, true, playerId, isBan 
+                    ? DisconnectReason.Banned 
+                    : DisconnectReason.Kicked);
+                SendToAllExcept(message, player);
+            }
+        }
+        
+        private void HandleJoinGameNew(ClientPlayer sender)
+        {
+            Logger.Information("{0} - Player {1} ({2}) is joining.", CodeStr, sender.Client.Name, sender.Client.Id);
+            
+            // Store player.
+            if (!_players.TryAdd(sender.Client.Id, sender))
+            {
+                throw new AmongUsException("Failed to add player to game.");
+            }
+            
+            // Assign player to this game for future packets.
+            sender.Game = this;
+
+            // Assign hostId if none is set.
+            if (HostId == -1)
+            {
+                HostId = sender.Client.Id;
+            }
+
+            if (HostId == sender.Client.Id)
+            {
+                sender.LimboState = LimboStates.NotLimbo;
+            }
+
+            using (var message = MessageWriter.Get(SendOption.Reliable))
+            {
+                WriteJoinedGameMessage(message, false, sender);
+                WriteAlterGameMessage(message, false);
+                
+                sender.Client.Send(message);
+
+                BroadcastJoinMessage(message, true, sender);
+            }
+        }
+
+        private void HandleJoinGameNext(ClientPlayer sender)
+        {
+            Logger.Information("{0} - Player {1} ({2}) is rejoining.", CodeStr, sender.Client.Name, sender.Client.Id);
+            
+            if (sender.Client.Id == HostId)
+            {
+                GameState = GameStates.NotStarted;
+                HandleJoinGameNew(sender);
+
+                using (var message = MessageWriter.Get(SendOption.Reliable))
+                {
+                    foreach (var (_, player) in _players.Where(x => x.Value != sender))
+                    {
+                        WriteJoinedGameMessage(message, true, player);
+                        WriteAlterGameMessage(message, false);
+                        player.Client.Send(message);
+                    }
+                }
+                
+                return;
+            }
+
+            if (_players.Count >= 9)
+            {
+                sender.SendDisconnectReason(DisconnectReason.GameFull);
+                return;
+            }
+
+            // Store player.
+            if (!_players.TryAdd(sender.Client.Id, sender))
+            {
+                throw new AmongUsException("Failed to add player to game.");
+            }
+            
+            // Assign player to this game for future packets.
+            sender.Game = this;
+            
+            // Limbo, yes.
+            sender.LimboState = LimboStates.WaitingForHost;
+
+            using (var packet = MessageWriter.Get(SendOption.Reliable))
+            {
+                WriteWaitForHostMessage(packet, false, sender);
+                sender.Client.Send(packet);
+
+                BroadcastJoinMessage(packet, true, sender);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Impostor.Server/Net/State/Game.Outgoing.cs b/src/Impostor.Server/Net/State/Game.Outgoing.cs
new file mode 100644 (file)
index 0000000..c03b3b4
--- /dev/null
@@ -0,0 +1,40 @@
+using System.Linq;
+using Hazel;
+using Impostor.Server.Net.Messages;
+using Impostor.Shared.Innersloth.Data;
+
+namespace Impostor.Server.Net.State
+{
+    public partial class Game
+    {
+        private void WriteRemovePlayerMessage(MessageWriter message, bool clear, int playerId, DisconnectReason reason)
+        {
+            Message04RemovePlayer.Serialize(message, clear, Code, playerId, HostId, reason);
+        }
+        
+        private void WriteJoinedGameMessage(MessageWriter message, bool clear, ClientPlayer player)
+        {
+            var playerIds = _players
+                .Where(x => x.Value != player)
+                .Select(x => x.Key)
+                .ToArray();
+            
+            Message07JoinedGame.Serialize(message, clear, Code, player.Client.Id, HostId, playerIds);
+        }
+
+        private void WriteAlterGameMessage(MessageWriter message, bool clear)
+        {
+            Message10AlterGame.Serialize(message, clear, Code);
+        }
+
+        private void WriteKickPlayerMessage(MessageWriter message, bool clear, int playerId, bool isBan)
+        {
+            Message11KickPlayer.Serialize(message, clear, Code, playerId, isBan);
+        }
+        
+        private void WriteWaitForHostMessage(MessageWriter message, bool clear, ClientPlayer player)
+        {
+            Message12WaitForHost.Serialize(message, clear, Code, player.Client.Id);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Impostor.Server/Net/State/Game.cs b/src/Impostor.Server/Net/State/Game.cs
new file mode 100644 (file)
index 0000000..fe043a1
--- /dev/null
@@ -0,0 +1,95 @@
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using Hazel;
+using Impostor.Server.Net.Manager;
+using Impostor.Server.Net.Messages;
+using Impostor.Shared.Innersloth;
+using Impostor.Shared.Innersloth.Data;
+using Serilog;
+using ILogger = Serilog.ILogger;
+
+namespace Impostor.Server.Net.State
+{
+    public partial class Game
+    {
+        private static readonly ILogger Logger = Log.ForContext<Game>();
+        
+        private readonly GameManager _gameManager;
+        private readonly ConcurrentDictionary<int, ClientPlayer> _players;
+        private readonly HashSet<IPAddress> _bannedIps;
+
+        public Game(GameManager gameManager, int code, GameOptionsData options)
+        {
+            _gameManager = gameManager;
+            _players = new ConcurrentDictionary<int, ClientPlayer>();
+            _bannedIps = new HashSet<IPAddress>();
+            
+            Code = code;
+            CodeStr = GameCode.IntToGameName(code);
+            HostId = -1;
+            GameState = GameStates.NotStarted;
+            Options = options;
+        }
+        
+        public int Code { get; }
+        public string CodeStr { get; }
+        public bool IsPublic { get; private set; }
+        public int HostId { get; private set; }
+        public GameStates GameState { get; private set; }
+        public GameOptionsData Options { get; }
+
+        /// <summary>
+        ///     Send a message to all players except one.
+        /// </summary>
+        /// <param name="message">The message to send.</param>
+        /// <param name="sender">
+        ///     The player to exclude from sending the message.
+        ///     Set to null to send a message to everyone.
+        /// </param>
+        public void SendToAllExcept(MessageWriter message, ClientPlayer sender)
+        {
+            foreach (var (_, player) in _players.Where(x => x.Value != sender))
+            {
+                if (player.Client.Connection.State != ConnectionState.Connected)
+                {
+                    Logger.Warning("[{0}] Tried to sent data to a disconnected player ({1}).", sender?.Client.Id, player.Client.Id);
+                    continue;
+                }
+                
+                player.Client.Send(message);
+            }
+        }
+
+        /// <summary>
+        ///     Send a message to a specific player.
+        /// </summary>
+        /// <param name="message">The message to send.</param>
+        /// <param name="playerId"></param>
+        public void SendTo(MessageWriter message, int playerId)
+        {
+            if (_players.TryGetValue(playerId, out var player))
+            {
+                if (player.Client.Connection.State != ConnectionState.Connected)
+                {
+                    Logger.Warning("[{0}] Sending data to {1} failed, player is not connected.", CodeStr, player.Client.Id);
+                    return;
+                }
+                
+                player.Client.Send(message);
+            }
+            else
+            {
+                Logger.Warning("[{0}] Sending data to {1} failed, player does not exist.", CodeStr, playerId);
+            }
+        }
+        
+        private void BroadcastJoinMessage(MessageWriter message, bool clear, ClientPlayer player)
+        {
+            Message01JoinGame.SerializeJoin(message, clear, Code, player.Client.Id, HostId);
+            
+            SendToAllExcept(message, player);
+        }
+    }
+}
\ No newline at end of file
index ec2a2f2a27dbcecccd00a7b7fb4494662a72d2d2..7f165e911d1a53917ebd46ba400e145bd916549c 100644 (file)
@@ -2,9 +2,9 @@
 {
     public enum GameStates : byte
     {
-        NotStarted,
-        Started,
-        Ended,
-        Destroyed
+        NotStarted = 0,
+        Started = 1,
+        Ended = 2,
+        Destroyed = 3
     }
 }
\ No newline at end of file