From d47343df089191392875be440967d201ce9dab11 Mon Sep 17 00:00:00 2001 From: Minorusama Date: Tue, 13 Apr 2021 18:29:44 +0200 Subject: [PATCH] BeforeGameCreatedEvent (#266) Co-authored-by: js6pak --- .../Events/Game/IGameCreationEvent.cs | 25 ++++++++++ src/Impostor.Api/Games/GameCode.cs | 6 ++- .../Games/Managers/IGameManager.cs | 8 +++- src/Impostor.Plugins.Example/ExamplePlugin.cs | 13 ++++-- .../Handlers/GameEventListener.cs | 24 ++++++++++ .../Events/Game/GameCreationEvent.cs | 46 +++++++++++++++++++ src/Impostor.Server/Net/Client.cs | 8 +++- .../Net/Manager/GameManager.cs | 22 +++++++-- src/Impostor.Tools.ServerReplay/Program.cs | 2 +- 9 files changed, 142 insertions(+), 12 deletions(-) create mode 100644 src/Impostor.Api/Events/Game/IGameCreationEvent.cs create mode 100644 src/Impostor.Server/Events/Game/GameCreationEvent.cs diff --git a/src/Impostor.Api/Events/Game/IGameCreationEvent.cs b/src/Impostor.Api/Events/Game/IGameCreationEvent.cs new file mode 100644 index 0000000..b51b9f4 --- /dev/null +++ b/src/Impostor.Api/Events/Game/IGameCreationEvent.cs @@ -0,0 +1,25 @@ +using Impostor.Api.Games; +using Impostor.Api.Net; + +namespace Impostor.Api.Events +{ + /// + /// Called just before a new is created. + /// + public interface IGameCreationEvent : IEventCancelable + { + /// + /// Gets the client that requested creation of the game. + /// + /// + /// Will be null if game creation was requested by a plugin. + /// + IClient? Client { get; } + + /// + /// Gets or sets the desired . + /// + /// If the GameCode is invalid or already used in another game. + GameCode? GameCode { get; set; } + } +} diff --git a/src/Impostor.Api/Games/GameCode.cs b/src/Impostor.Api/Games/GameCode.cs index e24c28a..915ffb7 100644 --- a/src/Impostor.Api/Games/GameCode.cs +++ b/src/Impostor.Api/Games/GameCode.cs @@ -1,4 +1,4 @@ -using System; +using System; using Impostor.Api.Innersloth; namespace Impostor.Api.Games @@ -14,13 +14,15 @@ namespace Impostor.Api.Games public GameCode(string code) { Value = GameCodeParser.GameNameToInt(code); - Code = code; + Code = code.ToUpperInvariant(); } public string Code { get; } public int Value { get; } + public bool IsInvalid => Value == -1; + public static implicit operator string(GameCode code) => code.Code; public static implicit operator int(GameCode code) => code.Value; diff --git a/src/Impostor.Api/Games/Managers/IGameManager.cs b/src/Impostor.Api/Games/Managers/IGameManager.cs index cdf8558..f417d31 100644 --- a/src/Impostor.Api/Games/Managers/IGameManager.cs +++ b/src/Impostor.Api/Games/Managers/IGameManager.cs @@ -10,6 +10,12 @@ namespace Impostor.Api.Games.Managers IGame? Find(GameCode code); - ValueTask CreateAsync(GameOptionsData options); + /// + /// Creates a new game. + /// + /// Game options. + /// Created game or null if creation was cancelled by a plugin. + /// Thrown when game creation failed. + ValueTask CreateAsync(GameOptionsData options); } } diff --git a/src/Impostor.Plugins.Example/ExamplePlugin.cs b/src/Impostor.Plugins.Example/ExamplePlugin.cs index 73fea6a..a3c50e4 100644 --- a/src/Impostor.Plugins.Example/ExamplePlugin.cs +++ b/src/Impostor.Plugins.Example/ExamplePlugin.cs @@ -27,10 +27,17 @@ namespace Impostor.Plugins.Example _logger.LogInformation("Example is being enabled."); var game = await _gameManager.CreateAsync(new GameOptionsData()); - game.DisplayName = "Example game"; - await game.SetPrivacyAsync(true); + if (game == null) + { + _logger.LogWarning("Example game creation was cancelled"); + } + else + { + game.DisplayName = "Example game"; + await game.SetPrivacyAsync(true); - _logger.LogInformation("Created game {0}.", game.Code.Code); + _logger.LogInformation("Created game {0}.", game.Code.Code); + } } public override ValueTask DisableAsync() diff --git a/src/Impostor.Plugins.Example/Handlers/GameEventListener.cs b/src/Impostor.Plugins.Example/Handlers/GameEventListener.cs index 0125a19..1384e8b 100644 --- a/src/Impostor.Plugins.Example/Handlers/GameEventListener.cs +++ b/src/Impostor.Plugins.Example/Handlers/GameEventListener.cs @@ -1,4 +1,6 @@ using Impostor.Api.Events; +using Impostor.Api.Games; +using Impostor.Api.Innersloth; using Microsoft.Extensions.Logging; namespace Impostor.Plugins.Example.Handlers @@ -12,6 +14,28 @@ namespace Impostor.Plugins.Example.Handlers _logger = logger; } + [EventListener] + public void OnGameCreated(IGameCreationEvent e) + { + _logger.LogInformation("Game creation requested by {client}", e.Client == null ? "a plugin" : e.Client.Name); + + if (e.Client != null) + { + var gameCode = GameCode.From(e.Client.Name); + + if (!gameCode.IsInvalid) + { + e.GameCode = gameCode; + } + + if (e.Client.Name == "dima") + { + e.IsCancelled = true; + e.Client.DisconnectAsync(DisconnectReason.Custom, "No you dont >:("); + } + } + } + [EventListener] public void OnGameCreated(IGameCreatedEvent e) { diff --git a/src/Impostor.Server/Events/Game/GameCreationEvent.cs b/src/Impostor.Server/Events/Game/GameCreationEvent.cs new file mode 100644 index 0000000..29e95d9 --- /dev/null +++ b/src/Impostor.Server/Events/Game/GameCreationEvent.cs @@ -0,0 +1,46 @@ +using Impostor.Api; +using Impostor.Api.Events; +using Impostor.Api.Games; +using Impostor.Api.Games.Managers; +using Impostor.Api.Net; + +namespace Impostor.Server.Events +{ + public class GameCreationEvent : IGameCreationEvent + { + private readonly IGameManager _gameManager; + private GameCode? _gameCode; + + public GameCreationEvent(IGameManager gameManager, IClient? client) + { + _gameManager = gameManager; + Client = client; + } + + public IClient? Client { get; } + + public GameCode? GameCode + { + get => _gameCode; + set + { + if (value.HasValue) + { + if (value.Value.IsInvalid) + { + throw new ImpostorException("GameCode is invalid."); + } + + if (_gameManager.Find(value.Value) != null) + { + throw new ImpostorException($"GameCode [{value.Value.Code}] is already used."); + } + } + + _gameCode = value; + } + } + + public bool IsCancelled { get; set; } + } +} diff --git a/src/Impostor.Server/Net/Client.cs b/src/Impostor.Server/Net/Client.cs index 6c5547e..dcc501c 100644 --- a/src/Impostor.Server/Net/Client.cs +++ b/src/Impostor.Server/Net/Client.cs @@ -64,7 +64,13 @@ namespace Impostor.Server.Net var gameInfo = Message00HostGameC2S.Deserialize(reader, out _); // Create game. - var game = await _gameManager.CreateAsync(gameInfo); + var game = await _gameManager.CreateAsync(this, gameInfo); + + if (game == null) + { + await DisconnectAsync(DisconnectReason.GameMissing); + return; + } // Code in the packet below will be used in JoinGame. using (var writer = MessageWriter.Get(MessageType.Reliable)) diff --git a/src/Impostor.Server/Net/Manager/GameManager.cs b/src/Impostor.Server/Net/Manager/GameManager.cs index b10e791..528581b 100644 --- a/src/Impostor.Server/Net/Manager/GameManager.cs +++ b/src/Impostor.Server/Net/Manager/GameManager.cs @@ -9,6 +9,7 @@ using Impostor.Api.Events.Managers; using Impostor.Api.Games; using Impostor.Api.Games.Managers; using Impostor.Api.Innersloth; +using Impostor.Api.Net; using Impostor.Server.Config; using Impostor.Server.Events; using Impostor.Server.Net.Redirector; @@ -110,10 +111,18 @@ namespace Impostor.Server.Net.Manager await _eventManager.CallAsync(new GameDestroyedEvent(game)); } - public async ValueTask CreateAsync(GameOptionsData options) + public async ValueTask CreateAsync(IClient? owner, GameOptionsData options) { + var @event = new GameCreationEvent(this, owner); + await _eventManager.CallAsync(@event); + + if (@event.IsCancelled) + { + return null; + } + // TODO: Prevent duplicates when using server redirector using INodeProvider. - var (success, game) = await TryCreateAsync(options); + var (success, game) = await TryCreateAsync(options, @event.GameCode); for (var i = 0; i < 10 && !success; i++) { @@ -128,9 +137,14 @@ namespace Impostor.Server.Net.Manager return game; } - private async ValueTask<(bool Success, Game? Game)> TryCreateAsync(GameOptionsData options) + public ValueTask CreateAsync(GameOptionsData options) + { + return CreateAsync(null, options); + } + + private async ValueTask<(bool Success, Game? Game)> TryCreateAsync(GameOptionsData options, GameCode? desiredGameCode = null) { - var gameCode = _gameCodeFactory.Create(); + var gameCode = desiredGameCode ?? _gameCodeFactory.Create(); var gameCodeStr = gameCode.Code; var game = ActivatorUtilities.CreateInstance(_serviceProvider, _publicIp, gameCode, options); diff --git a/src/Impostor.Tools.ServerReplay/Program.cs b/src/Impostor.Tools.ServerReplay/Program.cs index 16b756e..8bfb8e0 100644 --- a/src/Impostor.Tools.ServerReplay/Program.cs +++ b/src/Impostor.Tools.ServerReplay/Program.cs @@ -209,7 +209,7 @@ namespace Impostor.Tools.ServerReplay case RecordedPacketType.GameCreated: _gameCodeFactory.Result = GameCode.From(reader.ReadString()); - await _gameManager.CreateAsync(GameOptions[clientId]); + await _gameManager.CreateAsync(Connections[clientId].Client, GameOptions[clientId]); GameOptions.Remove(clientId); break; -- 2.39.5