--- /dev/null
+using Impostor.Api.Games;
+using Impostor.Api.Net;
+
+namespace Impostor.Api.Events
+{
+ /// <summary>
+ /// Called just before a new <see cref="IGame"/> is created.
+ /// </summary>
+ public interface IGameCreationEvent : IEventCancelable
+ {
+ /// <summary>
+ /// Gets the client that requested creation of the game.
+ /// </summary>
+ /// <remarks>
+ /// Will be null if game creation was requested by a plugin.
+ /// </remarks>
+ IClient? Client { get; }
+
+ /// <summary>
+ /// Gets or sets the desired <see cref="Games.GameCode"/>.
+ /// </summary>
+ /// <exception cref="ImpostorException">If the GameCode is invalid or already used in another game.</exception>
+ GameCode? GameCode { get; set; }
+ }
+}
-using System;
+using System;
using Impostor.Api.Innersloth;
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;
IGame? Find(GameCode code);
- ValueTask<IGame> CreateAsync(GameOptionsData options);
+ /// <summary>
+ /// Creates a new game.
+ /// </summary>
+ /// <param name="options">Game options.</param>
+ /// <returns>Created game or null if creation was cancelled by a plugin.</returns>
+ /// <exception cref="ImpostorException">Thrown when game creation failed.</exception>
+ ValueTask<IGame?> CreateAsync(GameOptionsData options);
}
}
_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()
using Impostor.Api.Events;
+using Impostor.Api.Games;
+using Impostor.Api.Innersloth;
using Microsoft.Extensions.Logging;
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)
{
--- /dev/null
+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; }
+ }
+}
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))
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;
await _eventManager.CallAsync(new GameDestroyedEvent(game));
}
- public async ValueTask<IGame> CreateAsync(GameOptionsData options)
+ public async ValueTask<IGame?> 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++)
{
return game;
}
- private async ValueTask<(bool Success, Game? Game)> TryCreateAsync(GameOptionsData options)
+ public ValueTask<IGame?> 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<Game>(_serviceProvider, _publicIp, gameCode, options);
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;