]> git.deb.at Git - rhonda/impostor.git/commitdiff
Possible to create / join a game
authorAeonLucid <aeonlucid@gmail.com>
Sun, 20 Sep 2020 04:12:08 +0000 (06:12 +0200)
committerMike <aeonlucid@outlook.com>
Sun, 20 Sep 2020 21:13:57 +0000 (23:13 +0200)
13 files changed:
src/AmongUs.Client/Program.cs
src/AmongUs.Server/Data/DisconnectMessages.cs [new file with mode: 0644]
src/AmongUs.Server/Exceptions/AmongUsException.cs [new file with mode: 0644]
src/AmongUs.Server/Net/Client.cs
src/AmongUs.Server/Net/ClientManager.cs
src/AmongUs.Server/Net/ClientPlayer.cs [new file with mode: 0644]
src/AmongUs.Server/Net/ClientState.cs [deleted file]
src/AmongUs.Server/Net/Game.cs
src/AmongUs.Server/Net/GameManager.cs
src/AmongUs.Server/Net/GameStates.cs [new file with mode: 0644]
src/AmongUs.Server/Net/Matchmaker.cs
src/AmongUs.Server/Net/Response/Message1DisconnectReason.cs
src/AmongUs.Shared/Innersloth/Data/LimboStates.cs [new file with mode: 0644]

index 5a4709d9c3b11e01fdf991d1db0fe078e2506bc3..a6436092e6d2209079234e872ac99fa211ff33b2 100644 (file)
@@ -10,9 +10,9 @@ namespace AmongUs.Client
         {
             var appData = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "..\\LocalLow");
             var regionFile = Path.Combine(appData, "Innersloth", "Among Us", "regionInfo.dat");
-            var region = new RegionInfo("Private", "127.0.0.1", new []
+            var region = new RegionInfo("Private", "192.168.1.211", new []
             {
-                new ServerInfo("Private-Master-1", "127.0.0.1", 22023)
+                new ServerInfo("Private-Master-1", "192.168.1.211", 22023)
             });
             
             using (var file = File.Open(regionFile, FileMode.Create, FileAccess.Write))
diff --git a/src/AmongUs.Server/Data/DisconnectMessages.cs b/src/AmongUs.Server/Data/DisconnectMessages.cs
new file mode 100644 (file)
index 0000000..4f95954
--- /dev/null
@@ -0,0 +1,9 @@
+namespace AmongUs.Server.Data
+{
+    public static class DisconnectMessages
+    {
+        public const string Error = "There was an internal server error. " +
+                                    "Check the server console for more information. " +
+                                    "Please report the issue on the AmongUsServer GitHub if it keeps happening.";
+    }
+}
\ No newline at end of file
diff --git a/src/AmongUs.Server/Exceptions/AmongUsException.cs b/src/AmongUs.Server/Exceptions/AmongUsException.cs
new file mode 100644 (file)
index 0000000..1009b87
--- /dev/null
@@ -0,0 +1,24 @@
+using System;
+using System.Runtime.Serialization;
+
+namespace AmongUs.Server.Exceptions
+{
+    public class AmongUsException : Exception
+    {
+        public AmongUsException()
+        {
+        }
+
+        protected AmongUsException(SerializationInfo info, StreamingContext context) : base(info, context)
+        {
+        }
+
+        public AmongUsException(string? message) : base(message)
+        {
+        }
+
+        public AmongUsException(string? message, Exception? innerException) : base(message, innerException)
+        {
+        }
+    }
+}
\ No newline at end of file
index ee23ebbc115121397702846ca93256c526d06868..0ce555169640893834559519a8a9761a0026092f 100644 (file)
@@ -1,5 +1,6 @@
 using System;
-using System.Text.Json;
+using AmongUs.Server.Data;
+using AmongUs.Server.Exceptions;
 using AmongUs.Server.Extensions;
 using AmongUs.Server.Net.Request;
 using AmongUs.Server.Net.Response;
@@ -17,46 +18,55 @@ namespace AmongUs.Server.Net
         
         private readonly ClientManager _clientManager;
         private readonly GameManager _gameManager;
-        private readonly Connection _connection;
-        private readonly ClientState _state;
-        private readonly int _version;
-        private readonly string _name;
 
-        public Client(ClientManager clientManager, GameManager gameManager, Connection connection, int version, string name)
+        public Client(ClientManager clientManager, GameManager gameManager, int id, string name, Connection connection)
         {
             _clientManager = clientManager;
             _gameManager = gameManager;
-            _connection = connection;
-            _connection.DataReceived += OnDataReceived;
-            _connection.Disconnected += OnDisconnected;
-            _state = new ClientState(_connection);
-            _version = version;
-            _name = name;
+            Id = id;
+            Name = name;
+            Connection = connection;
+            Connection.DataReceived += OnDataReceived;
+            Connection.Disconnected += OnDisconnected;
+            Player = new ClientPlayer(this);
         }
 
+        public int Id { get; }
+        public string Name { get; }
+        public Connection Connection { get; }
+        public ClientPlayer Player { get; }
+
         private void OnDataReceived(DataReceivedEventArgs e)
         {
-            while (true)
+            try
             {
-                if (e.Message.Position >= e.Message.Length)
+                while (true)
                 {
-                    break;
+                    if (e.Message.Position >= e.Message.Length)
+                    {
+                        break;
+                    }
+
+                    OnMessageReceived(e.Message.ReadMessage(), e.SendOption);
                 }
-                
-                OnMessageReceived(e.Message.ReadMessage());
+            }
+            catch (Exception ex)
+            {
+                Logger.Error(ex, "Exception caught in client data handler.");
+                Connection.Send(new Message1DisconnectReason(DisconnectReason.Custom, DisconnectMessages.Error));
             }
         }
 
-        private void OnMessageReceived(MessageReader message)
+        private void OnMessageReceived(MessageReader message, SendOption sendOption)
         {
             var flag = (RequestFlag) message.Tag;
             
+            Logger.Verbose("[{0}] Server got {1}.", Id, flag);
+            
             switch (flag)
             {
-                // 101A3813
                 case RequestFlag.HostGame:
-                    Logger.Debug("Server got host game");
-                    
+                {
                     // Read game settings.
                     var gameInfoBytes = message.ReadBytesAndSize();
                     var gameInfo = GameOptionsData.Deserialize(gameInfoBytes);
@@ -65,69 +75,98 @@ namespace AmongUs.Server.Net
                     var game = _gameManager.Create(this, gameInfo);
                     if (game == null)
                     {
-                        _connection.Send(new Message1DisconnectReason(DisconnectReason.ServerFull));
+                        Connection.Send(new Message1DisconnectReason(DisconnectReason.ServerFull));
                         return;
                     }
 
-                    // Code (32) in the packet below will be used in JoinGame.
+                    // Code in the packet below will be used in JoinGame.
                     using (var writer = MessageWriter.Get(SendOption.Reliable))
                     {
                         writer.StartMessage(0);
-                        writer.Write(32);
+                        writer.Write(game.Code);
                         writer.EndMessage();
                 
-                        _connection.Send(writer);
+                        Connection.Send(writer);
                     }
                     break;
-                // 101A388C
+                }
+                
                 case RequestFlag.JoinGame:
-                    Logger.Debug("Server got join game");
-
+                {
                     var gameCode = message.ReadInt32();
-                    if (gameCode != 32)
+                    var unknown = message.ReadByte();
+                    var game = _gameManager.Find(gameCode);
+                    if (game == null)
                     {
-                        Logger.Debug("- Code {0}", gameCode, GameCode.IntToGameName(gameCode));
-                        
-                        _connection.Send(new Message1DisconnectReason(DisconnectReason.GameMissing));
+                        Connection.Send(new Message1DisconnectReason(DisconnectReason.GameMissing));
                         return;
                     }
-                    
-                    // TODO: JoinGame
-                    Logger.Debug("JoinGame {0} {1}", gameCode, message.ReadByte());
+
+                    game.HandleJoinGame(Player);
                     break;
+                }
+                
                 // 101A3960
                 case RequestFlag.StartGame:
-                    Logger.Debug("Server got StartGame");
                     break;
+                
                 // 101A39EC
                 case RequestFlag.RemoveGame:
-                    Logger.Debug("Server got RemoveGame");
                     break;
+                
                 case RequestFlag.RemovePlayer:
-                    Logger.Debug("Server got RemovePlayer");
                     break;
-                // 101A3A15
+                
                 case RequestFlag.GameData:
-                    Logger.Debug("Server got GameData");
-                    break;
-                // 101A3AAB
                 case RequestFlag.GameDataTo:
-                    Logger.Debug("Server got GameDataTo");
+                {
+                    var game = Player.Game;
+                    if (game == null)
+                    {
+                        throw new NullReferenceException("Game was not set for the client.");
+                    }
+                    
+                    var code = message.ReadInt32();
+                    if (code != game.Code)
+                    {
+                        // Packet was meant for another game.
+                        return;
+                    }
+
+                    // Broadcast packet to all other players.
+                    using (var writer = MessageWriter.Get(sendOption))
+                    {
+                        if (flag == RequestFlag.GameDataTo)
+                        {
+                            var target = message.ReadPackedInt32();
+                            writer.CopyFrom(message);
+                            game.SendTo(writer, target);
+                        }
+                        else
+                        {
+                            writer.CopyFrom(message);
+                            game.SendToAllExcept(writer, Player);
+                        }
+                    }
                     break;
+                }
+                
                 // 101A3BA6
                 case RequestFlag.JoinedGame:
-                    Logger.Debug("Server got JoinedGame");
                     break;
+                
                 // 101A3BD0
                 case RequestFlag.EndGame:
-                    Logger.Debug("Server got EndGame");
                     break;
+                
                 default:
-                    Logger.Debug("Server received unknown {0}", flag);
+                    Logger.Warning("Server received unknown flag {0}.", flag);
                     break;
             }
 
-            if (message.Position < message.Length)
+            if (flag != RequestFlag.GameData &&
+                flag != RequestFlag.GameDataTo &&
+                message.Position < message.Length)
             {
                 Logger.Warning("Server did not consume all bytes from {0} ({1} < {2}).", 
                     flag, 
index a47c183ffcc1530422b72269788bc525edf7a1a7..9a3cb7b51328d9414bdc24fdac264e0357254093 100644 (file)
@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System.Collections.Concurrent;
+using AmongUs.Server.Exceptions;
 using Serilog;
 
 namespace AmongUs.Server.Net
@@ -7,25 +8,57 @@ namespace AmongUs.Server.Net
     {
         private static readonly ILogger Logger = Log.ForContext<ClientManager>();
         
-        private readonly HashSet<Client> _clients;
+        private readonly ConcurrentDictionary<int, Client> _clients;
+        private readonly object _idLock;
+        private int _idLast;
         
         public ClientManager()
         {
-            _clients = new HashSet<Client>();
+            _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.Add(client);
+
+            _clients.TryAdd(client.Id, client);
         }
 
         public void Remove(Client client)
         {
             Logger.Information("Client disconnected.");
-            
-            _clients.Remove(client);
+
+            _clients.TryRemove(client.Id, out _);
         }
     }
 }
\ No newline at end of file
diff --git a/src/AmongUs.Server/Net/ClientPlayer.cs b/src/AmongUs.Server/Net/ClientPlayer.cs
new file mode 100644 (file)
index 0000000..1b9322c
--- /dev/null
@@ -0,0 +1,16 @@
+using AmongUs.Shared.Innersloth.Data;
+
+namespace AmongUs.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/AmongUs.Server/Net/ClientState.cs b/src/AmongUs.Server/Net/ClientState.cs
deleted file mode 100644 (file)
index ca93160..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-using Hazel;
-
-namespace AmongUs.Server.Net
-{
-    public class ClientState
-    {
-        private readonly Connection _connection;
-
-        public ClientState(Connection connection)
-        {
-            _connection = connection;
-        }
-    }
-}
\ No newline at end of file
index ee8796ce7d9c878b778af0acd1427f02250b8a6f..6fb35ef5e4f16240aac619d4de63db08ef39fb33 100644 (file)
-using AmongUs.Shared.Innersloth;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using AmongUs.Server.Exceptions;
+using AmongUs.Server.Extensions;
+using AmongUs.Server.Net.Response;
+using AmongUs.Shared.Innersloth;
+using AmongUs.Shared.Innersloth.Data;
+using Hazel;
+using Serilog;
+using ILogger = Serilog.ILogger;
 
 namespace AmongUs.Server.Net
 {
     public class Game
     {
+        private static readonly ILogger Logger = Log.ForContext<Game>();
+        
+        private readonly ConcurrentDictionary<int, ClientPlayer> _players;
+        private int _hostId;
+        
         public Game(int code, GameOptionsData options)
         {
             Code = code;
+            CodeStr = GameCode.IntToGameName(code);
+            GameState = GameStates.NotStarted;
             Options = options;
+
+            _hostId = -1;
+            _players = new ConcurrentDictionary<int, ClientPlayer>();
         }
         
         public int Code { get; }
+        public string CodeStr { get; }
+        public GameStates GameState { get; }
         public GameOptionsData Options { get; }
+
+        public void SendToAllExcept(MessageWriter message, ClientPlayer sender)
+        {
+            foreach (var (_, player) in _players.Where(x => x.Value != sender))
+            {
+                player.Client.Connection.Send(message);
+            }
+        }
+
+        public void SendTo(MessageWriter message, int playerId)
+        {
+            if (_players.TryGetValue(playerId, out var player))
+            {
+                player.Client.Connection.Send(message);
+            }
+            else
+            {
+                Logger.Warning("[{0}] Sending data to {1} failed, player does not exist.", CodeStr, playerId);
+            }
+        }
+
+        public void HandleJoinGame(ClientPlayer player)
+        {
+            switch (GameState)
+            {
+                case GameStates.NotStarted:
+                    HandleJoinGameNew(player);
+                    break;
+                case GameStates.Started:
+                    HandleJoinGameNext(player);
+                    break;
+                case GameStates.Ended:
+                case GameStates.Destroyed:
+                    player.Client.Connection.Send(new Message1DisconnectReason(DisconnectReason.GameStarted));
+                    return;
+                default:
+                    throw new ArgumentOutOfRangeException();
+            }
+        }
+
+        private void HandleJoinGameNew(ClientPlayer player)
+        {
+            Logger.Verbose("[{0}] Player joined.", CodeStr);
+            
+            // Store player.
+            if (!_players.TryAdd(player.Client.Id, player))
+            {
+                throw new AmongUsException("Failed to add player to game.");
+            }
+            
+            // Assign player to this game for future packets.
+            player.Game = this;
+
+            // Assign hostId if none is set.
+            if (_hostId == -1)
+            {
+                _hostId = player.Client.Id;
+            }
+
+            if (_hostId == player.Client.Id)
+            {
+                player.LimboState = LimboStates.NotLimbo;
+            }
+
+            using (var message = MessageWriter.Get(SendOption.Reliable))
+            {
+                // TODO: WriteJoinedMessage - Move to own method / class
+                message.StartMessage(7);
+                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();
+
+                message.StartMessage(10);
+                message.Write(Code);
+                message.Write((sbyte)1);
+                message.Write(false); // Private / Public
+                message.EndMessage();
+                
+                player.Client.Connection.Send(message);
+            
+                // TODO: BroadcastJoinMessage - Move to own method / class
+                message.Clear(SendOption.Reliable);
+                message.StartMessage(1);
+                message.Write(Code);
+                message.Write(player.Client.Id);
+                message.Write(_hostId);
+                message.EndMessage();
+                
+                SendToAllExcept(message, player);
+            }
+        }
+
+        private void HandleJoinGameNext(ClientPlayer player)
+        {
+            throw new NotImplementedException();
+        }
     }
 }
\ No newline at end of file
index 02e476bb328abd31473eb7e418ca54f81b959059..e6fe541bb8051557c727c0bd2c0feb38829d4b5a 100644 (file)
@@ -28,9 +28,15 @@ namespace AmongUs.Server.Net
             return null;
         }
 
-        public void Remove(int key)
+        public Game Find(int gameCode)
         {
-            _games.TryRemove(key, out _);
+            _games.TryGetValue(gameCode, out var game);
+            return game;
+        }
+
+        public void Remove(int gameCode)
+        {
+            _games.TryRemove(gameCode, out _);
         }
     }
 }
\ No newline at end of file
diff --git a/src/AmongUs.Server/Net/GameStates.cs b/src/AmongUs.Server/Net/GameStates.cs
new file mode 100644 (file)
index 0000000..a50e9e3
--- /dev/null
@@ -0,0 +1,10 @@
+namespace AmongUs.Server.Net
+{
+    public enum GameStates : byte
+    {
+        NotStarted = 0,
+        Started = 1,
+        Ended = 2,
+        Destroyed = 3
+    }
+}
\ No newline at end of file
index d9ceee05aad1a535b9da3302526305440b8d038b..5c2dc7789ea5809b1ddbc33a020adc1455d6617a 100644 (file)
@@ -44,7 +44,7 @@ namespace AmongUs.Server.Net
             }
             
             // Register client.
-            _clientManager.Add(new Client(_clientManager, _gameManager, e.Connection, clientVersion, clientName));
+            _clientManager.Add(new Client(_clientManager, _gameManager, _clientManager.NextId(), clientName, e.Connection));
         }
 
         public void Start()
index 878f579d89b1e32cba687306ee4d8903af50011b..86362aa12f37c03c09a6ee193698ed99fc9701b2 100644 (file)
@@ -6,18 +6,25 @@ namespace AmongUs.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) : base(SendOption.Reliable, MessageFlag.DisconnectReason)
+        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/AmongUs.Shared/Innersloth/Data/LimboStates.cs b/src/AmongUs.Shared/Innersloth/Data/LimboStates.cs
new file mode 100644 (file)
index 0000000..e81e4da
--- /dev/null
@@ -0,0 +1,9 @@
+namespace AmongUs.Shared.Innersloth.Data
+{
+    public enum LimboStates
+    {
+        PreSpawn,
+        NotLimbo,
+        WaitingForHost,
+    }
+}
\ No newline at end of file