--- /dev/null
+using Impostor.Api.Games;
+
+namespace Impostor.Api.Events
+{
+ public class GameEndedEvent : IGameEvent
+ {
+ public GameEndedEvent(IGame game)
+ {
+ Game = game;
+ }
+
+ public IGame Game { get; }
+ }
+}
\ No newline at end of file
--- /dev/null
+using Impostor.Api.Games;
+
+namespace Impostor.Api.Events
+{
+ /// <summary>
+ /// The game is started here and players have been initialized.
+ /// </summary>
+ public class GameStartedEvent : IGameEvent
+ {
+ public GameStartedEvent(IGame game)
+ {
+ Game = game;
+ }
+
+ public IGame Game { get; }
+ }
+}
\ No newline at end of file
--- /dev/null
+using Impostor.Api.Games;
+
+namespace Impostor.Api.Events
+{
+ /// <summary>
+ /// Called when the game is going to start.
+ /// When this is called, not all players are initialized properly yet.
+ /// If you want to get correct player states, use <see cref="GameStartedEvent"/>.
+ /// </summary>
+ public class GameStartingEvent : IGameEvent
+ {
+ public GameStartingEvent(IGame game)
+ {
+ Game = game;
+ }
+
+ public IGame Game { get; }
+ }
+}
\ No newline at end of file
+++ /dev/null
-using Impostor.Api.Games;
-using Impostor.Api.Net.Inner.Objects;
-
-namespace Impostor.Api.Events.Net
-{
- public class PlayerChatEvent : IGameEvent
- {
- public PlayerChatEvent(IGame game, IInnerPlayerControl playerControl, string message)
- {
- Game = game;
- PlayerControl = playerControl;
- Message = message;
- }
-
- public IGame Game { get; }
-
- public IInnerPlayerControl PlayerControl { get; }
-
- public string Message { get; }
- }
-}
\ No newline at end of file
+++ /dev/null
-using Impostor.Api.Games;
-using Impostor.Api.Net.Inner.Objects;
-
-namespace Impostor.Api.Events.Net
-{
- public class PlayerDestroyedEvent : IGameEvent
- {
- public PlayerDestroyedEvent(IGame game, IInnerPlayerControl playerControl)
- {
- Game = game;
- PlayerControl = playerControl;
- }
-
- public IGame Game { get; }
-
- public IInnerPlayerControl PlayerControl { get; }
- }
-}
\ No newline at end of file
+++ /dev/null
-using Impostor.Api.Games;
-using Impostor.Api.Net.Inner.Objects;
-
-namespace Impostor.Api.Events.Net
-{
- public class PlayerSpawnedEvent : IGameEvent
- {
- public PlayerSpawnedEvent(IGame game, IInnerPlayerControl playerControl)
- {
- Game = game;
- PlayerControl = playerControl;
- }
-
- public IGame Game { get; }
-
- public IInnerPlayerControl PlayerControl { get; }
- }
-}
\ No newline at end of file
--- /dev/null
+using Impostor.Api.Games;
+using Impostor.Api.Net.Inner.Objects;
+
+namespace Impostor.Api.Events
+{
+ public class PlayerChatEvent : IGameEvent
+ {
+ public PlayerChatEvent(IGame game, IInnerPlayerControl playerControl, string message)
+ {
+ Game = game;
+ PlayerControl = playerControl;
+ Message = message;
+ }
+
+ public IGame Game { get; }
+
+ public IInnerPlayerControl PlayerControl { get; }
+
+ public string Message { get; }
+ }
+}
\ No newline at end of file
--- /dev/null
+using Impostor.Api.Games;
+using Impostor.Api.Net.Inner.Objects;
+
+namespace Impostor.Api.Events.Net
+{
+ public class PlayerDestroyedEvent : IGameEvent
+ {
+ public PlayerDestroyedEvent(IGame game, IInnerPlayerControl playerControl)
+ {
+ Game = game;
+ PlayerControl = playerControl;
+ }
+
+ public IGame Game { get; }
+
+ public IInnerPlayerControl PlayerControl { get; }
+ }
+}
\ No newline at end of file
--- /dev/null
+using Impostor.Api.Games;
+
+namespace Impostor.Api.Events
+{
+ public class PlayerMovementEvent : IGameEvent
+ {
+ public PlayerMovementEvent(IGame game)
+ {
+ Game = game;
+ }
+
+ public IGame Game { get; }
+ }
+}
\ No newline at end of file
--- /dev/null
+using Impostor.Api.Games;
+using Impostor.Api.Net.Inner.Objects;
+
+namespace Impostor.Api.Events.Net
+{
+ public class PlayerSpawnedEvent : IGameEvent
+ {
+ public PlayerSpawnedEvent(IGame game, IInnerPlayerControl playerControl)
+ {
+ Game = game;
+ PlayerControl = playerControl;
+ }
+
+ public IGame Game { get; }
+
+ public IInnerPlayerControl PlayerControl { get; }
+ }
+}
\ No newline at end of file
public enum GameStates : byte
{
NotStarted = 0,
- Started = 1,
- Ended = 2,
- Destroyed = 3
+ Starting = 1,
+ Started = 2,
+ Ended = 3,
+ Destroyed = 4,
}
}
\ No newline at end of file
-namespace Impostor.Api.Net.Inner.Objects
+using System.Threading.Tasks;
+
+namespace Impostor.Api.Net.Inner.Objects
{
public interface IInnerPlayerControl
{
IInnerPlayerInfo PlayerInfo { get; }
+
+ ValueTask SetNameAsync(string name);
+
+ ValueTask SendChatAsync(string text);
}
}
\ No newline at end of file
-namespace Impostor.Api.Net.Inner.Objects
+using Impostor.Api.Innersloth;
+
+namespace Impostor.Api.Net.Inner.Objects
{
public interface IInnerPlayerInfo
{
/// <summary>
/// Gets the name of the player as decided by the host.
/// </summary>
- public string PlayerName { get; }
+ string PlayerName { get; }
+
+ /// <summary>
+ /// Gets the color of the player.
+ /// </summary>
+ byte ColorId { get; }
+
+ /// <summary>
+ /// Gets the hat of the player.
+ /// </summary>
+ uint HatId { get; }
+
+ /// <summary>
+ /// Gets the pet of the player.
+ /// </summary>
+ uint PetId { get; }
+
+ /// <summary>
+ /// Gets the skin of the player.
+ /// </summary>
+ uint SkinId { get; }
+
+ /// <summary>
+ /// Gets a value indicating whether the player is an impostor.
+ /// </summary>
+ bool IsImpostor { get; }
+
+ /// <summary>
+ /// Gets a value indicating whether the player is a dead in the current game.
+ /// </summary>
+ bool IsDead { get; }
+
+ /// <summary>
+ /// Gets the reason why the player is dead in the current game.
+ /// </summary>
+ DeathReason LastDeathReason { get; }
}
}
\ No newline at end of file
[EventListener]
public void OnGameCreated(GameCreatedEvent e)
{
- Console.WriteLine("Game was created.");
+ Console.WriteLine("Game > created");
+ }
+
+ [EventListener]
+ public void OnGameStarting(GameStartingEvent e)
+ {
+ Console.WriteLine("Game > starting");
+ }
+
+ [EventListener]
+ public void OnGameStarted(GameStartedEvent e)
+ {
+ Console.WriteLine("Game > started");
+
+ foreach (var player in e.Game.Players)
+ {
+ var info = player.Character.PlayerInfo;
+
+ Console.WriteLine($"- {info.PlayerName} {info.IsImpostor}");
+ }
+ }
+
+ [EventListener]
+ public void OnGameEnded(GameEndedEvent e)
+ {
+ Console.WriteLine("Game > ended");
}
[EventListener]
public void OnGameDestroyed(GameDestroyedEvent e)
{
- Console.WriteLine("Game was destroyed.");
+ Console.WriteLine("Game > destroyed");
}
[EventListener]
using System;
+using System.Threading.Tasks;
using Impostor.Api.Events;
using Impostor.Api.Events.Net;
}
[EventListener]
- public void OnPlayerChat(PlayerChatEvent e)
+ public async ValueTask OnPlayerChat(PlayerChatEvent e)
{
Console.WriteLine(e.PlayerControl.PlayerInfo.PlayerName + " said " + e.Message);
+
+ await e.PlayerControl.SetNameAsync(e.Message);
+ await e.PlayerControl.SendChatAsync(e.Message);
}
}
}
\ No newline at end of file
-using System.Numerics;
+using System;
+using System.Numerics;
using System.Threading.Tasks;
using Impostor.Api;
using Impostor.Api.Innersloth;
private static readonly FloatRange YRange = new FloatRange(-40f, 40f);
private readonly ILogger<InnerCustomNetworkTransform> _logger;
+ private readonly InnerPlayerControl _playerControl;
+ private readonly Game _game;
private ushort _lastSequenceId;
private Vector2 _targetSyncPosition;
private Vector2 _targetSyncVelocity;
- public InnerCustomNetworkTransform(ILogger<InnerCustomNetworkTransform> logger)
+ public InnerCustomNetworkTransform(ILogger<InnerCustomNetworkTransform> logger, InnerPlayerControl playerControl, Game game)
{
_logger = logger;
+ _playerControl = playerControl;
+ _game = game;
}
private static bool SidGreaterThan(ushort newSid, ushort prevSid)
return new Vector2(XRange.Lerp(v1), YRange.Lerp(v2));
}
- public override ValueTask HandleRpc(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader)
+ public override async ValueTask HandleRpc(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader)
{
if (call == RpcCalls.SnapTo)
{
+ if (!sender.IsOwner(this))
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SnapTo)} to an unowned {nameof(InnerPlayerControl)}");
+ }
+
+ if (target != null)
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SnapTo)} to a specific player instead of broadcast");
+ }
+
+ if (!sender.Character.PlayerInfo.IsImpostor)
+ {
+ throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SnapTo)} as crewmate");
+ }
+
SnapTo(ReadVector2(reader), reader.ReadUInt16());
}
else
{
_logger.LogWarning("{0}: Unknown rpc call {1}", nameof(InnerCustomNetworkTransform), call);
}
-
- return default;
}
public override bool Serialize(IMessageWriter writer, bool initialState)
internal class InnerPlayerPhysics : InnerNetObject
{
private readonly ILogger<InnerPlayerPhysics> _logger;
+ private readonly InnerPlayerControl _playerControl;
- public InnerPlayerPhysics(ILogger<InnerPlayerPhysics> logger)
+ public InnerPlayerPhysics(ILogger<InnerPlayerPhysics> logger, InnerPlayerControl playerControl)
{
_logger = logger;
+ _playerControl = playerControl;
}
public override ValueTask HandleRpc(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader)
-using Impostor.Api.Net.Inner.Objects;
+using System.Threading.Tasks;
+using Impostor.Api.Net.Inner.Objects;
namespace Impostor.Server.Net.Inner.Objects
{
internal partial class InnerPlayerControl : IInnerPlayerControl
{
IInnerPlayerInfo IInnerPlayerControl.PlayerInfo => PlayerInfo;
+
+ public async ValueTask SetNameAsync(string name)
+ {
+ using (var writer = _game.StartRpc(NetId, RpcCalls.SetName))
+ {
+ writer.Write(name);
+ await _game.FinishRpcAsync(writer);
+ }
+ }
+
+ public async ValueTask SendChatAsync(string text)
+ {
+ using (var writer = _game.StartRpc(NetId, RpcCalls.SendChat))
+ {
+ writer.Write(text);
+ await _game.FinishRpcAsync(writer);
+ }
+ }
}
}
\ No newline at end of file
using System;
using System.Threading.Tasks;
using Impostor.Api;
+using Impostor.Api.Events;
using Impostor.Api.Events.Managers;
using Impostor.Api.Events.Net;
using Impostor.Api.Innersloth;
_eventManager = eventManager;
_game = game;
+ Physics = ActivatorUtilities.CreateInstance<InnerPlayerPhysics>(serviceProvider, this);
+ NetworkTransform = ActivatorUtilities.CreateInstance<InnerCustomNetworkTransform>(serviceProvider, this, _game);
+
Components.Add(this);
- Components.Add(ActivatorUtilities.CreateInstance<InnerPlayerPhysics>(serviceProvider));
- Components.Add(ActivatorUtilities.CreateInstance<InnerCustomNetworkTransform>(serviceProvider));
+ Components.Add(Physics);
+ Components.Add(NetworkTransform);
PlayerId = byte.MaxValue;
}
public byte PlayerId { get; private set; }
+ public InnerPlayerPhysics Physics { get; }
+
+ public InnerCustomNetworkTransform NetworkTransform { get; }
+
public InnerPlayerInfo PlayerInfo { get; internal set; }
public override async ValueTask HandleRpc(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader)
{
var playerId = reader.ReadByte();
var player = _game.GameNet.GameData.GetPlayerById(playerId);
+ if (player != null)
+ {
+ player.IsImpostor = true;
+ }
+ }
- player.IsImpostor = true;
+ if (_game.GameState == GameStates.Starting)
+ {
+ await _game.StartedAsync();
}
break;
using System;
using System.Threading;
using System.Threading.Tasks;
+using Impostor.Api.Events;
using Impostor.Api.Games;
using Impostor.Api.Innersloth;
using Impostor.Api.Net;
public async ValueTask HandleStartGame(IMessageReader message)
{
- GameState = GameStates.Started;
+ GameState = GameStates.Starting;
using var packet = MessageWriter.Get(MessageType.Reliable);
message.CopyTo(packet);
await SendToAllAsync(packet);
+
+ await _eventManager.CallAsync(new GameStartingEvent(this));
}
public async ValueTask<GameJoinResult> AddClientAsync(ClientBase client)
return GameJoinResult.FromError(GameJoinError.GameFull);
}
- if (GameState == GameStates.Started)
+ if (GameState == GameStates.Starting || GameState == GameStates.Started)
{
return GameJoinResult.FromError(GameJoinError.GameStarted);
}
{
player.Value.Limbo = LimboStates.PreSpawn;
}
+
+ await _eventManager.CallAsync(new GameEndedEvent(this));
}
public async ValueTask HandleAlterGame(IMessageReader message, IClientPlayer sender, bool isPublic)
using Impostor.Api.Net;
using Impostor.Api.Net.Messages;
using Impostor.Api.Net.Messages.S2C;
+using Impostor.Hazel;
+using Impostor.Hazel.Udp;
+using Impostor.Server.Net.Inner;
namespace Impostor.Server.Net.State
{
}
}
+ internal IMessageWriter StartRpc(uint targetNetId, RpcCalls callId, int targetClientId = -1, MessageType type = MessageType.Reliable)
+ {
+ var writer = MessageWriter.Get(type);
+
+ if (targetClientId < 0)
+ {
+ writer.StartMessage(MessageFlags.GameData);
+ writer.Write(Code);
+ }
+ else
+ {
+ writer.StartMessage(MessageFlags.GameDataTo);
+ writer.Write(Code);
+ writer.WritePacked(targetClientId);
+ }
+
+ writer.StartMessage(GameDataTag.RpcFlag);
+ writer.WritePacked(targetNetId);
+ writer.Write((byte) callId);
+
+ return writer;
+ }
+
+ internal ValueTask FinishRpcAsync(IMessageWriter writer)
+ {
+ writer.EndMessage();
+ writer.EndMessage();
+
+ return SendToAllAsync(writer);
+ }
+
private void WriteRemovePlayerMessage(IMessageWriter message, bool clear, int playerId, DisconnectReason reason)
{
Message04RemovePlayerS2C.Serialize(message, clear, Code, playerId, HostId, reason);
_logger.LogInformation("{0} - Player {1} ({2}) has left.", Code, player.Client.Name, playerId);
- if (GameState == GameStates.Started)
+ if (GameState == GameStates.Starting || GameState == GameStates.Started)
{
if (player.Character?.PlayerInfo != null)
{
using System.Linq;
using System.Net;
using System.Threading.Tasks;
+using Impostor.Api.Events;
using Impostor.Api.Events.Managers;
using Impostor.Api.Games;
using Impostor.Api.Innersloth;
return _players.TryGetValue(clientId, out var clientPlayer) ? clientPlayer : null;
}
+ internal ValueTask StartedAsync()
+ {
+ if (GameState == GameStates.Starting)
+ {
+ GameState = GameStates.Started;
+
+ return _eventManager.CallAsync(new GameStartedEvent(this));
+ }
+
+ return default;
+ }
+
public ValueTask EndAsync()
{
return _gameManager.RemoveAsync(Code);