From af38628fd4a09e01f657a6f1c1de725914711db6 Mon Sep 17 00:00:00 2001 From: miniduikboot Date: Sun, 21 Jul 2024 23:13:25 +0200 Subject: [PATCH 1/9] Add Phantom to the list of Impostor roles --- src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.cs b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.cs index 498f8f6b..af464ae8 100644 --- a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.cs +++ b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.cs @@ -46,7 +46,7 @@ namespace Impostor.Server.Net.Inner.Objects public bool Disconnected { get; internal set; } - public bool IsImpostor => RoleType is RoleTypes.Impostor or RoleTypes.Shapeshifter or RoleTypes.ImpostorGhost; + public bool IsImpostor => RoleType is RoleTypes.Impostor or RoleTypes.Shapeshifter or RoleTypes.ImpostorGhost or RoleTypes.Phantom; public bool CanVent => RoleType is RoleTypes.Impostor or RoleTypes.Shapeshifter or RoleTypes.Engineer; From 3c2346e19fb54040cd2da8ab66514fb38c44881b Mon Sep 17 00:00:00 2001 From: Niko233 <139348239+NikoCat233@users.noreply.github.com> Date: Mon, 24 Jun 2024 16:54:33 +0800 Subject: [PATCH 2/9] Add Phantom Rpcs support --- .../Net/Messages/Rpcs/Rpc62CheckVanish.cs | 15 ++++++ .../Net/Messages/Rpcs/Rpc63StartVanish.cs | 13 +++++ .../Net/Messages/Rpcs/Rpc64CheckAppear.cs | 15 ++++++ .../Net/Messages/Rpcs/Rpc65StartAppear.cs | 15 ++++++ .../Net/Inner/Objects/InnerPlayerControl.cs | 52 ++++++++++++++++++- 5 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 src/Impostor.Api/Net/Messages/Rpcs/Rpc62CheckVanish.cs create mode 100644 src/Impostor.Api/Net/Messages/Rpcs/Rpc63StartVanish.cs create mode 100644 src/Impostor.Api/Net/Messages/Rpcs/Rpc64CheckAppear.cs create mode 100644 src/Impostor.Api/Net/Messages/Rpcs/Rpc65StartAppear.cs diff --git a/src/Impostor.Api/Net/Messages/Rpcs/Rpc62CheckVanish.cs b/src/Impostor.Api/Net/Messages/Rpcs/Rpc62CheckVanish.cs new file mode 100644 index 00000000..9011e43c --- /dev/null +++ b/src/Impostor.Api/Net/Messages/Rpcs/Rpc62CheckVanish.cs @@ -0,0 +1,15 @@ +namespace Impostor.Api.Net.Messages.Rpcs +{ + public static class Rpc62CheckVanish + { + public static void Serialize(IMessageWriter writer, float maxDuration = 0f) + { + writer.Write(maxDuration); + } + + public static void Deserialize(IMessageReader reader, out float maxDuration) + { + maxDuration = reader.ReadSingle(); + } + } +} diff --git a/src/Impostor.Api/Net/Messages/Rpcs/Rpc63StartVanish.cs b/src/Impostor.Api/Net/Messages/Rpcs/Rpc63StartVanish.cs new file mode 100644 index 00000000..9a0ecc18 --- /dev/null +++ b/src/Impostor.Api/Net/Messages/Rpcs/Rpc63StartVanish.cs @@ -0,0 +1,13 @@ +namespace Impostor.Api.Net.Messages.Rpcs +{ + public static class Rpc63StartVanish + { + public static void Serialize(IMessageWriter writer) + { + } + + public static void Deserialize(IMessageReader reader) + { + } + } +} diff --git a/src/Impostor.Api/Net/Messages/Rpcs/Rpc64CheckAppear.cs b/src/Impostor.Api/Net/Messages/Rpcs/Rpc64CheckAppear.cs new file mode 100644 index 00000000..cfa8a6b2 --- /dev/null +++ b/src/Impostor.Api/Net/Messages/Rpcs/Rpc64CheckAppear.cs @@ -0,0 +1,15 @@ +namespace Impostor.Api.Net.Messages.Rpcs +{ + public static class Rpc64CheckAppear + { + public static void Serialize(IMessageWriter writer, bool shouldAnimate) + { + writer.Write(shouldAnimate); + } + + public static void Deserialize(IMessageReader reader, out bool shouldAnimate) + { + shouldAnimate = reader.ReadBoolean(); + } + } +} diff --git a/src/Impostor.Api/Net/Messages/Rpcs/Rpc65StartAppear.cs b/src/Impostor.Api/Net/Messages/Rpcs/Rpc65StartAppear.cs new file mode 100644 index 00000000..7f354495 --- /dev/null +++ b/src/Impostor.Api/Net/Messages/Rpcs/Rpc65StartAppear.cs @@ -0,0 +1,15 @@ +namespace Impostor.Api.Net.Messages.Rpcs +{ + public static class Rpc65StartAppear + { + public static void Serialize(IMessageWriter writer, bool shouldAnimate) + { + writer.Write(shouldAnimate); + } + + public static void Deserialize(IMessageReader reader, out bool shouldAnimate) + { + shouldAnimate = reader.ReadBoolean(); + } + } +} diff --git a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs index 16fa42ed..a105b83f 100644 --- a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs +++ b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs @@ -511,7 +511,7 @@ namespace Impostor.Server.Net.Inner.Objects return false; } - Rpc55CheckShapeshift.Deserialize(reader, Game, out var playerControl, out var shouldAnimate); + Rpc55CheckShapeshift.Deserialize(reader, Game, out var playerControl, out _); break; } @@ -526,6 +526,56 @@ namespace Impostor.Server.Net.Inner.Objects break; } + case RpcCalls.CheckVanish: + { + if (!await ValidateOwnership(call, sender) || + !await ValidateRole(call, sender, PlayerInfo, RoleTypes.Phantom) || + !await ValidateCmd(call, sender, target)) + { + return false; + } + + Rpc62CheckVanish.Deserialize(reader, out var maxDuration); + break; + } + + case RpcCalls.StartVanish: + { + if (!await ValidateHost(call, sender) || + !await ValidateBroadcast(call, sender, target)) + { + return false; + } + + Rpc63StartVanish.Deserialize(reader); + break; + } + + case RpcCalls.CheckAppear: + { + if (!await ValidateOwnership(call, sender) || + !await ValidateRole(call, sender, PlayerInfo, RoleTypes.Phantom) || + !await ValidateCmd(call, sender, target)) + { + return false; + } + + Rpc64CheckAppear.Deserialize(reader, out bool shouldAnimate); + break; + } + + case RpcCalls.StartAppear: + { + if (!await ValidateHost(call, sender) || + !await ValidateBroadcast(call, sender, target)) + { + return false; + } + + Rpc65StartAppear.Deserialize(reader, out _); + break; + } + default: return await base.HandleRpcAsync(sender, target, call, reader); } From f33dc1eb0977e971b74d5936783cd6d193b4d464 Mon Sep 17 00:00:00 2001 From: Niko233 <139348239+NikoCat233@users.noreply.github.com> Date: Mon, 24 Jun 2024 18:00:09 +0800 Subject: [PATCH 3/9] Add handler for phantom rpcs --- .../Inner/Objects/InnerPlayerControl.Api.cs | 13 ++++ .../Net/Inner/Objects/InnerPlayerControl.cs | 68 +++++++++++++++++-- 2 files changed, 75 insertions(+), 6 deletions(-) diff --git a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.Api.cs b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.Api.cs index bd711618..f1b47451 100644 --- a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.Api.cs +++ b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.Api.cs @@ -148,5 +148,18 @@ namespace Impostor.Server.Net.Inner.Objects // Notify plugins. await _eventManager.CallAsync(new PlayerExileEvent(Game, Game.GetClientPlayer(OwnerId)!, this)); } + + public async ValueTask StartVanishAsync() + { + using var writer = Game.StartRpc(NetId, RpcCalls.StartVanish); + await Game.FinishRpcAsync(writer); + } + + public async ValueTask StartAppearAsync(bool shouldAnimate) + { + using var writer = Game.StartRpc(NetId, RpcCalls.StartAppear); + writer.Write(shouldAnimate); + await Game.FinishRpcAsync(writer); + } } } diff --git a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs index a105b83f..76297045 100644 --- a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs +++ b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs @@ -536,7 +536,7 @@ namespace Impostor.Server.Net.Inner.Objects } Rpc62CheckVanish.Deserialize(reader, out var maxDuration); - break; + return await HandleCheckVanish(sender, maxDuration); } case RpcCalls.StartVanish: @@ -548,7 +548,7 @@ namespace Impostor.Server.Net.Inner.Objects } Rpc63StartVanish.Deserialize(reader); - break; + return await HandleStartVanish(sender); } case RpcCalls.CheckAppear: @@ -560,8 +560,8 @@ namespace Impostor.Server.Net.Inner.Objects return false; } - Rpc64CheckAppear.Deserialize(reader, out bool shouldAnimate); - break; + Rpc64CheckAppear.Deserialize(reader, out var shouldAnimate); + return await HandleCheckAppear(sender, shouldAnimate); } case RpcCalls.StartAppear: @@ -572,8 +572,8 @@ namespace Impostor.Server.Net.Inner.Objects return false; } - Rpc65StartAppear.Deserialize(reader, out _); - break; + Rpc65StartAppear.Deserialize(reader, out var shouldAnimate); + return await HandleStartAppear(sender, shouldAnimate); } default: @@ -1058,5 +1058,61 @@ namespace Impostor.Server.Net.Inner.Objects return true; } + + private async ValueTask HandleCheckVanish(ClientPlayer sender, float maxDuration) + { + // TODO: Check if operation is taking place during lobby/meetings + // TODO: Check max duration + // If the game is host authoritive, the RPC is handled by the host, otherwise by the server + if (_game.IsHostAuthoritive) + { + return true; + } + else + { + await StartVanishAsync(); + return false; + } + } + + private async ValueTask HandleStartVanish(ClientPlayer sender) + { + if (!_game.IsHostAuthoritive) + { + if (await sender.Client.ReportCheatAsync(RpcCalls.StartVanish, CheatCategory.GameFlow, "Client tried to send StartVanish directly")) + { + return false; + } + } + + return true; + } + + private async ValueTask HandleCheckAppear(ClientPlayer sender, bool shouldAnimate) + { + // If the game is host authoritive, the RPC is handled by the host, otherwise by the server + if (_game.IsHostAuthoritive) + { + return true; + } + else + { + await StartAppearAsync(shouldAnimate); + return false; + } + } + + private async ValueTask HandleStartAppear(ClientPlayer sender, bool shouldAnimate) + { + if (!_game.IsHostAuthoritive) + { + if (await sender.Client.ReportCheatAsync(RpcCalls.StartAppear, CheatCategory.GameFlow, "Client tried to send StartAppear directly")) + { + return false; + } + } + + return true; + } } } From 6b8a32d09018f5014d5b92aa4ae025565fa7f50f Mon Sep 17 00:00:00 2001 From: miniduikboot Date: Sun, 30 Jun 2024 23:29:01 +0200 Subject: [PATCH 4/9] Add extra debug log statements To help debugging Impostor, this commit prints a message every time a GameData message arrives, and one more if it's an Rpc or a Data update. Most other interesting Gamedata tags already have log statements attached, but these two didn't as they're very frequent. They only show up when the Debug verbosity is used, which production servers shouldn't have enabled. --- src/Impostor.Server/Net/State/Game.Data.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Impostor.Server/Net/State/Game.Data.cs b/src/Impostor.Server/Net/State/Game.Data.cs index 65632d50..1a76ff3f 100644 --- a/src/Impostor.Server/Net/State/Game.Data.cs +++ b/src/Impostor.Server/Net/State/Game.Data.cs @@ -83,6 +83,8 @@ namespace Impostor.Server.Net.State { using var reader = parent.ReadMessage(); + _logger.LogTrace("Client {SenderId} sent GameData {Tag}", sender.Client.Id, reader.Tag); + switch (reader.Tag) { case GameDataTag.DataFlag: @@ -90,6 +92,7 @@ namespace Impostor.Server.Net.State var netId = reader.ReadPackedUInt32(); if (_allObjectsFast.TryGetValue(netId, out var obj)) { + _logger.LogTrace("Received Data for {NetId}, which is of type {Type}", netId, obj.GetType().Name); await obj.DeserializeAsync(sender, target, reader, false); } else @@ -105,7 +108,15 @@ namespace Impostor.Server.Net.State var netId = reader.ReadPackedUInt32(); if (_allObjectsFast.TryGetValue(netId, out var obj)) { - if (!await obj.HandleRpcAsync(sender, target, (RpcCalls)reader.ReadByte(), reader)) + var call = (RpcCalls)reader.ReadByte(); + _logger.LogTrace( + "Client {SenderId} called Rpc {Call} on NetId {CallerId} and sent it to {Target}", + sender.Client.Id, + call, + obj.NetId, + target?.Client.Id.ToString() ?? "everyone"); + + if (!await obj.HandleRpcAsync(sender, target, call, reader)) { parent.RemoveMessage(reader); continue; From 9a6d98dd95ea7dcce5552e6ed05197e04eb585c7 Mon Sep 17 00:00:00 2001 From: miniduikboot Date: Thu, 8 Aug 2024 21:31:07 +0200 Subject: [PATCH 5/9] Add scene param to forbidden scene log statement I'm seeing multiple warnings in my server log about forbidden scene changes. Unfortunately this warning does not include the actual scene that was triggering the issue, which makes diagnosis/remediation difficult. Additionally change this logging call to use names suitable for structured logging. --- src/Impostor.Server/Net/State/Game.Data.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Impostor.Server/Net/State/Game.Data.cs b/src/Impostor.Server/Net/State/Game.Data.cs index 65632d50..5f0e56cc 100644 --- a/src/Impostor.Server/Net/State/Game.Data.cs +++ b/src/Impostor.Server/Net/State/Game.Data.cs @@ -245,9 +245,10 @@ namespace Impostor.Server.Net.State if (scene != "OnlineGame") { _logger.LogWarning( - "Player {0} ({1}) tried to send SceneChangeFlag with disallowed scene.", + "Player {PlayerName} ({ClientId}) tried to send SceneChangeFlag with disallowed scene \"{Scene}\".", sender.Client.Name, - sender.Client.Id); + sender.Client.Id, + scene); return false; } From aec3357e7b81a80ae19991200901ac5c16d01e66 Mon Sep 17 00:00:00 2001 From: miniduikboot Date: Thu, 20 Jun 2024 22:26:16 +0200 Subject: [PATCH 6/9] Clean up owned playerinfo's when disconnecting This commit does what official servers do: - despawn the PlayerInfo if someone disconnects while in the lobby - despawn all PlayerInfos at the end of a game when the EndGame screen is shown. At this point a lot of other netobjects are destroyed as well, with the PlayerInfos generally done first. --- src/Impostor.Server/Net/State/Game.Data.cs | 12 ++++++++++++ src/Impostor.Server/Net/State/Game.Incoming.cs | 7 +++++++ src/Impostor.Server/Net/State/Game.Outgoing.cs | 9 +++++++++ src/Impostor.Server/Net/State/Game.State.cs | 7 +++++++ 4 files changed, 35 insertions(+) diff --git a/src/Impostor.Server/Net/State/Game.Data.cs b/src/Impostor.Server/Net/State/Game.Data.cs index a3ce69f2..2c5e7023 100644 --- a/src/Impostor.Server/Net/State/Game.Data.cs +++ b/src/Impostor.Server/Net/State/Game.Data.cs @@ -510,6 +510,18 @@ namespace Impostor.Server.Net.State await SendObjectSpawnAsync(playerInfo); } + private async ValueTask DespawnPlayerInfoAsync(InnerPlayerInfo playerInfo) + { + if (playerInfo.OwnerId == ServerOwned) + { + _logger.LogDebug("Despawning PlayerInfo {nid}", playerInfo.NetId); + GameNet.GameData.RemovePlayer(playerInfo.PlayerId); + RemoveNetObject(playerInfo); + + await SendObjectDespawnAsync(playerInfo); + } + } + private bool AddNetObject(InnerNetObject obj) { if (_allObjectsFast.ContainsKey(obj.NetId)) diff --git a/src/Impostor.Server/Net/State/Game.Incoming.cs b/src/Impostor.Server/Net/State/Game.Incoming.cs index 3d637e4d..cce27c8d 100644 --- a/src/Impostor.Server/Net/State/Game.Incoming.cs +++ b/src/Impostor.Server/Net/State/Game.Incoming.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Impostor.Api.Games; @@ -43,6 +44,12 @@ namespace Impostor.Server.Net.State player.Value.Limbo = LimboStates.PreSpawn; } + // Delete all PlayerInfo objects + foreach (var playerInfo in GameNet.GameData.Players.Values.ToList()) + { + await DespawnPlayerInfoAsync(playerInfo); + } + await _eventManager.CallAsync(new GameEndedEvent(this, gameOverReason)); } diff --git a/src/Impostor.Server/Net/State/Game.Outgoing.cs b/src/Impostor.Server/Net/State/Game.Outgoing.cs index 1ab0086e..7e672547 100644 --- a/src/Impostor.Server/Net/State/Game.Outgoing.cs +++ b/src/Impostor.Server/Net/State/Game.Outgoing.cs @@ -134,5 +134,14 @@ namespace Impostor.Server.Net.State writer.EndMessage(); await FinishGameDataAsync(writer, targetClientId); } + + private ValueTask SendObjectDespawnAsync(InnerNetObject obj, int? targetClientId = null) + { + using var writer = StartGameData(targetClientId); + writer.StartMessage(GameDataTag.DespawnFlag); + writer.WritePacked(obj.NetId); + writer.EndMessage(); + return FinishGameDataAsync(writer, targetClientId); + } } } diff --git a/src/Impostor.Server/Net/State/Game.State.cs b/src/Impostor.Server/Net/State/Game.State.cs index e570c98f..fcd2c879 100644 --- a/src/Impostor.Server/Net/State/Game.State.cs +++ b/src/Impostor.Server/Net/State/Game.State.cs @@ -85,6 +85,13 @@ namespace Impostor.Server.Net.State } }); + // Clean up the PlayerInfo if we own it and we're still in the lobby + if (GameState == GameStates.NotStarted) + { + var playerInfo = GameNet.GameData.PlayersByClientId[playerId]; + await DespawnPlayerInfoAsync(playerInfo); + } + return true; } From 525af858487ef9cd071d16a79581c07c72641bca Mon Sep 17 00:00:00 2001 From: miniduikboot Date: Sun, 18 Aug 2024 11:16:44 +0200 Subject: [PATCH 7/9] Switch PlayerName writes to Outfit In 2024.6.18 Innersloth changed the storage location of the PlayerName from PlayerInfo to PlayerOutfit. If we don't set the PlayerName, we'll send an incomplete PlayerInfo to new players, so new players are unable to stay connected as they will not receive complete PlayerInfo objects before a timeout in the client activates. Because we publish PlayerName in the API, we can't completely remove the PlayerName field from PlayerInfo. We also use it internally in a few places, so I don't this is worth removing. --- src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.Api.cs | 2 +- src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs | 2 +- src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.Api.cs b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.Api.cs index bd711618..46bb7094 100644 --- a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.Api.cs +++ b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.Api.cs @@ -20,7 +20,7 @@ namespace Impostor.Server.Net.Inner.Objects public async ValueTask SetNameAsync(string name) { - PlayerInfo.PlayerName = name; + PlayerInfo.CurrentOutfit.PlayerName = name; using var writer = Game.StartRpc(NetId, RpcCalls.SetName); writer.Write(name); diff --git a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs index 16fa42ed..113226d6 100644 --- a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs +++ b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs @@ -686,7 +686,7 @@ namespace Impostor.Server.Net.Inner.Objects } } - PlayerInfo.PlayerName = name; + PlayerInfo.CurrentOutfit.PlayerName = name; return true; } diff --git a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.cs b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.cs index 498f8f6b..d37496d6 100644 --- a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.cs +++ b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.cs @@ -29,7 +29,7 @@ namespace Impostor.Server.Net.Inner.Objects public int ClientId { get; internal set; } - public string PlayerName { get; internal set; } = string.Empty; + public string PlayerName => CurrentOutfit.PlayerName; public Dictionary Outfits { get; } = new() { From bd42ba80b20efcd165bcf58d82c0b36ad21844e7 Mon Sep 17 00:00:00 2001 From: miniduikboot Date: Fri, 21 Jun 2024 23:05:07 +0200 Subject: [PATCH 8/9] Sync over tasks As tasks used to be part of the GameData, 2024.6.18 has moved them to the PlayerInfo. Because they are now called on the PlayerInfo of the correct player, the playerId field in the RPC is no longer necessary. --- .../Net/Messages/Rpcs/Rpc29SetTasks.cs | 6 +- .../Objects/InnerGameData.TaskInfo.Api.cs | 39 ----------- .../Inner/Objects/InnerGameData.TaskInfo.cs | 41 ----------- .../Net/Inner/Objects/InnerPlayerInfo.cs | 43 +++++++++++- .../Net/Inner/Objects/TaskInfo.cs | 68 +++++++++++++++++++ 5 files changed, 111 insertions(+), 86 deletions(-) delete mode 100644 src/Impostor.Server/Net/Inner/Objects/InnerGameData.TaskInfo.Api.cs delete mode 100644 src/Impostor.Server/Net/Inner/Objects/InnerGameData.TaskInfo.cs create mode 100644 src/Impostor.Server/Net/Inner/Objects/TaskInfo.cs diff --git a/src/Impostor.Api/Net/Messages/Rpcs/Rpc29SetTasks.cs b/src/Impostor.Api/Net/Messages/Rpcs/Rpc29SetTasks.cs index aba9a715..eb2bab49 100644 --- a/src/Impostor.Api/Net/Messages/Rpcs/Rpc29SetTasks.cs +++ b/src/Impostor.Api/Net/Messages/Rpcs/Rpc29SetTasks.cs @@ -4,15 +4,13 @@ namespace Impostor.Api.Net.Messages.Rpcs { public static class Rpc29SetTasks { - public static void Serialize(IMessageWriter writer, byte playerId, ReadOnlyMemory taskTypeIds) + public static void Serialize(IMessageWriter writer, ReadOnlyMemory taskTypeIds) { - writer.Write(playerId); writer.Write(taskTypeIds); } - public static void Deserialize(IMessageReader reader, out byte playerId, out ReadOnlyMemory taskTypeIds) + public static void Deserialize(IMessageReader reader, out ReadOnlyMemory taskTypeIds) { - playerId = reader.ReadByte(); taskTypeIds = reader.ReadBytesAndSize(); } } diff --git a/src/Impostor.Server/Net/Inner/Objects/InnerGameData.TaskInfo.Api.cs b/src/Impostor.Server/Net/Inner/Objects/InnerGameData.TaskInfo.Api.cs deleted file mode 100644 index c8aa17fa..00000000 --- a/src/Impostor.Server/Net/Inner/Objects/InnerGameData.TaskInfo.Api.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System.Threading.Tasks; -using Impostor.Api; -using Impostor.Api.Net.Inner; -using Impostor.Api.Net.Messages.Rpcs; -using Impostor.Server.Events.Player; - -namespace Impostor.Server.Net.Inner.Objects -{ - internal partial class InnerGameData - { - public partial class TaskInfo - { - public async ValueTask CompleteAsync() - { - if (_playerInfo.Controller == null) - { - throw new ImpostorException("Can't complete a task that doesn't have a player assigned"); - } - - var player = _playerInfo.Controller; - - if (Complete) - { - throw new ImpostorException("Can't complete a task that is already completed"); - } - - Complete = true; - - // Send RPC. - using var writer = player.Game.StartRpc(player.NetId, RpcCalls.CompleteTask); - Rpc01CompleteTask.Serialize(writer, Id); - await player.Game.FinishRpcAsync(writer); - - // Notify plugins. - await _eventManager.CallAsync(new PlayerCompletedTaskEvent(player.Game, player.Game.GetClientPlayer(player.OwnerId)!, player, this)); - } - } - } -} diff --git a/src/Impostor.Server/Net/Inner/Objects/InnerGameData.TaskInfo.cs b/src/Impostor.Server/Net/Inner/Objects/InnerGameData.TaskInfo.cs deleted file mode 100644 index b4a96209..00000000 --- a/src/Impostor.Server/Net/Inner/Objects/InnerGameData.TaskInfo.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Impostor.Api.Events.Managers; -using Impostor.Api.Innersloth.Maps; -using Impostor.Api.Net.Inner.Objects; - -namespace Impostor.Server.Net.Inner.Objects -{ - internal partial class InnerGameData - { - public partial class TaskInfo : ITaskInfo - { - private readonly InnerPlayerInfo _playerInfo; - private readonly IEventManager _eventManager; - - public TaskInfo(InnerPlayerInfo playerInfo, IEventManager eventManager, uint id, TaskData? task) - { - _playerInfo = playerInfo; - _eventManager = eventManager; - Id = id; - Task = task; - } - - public uint Id { get; internal set; } - - public TaskData? Task { get; internal set; } - - public bool Complete { get; internal set; } - - public void Serialize(IMessageWriter writer) - { - writer.WritePacked(Id); - writer.Write(Complete); - } - - public void Deserialize(IMessageReader reader) - { - Id = reader.ReadPackedUInt32(); - Complete = reader.ReadBoolean(); - } - } - } -} diff --git a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.cs b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.cs index 498f8f6b..1a9de063 100644 --- a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.cs +++ b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.cs @@ -1,12 +1,15 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using Impostor.Api.Events.Managers; using Impostor.Api.Games; using Impostor.Api.Innersloth; using Impostor.Api.Innersloth.Customization; using Impostor.Api.Innersloth.GameOptions; using Impostor.Api.Net; using Impostor.Api.Net.Custom; +using Impostor.Api.Net.Inner; +using Impostor.Api.Net.Messages.Rpcs; using Impostor.Api.Utils; using Impostor.Server.Net.State; using Microsoft.Extensions.Logging; @@ -15,11 +18,13 @@ namespace Impostor.Server.Net.Inner.Objects { internal partial class InnerPlayerInfo { + private readonly IEventManager _eventManager; private readonly ILogger _logger; - public InnerPlayerInfo(ICustomMessageManager customMessageManager, Game game, ILogger logger) : base(customMessageManager, game) + public InnerPlayerInfo(ICustomMessageManager customMessageManager, IEventManager eventManager, Game game, ILogger logger) : base(customMessageManager, game) { Components.Add(this); + _eventManager = eventManager; _logger = logger; } @@ -54,7 +59,7 @@ namespace Impostor.Server.Net.Inner.Objects public DeathReason LastDeathReason { get; internal set; } - public List Tasks { get; internal set; } = new List(0); + public List Tasks { get; internal set; } = new List(0); public DateTimeOffset LastMurder { get; set; } @@ -176,5 +181,39 @@ namespace Impostor.Server.Net.Inner.Objects return ValueTask.CompletedTask; } + + public override async ValueTask HandleRpcAsync(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader) + { + switch (call) + { + case RpcCalls.SetTasks: + Rpc29SetTasks.Deserialize(reader, out var taskTypeIds); + SetTasks(taskTypeIds); + break; + + default: + return await base.HandleRpcAsync(sender, target, call, reader); + } + + return true; + } + + private void SetTasks(ReadOnlyMemory taskTypeIds) + { + if (Disconnected) + { + return; + } + + Tasks = new List(taskTypeIds.Length); + + var taskId = 0u; + foreach (var taskTypeId in taskTypeIds.Span) + { + var mapTasks = Game.GameNet!.ShipStatus?.Data.Tasks; + var taskType = (mapTasks != null && mapTasks.ContainsKey(taskTypeId)) ? mapTasks[taskTypeId] : null; + Tasks.Add(new TaskInfo(this, _eventManager, taskId++, taskType)); + } + } } } diff --git a/src/Impostor.Server/Net/Inner/Objects/TaskInfo.cs b/src/Impostor.Server/Net/Inner/Objects/TaskInfo.cs new file mode 100644 index 00000000..c04c27d9 --- /dev/null +++ b/src/Impostor.Server/Net/Inner/Objects/TaskInfo.cs @@ -0,0 +1,68 @@ +using System.Threading.Tasks; +using Impostor.Api; +using Impostor.Api.Events.Managers; +using Impostor.Api.Innersloth.Maps; +using Impostor.Api.Net.Inner; +using Impostor.Api.Net.Inner.Objects; +using Impostor.Api.Net.Messages.Rpcs; +using Impostor.Server.Events.Player; + +namespace Impostor.Server.Net.Inner.Objects +{ + internal class TaskInfo : ITaskInfo + { + private readonly InnerPlayerInfo _playerInfo; + private readonly IEventManager _eventManager; + + public TaskInfo(InnerPlayerInfo playerInfo, IEventManager eventManager, uint id, TaskData? task) + { + _playerInfo = playerInfo; + _eventManager = eventManager; + Id = id; + Task = task; + } + + public uint Id { get; internal set; } + + public TaskData? Task { get; internal set; } + + public bool Complete { get; internal set; } + + public void Serialize(IMessageWriter writer) + { + writer.WritePacked(Id); + writer.Write(Complete); + } + + public void Deserialize(IMessageReader reader) + { + Id = reader.ReadPackedUInt32(); + Complete = reader.ReadBoolean(); + } + + public async ValueTask CompleteAsync() + { + if (_playerInfo.Controller == null) + { + throw new ImpostorException("Can't complete a task that doesn't have a player assigned"); + } + + var player = _playerInfo.Controller; + + if (Complete) + { + throw new ImpostorException("Can't complete a task that is already completed"); + } + + Complete = true; + + // Send RPC. + using var writer = player.Game.StartRpc(player.NetId, RpcCalls.CompleteTask); + Rpc01CompleteTask.Serialize(writer, Id); + await player.Game.FinishRpcAsync(writer); + + // Notify plugins. + await _eventManager.CallAsync(new PlayerCompletedTaskEvent(player.Game, player.Game.GetClientPlayer(player.OwnerId)!, player, this)); + } + } +} From 79410f2b96a115cf59d733113da8793478698b52 Mon Sep 17 00:00:00 2001 From: miniduikboot Date: Fri, 2 Aug 2024 11:25:52 +0200 Subject: [PATCH 9/9] Set PlayerLevel in PlayerInfo on RPC SetLevel Previously this used to be done by the host, but this is now the server's responsibility. --- .../Net/Inner/Objects/InnerPlayerControl.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs index 16fa42ed..f1478722 100644 --- a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs +++ b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs @@ -251,7 +251,7 @@ namespace Impostor.Server.Net.Inner.Objects } Rpc38SetLevel.Deserialize(reader, out var level); - return true; + return await HandleSetLevel(sender, level); } case RpcCalls.ReportDeadBody: @@ -834,6 +834,19 @@ namespace Impostor.Server.Net.Inner.Objects return true; } + private async ValueTask HandleSetLevel(ClientPlayer sender, uint level) + { + if (Game.GameState == GameStates.Started && + await sender.Client.ReportCheatAsync(RpcCalls.SetLevel, CheatCategory.GameFlow, "Client tried to set level while not in game")) + { + return false; + } + + PlayerInfo.PlayerLevel = level; + + return true; + } + private async ValueTask HandleCheckMurder(ClientPlayer sender, InnerPlayerControl? target) { if (!PlayerInfo.CanMurder(Game, _dateTimeProvider))