]> git.deb.at Git - rhonda/impostor.git/commitdiff
Plugin loader (#59)
authorGerard Smit <gsmit1996@gmail.com>
Mon, 19 Oct 2020 09:31:25 +0000 (11:31 +0200)
committerGitHub <noreply@github.com>
Mon, 19 Oct 2020 09:31:25 +0000 (11:31 +0200)
* Parse GameData headers

* Added GameData spawn

* GameData playerinfo parsing

* Added BufferMessageReader tests

* Fix InnerGameData

* Added CustomNetworkTransform and VoteBanSystem

* Revert changes

* Finish CustomNetworkTransform

* Implemented a simple plugin loader

* Replaced Reference with PackageReference

Note to myself: don't trust Rider auto-add reference

* Use classes and moved all API-related stuff to one folder

* Added PlayerJoinedGameEvent and PlayerLeftGameEvent

* Removed Impostor.Server.Hazel

* Moved more API-specific stuff

Co-authored-by: AeonLucid <aeonlucid@gmail.com>
76 files changed:
src/Impostor.Plugins.Debugger/App.razor [new file with mode: 0644]
src/Impostor.Plugins.Debugger/DebugPlugin.cs [new file with mode: 0644]
src/Impostor.Plugins.Debugger/Impostor.Plugins.Debugger.csproj [new file with mode: 0644]
src/Impostor.Plugins.Debugger/Pages/Index.razor [new file with mode: 0644]
src/Impostor.Plugins.Debugger/Pages/_Host.cshtml [new file with mode: 0644]
src/Impostor.Plugins.Debugger/Shared/MainLayout.razor [new file with mode: 0644]
src/Impostor.Plugins.Debugger/_Imports.razor [new file with mode: 0644]
src/Impostor.Server.Api/Events/Attributes/EventListenerAttribute.cs
src/Impostor.Server.Api/Events/Game/GameDestroyedEvent.cs [new file with mode: 0644]
src/Impostor.Server.Api/Events/Game/PlayerJoinedGameEvent.cs [new file with mode: 0644]
src/Impostor.Server.Api/Events/Game/PlayerLeftGameEvent.cs [new file with mode: 0644]
src/Impostor.Server.Api/Events/Managers/IEventManager.cs
src/Impostor.Server.Api/Extensions/GameManagerExtensions.cs [deleted file]
src/Impostor.Server.Api/Games/Extensions/GameManagerExtensions.cs [new file with mode: 0644]
src/Impostor.Server.Api/Games/IGame.cs
src/Impostor.Server.Api/Games/Managers/IGameManager.cs
src/Impostor.Server.Api/Impostor.Server.Api.csproj
src/Impostor.Server.Api/Impostor.Server.Api.csproj.DotSettings
src/Impostor.Server.Api/Net/Factories/IClientFactory.cs [deleted file]
src/Impostor.Server.Api/Net/IClient.cs
src/Impostor.Server.Api/Net/IConnection.cs
src/Impostor.Server.Api/Net/Manager/IClientManager.cs
src/Impostor.Server.Api/Net/Manager/IMatchmaker.cs [deleted file]
src/Impostor.Server.Api/Plugins/IPlugin.cs
src/Impostor.Server.Api/Plugins/PluginBase.cs
src/Impostor.Server.Hazel/Extensions/ServiceExtensions.cs [deleted file]
src/Impostor.Server.Hazel/HazelConnection.cs [deleted file]
src/Impostor.Server.Hazel/HazelMatchmaker.cs [deleted file]
src/Impostor.Server.Hazel/Impostor.Server.Hazel.csproj [deleted file]
src/Impostor.Server.Hazel/Impostor.Server.Hazel.csproj.DotSettings [deleted file]
src/Impostor.Server.Hazel/Messages/BufferMessageReader.cs [deleted file]
src/Impostor.Server.Hazel/Messages/HazelConnectionMessageWriter.cs [deleted file]
src/Impostor.Server.Hazel/Messages/HazelGameMessageWriter.cs [deleted file]
src/Impostor.Server.Hazel/Messages/HazelMessage.cs [deleted file]
src/Impostor.Server.Hazel/Messages/HazelMessageWriter.cs [deleted file]
src/Impostor.Server/Api/ClientBase.Api.cs [new file with mode: 0644]
src/Impostor.Server/Api/ClientManager.Api.cs [new file with mode: 0644]
src/Impostor.Server/Api/ClientPlayer.Api.cs [new file with mode: 0644]
src/Impostor.Server/Api/Game.Api.cs [new file with mode: 0644]
src/Impostor.Server/Api/GameManager.Api.cs [new file with mode: 0644]
src/Impostor.Server/Api/HazelConnection.Api.cs [new file with mode: 0644]
src/Impostor.Server/Events/EventManager.cs
src/Impostor.Server/Events/MultiDisposable.cs [new file with mode: 0644]
src/Impostor.Server/Events/RegisteredEventListener.cs
src/Impostor.Server/Events/TemporaryEventRegister.cs [new file with mode: 0644]
src/Impostor.Server/Impostor.Server.csproj
src/Impostor.Server/Net/Client.cs
src/Impostor.Server/Net/ClientBase.cs
src/Impostor.Server/Net/Factories/ClientFactory.cs
src/Impostor.Server/Net/Factories/IClientFactory.cs [new file with mode: 0644]
src/Impostor.Server/Net/Hazel/HazelConnection.cs [new file with mode: 0644]
src/Impostor.Server/Net/Hazel/Messages/BufferMessageReader.cs [new file with mode: 0644]
src/Impostor.Server/Net/Hazel/Messages/HazelConnectionMessageWriter.cs [new file with mode: 0644]
src/Impostor.Server/Net/Hazel/Messages/HazelGameMessageWriter.cs [new file with mode: 0644]
src/Impostor.Server/Net/Hazel/Messages/HazelMessage.cs [new file with mode: 0644]
src/Impostor.Server/Net/Hazel/Messages/HazelMessageWriter.cs [new file with mode: 0644]
src/Impostor.Server/Net/Manager/ClientManager.cs
src/Impostor.Server/Net/Manager/GameManager.cs
src/Impostor.Server/Net/Matchmaker.cs [new file with mode: 0644]
src/Impostor.Server/Net/MatchmakerService.cs
src/Impostor.Server/Net/Redirector/ClientRedirector.cs
src/Impostor.Server/Net/State/ClientPlayer.cs
src/Impostor.Server/Net/State/Game.Data.cs
src/Impostor.Server/Net/State/Game.Incoming.cs
src/Impostor.Server/Net/State/Game.State.cs
src/Impostor.Server/Net/State/Game.cs
src/Impostor.Server/Plugins/AssemblyInformation.cs [new file with mode: 0644]
src/Impostor.Server/Plugins/IAssemblyInformation.cs [new file with mode: 0644]
src/Impostor.Server/Plugins/LoadedAssemblyInformation.cs [new file with mode: 0644]
src/Impostor.Server/Plugins/PluginConfig.cs [new file with mode: 0644]
src/Impostor.Server/Plugins/PluginLoader.cs [new file with mode: 0644]
src/Impostor.Server/Program.cs
src/Impostor.Server/Properties/AssemblyInfo.cs [new file with mode: 0644]
src/Impostor.Tests/Hazel/BufferMessageReaderTests.cs
src/Impostor.Tests/Impostor.Tests.csproj
src/Impostor.sln

diff --git a/src/Impostor.Plugins.Debugger/App.razor b/src/Impostor.Plugins.Debugger/App.razor
new file mode 100644 (file)
index 0000000..38e8633
--- /dev/null
@@ -0,0 +1,10 @@
+<Router AppAssembly="@typeof(DebugPlugin).Assembly">
+    <Found Context="routeData">
+        <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"/>
+    </Found>
+    <NotFound>
+        <LayoutView Layout="@typeof(MainLayout)">
+            <p>Sorry, there's nothing at this address.</p>
+        </LayoutView>
+    </NotFound>
+</Router>
\ No newline at end of file
diff --git a/src/Impostor.Plugins.Debugger/DebugPlugin.cs b/src/Impostor.Plugins.Debugger/DebugPlugin.cs
new file mode 100644 (file)
index 0000000..de18d73
--- /dev/null
@@ -0,0 +1,36 @@
+using System;
+using Impostor.Server.Plugins;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+
+namespace Impostor.Plugins.Debugger
+{
+    public class DebugPlugin : PluginBase
+    {
+        public override void ConfigureServices(IServiceCollection services)
+        {
+            services.AddRazorPages();
+            services.AddServerSideBlazor();
+        }
+
+        public override void ConfigureHost(IHostBuilder host)
+        {
+            host.ConfigureWebHostDefaults(webBuilder =>
+            {
+                webBuilder.Configure(app =>
+                {
+                    app.UseStaticFiles();
+                    app.UseRouting();
+
+                    app.UseEndpoints(endpoints =>
+                    {
+                        endpoints.MapBlazorHub();
+                        endpoints.MapFallbackToPage("/_Host");
+                    });
+                });
+            });
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Impostor.Plugins.Debugger/Impostor.Plugins.Debugger.csproj b/src/Impostor.Plugins.Debugger/Impostor.Plugins.Debugger.csproj
new file mode 100644 (file)
index 0000000..1852d26
--- /dev/null
@@ -0,0 +1,12 @@
+<Project Sdk="Microsoft.NET.Sdk.Web">
+
+    <PropertyGroup>
+        <TargetFramework>net5.0</TargetFramework>
+        <OutputType>Library</OutputType>
+    </PropertyGroup>
+
+    <ItemGroup>
+      <ProjectReference Include="..\Impostor.Server.Api\Impostor.Server.Api.csproj" />
+    </ItemGroup>
+
+</Project>
\ No newline at end of file
diff --git a/src/Impostor.Plugins.Debugger/Pages/Index.razor b/src/Impostor.Plugins.Debugger/Pages/Index.razor
new file mode 100644 (file)
index 0000000..7ca76ed
--- /dev/null
@@ -0,0 +1,68 @@
+@page "/"
+@using Impostor.Server.Events
+@using Impostor.Server.Events.Managers
+@using Impostor.Server.Games.Managers
+@implements IDisposable
+@implements IEventListener
+@inject IEventManager EventManager
+@inject IGameManager GameManager
+
+<div class="container">
+    <h2>Games</h2>
+    @if (GameManager.Games.Any())
+    {
+        <table class="table table-striped">
+            <thead>
+            <tr>
+                <th>Code</th>
+                <th>Players</th>
+            </tr>
+            </thead>
+            <tbody>
+            @foreach (var game in GameManager.Games)
+            {
+                <tr>
+                    <td>@game.Code</td>
+                    <td>
+                        <ul class="mb-0">
+                            @foreach (var player in game.Players)
+                            {
+                                <li>@player.Client.Name</li>
+                            }
+                        </ul>
+                    </td>
+                </tr>
+            }
+            </tbody>
+        </table>
+    }
+    else
+    {
+        <div class="text-center">
+            <i class="text-muted">There are no active games.</i>
+        </div>
+    }
+</div>
+
+@code {
+    private IDisposable _disposable;
+
+    [EventListener(typeof(GameCreatedEvent))]
+    [EventListener(typeof(GameDestroyedEvent))]
+    [EventListener(typeof(PlayerJoinedGameEvent))]
+    [EventListener(typeof(PlayerLeftGameEvent))]
+    public void OnGameCreated(IGameEvent e)
+    {
+        StateHasChanged();
+    }
+
+    protected override void OnInitialized()
+    {
+        _disposable = EventManager.RegisterListener(this, InvokeAsync);
+    }
+
+    public void Dispose()
+    {
+        _disposable?.Dispose();
+    }
+}
\ No newline at end of file
diff --git a/src/Impostor.Plugins.Debugger/Pages/_Host.cshtml b/src/Impostor.Plugins.Debugger/Pages/_Host.cshtml
new file mode 100644 (file)
index 0000000..eed3aaf
--- /dev/null
@@ -0,0 +1,19 @@
+@page "/"
+@namespace Impostor.Plugins.Debugger.Pages
+@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
+
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="utf-8"/>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
+    <title>Impostor Debugger</title>
+    <base href="~/"/>
+    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">
+</head>
+<body>
+<component type="typeof(App)" render-mode="Server"/>
+
+<script src="_framework/blazor.server.js"></script>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/Impostor.Plugins.Debugger/Shared/MainLayout.razor b/src/Impostor.Plugins.Debugger/Shared/MainLayout.razor
new file mode 100644 (file)
index 0000000..07da9d6
--- /dev/null
@@ -0,0 +1,5 @@
+@inherits LayoutComponentBase
+
+<div class="page">
+    @Body
+</div>
\ No newline at end of file
diff --git a/src/Impostor.Plugins.Debugger/_Imports.razor b/src/Impostor.Plugins.Debugger/_Imports.razor
new file mode 100644 (file)
index 0000000..109ec4e
--- /dev/null
@@ -0,0 +1,8 @@
+@using System.Net.Http
+@using Microsoft.AspNetCore.Authorization
+@using Microsoft.AspNetCore.Components.Authorization
+@using Microsoft.AspNetCore.Components.Forms
+@using Microsoft.AspNetCore.Components.Routing
+@using Microsoft.AspNetCore.Components.Web
+@using Microsoft.JSInterop
+@using Impostor.Plugins.Debugger.Shared
\ No newline at end of file
index bea45c1660c3ece364a2155d791ded9a9221316b..c8b9360dd437c3622361aadf506120dea4d48d64 100644 (file)
@@ -2,19 +2,18 @@
 
 namespace Impostor.Server.Events
 {
-    [AttributeUsage(AttributeTargets.Method)]
+    [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
     public class EventListenerAttribute : Attribute
     {
         public EventListenerAttribute(EventPriority priority = EventPriority.Normal)
         {
             Priority = priority;
-            Events = new Type[0];
         }
 
         public EventListenerAttribute(Type @event, EventPriority priority = EventPriority.Normal)
         {
             Priority = priority;
-            Events = new[] { @event };
+            Event = @event;
         }
 
         /// <summary>
@@ -25,7 +24,7 @@ namespace Impostor.Server.Events
         /// <summary>
         ///     The events that the listener is listening to.
         /// </summary>
-        public Type[] Events { get; set; }
+        public Type? Event { get; set; }
 
         /// <summary>
         ///     If set to true, the listener will be called regardless of the <see cref="IEventCancelable.IsCancelled"/>.
diff --git a/src/Impostor.Server.Api/Events/Game/GameDestroyedEvent.cs b/src/Impostor.Server.Api/Events/Game/GameDestroyedEvent.cs
new file mode 100644 (file)
index 0000000..5d32143
--- /dev/null
@@ -0,0 +1,22 @@
+using Impostor.Server.Games;
+
+namespace Impostor.Server.Events
+{
+    /// <summary>
+    ///     Called whenever a new <see cref="IGame"/> is destroyed.
+    /// </summary>
+    public sealed class GameDestroyedEvent : IGameEvent
+    {
+        /// <summary>
+        ///     Initializes a new instance of the <see cref="GameDestroyedEvent"/> class.
+        /// </summary>
+        /// <param name="game">Instance of the game.</param>
+        public GameDestroyedEvent(IGame game)
+        {
+            Game = game;
+        }
+
+        /// <inheritdoc/>
+        public IGame Game { get; }
+    }
+}
\ No newline at end of file
diff --git a/src/Impostor.Server.Api/Events/Game/PlayerJoinedGameEvent.cs b/src/Impostor.Server.Api/Events/Game/PlayerJoinedGameEvent.cs
new file mode 100644 (file)
index 0000000..0a47f64
--- /dev/null
@@ -0,0 +1,18 @@
+using Impostor.Server.Games;
+using Impostor.Server.Net;
+
+namespace Impostor.Server.Events
+{
+    public class PlayerJoinedGameEvent : IGameEvent
+    {
+        public PlayerJoinedGameEvent(IGame game, IClientPlayer player)
+        {
+            Game = game;
+            Player = player;
+        }
+
+        public IGame Game { get; }
+
+        public IClientPlayer Player { get; }
+    }
+}
\ No newline at end of file
diff --git a/src/Impostor.Server.Api/Events/Game/PlayerLeftGameEvent.cs b/src/Impostor.Server.Api/Events/Game/PlayerLeftGameEvent.cs
new file mode 100644 (file)
index 0000000..e17c333
--- /dev/null
@@ -0,0 +1,21 @@
+using Impostor.Server.Games;
+using Impostor.Server.Net;
+
+namespace Impostor.Server.Events
+{
+    public class PlayerLeftGameEvent : IGameEvent
+    {
+        public PlayerLeftGameEvent(IGame game, IClientPlayer player, bool isBan)
+        {
+            Game = game;
+            Player = player;
+            IsBan = isBan;
+        }
+
+        public IGame Game { get; }
+
+        public IClientPlayer Player { get; }
+
+        public bool IsBan { get; }
+    }
+}
\ No newline at end of file
index 488e38ccadabfdd0b0e84534107c835497c1bc56..11ae01d61d2173680fd3dd37ade64a27450bccd2 100644 (file)
@@ -1,9 +1,29 @@
-using System.Threading.Tasks;
+using System;
+using System.Threading.Tasks;
 
 namespace Impostor.Server.Events.Managers
 {
     public interface IEventManager
     {
+        /// <summary>
+        ///     Register a temporary event listener.
+        /// </summary>
+        /// <param name="callback">Event callback.</param>
+        /// <returns>Disposable that unregisters the callback from the event manager.</returns>
+        /// <typeparam name="TEvent">Type of the event.</typeparam>
+        IDisposable Register<TEvent>(Func<IServiceProvider, TEvent, ValueTask> callback)
+            where TEvent : IEvent;
+
+        /// <summary>
+        ///     Register a temporary event listener.
+        /// </summary>
+        /// <param name="listener">Event listener.</param>
+        /// <param name="invoker">Middleware between the events, which can be used to swap to the correct thread dispatcher.</param>
+        /// <returns>Disposable that unregisters the callback from the event manager.</returns>
+        /// <typeparam name="TListener">Type of the event listener.</typeparam>
+        IDisposable RegisterListener<TListener>(TListener listener, Func<Func<Task>, Task>? invoker = null)
+            where TListener : IEventListener;
+
         /// <summary>
         ///     Returns true if an event with the type <see cref="TEvent"/> is registered.
         /// </summary>
diff --git a/src/Impostor.Server.Api/Extensions/GameManagerExtensions.cs b/src/Impostor.Server.Api/Extensions/GameManagerExtensions.cs
deleted file mode 100644 (file)
index df89168..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-using System.Linq;
-using Impostor.Server.Games.Managers;
-using Impostor.Shared.Innersloth.Data;
-
-namespace Impostor.Server
-{
-    public static class GameManagerExtensions
-    {
-        public static int GetGameCount(this IGameManager manager, MapFlags map)
-        {
-            return manager.Games.Count(game => map.HasFlag((MapFlags)(1 << game.Options.MapId)));
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/Impostor.Server.Api/Games/Extensions/GameManagerExtensions.cs b/src/Impostor.Server.Api/Games/Extensions/GameManagerExtensions.cs
new file mode 100644 (file)
index 0000000..db173c2
--- /dev/null
@@ -0,0 +1,14 @@
+using System.Linq;
+using Impostor.Server.Games.Managers;
+using Impostor.Shared.Innersloth.Data;
+
+namespace Impostor.Server.Games
+{
+    public static class GameManagerExtensions
+    {
+        public static int GetGameCount(this IGameManager manager, MapFlags map)
+        {
+            return manager.Games.Count(game => map.HasFlag((MapFlags)(1 << game.Options.MapId)));
+        }
+    }
+}
\ No newline at end of file
index 84a301dda2258f91d1e593c4145e37103bd7e003..c4931b7b94a0a7209442a1dd036f922815614811 100644 (file)
@@ -32,32 +32,5 @@ namespace Impostor.Server.Games
         int HostId { get; }
 
         IGameMessageWriter CreateMessage(MessageType type);
-
-        bool TryGetPlayer(int id, [NotNullWhen(true)] out IClientPlayer player);
-
-        /// <summary>
-        ///     Register a new client to the game.
-        /// </summary>
-        /// <param name="client">Client to register.</param>
-        /// <returns>Join result.</returns>
-        ValueTask<GameJoinResult> AddClientAsync(IClient client);
-
-        /// <summary>
-        ///     Kicks all the players from the game to end the game.
-        /// </summary>
-        /// <returns>A <see cref="ValueTask"/> representing the asynchronous operation.</returns>
-        ValueTask EndAsync();
-
-        ValueTask HandleStartGame(IMessageReader reader);
-
-        ValueTask HandleEndGame(IMessageReader reader);
-
-        ValueTask HandleKickPlayer(int playerId, bool isBan);
-
-        ValueTask HandleRemovePlayer(int playerId, DisconnectReason reason);
-
-        ValueTask HandleAlterGame(IMessageReader message, IClientPlayer sender, bool isPublic);
-
-        ValueTask HandleGameData(IMessageReader parent, IClientPlayer sender, bool toPlayer);
     }
 }
\ No newline at end of file
index 9cb964621df87f8c5012a0ffafc50586be98e493..d089772349f4983fbf02b586bebc484811b774a9 100644 (file)
@@ -1,7 +1,4 @@
 using System.Collections.Generic;
-using System.Threading.Tasks;
-using Impostor.Shared.Innersloth;
-using Impostor.Shared.Innersloth.Data;
 
 namespace Impostor.Server.Games.Managers
 {
@@ -9,12 +6,6 @@ namespace Impostor.Server.Games.Managers
     {
         IEnumerable<IGame> Games { get; }
 
-        ValueTask<IGame> CreateAsync(GameOptionsData options);
-
         IGame? Find(GameCode code);
-
-        IEnumerable<IGame> FindListings(MapFlags map, int impostorCount, GameKeywords language, int count = 10);
-
-        ValueTask RemoveAsync(GameCode code);
     }
 }
\ No newline at end of file
index 1518fb6c8c62b1ae5301b46b0310e3920a30f772..0e72d47c51e472c97142ec6bca6601e4ef89affa 100644 (file)
@@ -14,7 +14,7 @@
 
     <ItemGroup>
       <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="5.0.0-rc.1.20451.14" />
-      <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="5.0.0-rc.1.20451.14" />
+      <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0-rc.1.20451.14" />
       <PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
         <PrivateAssets>all</PrivateAssets>
         <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
index c8f8b80d12c6cdc4137ecb7ced1dff8ac6c2ae82..2df7445be62c4ee6c79b5ef0e774c65ed35856be 100644 (file)
@@ -4,4 +4,5 @@
        <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=events_005Cgame/@EntryIndexedValue">True</s:Boolean>
        <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=exceptions/@EntryIndexedValue">True</s:Boolean>
        <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=extensions/@EntryIndexedValue">True</s:Boolean>
+       <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=games_005Cextensions/@EntryIndexedValue">True</s:Boolean>
        <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=net_005Cextensions/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
\ No newline at end of file
diff --git a/src/Impostor.Server.Api/Net/Factories/IClientFactory.cs b/src/Impostor.Server.Api/Net/Factories/IClientFactory.cs
deleted file mode 100644 (file)
index ac0faec..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-using System.Threading.Tasks;
-
-namespace Impostor.Server.Net.Factories
-{
-    public interface IClientFactory
-    {
-        /// <summary>
-        ///     Creates a client for the Hazel <see cref="connection"/>.
-        /// </summary>
-        /// <param name="connection">Hazel connection.</param>
-        /// <param name="name"></param>
-        /// <param name="clientVersion"></param>
-        IClient Create(IConnection connection, string name, int clientVersion);
-    }
-}
\ No newline at end of file
index 0283a7578d3a91df0a3914766c77aa28b7e7beea..4d6d22dee0625b6ed156022624c489800296b07a 100644 (file)
@@ -57,10 +57,6 @@ namespace Impostor.Server.Net
         /// <summary>
         ///     Gets or sets the current game data of the <see cref="IClient"/>.
         /// </summary>
-        IClientPlayer? Player { get; set; }
-
-        ValueTask HandleMessageAsync(IMessage message);
-
-        ValueTask HandleDisconnectAsync(string reason);
+        IClientPlayer? Player { get; }
     }
 }
\ No newline at end of file
index 371c5aca36ebd89b5adabc3ca310a77cc41961f6..69d91197e959dfd9890167911b7d6f33b1cddd08 100644 (file)
@@ -22,9 +22,9 @@ namespace Impostor.Server.Net
         bool IsConnected { get; }
 
         /// <summary>
-        ///     Gets or sets the client of the connection.
+        ///     Gets the client of the connection.
         /// </summary>
-        IClient? Client { get; set; }
+        IClient? Client { get; }
 
         /// <summary>
         ///     Create a message writer that can be send to the connection.
index 46da8484d5736a1da080403e7a258f83c8d9542d..6405a653e8f5a163b3da9735296fd30c7b700451 100644 (file)
@@ -1,15 +1,9 @@
-using System.Threading.Tasks;
+using System.Collections.Generic;
 
 namespace Impostor.Server.Net.Manager
 {
     public interface IClientManager
     {
-        ValueTask RegisterConnectionAsync(IConnection connection, string name, int clientVersion);
-
-        void Register(IClient client);
-
-        void Remove(IClient client);
-
-        bool Validate(IClient client);
+        IEnumerable<IClient> Clients { get; }
     }
 }
\ No newline at end of file
diff --git a/src/Impostor.Server.Api/Net/Manager/IMatchmaker.cs b/src/Impostor.Server.Api/Net/Manager/IMatchmaker.cs
deleted file mode 100644 (file)
index 19ef796..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-using System.Net;
-using System.Threading.Tasks;
-using Impostor.Server.Games;
-using Impostor.Server.Net.Messages;
-
-namespace Impostor.Server.Net.Manager
-{
-    /// <summary>
-    ///     Represents the matchmaker which will listen for incoming connections.
-    /// </summary>
-    public interface IMatchmaker
-    {
-        /// <summary>
-        ///     Starts the matchmaker on the given endpoint.
-        /// </summary>
-        /// <param name="ipEndPoint">Endpoint where the matchmaker should listen to.</param>
-        /// <returns>A <see cref="ValueTask"/> representing the asynchronous operation.</returns>
-        ValueTask StartAsync(IPEndPoint ipEndPoint);
-
-        /// <summary>
-        ///     Stop the matchmaker.
-        /// </summary>
-        /// <returns>A <see cref="ValueTask"/> representing the asynchronous operation.</returns>
-        ValueTask StopAsync();
-
-        /// <summary>
-        ///     Create a message writer that can be send to players in the game.
-        /// </summary>
-        /// <param name="game">The game.</param>
-        /// <param name="messageType">Type of the message.</param>
-        /// <returns>Message writer for the given game.</returns>
-        IGameMessageWriter CreateGameMessageWriter(IGame game, MessageType messageType);
-    }
-}
\ No newline at end of file
index d31520b153f441673c7beac9293a1bea0629d515..e5f86458d226fda224fd6d251857266e13a30002 100644 (file)
@@ -1,5 +1,7 @@
 using System.Threading.Tasks;
 using Impostor.Server.Events;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
 
 namespace Impostor.Server.Plugins
 {
@@ -10,5 +12,9 @@ namespace Impostor.Server.Plugins
         ValueTask DisableAsync();
 
         ValueTask ReloadAsync();
+
+        void ConfigureHost(IHostBuilder host);
+
+        void ConfigureServices(IServiceCollection services);
     }
 }
\ No newline at end of file
index c392bba46f24918dcbb0dcd5425398dec49e030e..5ef6cf3c02324add6ddc91657ecb3b4278a2420a 100644 (file)
@@ -1,4 +1,6 @@
 using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
 
 namespace Impostor.Server.Plugins
 {
@@ -18,5 +20,13 @@ namespace Impostor.Server.Plugins
         {
             return default;
         }
+
+        public virtual void ConfigureHost(IHostBuilder host)
+        {
+        }
+
+        public virtual void ConfigureServices(IServiceCollection services)
+        {
+        }
     }
 }
\ No newline at end of file
diff --git a/src/Impostor.Server.Hazel/Extensions/ServiceExtensions.cs b/src/Impostor.Server.Hazel/Extensions/ServiceExtensions.cs
deleted file mode 100644 (file)
index b97a65f..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-using Impostor.Server.Net.Manager;
-using Microsoft.Extensions.DependencyInjection;
-
-namespace Impostor.Server.Hazel
-{
-    public static class ServiceExtensions
-    {
-        public static IServiceCollection UseHazelMatchmaking(this IServiceCollection services)
-        {
-            services.AddSingleton<IMatchmaker, HazelMatchmaker>();
-            return services;
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/Impostor.Server.Hazel/HazelConnection.cs b/src/Impostor.Server.Hazel/HazelConnection.cs
deleted file mode 100644 (file)
index b0f5d5e..0000000
+++ /dev/null
@@ -1,74 +0,0 @@
-using System;
-using System.Net;
-using System.Threading.Tasks;
-using Hazel;
-using Impostor.Server.Hazel.Messages;
-using Impostor.Server.Net;
-using Impostor.Server.Net.Messages;
-using Microsoft.Extensions.Logging;
-
-namespace Impostor.Server.Hazel
-{
-    internal class HazelConnection : IConnection
-    {
-        private readonly ILogger<HazelConnection> _logger;
-
-        public HazelConnection(Connection innerConnection, ILogger<HazelConnection> logger)
-        {
-            _logger = logger;
-            InnerConnection = innerConnection;
-            innerConnection.DataReceived = ConnectionOnDataReceived;
-            innerConnection.Disconnected = ConnectionOnDisconnected;
-        }
-
-        public Connection InnerConnection { get; }
-
-        public IPEndPoint EndPoint => InnerConnection.EndPoint;
-
-        public bool IsConnected => InnerConnection.State == ConnectionState.Connected;
-
-        public IClient Client { get; set; }
-
-        private async ValueTask ConnectionOnDisconnected(DisconnectedEventArgs e)
-        {
-            if (Client != null)
-            {
-                await Client.HandleDisconnectAsync(e.Reason);
-            }
-        }
-
-        private async ValueTask ConnectionOnDataReceived(DataReceivedEventArgs e)
-        {
-            if (Client == null)
-            {
-                _logger.LogWarning("Client was null.");
-                return;
-            }
-
-            while (true)
-            {
-                if (e.Message.Position >= e.Message.Length)
-                {
-                    break;
-                }
-
-                var reader = e.Message.ReadMessage();
-                var type = e.SendOption switch
-                {
-                    SendOption.None => MessageType.Unreliable,
-                    SendOption.Reliable => MessageType.Reliable,
-                    _ => throw new NotSupportedException()
-                };
-
-                using var message = new HazelMessage(reader, type);
-
-                await Client.HandleMessageAsync(message);
-            }
-        }
-
-        public IConnectionMessageWriter CreateMessage(MessageType messageType)
-        {
-            return new HazelConnectionMessageWriter(messageType, this);
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/Impostor.Server.Hazel/HazelMatchmaker.cs b/src/Impostor.Server.Hazel/HazelMatchmaker.cs
deleted file mode 100644 (file)
index cc647a1..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-using System;
-using System.Net;
-using System.Net.Sockets;
-using System.Threading.Tasks;
-using Hazel;
-using Hazel.Udp;
-using Impostor.Server.Games;
-using Impostor.Server.Hazel.Messages;
-using Impostor.Server.Net.Manager;
-using Impostor.Server.Net.Messages;
-using Microsoft.Extensions.Logging;
-
-namespace Impostor.Server.Hazel
-{
-    internal class HazelMatchmaker : IMatchmaker
-    {
-        private readonly IClientManager _clientManager;
-        private readonly ILogger<HazelMatchmaker> _logger;
-        private readonly ILogger<HazelConnection> _connectionLogger;
-        private UdpConnectionListener _connection;
-
-        public HazelMatchmaker(
-            ILogger<HazelMatchmaker> logger,
-            IClientManager clientManager,
-            ILogger<HazelConnection> connectionLogger)
-        {
-            _logger = logger;
-            _clientManager = clientManager;
-            _connectionLogger = connectionLogger;
-        }
-
-        public async ValueTask StartAsync(IPEndPoint ipEndPoint)
-        {
-            var mode = ipEndPoint.AddressFamily switch
-            {
-                AddressFamily.InterNetwork => IPMode.IPv4,
-                AddressFamily.InterNetworkV6 => IPMode.IPv6,
-                _ => throw new InvalidOperationException()
-            };
-
-            _connection = new UdpConnectionListener(ipEndPoint, mode);
-            _connection.NewConnection = OnNewConnection;
-
-            await _connection.StartAsync();
-        }
-
-        public async ValueTask StopAsync()
-        {
-            await _connection.DisposeAsync();
-        }
-
-        private async ValueTask OnNewConnection(NewConnectionEventArgs e)
-        {
-            // Handshake.
-            var clientVersion = e.HandshakeData.ReadInt32();
-            var name = e.HandshakeData.ReadString();
-
-            var connection = new HazelConnection(e.Connection, _connectionLogger);
-
-            // Register client
-            await _clientManager.RegisterConnectionAsync(connection, name, clientVersion);
-        }
-
-        public IGameMessageWriter CreateGameMessageWriter(IGame game, MessageType messageType)
-        {
-            return new HazelGameMessageWriter(messageType, game);
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/Impostor.Server.Hazel/Impostor.Server.Hazel.csproj b/src/Impostor.Server.Hazel/Impostor.Server.Hazel.csproj
deleted file mode 100644 (file)
index 7d699cf..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-<Project Sdk="Microsoft.NET.Sdk">
-
-    <PropertyGroup>
-        <TargetFramework>net5.0</TargetFramework>
-        <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
-        <DebugType Condition=" '$(Configuration)' == 'Release' ">None</DebugType>
-    </PropertyGroup>
-
-    <ItemGroup>
-      <ProjectReference Include="..\..\submodules\Hazel-Networking\Hazel\Hazel.csproj" />
-      <ProjectReference Include="..\Impostor.Server.Api\Impostor.Server.Api.csproj" />
-    </ItemGroup>
-
-    <ItemGroup>
-      <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="5.0.0-rc.1.20451.14" />
-    </ItemGroup>
-
-    <ItemGroup>
-        <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
-            <_Parameter1>Impostor.Tests</_Parameter1>
-        </AssemblyAttribute>
-    </ItemGroup>
-
-</Project>
diff --git a/src/Impostor.Server.Hazel/Impostor.Server.Hazel.csproj.DotSettings b/src/Impostor.Server.Hazel/Impostor.Server.Hazel.csproj.DotSettings
deleted file mode 100644 (file)
index 17962b1..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
-       <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=extensions/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
\ No newline at end of file
diff --git a/src/Impostor.Server.Hazel/Messages/BufferMessageReader.cs b/src/Impostor.Server.Hazel/Messages/BufferMessageReader.cs
deleted file mode 100644 (file)
index 3e560c7..0000000
+++ /dev/null
@@ -1,159 +0,0 @@
-using System;
-using System.Buffers.Binary;
-using System.Runtime.CompilerServices;
-using System.Text;
-using Impostor.Server.Net.Messages;
-
-namespace Impostor.Server.Hazel.Messages
-{
-    public class BufferMessageReader : IMessageReader
-    {
-        public byte Tag { get; }
-        public ReadOnlyMemory<byte> Buffer { get; }
-        public int Position { get; set; }
-        public int Length => Buffer.Length;
-
-        public BufferMessageReader(byte tag, ReadOnlyMemory<byte> buffer)
-        {
-            Tag = tag;
-            Buffer = buffer;
-        }
-
-        public IMessageReader ReadMessage()
-        {
-            var length = ReadUInt16();
-            var tag = ReadByte();
-            var pos = Position;
-
-            Position += length;
-
-            return new BufferMessageReader(tag, Buffer.Slice(pos, length));
-        }
-
-        public bool ReadBoolean()
-        {
-            byte val = FastByte();
-            return val != 0;
-        }
-
-        public sbyte ReadSByte()
-        {
-            return (sbyte)FastByte();
-        }
-
-        public byte ReadByte()
-        {
-            return FastByte();
-        }
-
-        public ushort ReadUInt16()
-        {
-            var output = BinaryPrimitives.ReadUInt16LittleEndian(Buffer.Span.Slice(Position));
-            Position += sizeof(ushort);
-            return output;
-        }
-
-        public short ReadInt16()
-        {
-            var output = BinaryPrimitives.ReadInt16LittleEndian(Buffer.Span.Slice(Position));
-            Position += sizeof(short);
-            return output;
-        }
-
-        public uint ReadUInt32()
-        {
-            var output = BinaryPrimitives.ReadUInt32LittleEndian(Buffer.Span.Slice(Position));
-            Position += sizeof(uint);
-            return output;
-        }
-
-        public int ReadInt32()
-        {
-            var output = BinaryPrimitives.ReadInt32LittleEndian(Buffer.Span.Slice(Position));
-            Position += sizeof(int);
-            return output;
-        }
-
-        public float ReadSingle()
-        {
-            var output = BinaryPrimitives.ReadSingleLittleEndian(Buffer.Span.Slice(Position));
-            Position += sizeof(float);
-            return output;
-        }
-
-        public string ReadString()
-        {
-            var len = ReadPackedInt32();
-            var output = Encoding.UTF8.GetString(Buffer.Span.Slice(Position, len));
-            Position += len;
-            return output;
-        }
-
-        public ReadOnlyMemory<byte> ReadBytesAndSize()
-        {
-            var len = ReadPackedInt32();
-            return ReadBytes(len);
-        }
-
-        public ReadOnlyMemory<byte> ReadBytes(int length)
-        {
-            var output = Buffer.Slice(Position, length);
-            Position += length;
-            return output;
-        }
-
-        public int ReadPackedInt32()
-        {
-            return (int)ReadPackedUInt32();
-        }
-
-        public uint ReadPackedUInt32()
-        {
-            bool readMore = true;
-            int shift = 0;
-            uint output = 0;
-
-            while (readMore)
-            {
-                byte b = ReadByte();
-                if (b >= 0x80)
-                {
-                    readMore = true;
-                    b ^= 0x80;
-                }
-                else
-                {
-                    readMore = false;
-                }
-
-                output |= (uint)(b << shift);
-                shift += 7;
-            }
-
-            return output;
-        }
-
-        public void CopyTo(IMessageWriter writer)
-        {
-            writer.Write((ushort) Length);
-            writer.Write(Tag);
-            writer.Write(Buffer);
-        }
-
-        public IMessageReader Slice(int start)
-        {
-            return new BufferMessageReader(Tag, Buffer.Slice(start));
-        }
-
-        public IMessageReader Slice(int start, int length)
-        {
-            return new BufferMessageReader(Tag, Buffer.Slice(start, length));
-        }
-
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private byte FastByte()
-        {
-            return Buffer.Span[Position++];
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/Impostor.Server.Hazel/Messages/HazelConnectionMessageWriter.cs b/src/Impostor.Server.Hazel/Messages/HazelConnectionMessageWriter.cs
deleted file mode 100644 (file)
index 7f45619..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-using System.Threading.Tasks;
-using Impostor.Server.Net;
-using Impostor.Server.Net.Messages;
-
-namespace Impostor.Server.Hazel.Messages
-{
-    internal class HazelConnectionMessageWriter : HazelMessageWriter, IConnectionMessageWriter
-    {
-        private readonly HazelConnection _connection;
-
-        public HazelConnectionMessageWriter(MessageType type, HazelConnection connection)
-            : base(type)
-        {
-            _connection = connection;
-        }
-
-        public IConnection Connection => _connection;
-
-        public async ValueTask SendAsync()
-        {
-            await _connection.InnerConnection.Send(Writer);
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/Impostor.Server.Hazel/Messages/HazelGameMessageWriter.cs b/src/Impostor.Server.Hazel/Messages/HazelGameMessageWriter.cs
deleted file mode 100644 (file)
index e99533b..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
-using Hazel;
-using Impostor.Server.Games;
-using Impostor.Server.Net;
-using Impostor.Server.Net.Messages;
-
-namespace Impostor.Server.Hazel.Messages
-{
-    internal class HazelGameMessageWriter : HazelMessageWriter, IGameMessageWriter
-    {
-        private readonly IGame _game;
-
-        public HazelGameMessageWriter(MessageType type, IGame game)
-            : base(type)
-        {
-            _game = game;
-        }
-
-        private IEnumerable<Connection> GetConnections(Func<IClientPlayer, bool> filter)
-        {
-            return _game.Players
-                .Where(filter)
-                .Select(p => p.Client.Connection)
-                .OfType<HazelConnection>()
-                .Select(c => c.InnerConnection);
-        }
-
-        public ValueTask SendToAllAsync(LimboStates states)
-        {
-            foreach (var connection in GetConnections(x => x.Limbo.HasFlag(states)))
-            {
-                connection.Send(Writer);
-            }
-
-            return default;
-        }
-
-        public ValueTask SendToAllExceptAsync(int senderId, LimboStates states)
-        {
-            foreach (var connection in GetConnections(x =>
-                x.Limbo.HasFlag(states) &&
-                x.Client.Id != senderId))
-            {
-                connection.Send(Writer);
-            }
-            return default;
-        }
-
-        public ValueTask SendToAsync(int id)
-        {
-            if (_game.TryGetPlayer(id, out var player)
-                && player.Client.Connection is HazelConnection hazelConnection)
-            {
-                hazelConnection.InnerConnection.Send(Writer);
-            }
-
-            return default;
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/Impostor.Server.Hazel/Messages/HazelMessage.cs b/src/Impostor.Server.Hazel/Messages/HazelMessage.cs
deleted file mode 100644 (file)
index 4f08eb4..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-using System;
-using Hazel;
-using Impostor.Server.Net.Messages;
-
-namespace Impostor.Server.Hazel.Messages
-{
-    internal class HazelMessage : IMessage, IDisposable
-    {
-        private bool _isDisposed;
-        private readonly MessageReader _reader;
-
-        public HazelMessage(MessageReader reader, MessageType type)
-        {
-            _reader = reader;
-            Type = type;
-        }
-
-        public MessageType Type { get; }
-
-        public IMessageReader CreateReader()
-        {
-            if (_isDisposed)
-            {
-                throw new ObjectDisposedException(nameof(_reader));
-            }
-
-            return new BufferMessageReader(_reader.Tag, _reader.Buffer);
-        }
-
-        private void Dispose(bool disposing)
-        {
-            if (disposing)
-            {
-                _isDisposed = true;
-            }
-        }
-
-        public void Dispose()
-        {
-            Dispose(true);
-            GC.SuppressFinalize(this);
-        }
-
-        ~HazelMessage()
-        {
-            Dispose(false);
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/Impostor.Server.Hazel/Messages/HazelMessageWriter.cs b/src/Impostor.Server.Hazel/Messages/HazelMessageWriter.cs
deleted file mode 100644 (file)
index 0d40313..0000000
+++ /dev/null
@@ -1,127 +0,0 @@
-using System;
-using System.Net;
-using Hazel;
-using Impostor.Server.Games;
-using Impostor.Server.Net.Messages;
-
-namespace Impostor.Server.Hazel.Messages
-{
-    internal abstract class HazelMessageWriter : IMessageWriter
-    {
-        protected readonly MessageWriter Writer;
-
-        protected HazelMessageWriter(MessageType type)
-        {
-            Writer = MessageWriter.Get(ToSendOption(type));
-        }
-
-        private static SendOption ToSendOption(MessageType type)
-        {
-            return type switch
-            {
-                MessageType.Unreliable => SendOption.None,
-                MessageType.Reliable => SendOption.Reliable,
-                _ => throw new NotSupportedException($"Message type {type} is not supported")
-            };
-        }
-
-        protected virtual void Dispose(bool disposing)
-        {
-            if (disposing)
-            {
-                Writer.Recycle();
-            }
-        }
-
-        public void Dispose()
-        {
-            Dispose(true);
-            GC.SuppressFinalize(this);
-        }
-
-        public void Write(bool value)
-        {
-            Writer.Write(value);
-        }
-
-        public void Write(sbyte value)
-        {
-            Writer.Write(value);
-        }
-
-        public void Write(byte value)
-        {
-            Writer.Write(value);
-        }
-
-        public void Write(short value)
-        {
-            Writer.Write(value);
-        }
-
-        public void Write(ushort value)
-        {
-            Writer.Write(value);
-        }
-
-        public void Write(uint value)
-        {
-            Writer.Write(value);
-        }
-
-        public void Write(int value)
-        {
-            Writer.Write(value);
-        }
-
-        public void Write(float value)
-        {
-            Writer.Write(value);
-        }
-
-        public void Write(string value)
-        {
-            Writer.Write(value);
-        }
-
-        public void Write(IPAddress value)
-        {
-            Writer.Write(value.GetAddressBytes());
-        }
-
-        public void WritePacked(int value)
-        {
-            Writer.WritePacked(value);
-        }
-
-        public void WritePacked(uint value)
-        {
-            Writer.WritePacked(value);
-        }
-
-        public void Write(ReadOnlyMemory<byte> data)
-        {
-            Writer.Write(data.ToArray()); // TODO: Fix memory allocation.
-        }
-
-        public void StartMessage(byte typeFlag)
-        {
-            Writer.StartMessage(typeFlag);
-        }
-
-        public void Write(GameCode value)
-        {
-            Write(value.Value);
-        }
-
-        public void EndMessage()
-        {
-            Writer.EndMessage();
-        }
-
-        public void Clear(MessageType type)
-        {
-            Writer.Clear(ToSendOption(type));
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/Impostor.Server/Api/ClientBase.Api.cs b/src/Impostor.Server/Api/ClientBase.Api.cs
new file mode 100644 (file)
index 0000000..1a375d2
--- /dev/null
@@ -0,0 +1,10 @@
+// ReSharper disable once CheckNamespace
+namespace Impostor.Server.Net
+{
+    internal abstract partial class ClientBase : IClient
+    {
+        IConnection IClient.Connection => Connection;
+
+        IClientPlayer IClient.Player => Player;
+    }
+}
\ No newline at end of file
diff --git a/src/Impostor.Server/Api/ClientManager.Api.cs b/src/Impostor.Server/Api/ClientManager.Api.cs
new file mode 100644 (file)
index 0000000..597dd5a
--- /dev/null
@@ -0,0 +1,10 @@
+using System.Collections.Generic;
+
+// ReSharper disable once CheckNamespace
+namespace Impostor.Server.Net.Manager
+{
+    internal partial class ClientManager : IClientManager
+    {
+        IEnumerable<IClient> IClientManager.Clients => _clients.Values;
+    }
+}
\ No newline at end of file
diff --git a/src/Impostor.Server/Api/ClientPlayer.Api.cs b/src/Impostor.Server/Api/ClientPlayer.Api.cs
new file mode 100644 (file)
index 0000000..b945f08
--- /dev/null
@@ -0,0 +1,14 @@
+using Impostor.Server.Games;
+
+// ReSharper disable once CheckNamespace
+namespace Impostor.Server.Net.State
+{
+    internal partial class ClientPlayer
+    {
+        /// <inheritdoc />
+        IClient IClientPlayer.Client => Client;
+
+        /// <inheritdoc />
+        IGame IClientPlayer.Game => Game;
+    }
+}
\ No newline at end of file
diff --git a/src/Impostor.Server/Api/Game.Api.cs b/src/Impostor.Server/Api/Game.Api.cs
new file mode 100644 (file)
index 0000000..74743a9
--- /dev/null
@@ -0,0 +1,10 @@
+using Impostor.Server.Games;
+
+// ReSharper disable once CheckNamespace
+namespace Impostor.Server.Net.State
+{
+    internal partial class Game : IGame
+    {
+        IClientPlayer IGame.Host => Host;
+    }
+}
\ No newline at end of file
diff --git a/src/Impostor.Server/Api/GameManager.Api.cs b/src/Impostor.Server/Api/GameManager.Api.cs
new file mode 100644 (file)
index 0000000..05010d3
--- /dev/null
@@ -0,0 +1,15 @@
+using System.Collections.Generic;
+using System.Linq;
+using Impostor.Server.Games;
+using Impostor.Server.Games.Managers;
+
+// ReSharper disable once CheckNamespace
+namespace Impostor.Server.Net.Manager
+{
+    internal partial class GameManager : IGameManager
+    {
+        IEnumerable<IGame> IGameManager.Games => _games.Select(kv => kv.Value);
+
+        IGame IGameManager.Find(GameCode code) => Find(code);
+    }
+}
\ No newline at end of file
diff --git a/src/Impostor.Server/Api/HazelConnection.Api.cs b/src/Impostor.Server/Api/HazelConnection.Api.cs
new file mode 100644 (file)
index 0000000..d70a8b8
--- /dev/null
@@ -0,0 +1,10 @@
+using Impostor.Server.Net;
+
+// ReSharper disable once CheckNamespace
+namespace Impostor.Server.Hazel
+{
+    internal partial class HazelConnection : IConnection
+    {
+        IClient IConnection.Client => Client;
+    }
+}
\ No newline at end of file
index f3d33caabc7b4b9708a3d72bc0b013f21d734143..69ceef53fef45c3f59428ec036e31a25c9b49cf2 100644 (file)
@@ -1,6 +1,8 @@
 using System;
+using System.Collections.Concurrent;
 using System.Collections.Generic;
 using System.Linq;
+using System.Reflection;
 using System.Threading.Tasks;
 using Impostor.Server.Events.Managers;
 using Microsoft.Extensions.DependencyInjection;
@@ -9,11 +11,49 @@ namespace Impostor.Server.Events
 {
     internal class EventManager : IEventManager
     {
+        private readonly ConcurrentDictionary<Type, object> _temporaryEventListeners;
         private readonly IServiceProvider _serviceProvider;
 
         public EventManager(IServiceProvider serviceProvider)
         {
             _serviceProvider = serviceProvider;
+            _temporaryEventListeners = new ConcurrentDictionary<Type, object>();
+        }
+
+        /// <inheritdoc />
+        public IDisposable Register<TEvent>(Func<IServiceProvider, TEvent, ValueTask> callback)
+            where TEvent : IEvent
+        {
+            var register = (TemporaryEventRegister<TEvent>) _temporaryEventListeners.GetOrAdd(
+                typeof(TEvent),
+                _ => new TemporaryEventRegister<TEvent>());
+
+            return register.Add(callback);
+        }
+
+        /// <inheritdoc />
+        public IDisposable RegisterListener<TListener>(TListener listener, Func<Func<Task>, Task> invoker = null)
+            where TListener : IEventListener
+        {
+            if (listener == null)
+            {
+                throw new ArgumentNullException(nameof(listener));
+            }
+
+            var registerMethod = typeof(EventManager).GetMethod(nameof(RegisterListenerImpl), BindingFlags.Instance | BindingFlags.NonPublic);
+            var methods = RegisteredEventListener.FromType(listener.GetType());
+            var disposes = new IDisposable[methods.Count];
+
+            for (var i = 0; i < methods.Count; i++)
+            {
+                var method = methods[i];
+
+                disposes[i] = (IDisposable) registerMethod!
+                    .MakeGenericMethod(method.EventType)
+                    .Invoke(this, new object[] { listener, method, invoker });
+            }
+
+            return new MultiDisposable(disposes);
         }
 
         /// <inheritdoc />
@@ -37,6 +77,11 @@ namespace Impostor.Server.Events
                 {
                     await eventListener.InvokeAsync(handler, @event, scope.ServiceProvider);
                 }
+
+                if (_temporaryEventListeners.TryGetValue(typeof(T), out var cb))
+                {
+                    await ((TemporaryEventRegister<T>) cb).CallAsync(scope.ServiceProvider, @event);
+                }
             }
             finally
             {
@@ -67,5 +112,13 @@ namespace Impostor.Server.Events
                 }
             }
         }
+
+        private IDisposable RegisterListenerImpl<TEvent>(object obj, RegisteredEventListener listener, Func<Func<Task>, Task> invoker = null)
+            where TEvent : IEvent
+        {
+            return invoker == null
+                ? Register<TEvent>((provider, @event) => listener.InvokeAsync(obj, @event, provider))
+                : Register<TEvent>((provider, @event) => new ValueTask(invoker(() => listener.InvokeAsync(obj, @event, provider).AsTask())));
+        }
     }
 }
\ No newline at end of file
diff --git a/src/Impostor.Server/Events/MultiDisposable.cs b/src/Impostor.Server/Events/MultiDisposable.cs
new file mode 100644 (file)
index 0000000..fc6a6f0
--- /dev/null
@@ -0,0 +1,26 @@
+using System;
+using System.Collections.Generic;
+
+namespace Impostor.Server.Events
+{
+    /// <summary>
+    ///     Disposes multiple <see cref="IDisposable"/>.
+    /// </summary>
+    internal class MultiDisposable : IDisposable
+    {
+        private readonly IEnumerable<IDisposable> _disposables;
+
+        public MultiDisposable(IEnumerable<IDisposable> disposables)
+        {
+            _disposables = disposables;
+        }
+
+        public void Dispose()
+        {
+            foreach (var disposable in _disposables)
+            {
+                disposable.Dispose();
+            }
+        }
+    }
+}
\ No newline at end of file
index 058f0991bcc56c3178187e4cc8851cc17f1409d0..d0021b340b47cf0e5eac99890c848a4296acbc66 100644 (file)
@@ -63,7 +63,8 @@ namespace Impostor.Server.Events
             {
                 var methodArgument = methodArguments[i];
 
-                if (methodArgument.ParameterType == EventType)
+                if (typeof(IEvent).IsAssignableFrom(methodArgument.ParameterType)
+                    && methodArgument.ParameterType.IsAssignableFrom(EventType))
                 {
                     arguments[i] = @event;
                 }
@@ -85,17 +86,17 @@ namespace Impostor.Server.Events
                     invoke = Expression.Block(
                         Expression.IfThenElse(
                             Expression.Property(@event, nameof(IEventCancelable.IsCancelled)),
-                            Expression.Return(returnTarget, Expression.Constant(Task.CompletedTask)),
+                            Expression.Return(returnTarget, Expression.Default(typeof(ValueTask))),
                             Expression.Block(
                                 invoke,
-                                Expression.Return(returnTarget, Expression.Constant(Task.CompletedTask)))),
-                        Expression.Label(returnTarget, Expression.Constant(Task.CompletedTask)));
+                                Expression.Return(returnTarget, Expression.Default(typeof(ValueTask))))),
+                        Expression.Label(returnTarget, Expression.Default(typeof(ValueTask))));
                 }
                 else
                 {
                     invoke = Expression.Block(
                         invoke,
-                        Expression.Label(returnTarget, Expression.Constant(Task.CompletedTask)));
+                        Expression.Label(returnTarget, Expression.Default(typeof(ValueTask))));
                 }
             }
             else if (method.ReturnType == typeof(ValueTask))
@@ -105,9 +106,9 @@ namespace Impostor.Server.Events
                     invoke = Expression.Block(
                         Expression.IfThenElse(
                             Expression.Property(@event, nameof(IEventCancelable.IsCancelled)),
-                            Expression.Return(returnTarget, Expression.Constant(Task.CompletedTask)),
+                            Expression.Return(returnTarget, Expression.Default(typeof(ValueTask))),
                             Expression.Return(returnTarget, invoke)),
-                        Expression.Label(returnTarget, Expression.Constant(Task.CompletedTask)));
+                        Expression.Label(returnTarget, Expression.Default(typeof(ValueTask))));
                 }
             }
             else
@@ -119,12 +120,12 @@ namespace Impostor.Server.Events
                 .Compile();
         }
 
-        public static IEnumerable<RegisteredEventListener> FromType(Type type)
+        public static IReadOnlyList<RegisteredEventListener> FromType(Type type)
         {
             return Instances.GetOrAdd(type, t =>
             {
                 return t.GetMethods()
-                    .Where(m => !m.IsStatic && m.GetCustomAttribute(typeof(EventListenerAttribute), false) != null)
+                    .Where(m => !m.IsStatic && m.GetCustomAttributes(typeof(EventListenerAttribute), false).Any())
                     .SelectMany(m => FromMethod(t, m))
                     .ToArray();
             });
@@ -141,34 +142,21 @@ namespace Impostor.Server.Events
             }
 
             // Register the event.
-            var attribute = methodType.GetCustomAttribute<EventListenerAttribute>(false);
-
-            if (attribute == null)
+            foreach (var attribute in methodType.GetCustomAttributes<EventListenerAttribute>(false))
             {
-                yield break;
-            }
-
-            Type[] eventTypes;
+                var eventType = attribute.Event;
 
-            if (attribute.Events.Length == 0)
-            {
-                if (methodType.GetParameters().Length == 0 || !typeof(IEvent).IsAssignableFrom(methodType.GetParameters()[0].ParameterType))
+                if (eventType == null)
                 {
-                    throw new InvalidOperationException($"The first parameter of the method {methodType.GetFriendlyName()} should be the type {nameof(IEvent)}.");
-                }
-
-                eventTypes = new[] { methodType.GetParameters()[0].ParameterType };
-            }
-            else
-            {
-                eventTypes = attribute.Events;
-            }
+                    if (methodType.GetParameters().Length == 0 || !typeof(IEvent).IsAssignableFrom(methodType.GetParameters()[0].ParameterType))
+                    {
+                        throw new InvalidOperationException($"The first parameter of the method {methodType.GetFriendlyName()} should be the type {nameof(IEvent)}.");
+                    }
 
-            foreach (var eventType in eventTypes)
-            {
-                var listener = new RegisteredEventListener(eventType, methodType, attribute, listenerType);
+                    eventType = methodType.GetParameters()[0].ParameterType;
+                }
 
-                yield return listener;
+                yield return new RegisteredEventListener(eventType, methodType, attribute, listenerType);
             }
         }
     }
diff --git a/src/Impostor.Server/Events/TemporaryEventRegister.cs b/src/Impostor.Server/Events/TemporaryEventRegister.cs
new file mode 100644 (file)
index 0000000..834fe41
--- /dev/null
@@ -0,0 +1,78 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Impostor.Server.Events
+{
+    internal class TemporaryEventRegister<T>
+        where T : IEvent
+    {
+        private readonly SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1);
+        private readonly List<Func<IServiceProvider, T, ValueTask>> _callbacks = new List<Func<IServiceProvider, T, ValueTask>>();
+
+        public async ValueTask CallAsync(IServiceProvider provider, T @event)
+        {
+            await semaphoreSlim.WaitAsync();
+
+            try
+            {
+                foreach (var callback in _callbacks)
+                {
+                    await callback.Invoke(provider, @event);
+                }
+            }
+            finally
+            {
+                semaphoreSlim.Release();
+            }
+        }
+
+        public IDisposable Add(Func<IServiceProvider, T, ValueTask> callback)
+        {
+            semaphoreSlim.Wait();
+
+            try
+            {
+                _callbacks.Add(callback);
+            }
+            finally
+            {
+                semaphoreSlim.Release();
+            }
+
+            return new UnregisterEvent(this, callback);
+        }
+
+        private void Remove(Func<IServiceProvider, T, ValueTask> callback)
+        {
+            semaphoreSlim.Wait();
+
+            try
+            {
+                _callbacks.Remove(callback);
+            }
+            finally
+            {
+                semaphoreSlim.Release();
+            }
+        }
+
+        private class UnregisterEvent : IDisposable
+        {
+            private readonly TemporaryEventRegister<T> _register;
+            private readonly Func<IServiceProvider, T, ValueTask> _callback;
+
+            public UnregisterEvent(TemporaryEventRegister<T> register, Func<IServiceProvider, T, ValueTask> callback)
+            {
+                _register = register;
+                _callback = callback;
+            }
+
+            public void Dispose()
+            {
+                _register.Remove(_callback);
+            }
+        }
+    }
+}
\ No newline at end of file
index 2f44e1d5de7ec5003d6a94fbb4e79df7413ace16..c7472cc926328bf452d93fea9bb15184ca67faca 100644 (file)
     </PropertyGroup>
 
     <ItemGroup>
+        <ProjectReference Include="..\..\submodules\Hazel-Networking\Hazel\Hazel.csproj" />
         <ProjectReference Include="..\Impostor.Server.Api\Impostor.Server.Api.csproj" />
-        <ProjectReference Include="..\Impostor.Server.Hazel\Impostor.Server.Hazel.csproj" />
         <ProjectReference Include="..\Impostor.Shared\Impostor.Shared.csproj" />
     </ItemGroup>
 
     <ItemGroup>
         <PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="5.0.0-rc.1.20451.7" />
+        <PackageReference Include="Microsoft.Extensions.FileSystemGlobbing" Version="5.0.0-rc.1.20451.14" />
         <PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0-rc.1.20451.14" />
         <PackageReference Include="Serilog.Extensions.Hosting" Version="3.1.0" />
         <PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
index 8293540c4755e481a06f00998dc863982bcd4c7e..9a7217bd4a9c8fd68ef5e34a91ed95fd8737f1cf 100644 (file)
@@ -3,6 +3,7 @@ using System.Threading.Tasks;
 using Impostor.Server.Data;
 using Impostor.Server.Games;
 using Impostor.Server.Games.Managers;
+using Impostor.Server.Hazel;
 using Impostor.Server.Net.Manager;
 using Impostor.Server.Net.Messages;
 using Impostor.Shared.Innersloth;
@@ -16,10 +17,10 @@ namespace Impostor.Server.Net
     internal class Client : ClientBase
     {
         private readonly ILogger<Client> _logger;
-        private readonly IClientManager _clientManager;
-        private readonly IGameManager _gameManager;
+        private readonly ClientManager _clientManager;
+        private readonly GameManager _gameManager;
 
-        public Client(ILogger<Client> logger, IClientManager clientManager, IGameManager gameManager, string name, IConnection connection)
+        public Client(ILogger<Client> logger, ClientManager clientManager, GameManager gameManager, string name, HazelConnection connection)
             : base(name, connection)
         {
             _logger = logger;
index 825acc1adbbff0fddbd2f98e3bea2da5579c1755..dfeacd39bc0539b0649d4ec64bb462e17b38a287 100644 (file)
@@ -2,13 +2,15 @@
 using System.Collections.Generic;
 using System.Threading.Tasks;
 using Hazel;
+using Impostor.Server.Hazel;
 using Impostor.Server.Net.Messages;
+using Impostor.Server.Net.State;
 
 namespace Impostor.Server.Net
 {
-    public abstract class ClientBase : IClient
+    internal abstract partial class ClientBase
     {
-        protected ClientBase(string name, IConnection connection)
+        protected ClientBase(string name, HazelConnection connection)
         {
             Name = name;
             Connection = connection;
@@ -19,13 +21,13 @@ namespace Impostor.Server.Net
 
         public string Name { get; }
 
-        public IConnection Connection { get; }
+        public HazelConnection Connection { get; }
 
         public bool IsBot => false;
 
         public IDictionary<object, object> Items { get; }
 
-        public IClientPlayer Player { get; set; }
+        public ClientPlayer Player { get; set; }
 
         public abstract ValueTask HandleMessageAsync(IMessage message);
 
index 653eeda5d432c68d9ba932f4b47049161899e00b..e9ff13e8f982773848f10097b20488631c3c71ec 100644 (file)
@@ -1,5 +1,6 @@
 using System;
 using System.Threading.Tasks;
+using Impostor.Server.Hazel;
 using Microsoft.Extensions.DependencyInjection;
 
 namespace Impostor.Server.Net.Factories
@@ -14,7 +15,7 @@ namespace Impostor.Server.Net.Factories
             _serviceProvider = serviceProvider;
         }
 
-        public IClient Create(IConnection connection, string name, int clientVersion)
+        public ClientBase Create(HazelConnection connection, string name, int clientVersion)
         {
             var client = ActivatorUtilities.CreateInstance<TClient>(_serviceProvider, name, connection);
             connection.Client = client;
diff --git a/src/Impostor.Server/Net/Factories/IClientFactory.cs b/src/Impostor.Server/Net/Factories/IClientFactory.cs
new file mode 100644 (file)
index 0000000..d09def4
--- /dev/null
@@ -0,0 +1,9 @@
+using Impostor.Server.Hazel;
+
+namespace Impostor.Server.Net.Factories
+{
+    internal interface IClientFactory
+    {
+        ClientBase Create(HazelConnection connection, string name, int clientVersion);
+    }
+}
\ No newline at end of file
diff --git a/src/Impostor.Server/Net/Hazel/HazelConnection.cs b/src/Impostor.Server/Net/Hazel/HazelConnection.cs
new file mode 100644 (file)
index 0000000..9e15256
--- /dev/null
@@ -0,0 +1,74 @@
+using System;
+using System.Net;
+using System.Threading.Tasks;
+using Hazel;
+using Impostor.Server.Hazel.Messages;
+using Impostor.Server.Net;
+using Impostor.Server.Net.Messages;
+using Microsoft.Extensions.Logging;
+
+namespace Impostor.Server.Hazel
+{
+    internal partial class HazelConnection
+    {
+        private readonly ILogger<HazelConnection> _logger;
+
+        public HazelConnection(Connection innerConnection, ILogger<HazelConnection> logger)
+        {
+            _logger = logger;
+            InnerConnection = innerConnection;
+            innerConnection.DataReceived = ConnectionOnDataReceived;
+            innerConnection.Disconnected = ConnectionOnDisconnected;
+        }
+
+        public Connection InnerConnection { get; }
+
+        public IPEndPoint EndPoint => InnerConnection.EndPoint;
+
+        public bool IsConnected => InnerConnection.State == ConnectionState.Connected;
+
+        public ClientBase Client { get; set; }
+
+        private async ValueTask ConnectionOnDisconnected(DisconnectedEventArgs e)
+        {
+            if (Client != null)
+            {
+                await Client.HandleDisconnectAsync(e.Reason);
+            }
+        }
+
+        private async ValueTask ConnectionOnDataReceived(DataReceivedEventArgs e)
+        {
+            if (Client == null)
+            {
+                _logger.LogWarning("Client was null.");
+                return;
+            }
+
+            while (true)
+            {
+                if (e.Message.Position >= e.Message.Length)
+                {
+                    break;
+                }
+
+                var reader = e.Message.ReadMessage();
+                var type = e.SendOption switch
+                {
+                    SendOption.None => MessageType.Unreliable,
+                    SendOption.Reliable => MessageType.Reliable,
+                    _ => throw new NotSupportedException()
+                };
+
+                using var message = new HazelMessage(reader, type);
+
+                await Client.HandleMessageAsync(message);
+            }
+        }
+
+        public IConnectionMessageWriter CreateMessage(MessageType messageType)
+        {
+            return new HazelConnectionMessageWriter(messageType, this);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Impostor.Server/Net/Hazel/Messages/BufferMessageReader.cs b/src/Impostor.Server/Net/Hazel/Messages/BufferMessageReader.cs
new file mode 100644 (file)
index 0000000..3e560c7
--- /dev/null
@@ -0,0 +1,159 @@
+using System;
+using System.Buffers.Binary;
+using System.Runtime.CompilerServices;
+using System.Text;
+using Impostor.Server.Net.Messages;
+
+namespace Impostor.Server.Hazel.Messages
+{
+    public class BufferMessageReader : IMessageReader
+    {
+        public byte Tag { get; }
+        public ReadOnlyMemory<byte> Buffer { get; }
+        public int Position { get; set; }
+        public int Length => Buffer.Length;
+
+        public BufferMessageReader(byte tag, ReadOnlyMemory<byte> buffer)
+        {
+            Tag = tag;
+            Buffer = buffer;
+        }
+
+        public IMessageReader ReadMessage()
+        {
+            var length = ReadUInt16();
+            var tag = ReadByte();
+            var pos = Position;
+
+            Position += length;
+
+            return new BufferMessageReader(tag, Buffer.Slice(pos, length));
+        }
+
+        public bool ReadBoolean()
+        {
+            byte val = FastByte();
+            return val != 0;
+        }
+
+        public sbyte ReadSByte()
+        {
+            return (sbyte)FastByte();
+        }
+
+        public byte ReadByte()
+        {
+            return FastByte();
+        }
+
+        public ushort ReadUInt16()
+        {
+            var output = BinaryPrimitives.ReadUInt16LittleEndian(Buffer.Span.Slice(Position));
+            Position += sizeof(ushort);
+            return output;
+        }
+
+        public short ReadInt16()
+        {
+            var output = BinaryPrimitives.ReadInt16LittleEndian(Buffer.Span.Slice(Position));
+            Position += sizeof(short);
+            return output;
+        }
+
+        public uint ReadUInt32()
+        {
+            var output = BinaryPrimitives.ReadUInt32LittleEndian(Buffer.Span.Slice(Position));
+            Position += sizeof(uint);
+            return output;
+        }
+
+        public int ReadInt32()
+        {
+            var output = BinaryPrimitives.ReadInt32LittleEndian(Buffer.Span.Slice(Position));
+            Position += sizeof(int);
+            return output;
+        }
+
+        public float ReadSingle()
+        {
+            var output = BinaryPrimitives.ReadSingleLittleEndian(Buffer.Span.Slice(Position));
+            Position += sizeof(float);
+            return output;
+        }
+
+        public string ReadString()
+        {
+            var len = ReadPackedInt32();
+            var output = Encoding.UTF8.GetString(Buffer.Span.Slice(Position, len));
+            Position += len;
+            return output;
+        }
+
+        public ReadOnlyMemory<byte> ReadBytesAndSize()
+        {
+            var len = ReadPackedInt32();
+            return ReadBytes(len);
+        }
+
+        public ReadOnlyMemory<byte> ReadBytes(int length)
+        {
+            var output = Buffer.Slice(Position, length);
+            Position += length;
+            return output;
+        }
+
+        public int ReadPackedInt32()
+        {
+            return (int)ReadPackedUInt32();
+        }
+
+        public uint ReadPackedUInt32()
+        {
+            bool readMore = true;
+            int shift = 0;
+            uint output = 0;
+
+            while (readMore)
+            {
+                byte b = ReadByte();
+                if (b >= 0x80)
+                {
+                    readMore = true;
+                    b ^= 0x80;
+                }
+                else
+                {
+                    readMore = false;
+                }
+
+                output |= (uint)(b << shift);
+                shift += 7;
+            }
+
+            return output;
+        }
+
+        public void CopyTo(IMessageWriter writer)
+        {
+            writer.Write((ushort) Length);
+            writer.Write(Tag);
+            writer.Write(Buffer);
+        }
+
+        public IMessageReader Slice(int start)
+        {
+            return new BufferMessageReader(Tag, Buffer.Slice(start));
+        }
+
+        public IMessageReader Slice(int start, int length)
+        {
+            return new BufferMessageReader(Tag, Buffer.Slice(start, length));
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private byte FastByte()
+        {
+            return Buffer.Span[Position++];
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Impostor.Server/Net/Hazel/Messages/HazelConnectionMessageWriter.cs b/src/Impostor.Server/Net/Hazel/Messages/HazelConnectionMessageWriter.cs
new file mode 100644 (file)
index 0000000..7f45619
--- /dev/null
@@ -0,0 +1,24 @@
+using System.Threading.Tasks;
+using Impostor.Server.Net;
+using Impostor.Server.Net.Messages;
+
+namespace Impostor.Server.Hazel.Messages
+{
+    internal class HazelConnectionMessageWriter : HazelMessageWriter, IConnectionMessageWriter
+    {
+        private readonly HazelConnection _connection;
+
+        public HazelConnectionMessageWriter(MessageType type, HazelConnection connection)
+            : base(type)
+        {
+            _connection = connection;
+        }
+
+        public IConnection Connection => _connection;
+
+        public async ValueTask SendAsync()
+        {
+            await _connection.InnerConnection.Send(Writer);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Impostor.Server/Net/Hazel/Messages/HazelGameMessageWriter.cs b/src/Impostor.Server/Net/Hazel/Messages/HazelGameMessageWriter.cs
new file mode 100644 (file)
index 0000000..a87db88
--- /dev/null
@@ -0,0 +1,64 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Hazel;
+using Impostor.Server.Games;
+using Impostor.Server.Net;
+using Impostor.Server.Net.Messages;
+using Impostor.Server.Net.State;
+
+namespace Impostor.Server.Hazel.Messages
+{
+    internal class HazelGameMessageWriter : HazelMessageWriter, IGameMessageWriter
+    {
+        private readonly Game _game;
+
+        public HazelGameMessageWriter(MessageType type, Game game)
+            : base(type)
+        {
+            _game = game;
+        }
+
+        private IEnumerable<Connection> GetConnections(Func<IClientPlayer, bool> filter)
+        {
+            return _game.Players
+                .Where(filter)
+                .Select(p => p.Client.Connection)
+                .OfType<HazelConnection>()
+                .Select(c => c.InnerConnection);
+        }
+
+        public ValueTask SendToAllAsync(LimboStates states)
+        {
+            foreach (var connection in GetConnections(x => x.Limbo.HasFlag(states)))
+            {
+                connection.Send(Writer);
+            }
+
+            return default;
+        }
+
+        public ValueTask SendToAllExceptAsync(int senderId, LimboStates states)
+        {
+            foreach (var connection in GetConnections(x =>
+                x.Limbo.HasFlag(states) &&
+                x.Client.Id != senderId))
+            {
+                connection.Send(Writer);
+            }
+            return default;
+        }
+
+        public ValueTask SendToAsync(int id)
+        {
+            if (_game.TryGetPlayer(id, out var player)
+                && player.Client.Connection is HazelConnection hazelConnection)
+            {
+                hazelConnection.InnerConnection.Send(Writer);
+            }
+
+            return default;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Impostor.Server/Net/Hazel/Messages/HazelMessage.cs b/src/Impostor.Server/Net/Hazel/Messages/HazelMessage.cs
new file mode 100644 (file)
index 0000000..4f08eb4
--- /dev/null
@@ -0,0 +1,49 @@
+using System;
+using Hazel;
+using Impostor.Server.Net.Messages;
+
+namespace Impostor.Server.Hazel.Messages
+{
+    internal class HazelMessage : IMessage, IDisposable
+    {
+        private bool _isDisposed;
+        private readonly MessageReader _reader;
+
+        public HazelMessage(MessageReader reader, MessageType type)
+        {
+            _reader = reader;
+            Type = type;
+        }
+
+        public MessageType Type { get; }
+
+        public IMessageReader CreateReader()
+        {
+            if (_isDisposed)
+            {
+                throw new ObjectDisposedException(nameof(_reader));
+            }
+
+            return new BufferMessageReader(_reader.Tag, _reader.Buffer);
+        }
+
+        private void Dispose(bool disposing)
+        {
+            if (disposing)
+            {
+                _isDisposed = true;
+            }
+        }
+
+        public void Dispose()
+        {
+            Dispose(true);
+            GC.SuppressFinalize(this);
+        }
+
+        ~HazelMessage()
+        {
+            Dispose(false);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Impostor.Server/Net/Hazel/Messages/HazelMessageWriter.cs b/src/Impostor.Server/Net/Hazel/Messages/HazelMessageWriter.cs
new file mode 100644 (file)
index 0000000..0d40313
--- /dev/null
@@ -0,0 +1,127 @@
+using System;
+using System.Net;
+using Hazel;
+using Impostor.Server.Games;
+using Impostor.Server.Net.Messages;
+
+namespace Impostor.Server.Hazel.Messages
+{
+    internal abstract class HazelMessageWriter : IMessageWriter
+    {
+        protected readonly MessageWriter Writer;
+
+        protected HazelMessageWriter(MessageType type)
+        {
+            Writer = MessageWriter.Get(ToSendOption(type));
+        }
+
+        private static SendOption ToSendOption(MessageType type)
+        {
+            return type switch
+            {
+                MessageType.Unreliable => SendOption.None,
+                MessageType.Reliable => SendOption.Reliable,
+                _ => throw new NotSupportedException($"Message type {type} is not supported")
+            };
+        }
+
+        protected virtual void Dispose(bool disposing)
+        {
+            if (disposing)
+            {
+                Writer.Recycle();
+            }
+        }
+
+        public void Dispose()
+        {
+            Dispose(true);
+            GC.SuppressFinalize(this);
+        }
+
+        public void Write(bool value)
+        {
+            Writer.Write(value);
+        }
+
+        public void Write(sbyte value)
+        {
+            Writer.Write(value);
+        }
+
+        public void Write(byte value)
+        {
+            Writer.Write(value);
+        }
+
+        public void Write(short value)
+        {
+            Writer.Write(value);
+        }
+
+        public void Write(ushort value)
+        {
+            Writer.Write(value);
+        }
+
+        public void Write(uint value)
+        {
+            Writer.Write(value);
+        }
+
+        public void Write(int value)
+        {
+            Writer.Write(value);
+        }
+
+        public void Write(float value)
+        {
+            Writer.Write(value);
+        }
+
+        public void Write(string value)
+        {
+            Writer.Write(value);
+        }
+
+        public void Write(IPAddress value)
+        {
+            Writer.Write(value.GetAddressBytes());
+        }
+
+        public void WritePacked(int value)
+        {
+            Writer.WritePacked(value);
+        }
+
+        public void WritePacked(uint value)
+        {
+            Writer.WritePacked(value);
+        }
+
+        public void Write(ReadOnlyMemory<byte> data)
+        {
+            Writer.Write(data.ToArray()); // TODO: Fix memory allocation.
+        }
+
+        public void StartMessage(byte typeFlag)
+        {
+            Writer.StartMessage(typeFlag);
+        }
+
+        public void Write(GameCode value)
+        {
+            Write(value.Value);
+        }
+
+        public void EndMessage()
+        {
+            Writer.EndMessage();
+        }
+
+        public void Clear(MessageType type)
+        {
+            Writer.Clear(ToSendOption(type));
+        }
+    }
+}
\ No newline at end of file
index a178f7756dfa0d9cf612d85001311a78b4a07ba3..37a1402d697a930a52dad2fad015d71b56b82255 100644 (file)
@@ -3,6 +3,7 @@ using System.Collections.Generic;
 using System.Threading;
 using System.Threading.Tasks;
 using Impostor.Server.Data;
+using Impostor.Server.Hazel;
 using Impostor.Server.Net.Factories;
 using Impostor.Server.Net.Messages;
 using Impostor.Shared.Innersloth;
@@ -11,7 +12,7 @@ using Microsoft.Extensions.Logging;
 
 namespace Impostor.Server.Net.Manager
 {
-    internal class ClientManager : IClientManager
+    internal partial class ClientManager
     {
         public static HashSet<int> SupportedVersions { get; } = new HashSet<int>
         {
@@ -20,7 +21,7 @@ namespace Impostor.Server.Net.Manager
         };
 
         private readonly ILogger<ClientManager> _logger;
-        private readonly ConcurrentDictionary<int, IClient> _clients;
+        private readonly ConcurrentDictionary<int, ClientBase> _clients;
         private readonly IClientFactory _clientFactory;
         private int _idLast;
 
@@ -28,9 +29,11 @@ namespace Impostor.Server.Net.Manager
         {
             _logger = logger;
             _clientFactory = clientFactory;
-            _clients = new ConcurrentDictionary<int, IClient>();
+            _clients = new ConcurrentDictionary<int, ClientBase>();
         }
 
+        public IEnumerable<ClientBase> Clients => _clients.Values;
+
         public int NextId()
         {
             var clientId = Interlocked.Increment(ref _idLast);
@@ -47,7 +50,7 @@ namespace Impostor.Server.Net.Manager
             return clientId;
         }
 
-        public async ValueTask RegisterConnectionAsync(IConnection connection, string name, int clientVersion)
+        public async ValueTask RegisterConnectionAsync(HazelConnection connection, string name, int clientVersion)
         {
             if (name.Length > 10)
             {
@@ -66,12 +69,6 @@ namespace Impostor.Server.Net.Manager
             }
 
             var client = _clientFactory.Create(connection, name, clientVersion);
-
-            Register(client);
-        }
-
-        public void Register(IClient client)
-        {
             var id = NextId();
 
             client.Id = id;
index a0e83394079bbf49b93cd380f6efdb0f55ef1551..e54f8661d1836e121d6b325d177e261884fd7612 100644 (file)
@@ -19,7 +19,7 @@ using Microsoft.Extensions.Options;
 
 namespace Impostor.Server.Net.Manager
 {
-    internal class GameManager : IGameManager
+    internal partial class GameManager
     {
         private readonly ILogger<GameManager> _logger;
         private readonly INodeLocator _nodeLocator;
@@ -38,9 +38,7 @@ namespace Impostor.Server.Net.Manager
             _games = new ConcurrentDictionary<int, Game>();
         }
 
-        public IEnumerable<IGame> Games => _games.Select(kv => kv.Value);
-
-        public async ValueTask<IGame> CreateAsync(GameOptionsData options)
+        public async ValueTask<Game> CreateAsync(GameOptionsData options)
         {
             // TODO: Prevent duplicates when using server redirector using INodeProvider.
             var gameCode = GameCode.Create();
@@ -60,13 +58,13 @@ namespace Impostor.Server.Net.Manager
             return game;
         }
 
-        public IGame Find(GameCode code)
+        public Game Find(GameCode code)
         {
             _games.TryGetValue(code, out var game);
             return game;
         }
 
-        public IEnumerable<IGame> FindListings(MapFlags map, int impostorCount, GameKeywords language, int count = 10)
+        public IEnumerable<Game> FindListings(MapFlags map, int impostorCount, GameKeywords language, int count = 10)
         {
             var results = 0;
 
@@ -115,13 +113,15 @@ namespace Impostor.Server.Net.Manager
                 return;
             }
 
-            if (!_games.TryRemove(gameCode, out _))
+            if (!_games.TryRemove(gameCode, out game))
             {
                 return;
             }
 
             _logger.LogDebug("Remove game with code {0} ({1}).", GameCodeParser.IntToGameName(gameCode), gameCode);
             _nodeLocator.Remove(GameCodeParser.IntToGameName(gameCode));
+
+            await _eventManager.CallAsync(new GameDestroyedEvent(game));
         }
     }
 }
\ No newline at end of file
diff --git a/src/Impostor.Server/Net/Matchmaker.cs b/src/Impostor.Server/Net/Matchmaker.cs
new file mode 100644 (file)
index 0000000..462c26c
--- /dev/null
@@ -0,0 +1,65 @@
+using System;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading.Tasks;
+using Hazel;
+using Hazel.Udp;
+using Impostor.Server.Games;
+using Impostor.Server.Hazel.Messages;
+using Impostor.Server.Net.Manager;
+using Impostor.Server.Net.Messages;
+using Impostor.Server.Net.State;
+using Microsoft.Extensions.Logging;
+
+namespace Impostor.Server.Hazel
+{
+    internal class Matchmaker
+    {
+        private readonly ClientManager _clientManager;
+        private readonly ILogger<Matchmaker> _logger;
+        private readonly ILogger<HazelConnection> _connectionLogger;
+        private UdpConnectionListener _connection;
+
+        public Matchmaker(
+            ILogger<Matchmaker> logger,
+            ClientManager clientManager,
+            ILogger<HazelConnection> connectionLogger)
+        {
+            _logger = logger;
+            _clientManager = clientManager;
+            _connectionLogger = connectionLogger;
+        }
+
+        public async ValueTask StartAsync(IPEndPoint ipEndPoint)
+        {
+            var mode = ipEndPoint.AddressFamily switch
+            {
+                AddressFamily.InterNetwork => IPMode.IPv4,
+                AddressFamily.InterNetworkV6 => IPMode.IPv6,
+                _ => throw new InvalidOperationException()
+            };
+
+            _connection = new UdpConnectionListener(ipEndPoint, mode);
+            _connection.NewConnection = OnNewConnection;
+
+            await _connection.StartAsync();
+        }
+
+        public async ValueTask StopAsync()
+        {
+            await _connection.DisposeAsync();
+        }
+
+        private async ValueTask OnNewConnection(NewConnectionEventArgs e)
+        {
+            // Handshake.
+            var clientVersion = e.HandshakeData.ReadInt32();
+            var name = e.HandshakeData.ReadString();
+
+            var connection = new HazelConnection(e.Connection, _connectionLogger);
+
+            // Register client
+            await _clientManager.RegisterConnectionAsync(connection, name, clientVersion);
+        }
+    }
+}
\ No newline at end of file
index 1405b97a0b9a44c78918abf7accdb00cc4a3d795..23847164773d7d31e1e377bf2478d0b2adddb6f2 100644 (file)
@@ -2,6 +2,7 @@
 using System.Threading;
 using System.Threading.Tasks;
 using Impostor.Server.Data;
+using Impostor.Server.Hazel;
 using Impostor.Server.Net.Manager;
 using Microsoft.Extensions.Hosting;
 using Microsoft.Extensions.Logging;
@@ -14,13 +15,13 @@ namespace Impostor.Server.Net
         private readonly ILogger<MatchmakerService> _logger;
         private readonly ServerConfig _serverConfig;
         private readonly ServerRedirectorConfig _redirectorConfig;
-        private readonly IMatchmaker _matchmaker;
+        private readonly Matchmaker _matchmaker;
 
         public MatchmakerService(
             ILogger<MatchmakerService> logger,
             IOptions<ServerConfig> serverConfig,
             IOptions<ServerRedirectorConfig> redirectorConfig,
-            IMatchmaker matchmaker)
+            Matchmaker matchmaker)
         {
             _logger = logger;
             _serverConfig = serverConfig.Value;
@@ -35,7 +36,7 @@ namespace Impostor.Server.Net
             await _matchmaker.StartAsync(endpoint);
 
             _logger.LogInformation(
-                "Matchmaker is listening on {0}:{1}, the public server ip is {2}:{3}.", 
+                "Matchmaker is listening on {0}:{1}, the public server ip is {2}:{3}.",
                 endpoint.Address,
                 endpoint.Port,
                 _serverConfig.PublicIp,
index 13023634e72d269f163f72ba2ff6faf215ac57af..8e18e04f63e34c0c74a1eb381f701dbb0869e7ea 100644 (file)
@@ -1,5 +1,6 @@
 using System.Threading.Tasks;
 using Impostor.Server.Data;
+using Impostor.Server.Hazel;
 using Impostor.Server.Net.Manager;
 using Impostor.Server.Net.Messages;
 using Impostor.Shared.Innersloth;
@@ -13,14 +14,14 @@ namespace Impostor.Server.Net.Redirector
     {
         private static readonly ILogger Logger = Log.ForContext<ClientRedirector>();
 
-        private readonly IClientManager _clientManager;
+        private readonly ClientManager _clientManager;
         private readonly INodeProvider _nodeProvider;
         private readonly INodeLocator _nodeLocator;
 
         public ClientRedirector(
             string name,
-            IConnection connection,
-            IClientManager clientManager,
+            HazelConnection connection,
+            ClientManager clientManager,
             INodeProvider nodeProvider,
             INodeLocator nodeLocator)
             : base(name, connection)
index b6734c4310092a3a85740ea3213d224c376e4b53..a744e8867b044027eac0a2c2ead00ff712ca9c14 100644 (file)
@@ -4,28 +4,22 @@ using Impostor.Shared.Innersloth.Data;
 
 namespace Impostor.Server.Net.State
 {
-    internal class ClientPlayer : IClientPlayer
+    internal partial class ClientPlayer : IClientPlayer
     {
-        public ClientPlayer(IClient client, Game game)
+        public ClientPlayer(ClientBase client, Game game)
         {
             Game = game;
             Client = client;
             Limbo = LimboStates.PreSpawn;
         }
 
-        public IClient Client { get; }
+        public ClientBase Client { get; }
 
         public Game Game { get; }
 
         /// <inheritdoc />
         public LimboStates Limbo { get; set; }
 
-        /// <inheritdoc />
-        IClient IClientPlayer.Client => Client;
-
-        /// <inheritdoc />
-        IGame IClientPlayer.Game => Game;
-
         /// <inheritdoc />
         public ValueTask KickAsync()
         {
index a1ba3619988a319332e2e3c8a9304dc3d50b52d6..d3577b3bbe24a295cecbd017a161a5482dc798ed 100644 (file)
@@ -26,10 +26,10 @@ namespace Impostor.Server.Net.State
         private readonly List<InnerNetObject> _allObjects = new List<InnerNetObject>();
         private readonly Dictionary<uint, InnerNetObject> _allObjectsFast = new Dictionary<uint, InnerNetObject>();
 
-        public async ValueTask HandleGameData(IMessageReader parent, IClientPlayer sender, bool toPlayer)
+        public async ValueTask HandleGameData(IMessageReader parent, ClientPlayer sender, bool toPlayer)
         {
             // Find target player.
-            IClientPlayer target = null;
+            ClientPlayer target = null;
 
             if (toPlayer)
             {
index f4399956181d540d1e2ca9edd242cc41fb8cf8a6..888f6d4ee0fa778c8dbc2e0fa045be3f10c8b5eb 100644 (file)
@@ -17,7 +17,7 @@ namespace Impostor.Server.Net.State
             await packet.SendToAllAsync();
         }
 
-        public async ValueTask<GameJoinResult> AddClientAsync(IClient client)
+        public async ValueTask<GameJoinResult> AddClientAsync(ClientBase client)
         {
             // Check if the IP of the player is banned.
             if (client.Connection != null && _bannedIps.Contains(client.Connection.EndPoint.Address))
@@ -140,14 +140,14 @@ namespace Impostor.Server.Net.State
             await message.SendToAllExceptAsync(playerId);
         }
 
-        private async ValueTask HandleJoinGameNew(IClientPlayer sender, bool isNew)
+        private async ValueTask HandleJoinGameNew(ClientPlayer sender, bool isNew)
         {
             Logger.Information("{0} - Player {1} ({2}) is joining.", Code, sender.Client.Name, sender.Client.Id);
 
             // Add player to the game.
             if (isNew)
             {
-                PlayerAdd(sender);
+                await PlayerAdd(sender);
             }
 
             using (var message = CreateMessage(MessageType.Reliable))
@@ -162,14 +162,14 @@ namespace Impostor.Server.Net.State
             }
         }
 
-        private async ValueTask HandleJoinGameNext(IClientPlayer sender, bool isNew)
+        private async ValueTask HandleJoinGameNext(ClientPlayer sender, bool isNew)
         {
             Logger.Information("{0} - Player {1} ({2}) is rejoining.", Code, sender.Client.Name, sender.Client.Id);
 
             // Add player to the game.
             if (isNew)
             {
-                PlayerAdd(sender);
+                await PlayerAdd(sender);
             }
 
             // Check if the host joined and let everyone join.
index 5e9b38baf20d4a70e798953e2b95380e3df2043d..fdca5d4ea242d84e375755aec12755163b4613fe 100644 (file)
@@ -1,5 +1,6 @@
 using System.Linq;
 using System.Threading.Tasks;
+using Impostor.Server.Events;
 using Impostor.Server.Exceptions;
 using Impostor.Server.Net.Messages;
 using Impostor.Shared.Innersloth.Data;
@@ -8,7 +9,7 @@ namespace Impostor.Server.Net.State
 {
     internal partial class Game
     {
-        private void PlayerAdd(IClientPlayer player)
+        private async ValueTask PlayerAdd(ClientPlayer player)
         {
             // Store player.
             if (!_players.TryAdd(player.Client.Id, player))
@@ -21,6 +22,8 @@ namespace Impostor.Server.Net.State
             {
                 HostId = player.Client.Id;
             }
+
+            await _eventManager.CallAsync(new PlayerJoinedGameEvent(this, player));
         }
 
         private async ValueTask<bool> PlayerRemove(int playerId, bool isBan = false)
@@ -55,6 +58,8 @@ namespace Impostor.Server.Net.State
                 _bannedIps.Add(player.Client.Connection.EndPoint.Address);
             }
 
+            await _eventManager.CallAsync(new PlayerLeftGameEvent(this, player, isBan));
+
             return true;
         }
 
index ca68fa7d0cec142766d7919dbdfbb90cd7fea1d7..c0d06f64cbdd81d7dfbd4c56f0b862858e66c547 100644 (file)
@@ -4,8 +4,11 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Net;
 using System.Threading.Tasks;
+using Impostor.Server.Events.Managers;
 using Impostor.Server.Games;
 using Impostor.Server.Games.Managers;
+using Impostor.Server.Hazel;
+using Impostor.Server.Hazel.Messages;
 using Impostor.Server.Net.Manager;
 using Impostor.Server.Net.Messages;
 using Impostor.Server.Net.Redirector;
@@ -16,30 +19,32 @@ using ILogger = Serilog.ILogger;
 
 namespace Impostor.Server.Net.State
 {
-    internal partial class Game : IGame
+    internal partial class Game
     {
         private static readonly ILogger Logger = Log.ForContext<Game>();
 
         private readonly IServiceProvider _serviceProvider;
-        private readonly IGameManager _gameManager;
-        private readonly IClientManager _clientManager;
-        private readonly IMatchmaker _matchmaker;
-        private readonly ConcurrentDictionary<int, IClientPlayer> _players;
+        private readonly GameManager _gameManager;
+        private readonly ClientManager _clientManager;
+        private readonly Matchmaker _matchmaker;
+        private readonly ConcurrentDictionary<int, ClientPlayer> _players;
         private readonly HashSet<IPAddress> _bannedIps;
+        private readonly IEventManager _eventManager;
 
         public Game(
             IServiceProvider serviceProvider,
-            IGameManager gameManager,
+            GameManager gameManager,
             INodeLocator nodeLocator,
             IPEndPoint publicIp,
             GameCode code,
             GameOptionsData options,
-            IMatchmaker matchmaker,
-            IClientManager clientManager)
+            Matchmaker matchmaker,
+            ClientManager clientManager,
+            IEventManager eventManager)
         {
             _serviceProvider = serviceProvider;
             _gameManager = gameManager;
-            _players = new ConcurrentDictionary<int, IClientPlayer>();
+            _players = new ConcurrentDictionary<int, ClientPlayer>();
             _bannedIps = new HashSet<IPAddress>();
 
             PublicIp = publicIp;
@@ -49,6 +54,7 @@ namespace Impostor.Server.Net.State
             Options = options;
             _matchmaker = matchmaker;
             _clientManager = clientManager;
+            _eventManager = eventManager;
             Items = new ConcurrentDictionary<object, object>();
         }
 
@@ -68,16 +74,16 @@ namespace Impostor.Server.Net.State
 
         public int PlayerCount => _players.Count;
 
-        public IClientPlayer Host => _players[HostId];
+        public ClientPlayer Host => _players[HostId];
 
         public IEnumerable<IClientPlayer> Players => _players.Select(p => p.Value);
 
         public IGameMessageWriter CreateMessage(MessageType type)
         {
-            return _matchmaker.CreateGameMessageWriter(this, type);
+            return new HazelGameMessageWriter(type, this);
         }
 
-        public bool TryGetPlayer(int id, out IClientPlayer player)
+        public bool TryGetPlayer(int id, out ClientPlayer player)
         {
             if (_players.TryGetValue(id, out var result))
             {
@@ -94,7 +100,7 @@ namespace Impostor.Server.Net.State
             return _gameManager.RemoveAsync(Code);
         }
 
-        private ValueTask BroadcastJoinMessage(IGameMessageWriter message, bool clear, IClientPlayer player)
+        private ValueTask BroadcastJoinMessage(IGameMessageWriter message, bool clear, ClientPlayer player)
         {
             Message01JoinGame.SerializeJoin(message, clear, Code, player.Client.Id, HostId);
 
diff --git a/src/Impostor.Server/Plugins/AssemblyInformation.cs b/src/Impostor.Server/Plugins/AssemblyInformation.cs
new file mode 100644 (file)
index 0000000..5f6aee1
--- /dev/null
@@ -0,0 +1,38 @@
+using System.IO;
+using System.Reflection;
+using System.Runtime.Loader;
+
+namespace Impostor.Server.Plugins
+{
+    public class AssemblyInformation : IAssemblyInformation
+    {
+        private Assembly _assembly;
+
+        public AssemblyInformation(AssemblyName assemblyName, string path, bool isPlugin)
+        {
+            AssemblyName = assemblyName;
+            Path = path;
+            IsPlugin = isPlugin;
+        }
+
+        public string Path { get; }
+
+        public bool IsPlugin { get; }
+
+        public AssemblyName AssemblyName { get; }
+
+        public Assembly Load(AssemblyLoadContext context)
+        {
+            if (_assembly != null)
+            {
+                return _assembly;
+            }
+
+            using var stream = File.Open(Path, FileMode.Open, FileAccess.Read, FileShare.Read);
+
+            _assembly = context.LoadFromStream(stream);
+
+            return _assembly;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Impostor.Server/Plugins/IAssemblyInformation.cs b/src/Impostor.Server/Plugins/IAssemblyInformation.cs
new file mode 100644 (file)
index 0000000..fb36e92
--- /dev/null
@@ -0,0 +1,14 @@
+using System.Reflection;
+using System.Runtime.Loader;
+
+namespace Impostor.Server.Plugins
+{
+    public interface IAssemblyInformation
+    {
+        AssemblyName AssemblyName { get; }
+
+        bool IsPlugin { get; }
+
+        Assembly Load(AssemblyLoadContext context);
+    }
+}
\ No newline at end of file
diff --git a/src/Impostor.Server/Plugins/LoadedAssemblyInformation.cs b/src/Impostor.Server/Plugins/LoadedAssemblyInformation.cs
new file mode 100644 (file)
index 0000000..720367c
--- /dev/null
@@ -0,0 +1,25 @@
+using System.Reflection;
+using System.Runtime.Loader;
+
+namespace Impostor.Server.Plugins
+{
+    public class LoadedAssemblyInformation : IAssemblyInformation
+    {
+        private readonly Assembly _assembly;
+
+        public LoadedAssemblyInformation(Assembly assembly)
+        {
+            AssemblyName = assembly.GetName();
+            _assembly = assembly;
+        }
+
+        public AssemblyName AssemblyName { get; }
+
+        public bool IsPlugin => false;
+
+        public Assembly Load(AssemblyLoadContext context)
+        {
+            return _assembly;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Impostor.Server/Plugins/PluginConfig.cs b/src/Impostor.Server/Plugins/PluginConfig.cs
new file mode 100644 (file)
index 0000000..22dc9e9
--- /dev/null
@@ -0,0 +1,11 @@
+using System.Collections.Generic;
+
+namespace Impostor.Server.Plugins
+{
+    public class PluginConfig
+    {
+        public List<string> Paths { get; set; } = new List<string>();
+
+        public List<string> LibraryPaths { get; set; } = new List<string>();
+    }
+}
\ No newline at end of file
diff --git a/src/Impostor.Server/Plugins/PluginLoader.cs b/src/Impostor.Server/Plugins/PluginLoader.cs
new file mode 100644 (file)
index 0000000..4fc0997
--- /dev/null
@@ -0,0 +1,99 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.Loader;
+using Microsoft.Extensions.FileSystemGlobbing;
+using Microsoft.Extensions.Hosting;
+
+namespace Impostor.Server.Plugins
+{
+    public static class PluginLoader
+    {
+        public static IHostBuilder UsePluginLoader(this IHostBuilder builder, PluginConfig config)
+        {
+            var assemblyInfos = new List<IAssemblyInformation>();
+            var context = AssemblyLoadContext.Default;
+
+            // Add the plugins and libraries.
+            var pluginPaths = new List<string>(config.Paths);
+            var libraryPaths = new List<string>(config.LibraryPaths);
+
+            var rootFolder = Assembly.GetEntryAssembly()?.Location;
+            if (rootFolder != null)
+            {
+                pluginPaths.Add(Path.Combine(rootFolder, "plugins"));
+                libraryPaths.Add(Path.Combine(rootFolder, "libraries"));
+            }
+
+            var matcher = new Matcher(StringComparison.OrdinalIgnoreCase);
+            matcher.AddInclude("*.dll");
+            matcher.AddExclude("Impostor.Server.Api.dll");
+            matcher.AddExclude("Impostor.Shared.dll");
+
+            RegisterAssemblies(pluginPaths, matcher, assemblyInfos, true);
+            RegisterAssemblies(libraryPaths, matcher, assemblyInfos, false);
+
+            // Register the resolver to the current context.
+            // TODO: Move this to a new context so we can unload/reload plugins.
+            context.Resolving += (loadContext, name) =>
+            {
+                var info = assemblyInfos.FirstOrDefault(a => a.AssemblyName.Name == name.Name);
+
+                return info?.Load(loadContext);
+            };
+
+            // TODO: Catch uncaught exceptions.
+            var assemblies = assemblyInfos
+                .Where(a => a.IsPlugin)
+                .Select(a => context.LoadFromAssemblyName(a.AssemblyName))
+                .ToList();
+
+            var plugins = assemblies
+                .SelectMany(a => a.GetTypes())
+                .Where(typeof(IPlugin).IsAssignableFrom)
+                .Select(Activator.CreateInstance)
+                .Cast<IPlugin>()
+                .ToList();
+
+            foreach (var plugin in plugins)
+            {
+                plugin.ConfigureHost(builder);
+            }
+
+            builder.ConfigureServices(services =>
+            {
+                foreach (var plugin in plugins)
+                {
+                    plugin.ConfigureServices(services);
+                }
+            });
+
+            return builder;
+        }
+
+        private static void RegisterAssemblies(
+            IEnumerable<string> paths,
+            Matcher matcher,
+            ICollection<IAssemblyInformation> assemblyInfos,
+            bool isPlugin)
+        {
+            foreach (var path in paths.SelectMany(matcher.GetResultsInFullPath))
+            {
+                AssemblyName assemblyName;
+
+                try
+                {
+                    assemblyName = AssemblyName.GetAssemblyName(path);
+                }
+                catch (BadImageFormatException)
+                {
+                    continue;
+                }
+
+                assemblyInfos.Add(new AssemblyInformation(assemblyName, path, isPlugin));
+            }
+        }
+    }
+}
\ No newline at end of file
index 31433b2af4b63874c8043e1c02743ce4ca777c01..43bc6f6bdebf52276795cba69dac035d546e0d41 100644 (file)
@@ -8,6 +8,7 @@ using Impostor.Server.Net;
 using Impostor.Server.Net.Factories;
 using Impostor.Server.Net.Manager;
 using Impostor.Server.Net.Redirector;
+using Impostor.Server.Plugins;
 using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Hosting;
@@ -49,8 +50,25 @@ namespace Impostor.Server
             }
         }
 
-        private static IHostBuilder CreateHostBuilder(string[] args) =>
-            Host.CreateDefaultBuilder(args)
+        private static IConfiguration CreateConfiguration(string[] args)
+        {
+            var configurationBuilder = new ConfigurationBuilder();
+
+            configurationBuilder.AddJsonFile("config.json", true);
+            configurationBuilder.AddJsonFile("config.Development.json", true);
+            configurationBuilder.AddEnvironmentVariables(prefix: "IMPOSTOR_");
+            configurationBuilder.AddCommandLine(args);
+
+            return configurationBuilder.Build();
+        }
+
+        private static IHostBuilder CreateHostBuilder(string[] args)
+        {
+            var configuration = CreateConfiguration(args);
+            var pluginConfig = configuration.GetSection("PluginLoader")
+                .Get<PluginConfig>();
+
+            return Host.CreateDefaultBuilder(args)
 #if DEBUG
                 .UseEnvironment(Environment.GetEnvironmentVariable("IMPOSTOR_ENV") ?? "Development")
 #else
@@ -58,10 +76,7 @@ namespace Impostor.Server
 #endif
                 .ConfigureAppConfiguration(builder =>
                 {
-                    builder.AddJsonFile("config.json", true);
-                    builder.AddJsonFile("config.Development.json", true);
-                    builder.AddEnvironmentVariables(prefix: "IMPOSTOR_");
-                    builder.AddCommandLine(args);
+                    builder.AddConfiguration(configuration);
                 })
                 .ConfigureServices((host, services) =>
                 {
@@ -73,7 +88,8 @@ namespace Impostor.Server
                     services.Configure<DebugConfig>(host.Configuration.GetSection(DebugConfig.Section));
 #endif
                     services.Configure<ServerConfig>(host.Configuration.GetSection(ServerConfig.Section));
-                    services.Configure<ServerRedirectorConfig>(host.Configuration.GetSection(ServerRedirectorConfig.Section));
+                    services.Configure<ServerRedirectorConfig>(
+                        host.Configuration.GetSection(ServerRedirectorConfig.Section));
 
                     if (redirector.Enabled)
                     {
@@ -115,7 +131,8 @@ namespace Impostor.Server
                         services.AddSingleton<INodeLocator, NodeLocatorNoOp>();
                     }
 
-                    services.AddSingleton<IClientManager, ClientManager>();
+                    services.AddSingleton<ClientManager>();
+                    services.AddSingleton<IClientManager>(p => p.GetRequiredService<ClientManager>());
 
                     if (redirector.Enabled && redirector.Master)
                     {
@@ -126,14 +143,17 @@ namespace Impostor.Server
                     else
                     {
                         services.AddSingleton<IClientFactory, ClientFactory<Client>>();
-                        services.AddSingleton<IGameManager, GameManager>();
+                        services.AddSingleton<GameManager>();
+                        services.AddSingleton<IGameManager>(p => p.GetRequiredService<GameManager>());
                     }
 
                     services.AddSingleton<IEventManager, EventManager>();
-                    services.UseHazelMatchmaking();
+                    services.AddSingleton<Matchmaker>();
                     services.AddHostedService<MatchmakerService>();
                 })
+                .UsePluginLoader(pluginConfig)
                 .UseConsoleLifetime()
                 .UseSerilog();
+        }
     }
 }
\ No newline at end of file
diff --git a/src/Impostor.Server/Properties/AssemblyInfo.cs b/src/Impostor.Server/Properties/AssemblyInfo.cs
new file mode 100644 (file)
index 0000000..eb56780
--- /dev/null
@@ -0,0 +1,3 @@
+using System.Runtime.CompilerServices;
+
+[assembly:InternalsVisibleTo("Impostor.Tests")]
\ No newline at end of file
index f896e693114d10113bd6ea05dd5623129fdddfe6..0b71ed75dcd12df69b82dd4efbd23fe132b082d7 100644 (file)
@@ -28,7 +28,7 @@ namespace Impostor.Tests.Hazel
             Assert.Equal(Test1, reader.ReadInt32());
             Assert.Equal(Test2, reader.ReadInt32());
         }
-        
+
         [Fact]
         public void ReadProperBool()
         {
index 536b4e5f2aca55174751a5f8aad540cdefcd1c15..407e920b23e6485389713cc0227841fa21f5c360 100644 (file)
@@ -14,9 +14,7 @@
     </ItemGroup>
 
     <ItemGroup>
-      <ProjectReference Include="..\..\submodules\Hazel-Networking\Hazel\Hazel.csproj" />
-      <ProjectReference Include="..\Impostor.Server.Hazel\Impostor.Server.Hazel.csproj" />
-      <ProjectReference Include="..\Impostor.Shared\Impostor.Shared.csproj" />
+      <ProjectReference Include="..\Impostor.Server\Impostor.Server.csproj" />
     </ItemGroup>
 
 </Project>
index 3ebbabcdcc349aca066d720e4f59b22ca73fa990..d35c75f1df163b23f103e11a94156e221a21d139 100644 (file)
@@ -21,8 +21,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Impostor.Server", "Impostor
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Impostor.Server.Api", "Impostor.Server.Api\Impostor.Server.Api.csproj", "{E096A7D7-D693-4A13-A526-38CC574D84F8}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Impostor.Server.Hazel", "Impostor.Server.Hazel\Impostor.Server.Hazel.csproj", "{C9E8E1E2-BFE3-41AD-8CB9-82F0F52E2306}"
-EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "patcher", "patcher", "{94FAED42-15BB-40CF-BE64-5D5C3B4ABC8F}"
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Impostor.Patcher.Shared", "Impostor.Patcher\Impostor.Patcher.Shared\Impostor.Patcher.Shared.csproj", "{7C3EB599-2292-4532-B280-D5BED1094DD4}"
@@ -31,6 +29,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Impostor.Patcher.WinForms",
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Impostor.Patcher.Cli", "Impostor.Patcher\Impostor.Patcher.Cli\Impostor.Patcher.Cli.csproj", "{82B36C4C-4EBD-49B7-A2DB-0B3308FBEF69}"
 EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "plugins", "plugins", "{36AA9913-E6EA-4A6C-90E6-2FD3CC2E3124}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Impostor.Plugins.Debugger", "Impostor.Plugins.Debugger\Impostor.Plugins.Debugger.csproj", "{ECBCAA3B-B974-41CF-AFFC-6F5AA4C42FA7}"
+EndProject
 Global
        GlobalSection(SolutionConfigurationPlatforms) = preSolution
                Debug|Any CPU = Debug|Any CPU
@@ -91,14 +93,6 @@ Global
                {E096A7D7-D693-4A13-A526-38CC574D84F8}.Release|Any CPU.Build.0 = Release|Any CPU
                {E096A7D7-D693-4A13-A526-38CC574D84F8}.Release|x86.ActiveCfg = Release|Any CPU
                {E096A7D7-D693-4A13-A526-38CC574D84F8}.Release|x86.Build.0 = Release|Any CPU
-               {C9E8E1E2-BFE3-41AD-8CB9-82F0F52E2306}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-               {C9E8E1E2-BFE3-41AD-8CB9-82F0F52E2306}.Debug|Any CPU.Build.0 = Debug|Any CPU
-               {C9E8E1E2-BFE3-41AD-8CB9-82F0F52E2306}.Debug|x86.ActiveCfg = Debug|Any CPU
-               {C9E8E1E2-BFE3-41AD-8CB9-82F0F52E2306}.Debug|x86.Build.0 = Debug|Any CPU
-               {C9E8E1E2-BFE3-41AD-8CB9-82F0F52E2306}.Release|Any CPU.ActiveCfg = Release|Any CPU
-               {C9E8E1E2-BFE3-41AD-8CB9-82F0F52E2306}.Release|Any CPU.Build.0 = Release|Any CPU
-               {C9E8E1E2-BFE3-41AD-8CB9-82F0F52E2306}.Release|x86.ActiveCfg = Release|Any CPU
-               {C9E8E1E2-BFE3-41AD-8CB9-82F0F52E2306}.Release|x86.Build.0 = Release|Any CPU
                {7C3EB599-2292-4532-B280-D5BED1094DD4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
                {7C3EB599-2292-4532-B280-D5BED1094DD4}.Debug|Any CPU.Build.0 = Debug|Any CPU
                {7C3EB599-2292-4532-B280-D5BED1094DD4}.Debug|x86.ActiveCfg = Debug|Any CPU
@@ -115,6 +109,14 @@ Global
                {82B36C4C-4EBD-49B7-A2DB-0B3308FBEF69}.Release|Any CPU.Build.0 = Release|Any CPU
                {82B36C4C-4EBD-49B7-A2DB-0B3308FBEF69}.Release|x86.ActiveCfg = Release|Any CPU
                {82B36C4C-4EBD-49B7-A2DB-0B3308FBEF69}.Release|x86.Build.0 = Release|Any CPU
+               {ECBCAA3B-B974-41CF-AFFC-6F5AA4C42FA7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+               {ECBCAA3B-B974-41CF-AFFC-6F5AA4C42FA7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+               {ECBCAA3B-B974-41CF-AFFC-6F5AA4C42FA7}.Debug|x86.ActiveCfg = Debug|Any CPU
+               {ECBCAA3B-B974-41CF-AFFC-6F5AA4C42FA7}.Debug|x86.Build.0 = Debug|Any CPU
+               {ECBCAA3B-B974-41CF-AFFC-6F5AA4C42FA7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+               {ECBCAA3B-B974-41CF-AFFC-6F5AA4C42FA7}.Release|Any CPU.Build.0 = Release|Any CPU
+               {ECBCAA3B-B974-41CF-AFFC-6F5AA4C42FA7}.Release|x86.ActiveCfg = Release|Any CPU
+               {ECBCAA3B-B974-41CF-AFFC-6F5AA4C42FA7}.Release|x86.Build.0 = Release|Any CPU
        EndGlobalSection
        GlobalSection(SolutionProperties) = preSolution
                HideSolutionNode = FALSE
@@ -128,8 +130,8 @@ Global
                {804CF172-0C87-4423-9688-BD97D549891E} = {94FAED42-15BB-40CF-BE64-5D5C3B4ABC8F}
                {1B0390AF-A4F3-4FE4-B093-708B0135C0B3} = {F2B205ED-4250-412E-9992-B11B7D6CE136}
                {E096A7D7-D693-4A13-A526-38CC574D84F8} = {F2B205ED-4250-412E-9992-B11B7D6CE136}
-               {C9E8E1E2-BFE3-41AD-8CB9-82F0F52E2306} = {F2B205ED-4250-412E-9992-B11B7D6CE136}
                {7C3EB599-2292-4532-B280-D5BED1094DD4} = {94FAED42-15BB-40CF-BE64-5D5C3B4ABC8F}
                {82B36C4C-4EBD-49B7-A2DB-0B3308FBEF69} = {94FAED42-15BB-40CF-BE64-5D5C3B4ABC8F}
+               {ECBCAA3B-B974-41CF-AFFC-6F5AA4C42FA7} = {36AA9913-E6EA-4A6C-90E6-2FD3CC2E3124}
        EndGlobalSection
 EndGlobal