]> git.deb.at Git - rhonda/impostor.git/commitdiff
Added .NET generic host, configuration and gamelist packets
authorAeonLucid <aeonlucid@gmail.com>
Fri, 25 Sep 2020 23:10:30 +0000 (01:10 +0200)
committerAeonLucid <aeonlucid@gmail.com>
Fri, 25 Sep 2020 23:10:30 +0000 (01:10 +0200)
13 files changed:
src/Impostor.Server/Data/ServerConfig.cs [new file with mode: 0644]
src/Impostor.Server/Impostor.Server.csproj
src/Impostor.Server/Net/Client.cs
src/Impostor.Server/Net/Manager/GameManager.cs
src/Impostor.Server/Net/Matchmaker.cs
src/Impostor.Server/Net/MatchmakerService.cs [new file with mode: 0644]
src/Impostor.Server/Net/Messages/Message00HostGame.cs
src/Impostor.Server/Net/Messages/Message16GetGameListV2.cs [new file with mode: 0644]
src/Impostor.Server/Net/State/ClientPlayer.Events.cs [new file with mode: 0644]
src/Impostor.Server/Net/State/ClientPlayer.cs
src/Impostor.Server/Net/State/Game.cs
src/Impostor.Server/Program.cs
src/Impostor.Server/config.json [new file with mode: 0644]

diff --git a/src/Impostor.Server/Data/ServerConfig.cs b/src/Impostor.Server/Data/ServerConfig.cs
new file mode 100644 (file)
index 0000000..ff7de58
--- /dev/null
@@ -0,0 +1,10 @@
+namespace Impostor.Server.Data
+{
+    public class ServerConfig
+    {
+        public string PublicIp { get; set; }
+        public ushort PublicPort { get; set; }
+        public string ListenIp { get; set; }
+        public ushort ListenPort { get; set; }
+    }
+}
\ No newline at end of file
index bed5d076ec36d1b8255d1af32be6139753613cf8..003e6364c4c30277f2f0eb7b503188230be6fb51 100644 (file)
     </ItemGroup>
 
     <ItemGroup>
+      <PackageReference Include="Microsoft.Extensions.Hosting" Version="3.1.8" />
+      <PackageReference Include="Serilog.Extensions.Hosting" Version="3.1.0" />
       <PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
     </ItemGroup>
 
+    <ItemGroup>
+      <None Update="config.json">
+        <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+      </None>
+    </ItemGroup>
+
 </Project>
index 780234ea1897021fe6cdee057eca0c6ea2a78622..f171a74515fe7accc02e0e1166523f27211c5ccc 100644 (file)
@@ -26,7 +26,7 @@ namespace Impostor.Server.Net
             Connection = connection;
             Connection.DataReceived += OnDataReceived;
             Connection.Disconnected += OnDisconnected;
-            Player = new ClientPlayer(this);
+            Player = new ClientPlayer(this, _gameManager);
         }
 
         public int Id { get; }
@@ -103,7 +103,7 @@ namespace Impostor.Server.Net
                     var gameInfo = Message00HostGame.Deserialize(message);
                     
                     // Create game.
-                    var game = _gameManager.Create(this, gameInfo);
+                    var game = _gameManager.Create(gameInfo);
                     if (game == null)
                     {
                         Player.SendDisconnectReason(DisconnectReason.ServerFull);
@@ -238,6 +238,13 @@ namespace Impostor.Server.Net
                     Player.Game.HandleKickPlayer(playerId, isBan);
                     break;
                 }
+
+                case MessageFlags.GetGameListV2:
+                {
+                    Message16GetGameListV2.Deserialize(message, out var options);
+                    Player.OnRequestGameList(options);
+                    break;
+                }
                 
                 default:
                     Logger.Warning("Server received unknown flag {0}.", flag);
index 0b2b251ae4a0c42cea9bf205edde949bb14cbea5..b9cc9d59340456efee854f1b43682b7c19965189 100644 (file)
@@ -1,33 +1,41 @@
 using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using Impostor.Server.Data;
 using Impostor.Server.Net.State;
 using Impostor.Shared.Innersloth;
-using Serilog;
+using Impostor.Shared.Innersloth.Data;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
 
 namespace Impostor.Server.Net.Manager
 {
     public class GameManager
     {
-        private static readonly ILogger Logger = Log.ForContext<GameManager>();
-        
+        private readonly ILogger<GameManager> _logger;
+        private readonly IPEndPoint _publicIp;
         private readonly ConcurrentDictionary<int, Game> _games;
 
-        public GameManager()
+        public GameManager(ILogger<GameManager> logger, IOptions<ServerConfig> config)
         {
+            _logger = logger;
+            _publicIp = new IPEndPoint(IPAddress.Parse(config.Value.PublicIp), config.Value.PublicPort);
             _games = new ConcurrentDictionary<int, Game>();
         }
         
-        public Game Create(Client owner, GameOptionsData options)
+        public Game Create(GameOptionsData options)
         {
             var gameCode = GameCode.GenerateCode(6);
-            var game = new Game(this, gameCode, options);
+            var game = new Game(this, _publicIp, gameCode, options);
 
             if (_games.TryAdd(gameCode, game))
             {
-                Logger.Debug("Created game with code {0} ({1}).", game.CodeStr, gameCode);        
+                _logger.LogDebug("Created game with code {0} ({1}).", game.CodeStr, gameCode);        
                 return game;
             }
 
-            Logger.Warning("Failed to create game.");
+            _logger.LogWarning("Failed to create game.");
             return null;
         }
 
@@ -37,9 +45,38 @@ namespace Impostor.Server.Net.Manager
             return game;
         }
 
+        public IEnumerable<Game> FindListings(byte mapId, int impostorCount, GameKeywords language, int count = 10)
+        {
+            var results = 0;
+            
+            // Find games that have not started yet.
+            foreach (var (code, game) in _games.Where(x => 
+                x.Value.GameState == GameStates.NotStarted && 
+                x.Value.PlayerCount < 10))
+            {
+                // Check for options.
+                // TODO: Re-enable map filter when GameData packets are done.
+                if (/* game.Options.MapId != mapId || */ 
+                    game.Options.Keywords != language ||
+                    (impostorCount != 0 && game.Options.NumImpostors != impostorCount))
+                {
+                    continue;
+                }
+                
+                // Add to result.
+                yield return game;
+
+                // Break out if we have enough.
+                if (++results == count)
+                {
+                    yield break;
+                }
+            }
+        }
+
         public void Remove(int gameCode)
         {
-            Logger.Debug("Remove game with code {0} ({1}).", GameCode.IntToGameName(gameCode), gameCode);   
+            _logger.LogDebug("Remove game with code {0} ({1}).", GameCode.IntToGameName(gameCode), gameCode);   
             _games.TryRemove(gameCode, out _);
         }
     }
index 5a1bec707ab5695a1aab85b1f07f7874fcf5bf2d..b7bdb3294518efdbe7488855bbdddadc36630621 100644 (file)
@@ -1,33 +1,38 @@
 using System.Net;
 using Hazel;
 using Hazel.Udp;
+using Impostor.Server.Data;
 using Impostor.Server.Net.Manager;
 using Impostor.Server.Net.Messages;
 using Impostor.Shared.Innersloth.Data;
-using Serilog;
-using ILogger = Serilog.ILogger;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
 
 namespace Impostor.Server.Net
 {
     public class Matchmaker
     {
-        private static readonly ILogger Logger = Log.ForContext<Matchmaker>();
-
+        private readonly ILogger<Matchmaker> _logger;
+        private readonly ServerConfig _config;
         private readonly GameManager _gameManager;
         private readonly ClientManager _clientManager;
         private readonly UdpConnectionListener _connection;
         
-        public Matchmaker(IPAddress ip, int port)
+        public Matchmaker(ILogger<Matchmaker> logger, IOptions<ServerConfig> configOptions, GameManager gameManager)
         {
-            _gameManager = new GameManager();
+            _logger = logger;
+            _config = configOptions.Value;
+            _gameManager = gameManager;
             _clientManager = new ClientManager();
-            _connection = new UdpConnectionListener(new IPEndPoint(ip, port), IPMode.IPv4, s =>
+            _connection = new UdpConnectionListener(new IPEndPoint(IPAddress.Parse(_config.ListenIp), _config.ListenPort), IPMode.IPv4, s =>
             {
-                Logger.Warning("Log from Hazel: {0}", s);
+                _logger.LogWarning("Log from Hazel: {0}", s);
             });
             
             _connection.NewConnection += OnNewConnection;
         }
+        
+        public IPEndPoint EndPoint => _connection.EndPoint;
 
         private void OnNewConnection(NewConnectionEventArgs e)
         {
diff --git a/src/Impostor.Server/Net/MatchmakerService.cs b/src/Impostor.Server/Net/MatchmakerService.cs
new file mode 100644 (file)
index 0000000..43fe21e
--- /dev/null
@@ -0,0 +1,37 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+
+namespace Impostor.Server.Net
+{
+    public class MatchmakerService : IHostedService
+    {
+        private readonly ILogger<MatchmakerService> _logger;
+        private readonly Matchmaker _matchmaker;
+
+        public MatchmakerService(ILogger<MatchmakerService> logger, Matchmaker matchmaker)
+        {
+            _logger = logger;
+            _matchmaker = matchmaker;
+        }
+        
+        public Task StartAsync(CancellationToken cancellationToken)
+        {
+            _matchmaker.Start();
+            _logger.LogInformation("Matchmaker is running on {0}:{1}.", 
+                _matchmaker.EndPoint.Address, 
+                _matchmaker.EndPoint.Port);
+            
+            return Task.CompletedTask;
+        }
+
+        public Task StopAsync(CancellationToken cancellationToken)
+        {
+            _logger.LogWarning("Matchmaker is shutting down!");
+            _matchmaker.Stop();
+            
+            return Task.CompletedTask;
+        }
+    }
+}
\ No newline at end of file
index 392bc385914e13dfed979b847f6b047495d8e8a3..60a5c641c8df34b7a5049973364e42b3d6b2b52c 100644 (file)
@@ -1,7 +1,5 @@
-using System.IO;
-using Hazel;
+using Hazel;
 using Impostor.Shared.Innersloth;
-using Impostor.Shared.Innersloth.Data;
 
 namespace Impostor.Server.Net.Messages
 {
@@ -16,45 +14,7 @@ namespace Impostor.Server.Net.Messages
 
         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;
-            }
+            return GameOptionsData.Deserialize(reader.ReadBytesAndSize());
         }
     }
 }
\ No newline at end of file
diff --git a/src/Impostor.Server/Net/Messages/Message16GetGameListV2.cs b/src/Impostor.Server/Net/Messages/Message16GetGameListV2.cs
new file mode 100644 (file)
index 0000000..b8786ec
--- /dev/null
@@ -0,0 +1,48 @@
+using System.Collections.Generic;
+using Hazel;
+using Impostor.Server.Net.State;
+using Impostor.Shared.Innersloth;
+
+namespace Impostor.Server.Net.Messages
+{
+    internal static class Message16GetGameListV2
+    {
+        public static void Deserialize(MessageReader reader, out GameOptionsData options)
+        {
+            reader.ReadPackedInt32(); // Hardcoded 0.
+            options = GameOptionsData.Deserialize(reader.ReadBytesAndSize());
+        }
+
+        public static void Serialize(MessageWriter writer, IEnumerable<Game> games)
+        {
+            writer.StartMessage(MessageFlags.GetGameListV2);
+                
+            // Count
+            writer.StartMessage(1);
+            writer.Write(123); // The Skeld
+            writer.Write(456); // Mira HQ
+            writer.Write(789); // Polus
+            writer.EndMessage();
+                
+            // Listing
+            writer.StartMessage(0);
+            foreach (var game in games)
+            {
+                writer.StartMessage(0);
+                writer.Write(game.PublicIp.Address.GetAddressBytes());
+                writer.Write((ushort) game.PublicIp.Port);
+                writer.Write(game.Code);
+                writer.Write(game.Host.Client.Name);
+                writer.Write(game.PlayerCount);
+                writer.WritePacked(1); // TODO: What does Age do?
+                writer.Write((byte) game.Options.MapId);
+                writer.Write((byte) game.Options.NumImpostors);
+                writer.Write((byte) game.Options.MaxPlayers);
+                writer.EndMessage();
+            }
+            writer.EndMessage();
+                
+            writer.EndMessage();
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Impostor.Server/Net/State/ClientPlayer.Events.cs b/src/Impostor.Server/Net/State/ClientPlayer.Events.cs
new file mode 100644 (file)
index 0000000..87a8028
--- /dev/null
@@ -0,0 +1,28 @@
+using Hazel;
+using Impostor.Server.Net.Messages;
+using Impostor.Shared.Innersloth;
+
+namespace Impostor.Server.Net.State
+{
+    public partial class ClientPlayer
+    {
+        /// <summary>
+        ///     Triggered when the connected client requests the game listing.
+        /// </summary>
+        /// <param name="options">
+        ///     All options given.
+        ///     At this moment, the client can only specify the map, impostor count and chat language.
+        /// </param>
+        public void OnRequestGameList(GameOptionsData options)
+        {
+            using (var message = MessageWriter.Get(SendOption.Reliable))
+            {
+                var games = _gameManager.FindListings(options.MapId, options.NumImpostors, options.Keywords);
+                
+                Message16GetGameListV2.Serialize(message, games);
+                
+                Client.Send(message);
+            }
+        }
+    }
+}
\ No newline at end of file
index 242d6b9c383d12deaf458ede91122edec73cf87e..d9c5c3dba533e67d95ee3232de7a8074ce0cea05 100644 (file)
@@ -1,19 +1,23 @@
 using Hazel;
+using Impostor.Server.Net.Manager;
 using Impostor.Server.Net.Messages;
 using Impostor.Shared.Innersloth.Data;
 
 namespace Impostor.Server.Net.State
 {
-    public class ClientPlayer
+    public partial class ClientPlayer
     {
-        public ClientPlayer(Client client)
+        private readonly GameManager _gameManager;
+
+        public ClientPlayer(Client client, GameManager gameManager)
         {
+            _gameManager = gameManager;
+            
             Client = client;
         }
         
         public Client Client { get; }
         public Game Game { get; set; }
-        public LimboStates LimboState { get; set; }
 
         public void SendDisconnectReason(DisconnectReason reason, string message = null)
         {
index fe043a1da002e58dde45493464b0e565f87d9545..8a60c2f94786e49b9fa89981832bcb22a4691657 100644 (file)
@@ -20,25 +20,30 @@ namespace Impostor.Server.Net.State
         private readonly ConcurrentDictionary<int, ClientPlayer> _players;
         private readonly HashSet<IPAddress> _bannedIps;
 
-        public Game(GameManager gameManager, int code, GameOptionsData options)
+        public Game(GameManager gameManager, IPEndPoint publicIp, int code, GameOptionsData options)
         {
             _gameManager = gameManager;
             _players = new ConcurrentDictionary<int, ClientPlayer>();
             _bannedIps = new HashSet<IPAddress>();
-            
+
+            PublicIp = publicIp;
             Code = code;
             CodeStr = GameCode.IntToGameName(code);
             HostId = -1;
             GameState = GameStates.NotStarted;
             Options = options;
         }
-        
+
+        public IPEndPoint PublicIp { get; }
         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 int PlayerCount => _players.Count;
+        public ClientPlayer Host => _players[HostId];
 
         /// <summary>
         ///     Send a message to all players except one.
index 9dab49113e33c0b42294b1b0e11cec7138c84698..8cf882634f2a093b81ed01edec2b79153ad308a8 100644 (file)
@@ -1,41 +1,65 @@
 using System;
-using System.Net;
-using System.Threading;
+using Impostor.Server.Data;
 using Impostor.Server.Net;
+using Impostor.Server.Net.Manager;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
 using Serilog;
+using Serilog.Events;
 
 namespace Impostor.Server
 {
     internal static class Program
     {
-        private static readonly ManualResetEvent QuitEvent = new ManualResetEvent(false);
-        
-        private static void Main(string[] args)
+        private static int Main(string[] args)
         {
-            // Listen for CTRL+C.
-            Console.CancelKeyPress += (sender, e) =>
-            {
-                e.Cancel = true;
-                QuitEvent.Set();
-            };
-            
-            // Configure logger.
             Log.Logger = new LoggerConfiguration()
 #if DEBUG
                 .MinimumLevel.Verbose()
 #else
                 .MinimumLevel.Information()
+                
 #endif
+                .MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
+                .Enrich.FromLogContext()
                 .WriteTo.Console()
                 .CreateLogger();
 
-            // Initialize matchmaker.
-            var matchMaker = new Matchmaker(IPAddress.Any, 22023);
-            matchMaker.Start();
-            Log.Logger.Information("Matchmaker is running on *:22023.");
-            QuitEvent.WaitOne();
-            Log.Logger.Warning("Matchmaker is shutting down!");
-            matchMaker.Stop();
+            try
+            {
+                Log.Information("Starting Impostor");
+                CreateHostBuilder(args).Build().Run();
+                return 0;
+            }
+            catch (Exception ex)
+            {
+                Log.Fatal(ex, "Impostor terminated unexpectedly");
+                return 1;
+            }
+            finally
+            {
+                Log.CloseAndFlush();
+            }
         }
+        
+        private static IHostBuilder CreateHostBuilder(string[] args) =>
+            Host.CreateDefaultBuilder(args)
+                .ConfigureAppConfiguration(builder =>
+                {
+                    builder.AddJsonFile("config.json", false);
+                    builder.AddEnvironmentVariables(prefix: "IMPOSTOR_");
+                    builder.AddCommandLine(args);
+                })
+                .ConfigureServices((host, services) =>
+                {
+                    services.Configure<ServerConfig>(host.Configuration.GetSection("Server"));
+
+                    services.AddSingleton<GameManager>();
+                    services.AddSingleton<Matchmaker>();
+                    
+                    services.AddHostedService<MatchmakerService>();
+                })
+                .UseSerilog();
     }
 }
\ No newline at end of file
diff --git a/src/Impostor.Server/config.json b/src/Impostor.Server/config.json
new file mode 100644 (file)
index 0000000..f969cbf
--- /dev/null
@@ -0,0 +1,8 @@
+{
+  "Server": {
+    "PublicIp": "127.0.0.1",
+    "PublicPort": 22023,
+    "ListenIp": "0.0.0.0",
+    "ListenPort": 22023
+  }
+}
\ No newline at end of file