From bf5a2c2be593b3c9e887defee8db4f7954cd9ee8 Mon Sep 17 00:00:00 2001 From: miniduikboot Date: Tue, 18 Jun 2024 17:30:16 +0200 Subject: [PATCH 01/21] Prepare for 2024.6.18 update Major changes are incoming, this doesn't yet work. --- src/Directory.Build.props | 2 +- src/Impostor.Server/Net/Manager/CompatibilityManager.cs | 8 +------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 4cbf4f4e..1fb11787 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -2,7 +2,7 @@ net8.0 11 - 1.9.3 + 1.10.0 dev true true diff --git a/src/Impostor.Server/Net/Manager/CompatibilityManager.cs b/src/Impostor.Server/Net/Manager/CompatibilityManager.cs index f339792c..c6fa7efd 100644 --- a/src/Impostor.Server/Net/Manager/CompatibilityManager.cs +++ b/src/Impostor.Server/Net/Manager/CompatibilityManager.cs @@ -17,13 +17,7 @@ internal class CompatibilityManager : ICompatibilityManager { new[] { - new GameVersion(2023, 10, 1), // 2023.10.24 - new GameVersion(2023, 11, 15), // 2023.11.28 - }, - new[] - { - new GameVersion(2024, 2, 1), // 2024.3.5 - new GameVersion(2024, 2, 3), // 2024.6.4 + new GameVersion(2024, 3, 1), // 2024.6.18 }, }; From 0e7372d9d9544ed771bfbb18eb73fce4a2f05136 Mon Sep 17 00:00:00 2001 From: miniduikboot Date: Tue, 18 Jun 2024 18:07:02 +0200 Subject: [PATCH 02/21] Update Dumpostor to v1.0.1 and run for 6.18 Add the RulesPresets and SpecialGamemodes enums --- .../SourceGenerator.cs | 2 + .../Innersloth/Data/SpawnableObjects.json | 3 +- .../Innersloth/Data/enums/RoleTypes.json | 5 +- .../Innersloth/Data/enums/RpcCalls.json | 6 +- .../Innersloth/Data/enums/RulesPresets.json | 6 ++ .../Data/enums/SanctionReasons.json | 23 ++++++ .../Data/enums/SpecialGameModes.json | 4 + .../Innersloth/Data/enums/StringNames.json | 82 ++++++++++++++++++- src/Impostor.Api/Innersloth/Data/info.json | 4 +- 9 files changed, 129 insertions(+), 6 deletions(-) create mode 100644 src/Impostor.Api/Innersloth/Data/enums/RulesPresets.json create mode 100644 src/Impostor.Api/Innersloth/Data/enums/SanctionReasons.json create mode 100644 src/Impostor.Api/Innersloth/Data/enums/SpecialGameModes.json diff --git a/src/Impostor.Api.Innersloth.Generator/SourceGenerator.cs b/src/Impostor.Api.Innersloth.Generator/SourceGenerator.cs index bf7f0052..f385006e 100644 --- a/src/Impostor.Api.Innersloth.Generator/SourceGenerator.cs +++ b/src/Impostor.Api.Innersloth.Generator/SourceGenerator.cs @@ -79,6 +79,8 @@ public sealed class SourceGenerator : IIncrementalGenerator enumGenerator.Generate("GameOverReason", underlyingType: CSharpEnumUnderlyingType.Byte); enumGenerator.Generate("Platforms"); enumGenerator.Generate("RoleTypes", underlyingType: CSharpEnumUnderlyingType.UnsignedShort); + enumGenerator.Generate("RulesPresets", underlyingType: CSharpEnumUnderlyingType.Byte); + enumGenerator.Generate("SpecialGameModes", underlyingType: CSharpEnumUnderlyingType.Byte); enumGenerator.Generate("StringNames"); enumGenerator.Generate("SystemTypes", underlyingType: CSharpEnumUnderlyingType.Byte); enumGenerator.Generate("Language", sourceName: "SupportedLangs"); diff --git a/src/Impostor.Api/Innersloth/Data/SpawnableObjects.json b/src/Impostor.Api/Innersloth/Data/SpawnableObjects.json index f2b7f74a..c001c939 100644 --- a/src/Impostor.Api/Innersloth/Data/SpawnableObjects.json +++ b/src/Impostor.Api/Innersloth/Data/SpawnableObjects.json @@ -2,7 +2,6 @@ "0": "SkeldShipStatus", "1": "MeetingHud", "2": "LobbyBehaviour", - "3": "GameData", "4": "PlayerControl", "5": "MiraShipStatus", "6": "PolusShipStatus", @@ -10,5 +9,7 @@ "8": "AirshipStatus", "9": "HideAndSeekManager", "10": "NormalGameManager", + "11": "NetworkedPlayerInfo", + "12": "VoteBanSystem", "13": "FungleShipStatus" } \ No newline at end of file diff --git a/src/Impostor.Api/Innersloth/Data/enums/RoleTypes.json b/src/Impostor.Api/Innersloth/Data/enums/RoleTypes.json index c29784fd..8fe73d0b 100644 --- a/src/Impostor.Api/Innersloth/Data/enums/RoleTypes.json +++ b/src/Impostor.Api/Innersloth/Data/enums/RoleTypes.json @@ -6,5 +6,8 @@ "GuardianAngel": 4, "Shapeshifter": 5, "CrewmateGhost": 6, - "ImpostorGhost": 7 + "ImpostorGhost": 7, + "Noisemaker": 8, + "Phantom": 9, + "Tracker": 10 } \ No newline at end of file diff --git a/src/Impostor.Api/Innersloth/Data/enums/RpcCalls.json b/src/Impostor.Api/Innersloth/Data/enums/RpcCalls.json index eabc4fa0..8f8b875f 100644 --- a/src/Impostor.Api/Innersloth/Data/enums/RpcCalls.json +++ b/src/Impostor.Api/Innersloth/Data/enums/RpcCalls.json @@ -55,5 +55,9 @@ "CheckShapeshift": 55, "RejectShapeshift": 56, "LobbyTimeExpiring": 60, - "ExtendLobbyTimer": 61 + "ExtendLobbyTimer": 61, + "CheckVanish": 62, + "StartVanish": 63, + "CheckAppear": 64, + "StartAppear": 65 } \ No newline at end of file diff --git a/src/Impostor.Api/Innersloth/Data/enums/RulesPresets.json b/src/Impostor.Api/Innersloth/Data/enums/RulesPresets.json new file mode 100644 index 00000000..fef02fec --- /dev/null +++ b/src/Impostor.Api/Innersloth/Data/enums/RulesPresets.json @@ -0,0 +1,6 @@ +{ + "Standard": 0, + "StandardRoles": 1, + "Flashlight": 2, + "Custom": 100 +} \ No newline at end of file diff --git a/src/Impostor.Api/Innersloth/Data/enums/SanctionReasons.json b/src/Impostor.Api/Innersloth/Data/enums/SanctionReasons.json new file mode 100644 index 00000000..6dcd0586 --- /dev/null +++ b/src/Impostor.Api/Innersloth/Data/enums/SanctionReasons.json @@ -0,0 +1,23 @@ +{ + "None": 0, + "ImpersonationCeleb": 1, + "Spamming": 2, + "InappropriateNameUnsportsmanlike": 3, + "UnsportsmanlikeConduct": 4, + "ImpersonationDevelopers": 5, + "InappropriateChatPersonalInfo": 6, + "InappropriateNameDerogatory": 7, + "InappropriateNameNsfw": 8, + "Bullying": 9, + "CheatingHacking": 10, + "InappropriateChatDating": 11, + "WeaponizingRules": 12, + "RepeatOffender3": 13, + "SexualMisconduct": 14, + "Doxing": 15, + "IllegalActivity": 16, + "Harassment": 17, + "SelfHarmPromotion": 18, + "RepeatOffender10": 19, + "InvalidUser": 20 +} \ No newline at end of file diff --git a/src/Impostor.Api/Innersloth/Data/enums/SpecialGameModes.json b/src/Impostor.Api/Innersloth/Data/enums/SpecialGameModes.json new file mode 100644 index 00000000..a0d68890 --- /dev/null +++ b/src/Impostor.Api/Innersloth/Data/enums/SpecialGameModes.json @@ -0,0 +1,4 @@ +{ + "None": 0, + "AprilFools": 1 +} \ No newline at end of file diff --git a/src/Impostor.Api/Innersloth/Data/enums/StringNames.json b/src/Impostor.Api/Innersloth/Data/enums/StringNames.json index daadc2f2..07dbe056 100644 --- a/src/Impostor.Api/Innersloth/Data/enums/StringNames.json +++ b/src/Impostor.Api/Innersloth/Data/enums/StringNames.json @@ -570,10 +570,39 @@ "RolesHelp_Engineer_02": 1563, "RolesHelp_GuardianAngel_01": 1564, "RolesHelp_GuardianAngel_02": 1565, + "RolesHelp_Noisemaker_01": 1566, + "RolesHelp_Noisemaker_02": 1567, + "RolesHelp_Tracker_01": 1568, + "RolesHelp_Tracker_02": 1569, "RolesHelp_Shapeshifter_01": 1600, "RolesHelp_Shapeshifter_02": 1601, + "RolesHelp_Phantom_01": 1602, + "RolesHelp_Phantom_02": 1603, "RolesHelpOutro_01": 1650, "RolesHelpOutro_02": 1651, + "NoisemakerRole": 1660, + "NoisemakerBlurb": 1661, + "NoisemakerBlurbMed": 1662, + "NoisemakerBlurbLong": 1663, + "NoisemakerImpostorAlert": 1664, + "NoisemakerAlertDuration": 1665, + "PhantomAbility": 1670, + "PhantomRole": 1671, + "PhantomBlurb": 1672, + "PhantomBlurbMed": 1673, + "PhantomBlurbLong": 1674, + "PhantomCooldown": 1675, + "PhantomDuration": 1676, + "PhantomAbilityUndo": 1677, + "TrackerAbility": 1680, + "TrackerRole": 1681, + "TrackerBlurb": 1682, + "TrackerBlurbMed": 1683, + "TrackerBlurbLong": 1684, + "TrackerCooldown": 1685, + "TrackerDuration": 1686, + "TrackerAbilityUndo": 1687, + "TrackerDelay": 1688, "SanctionDuration": 1700, "SanctionPermanent": 1701, "SanctionConduct": 1702, @@ -1304,6 +1333,24 @@ "ConfirmDeleteAccountsEmpty": 3209, "AccountRequestDelete": 3210, "HasBeenKilled": 3211, + "RoomSettingsLabel": 3280, + "ViewLabel": 3281, + "RoomCodeLabel": 3282, + "ModeLabel": 3283, + "PrivacyLabel": 3284, + "CapacityLabel": 3285, + "GameSettingsLabel": 3286, + "RoleSettingsLabel": 3287, + "GamePresetsLabel": 3288, + "GamePresetsDescription": 3289, + "StandardRulesPresetDescription": 3290, + "GameModeStandard": 3291, + "GameModeStandardRoles": 3292, + "GameModeCustom": 3293, + "ReturnToLobby": 3294, + "GameModeFlashlight": 3295, + "HostNounLabel": 3296, + "RoleSettingsAll": 3297, "ScrollList": 3300, "ScrollNews": 3301, "AmongUsAnnouncements": 3302, @@ -1331,6 +1378,33 @@ "ReportNotificationBody": 3324, "MissingGuardianEmailText": 3325, "AprilFoolsModeToggle": 3326, + "LobbyChangeSettingNotification": 3327, + "ImpostorsCategory": 3350, + "RolesCategory": 3351, + "MeetingsCategory": 3352, + "PlayersCategory": 3353, + "TasksCategory": 3354, + "GameSettingsDescription": 3355, + "RoleSettingsDescription": 3356, + "InfoLabel": 3357, + "RoleQuotaLabel": 3358, + "OverviewCategory": 3359, + "CrewmateRolesHeader": 3360, + "ImpostorRolesHeader": 3361, + "ViewingLabel": 3362, + "LocalLobbyLabel": 3363, + "NumberRoleHeader": 3364, + "ChanceRoleHeader": 3365, + "WaitingForPlayers": 3366, + "HideAndSeekModeDescription": 3367, + "RolesRulesPresetDescription": 3368, + "FlashlightRulesPresetDescription": 3369, + "RoomCodeCopied": 3370, + "FinalHideCategory": 3371, + "LobbyChangeSettingNotificationRole": 3372, + "StandardRulesPresetDescriptionHideAndSeek": 3373, + "GameModeStandardSeek": 3374, + "HostYouLabel": 3375, "MiningPit": 3400, "Jungle": 3401, "BuildSandcastle": 3402, @@ -1399,5 +1473,11 @@ "HttpErrorBadGateway": 3531, "HttpErrorServiceUnavailable": 3532, "HttpErrorGatewayTimeout": 3533, - "UserVersionError": 3534 + "UserVersionError": 3534, + "QCLobbyRoleNoisemakers": 3535, + "QCLobbyRoleTrackers": 3536, + "QCLobbyRolePhantoms": 3537, + "QCSelfWasTracking": 3538, + "QCSelfGoingToNoisemaker": 3539, + "QCSelfSawRoleInLocation": 3540 } \ No newline at end of file diff --git a/src/Impostor.Api/Innersloth/Data/info.json b/src/Impostor.Api/Innersloth/Data/info.json index f6f520b4..addce78b 100644 --- a/src/Impostor.Api/Innersloth/Data/info.json +++ b/src/Impostor.Api/Innersloth/Data/info.json @@ -1,5 +1,5 @@ { - "dumpostorVersion": "1.0.0", - "gameVersion": "2024.3.5", + "dumpostorVersion": "1.0.1", + "gameVersion": "2024.6.18", "platformType": "StandaloneItch" } From c315ef9c2881d6849aab14fbd1f9a72d5bfc0ee2 Mon Sep 17 00:00:00 2001 From: miniduikboot Date: Tue, 23 Jul 2024 21:34:28 +0200 Subject: [PATCH 03/21] VoteBanSystem now contains itself as a component This makes it consistent with the other spawnables. --- .../Net/Inner/Objects/Components/InnerVoteBanSystem.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Impostor.Server/Net/Inner/Objects/Components/InnerVoteBanSystem.cs b/src/Impostor.Server/Net/Inner/Objects/Components/InnerVoteBanSystem.cs index 68abcbe2..8b20d73a 100644 --- a/src/Impostor.Server/Net/Inner/Objects/Components/InnerVoteBanSystem.cs +++ b/src/Impostor.Server/Net/Inner/Objects/Components/InnerVoteBanSystem.cs @@ -21,6 +21,7 @@ namespace Impostor.Server.Net.Inner.Objects.Components { _logger = logger; _votes = new Dictionary(); + Components.Add(this); } public override ValueTask SerializeAsync(IMessageWriter writer, bool initialState) From 079f58a7b7f15b357531c2782aef3547c9442e74 Mon Sep 17 00:00:00 2001 From: miniduikboot Date: Tue, 18 Jun 2024 19:45:17 +0200 Subject: [PATCH 04/21] Implement GameOptions V8 This adds a field for RulesPresets and SpecialGameModes, as well as RoleOptions for the new roles. --- .../GameOptions/HideNSeekGameOptions.cs | 24 ++++++++++-- .../Innersloth/GameOptions/IGameOptions.cs | 12 +++++- .../GameOptions/LegacyGameOptionsData.cs | 6 +++ .../GameOptions/NormalGameOptions.cs | 24 ++++++++++-- .../RoleOptions/NoisemakerRoleOptions.cs | 33 +++++++++++++++++ .../RoleOptions/PhantomRoleOptions.cs | 33 +++++++++++++++++ .../RoleOptions/RoleOptionsCollection.cs | 3 ++ .../RoleOptions/TrackerRoleOptions.cs | 37 +++++++++++++++++++ 8 files changed, 165 insertions(+), 7 deletions(-) create mode 100644 src/Impostor.Api/Innersloth/GameOptions/RoleOptions/NoisemakerRoleOptions.cs create mode 100644 src/Impostor.Api/Innersloth/GameOptions/RoleOptions/PhantomRoleOptions.cs create mode 100644 src/Impostor.Api/Innersloth/GameOptions/RoleOptions/TrackerRoleOptions.cs diff --git a/src/Impostor.Api/Innersloth/GameOptions/HideNSeekGameOptions.cs b/src/Impostor.Api/Innersloth/GameOptions/HideNSeekGameOptions.cs index 6d061a90..87b01adc 100644 --- a/src/Impostor.Api/Innersloth/GameOptions/HideNSeekGameOptions.cs +++ b/src/Impostor.Api/Innersloth/GameOptions/HideNSeekGameOptions.cs @@ -2,7 +2,7 @@ namespace Impostor.Api.Innersloth.GameOptions; public class HideNSeekGameOptions : IGameOptions { - public const int LatestVersion = 7; + public const int LatestVersion = 8; public HideNSeekGameOptions(byte version = LatestVersion) { @@ -16,6 +16,12 @@ public class HideNSeekGameOptions : IGameOptions /// public GameModes GameMode => GameModes.HideNSeek; + /// + public SpecialGameModes SpecialMode { get; set; } = SpecialGameModes.None; + + /// + public RulesPresets RulesPreset { get; set; } = RulesPresets.Custom; + /// public byte MaxPlayers { get; set; } = 15; @@ -96,6 +102,12 @@ public class HideNSeekGameOptions : IGameOptions public void Deserialize(IMessageReader reader) { + if (Version >= 8) + { + SpecialMode = (SpecialGameModes)reader.ReadByte(); + RulesPreset = (RulesPresets)reader.ReadByte(); + } + MaxPlayers = reader.ReadByte(); Keywords = (GameKeywords)reader.ReadInt32(); Map = (MapTypes)reader.ReadByte(); @@ -121,7 +133,7 @@ public class HideNSeekGameOptions : IGameOptions MaxPingTime = reader.ReadSingle(); CrewmateTimeInVent = reader.ReadSingle(); - if (Version > 7) + if (Version > LatestVersion) { IGameOptions.ThrowUnknownVersion(Version); } @@ -129,6 +141,12 @@ public class HideNSeekGameOptions : IGameOptions public void Serialize(IMessageWriter writer) { + if (Version >= 8) + { + writer.Write((byte)SpecialMode); + writer.Write((byte)RulesPreset); + } + writer.Write(MaxPlayers); writer.Write((uint)Keywords); writer.Write((byte)Map); @@ -154,7 +172,7 @@ public class HideNSeekGameOptions : IGameOptions writer.Write(MaxPingTime); writer.Write(CrewmateTimeInVent); - if (Version > 7) + if (Version > LatestVersion) { IGameOptions.ThrowUnknownVersion(Version); } diff --git a/src/Impostor.Api/Innersloth/GameOptions/IGameOptions.cs b/src/Impostor.Api/Innersloth/GameOptions/IGameOptions.cs index 10200c15..fd604958 100644 --- a/src/Impostor.Api/Innersloth/GameOptions/IGameOptions.cs +++ b/src/Impostor.Api/Innersloth/GameOptions/IGameOptions.cs @@ -10,10 +10,20 @@ public interface IGameOptions public byte Version { get; } /// - /// Gets the currently active gamemode. + /// Gets the currently active gamemode. This is currently used for the Normal and HideAndSeek gamemodes. /// public GameModes GameMode { get; } + /// + /// Gets the currently active special gamemode. This is currently used for the AprilFools gamemode. + /// + public SpecialGameModes SpecialMode { get; } + + /// + /// Gets the rule preset. + /// + public RulesPresets RulesPreset { get; } + /// /// Gets or sets the maximum amount of players for this lobby. /// diff --git a/src/Impostor.Api/Innersloth/GameOptions/LegacyGameOptionsData.cs b/src/Impostor.Api/Innersloth/GameOptions/LegacyGameOptionsData.cs index 1ed7ecc5..25862c46 100644 --- a/src/Impostor.Api/Innersloth/GameOptions/LegacyGameOptionsData.cs +++ b/src/Impostor.Api/Innersloth/GameOptions/LegacyGameOptionsData.cs @@ -21,6 +21,12 @@ public class LegacyGameOptionsData : IGameOptions public GameModes GameMode => GameModes.Normal; + /// + public SpecialGameModes SpecialMode { get; } = SpecialGameModes.None; + + /// + public RulesPresets RulesPreset { get; } = RulesPresets.Custom; + /// /// Gets or sets the maximum amount of players for this lobby. /// diff --git a/src/Impostor.Api/Innersloth/GameOptions/NormalGameOptions.cs b/src/Impostor.Api/Innersloth/GameOptions/NormalGameOptions.cs index 537a0ed8..7095f6de 100644 --- a/src/Impostor.Api/Innersloth/GameOptions/NormalGameOptions.cs +++ b/src/Impostor.Api/Innersloth/GameOptions/NormalGameOptions.cs @@ -4,7 +4,7 @@ namespace Impostor.Api.Innersloth.GameOptions; public class NormalGameOptions : IGameOptions { - public const int LatestVersion = 7; + public const int LatestVersion = 8; public NormalGameOptions(byte version = LatestVersion) { @@ -19,6 +19,12 @@ public class NormalGameOptions : IGameOptions /// public GameModes GameMode => GameModes.Normal; + /// + public SpecialGameModes SpecialMode { get; set; } = SpecialGameModes.None; + + /// + public RulesPresets RulesPreset { get; set; } = RulesPresets.Custom; + /// public byte MaxPlayers { get; set; } = 10; @@ -133,6 +139,12 @@ public class NormalGameOptions : IGameOptions public void Deserialize(IMessageReader reader) { + if (Version >= 8) + { + SpecialMode = (SpecialGameModes)reader.ReadByte(); + RulesPreset = (RulesPresets)reader.ReadByte(); + } + MaxPlayers = reader.ReadByte(); Keywords = (GameKeywords)reader.ReadUInt32(); Map = (MapTypes)reader.ReadByte(); @@ -163,7 +175,7 @@ public class NormalGameOptions : IGameOptions RoleOptions.Deserialize(reader); - if (Version > 7) + if (Version > LatestVersion) { IGameOptions.ThrowUnknownVersion(Version); } @@ -171,6 +183,12 @@ public class NormalGameOptions : IGameOptions public void Serialize(IMessageWriter writer) { + if (Version >= 8) + { + writer.Write((byte)SpecialMode); + writer.Write((byte)RulesPreset); + } + writer.Write(MaxPlayers); writer.Write((uint)Keywords); writer.Write((byte)Map); @@ -201,7 +219,7 @@ public class NormalGameOptions : IGameOptions RoleOptions.Serialize(writer); - if (Version > 7) + if (Version > LatestVersion) { IGameOptions.ThrowUnknownVersion(Version); } diff --git a/src/Impostor.Api/Innersloth/GameOptions/RoleOptions/NoisemakerRoleOptions.cs b/src/Impostor.Api/Innersloth/GameOptions/RoleOptions/NoisemakerRoleOptions.cs new file mode 100644 index 00000000..d84a88d5 --- /dev/null +++ b/src/Impostor.Api/Innersloth/GameOptions/RoleOptions/NoisemakerRoleOptions.cs @@ -0,0 +1,33 @@ +namespace Impostor.Api.Innersloth.GameOptions.RoleOptions; + +public class NoisemakerRoleOptions : IRoleOptions +{ + public NoisemakerRoleOptions(byte version) + { + Version = version; + } + + public byte Version { get; } + + public RoleTypes Type => RoleTypes.Noisemaker; + + public bool ImpostorAlert { get; set; } = true; + + public byte AlertDuration { get; set; } = 10; + + public static NoisemakerRoleOptions Deserialize(IMessageReader reader, byte version) + { + var options = new NoisemakerRoleOptions(version); + + options.AlertDuration = reader.ReadByte(); + options.ImpostorAlert = reader.ReadBoolean(); + + return options; + } + + public void Serialize(IMessageWriter writer) + { + writer.Write(AlertDuration); + writer.Write(ImpostorAlert); + } +} diff --git a/src/Impostor.Api/Innersloth/GameOptions/RoleOptions/PhantomRoleOptions.cs b/src/Impostor.Api/Innersloth/GameOptions/RoleOptions/PhantomRoleOptions.cs new file mode 100644 index 00000000..72576220 --- /dev/null +++ b/src/Impostor.Api/Innersloth/GameOptions/RoleOptions/PhantomRoleOptions.cs @@ -0,0 +1,33 @@ +namespace Impostor.Api.Innersloth.GameOptions.RoleOptions; + +public class PhantomRoleOptions : IRoleOptions +{ + public PhantomRoleOptions(byte version) + { + Version = version; + } + + public byte Version { get; } + + public RoleTypes Type => RoleTypes.Phantom; + + public byte Cooldown { get; set; } = 15; + + public byte Duration { get; set; } = 30; + + public static PhantomRoleOptions Deserialize(IMessageReader reader, byte version) + { + var options = new PhantomRoleOptions(version); + + options.Cooldown = reader.ReadByte(); + options.Duration = reader.ReadByte(); + + return options; + } + + public void Serialize(IMessageWriter writer) + { + writer.Write(Cooldown); + writer.Write(Duration); + } +} diff --git a/src/Impostor.Api/Innersloth/GameOptions/RoleOptions/RoleOptionsCollection.cs b/src/Impostor.Api/Innersloth/GameOptions/RoleOptions/RoleOptionsCollection.cs index d8b70764..6e2da069 100644 --- a/src/Impostor.Api/Innersloth/GameOptions/RoleOptions/RoleOptionsCollection.cs +++ b/src/Impostor.Api/Innersloth/GameOptions/RoleOptions/RoleOptionsCollection.cs @@ -31,6 +31,9 @@ public class RoleOptionsCollection RoleTypes.Engineer => EngineerRoleOptions.Deserialize(roleOptionsReader, Version), RoleTypes.GuardianAngel => GuardianAngelRoleOptions.Deserialize(roleOptionsReader, Version), RoleTypes.Shapeshifter => ShapeshifterRoleOptions.Deserialize(roleOptionsReader, Version), + RoleTypes.Noisemaker => NoisemakerRoleOptions.Deserialize(roleOptionsReader, Version), + RoleTypes.Phantom => PhantomRoleOptions.Deserialize(roleOptionsReader, Version), + RoleTypes.Tracker => TrackerRoleOptions.Deserialize(roleOptionsReader, Version), _ => throw new ArgumentOutOfRangeException(nameof(roleType), roleType, null), }; diff --git a/src/Impostor.Api/Innersloth/GameOptions/RoleOptions/TrackerRoleOptions.cs b/src/Impostor.Api/Innersloth/GameOptions/RoleOptions/TrackerRoleOptions.cs new file mode 100644 index 00000000..4b8f50f6 --- /dev/null +++ b/src/Impostor.Api/Innersloth/GameOptions/RoleOptions/TrackerRoleOptions.cs @@ -0,0 +1,37 @@ +namespace Impostor.Api.Innersloth.GameOptions.RoleOptions; + +public class TrackerRoleOptions : IRoleOptions +{ + public TrackerRoleOptions(byte version) + { + Version = version; + } + + public byte Version { get; } + + public RoleTypes Type => RoleTypes.Tracker; + + public byte Cooldown { get; set; } = 15; + + public byte Duration { get; set; } = 30; + + public byte Delay { get; set; } = 1; + + public static TrackerRoleOptions Deserialize(IMessageReader reader, byte version) + { + var options = new TrackerRoleOptions(version); + + options.Cooldown = reader.ReadByte(); + options.Duration = reader.ReadByte(); + options.Delay = reader.ReadByte(); + + return options; + } + + public void Serialize(IMessageWriter writer) + { + writer.Write(Cooldown); + writer.Write(Duration); + writer.Write(Delay); + } +} From 2eca9b4e9fe1fc91d2e45073bb464d658f540554 Mon Sep 17 00:00:00 2001 From: miniduikboot Date: Sat, 22 Jun 2024 12:53:02 +0200 Subject: [PATCH 05/21] Add canOverrideRole param to SetRole This is currently unused by Innersloth --- src/Impostor.Api/Net/Messages/Rpcs/Rpc44SetRole.cs | 6 ++++-- src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Impostor.Api/Net/Messages/Rpcs/Rpc44SetRole.cs b/src/Impostor.Api/Net/Messages/Rpcs/Rpc44SetRole.cs index fed43113..ac57d0d5 100644 --- a/src/Impostor.Api/Net/Messages/Rpcs/Rpc44SetRole.cs +++ b/src/Impostor.Api/Net/Messages/Rpcs/Rpc44SetRole.cs @@ -4,14 +4,16 @@ namespace Impostor.Api.Net.Messages.Rpcs { public static class Rpc44SetRole { - public static void Serialize(IMessageWriter writer, RoleTypes role) + public static void Serialize(IMessageWriter writer, RoleTypes role, bool canOverrideRole) { writer.Write((ushort)role); + writer.Write(canOverrideRole); } - public static void Deserialize(IMessageReader reader, out RoleTypes role) + public static void Deserialize(IMessageReader reader, out RoleTypes role, out bool canOverrideRole) { role = (RoleTypes)reader.ReadUInt16(); + canOverrideRole = reader.ReadBoolean(); } } } diff --git a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs index 441205af..92020de4 100644 --- a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs +++ b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs @@ -383,7 +383,7 @@ namespace Impostor.Server.Net.Inner.Objects return false; } - Rpc44SetRole.Deserialize(reader, out var role); + Rpc44SetRole.Deserialize(reader, out var role, out var _); PlayerInfo.RoleType = role; if (Game.GameState == GameStates.Starting && Game.Players.All(clientPlayer => clientPlayer.Character?.PlayerInfo.RoleType != null)) From 482b8c2b72c591c4a776dfb980a1d9382b5acaca Mon Sep 17 00:00:00 2001 From: miniduikboot Date: Wed, 19 Jun 2024 20:33:53 +0200 Subject: [PATCH 06/21] Add sequence id's to playeroutfit --- .../Innersloth/Customization/PlayerOutfit.cs | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/Impostor.Api/Innersloth/Customization/PlayerOutfit.cs b/src/Impostor.Api/Innersloth/Customization/PlayerOutfit.cs index d2fe21d7..c6abce05 100644 --- a/src/Impostor.Api/Innersloth/Customization/PlayerOutfit.cs +++ b/src/Impostor.Api/Innersloth/Customization/PlayerOutfit.cs @@ -2,6 +2,8 @@ namespace Impostor.Api.Innersloth.Customization { public class PlayerOutfit { + public string PlayerName { get; internal set; } = string.Empty; + public ColorType Color { get; internal set; } = (ColorType)(-1); public string HatId { get; internal set; } = "missing"; @@ -14,7 +16,15 @@ namespace Impostor.Api.Innersloth.Customization public string NamePlateId { get; internal set; } = "missing"; - public string PlayerName { get; internal set; } = "missing"; + private byte HatSequenceId { get; set; } = 0; + + private byte PetSequenceId { get; set; } = 0; + + private byte SkinSequenceId { get; set; } = 0; + + private byte VisorSequenceId { get; set; } = 0; + + private byte NamePlateSequenceId { get; set; } = 0; public bool IsIncomplete { @@ -38,6 +48,11 @@ namespace Impostor.Api.Innersloth.Customization writer.Write(SkinId); writer.Write(VisorId); writer.Write(NamePlateId); + writer.Write(HatSequenceId); + writer.Write(PetSequenceId); + writer.Write(SkinSequenceId); + writer.Write(VisorSequenceId); + writer.Write(NamePlateSequenceId); } public void Deserialize(IMessageReader reader) @@ -49,6 +64,11 @@ namespace Impostor.Api.Innersloth.Customization SkinId = reader.ReadString(); VisorId = reader.ReadString(); NamePlateId = reader.ReadString(); + HatSequenceId = reader.ReadByte(); + PetSequenceId = reader.ReadByte(); + SkinSequenceId = reader.ReadByte(); + VisorSequenceId = reader.ReadByte(); + NamePlateSequenceId = reader.ReadByte(); } } } From 615469c6c89bbf45baa0f9d1772acb70dea19067 Mon Sep 17 00:00:00 2001 From: miniduikboot Date: Sun, 21 Jul 2024 22:31:41 +0200 Subject: [PATCH 07/21] Add dirty tracking to PlayerOutfit To prevent excessive modifications in the codebase, which is prone to issues if a callsite is missed, automatically do this using properties. Keep the access modifiers intact for now. --- .../Innersloth/Customization/PlayerOutfit.cs | 58 ++++++++++++++++--- 1 file changed, 51 insertions(+), 7 deletions(-) diff --git a/src/Impostor.Api/Innersloth/Customization/PlayerOutfit.cs b/src/Impostor.Api/Innersloth/Customization/PlayerOutfit.cs index c6abce05..a93fc5e8 100644 --- a/src/Impostor.Api/Innersloth/Customization/PlayerOutfit.cs +++ b/src/Impostor.Api/Innersloth/Customization/PlayerOutfit.cs @@ -2,19 +2,57 @@ namespace Impostor.Api.Innersloth.Customization { public class PlayerOutfit { - public string PlayerName { get; internal set; } = string.Empty; + private string _playerName = string.Empty; + private ColorType _color = (ColorType)(-1); + private string _hatId = "missing"; + private string _petId = "missing"; + private string _skinId = "missing"; + private string _visorId = "missing"; + private string _namePlateId = "missing"; - public ColorType Color { get; internal set; } = (ColorType)(-1); + public bool IsDirty { get; internal set; } - public string HatId { get; internal set; } = "missing"; + public string PlayerName + { + get => _playerName; + internal set => SetField(ref _playerName, value); + } - public string PetId { get; internal set; } = "missing"; + public ColorType Color + { + get => _color; + internal set => SetField(ref _color, value); + } - public string SkinId { get; internal set; } = "missing"; + public string HatId + { + get => _hatId; + internal set => SetField(ref _hatId, value); + } - public string VisorId { get; internal set; } = "missing"; + public string PetId + { + get => _petId; + internal set => SetField(ref _petId, value); + } - public string NamePlateId { get; internal set; } = "missing"; + public string SkinId + { + get => _skinId; + internal set => SetField(ref _skinId, value); + } + + public string VisorId + { + get => _visorId; + internal set => SetField(ref _visorId, value); + } + + public string NamePlateId + { + get => _namePlateId; + internal set => SetField(ref _namePlateId, value); + } private byte HatSequenceId { get; set; } = 0; @@ -70,5 +108,11 @@ namespace Impostor.Api.Innersloth.Customization VisorSequenceId = reader.ReadByte(); NamePlateSequenceId = reader.ReadByte(); } + + private void SetField(ref T field, T value) + { + field = value; + IsDirty = true; + } } } From c43fdd2a69f60227a6f860bfa194b397d82da773 Mon Sep 17 00:00:00 2001 From: miniduikboot Date: Wed, 19 Jun 2024 20:42:18 +0200 Subject: [PATCH 08/21] Demote InnerGameData, promote InnerPlayerInfo In 2024.6.18 Innersloth promoted PlayerInfo to its own network object, and gave it to the server. InnerGameData is now a container that holds these players. This commit implements the changes necessary to handle these separate PlayerInfo network objects. --- .../Net/Inner/Objects/IInnerGameData.cs | 2 +- .../Net/Inner/Objects/InnerGameData.cs | 125 +----------------- .../Net/Inner/Objects/InnerPlayerInfo.Api.cs | 2 +- .../Net/Inner/Objects/InnerPlayerInfo.cs | 83 +++++++++++- src/Impostor.Server/Net/State/Game.Data.cs | 32 +++-- src/Impostor.Server/Net/State/GameNet.Api.cs | 2 +- src/Impostor.Server/Net/State/GameNet.cs | 2 +- 7 files changed, 106 insertions(+), 142 deletions(-) diff --git a/src/Impostor.Api/Net/Inner/Objects/IInnerGameData.cs b/src/Impostor.Api/Net/Inner/Objects/IInnerGameData.cs index 6e410203..a535131f 100644 --- a/src/Impostor.Api/Net/Inner/Objects/IInnerGameData.cs +++ b/src/Impostor.Api/Net/Inner/Objects/IInnerGameData.cs @@ -1,6 +1,6 @@ namespace Impostor.Api.Net.Inner.Objects { - public interface IInnerGameData : IInnerNetObject + public interface IInnerGameData { } } diff --git a/src/Impostor.Server/Net/Inner/Objects/InnerGameData.cs b/src/Impostor.Server/Net/Inner/Objects/InnerGameData.cs index 47fe4a0e..26ec23bc 100644 --- a/src/Impostor.Server/Net/Inner/Objects/InnerGameData.cs +++ b/src/Impostor.Server/Net/Inner/Objects/InnerGameData.cs @@ -1,36 +1,12 @@ -using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Threading.Tasks; -using Impostor.Api; -using Impostor.Api.Events.Managers; -using Impostor.Api.Net; -using Impostor.Api.Net.Custom; -using Impostor.Api.Net.Inner; using Impostor.Api.Net.Inner.Objects; -using Impostor.Api.Net.Messages.Rpcs; -using Impostor.Server.Net.Inner.Objects.Components; -using Impostor.Server.Net.State; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; namespace Impostor.Server.Net.Inner.Objects { - internal partial class InnerGameData : InnerNetObject, IInnerGameData + internal partial class InnerGameData : IInnerGameData { - private readonly ILogger _logger; - private readonly IEventManager _eventManager; - private readonly ConcurrentDictionary _allPlayers; - - public InnerGameData(ICustomMessageManager customMessageManager, Game game, ILogger logger, IEventManager eventManager, IServiceProvider serviceProvider) : base(customMessageManager, game) - { - _logger = logger; - _eventManager = eventManager; - _allPlayers = new ConcurrentDictionary(); - - Components.Add(this); - Components.Add(ActivatorUtilities.CreateInstance(serviceProvider, game)); - } + private readonly ConcurrentDictionary _allPlayers = new(); public int PlayerCount => _allPlayers.Count; @@ -46,103 +22,14 @@ namespace Impostor.Server.Net.Inner.Objects return _allPlayers.TryGetValue(id, out var player) ? player : null; } - public override ValueTask SerializeAsync(IMessageWriter writer, bool initialState) + internal bool AddPlayer(InnerPlayerInfo playerInfo) { - throw new NotImplementedException(); + return _allPlayers.TryAdd(playerInfo.PlayerId, playerInfo); } - public override async ValueTask DeserializeAsync(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState) + internal void RemovePlayer(byte playerId) { - if (!await ValidateHost(CheatContext.Deserialize, sender)) - { - return; - } - - while (reader.Position < reader.Length) - { - var inner = reader.ReadMessage(); - var playerInfo = this.GetPlayerById(inner.Tag); - if (playerInfo != null) - { - playerInfo.Deserialize(inner); - } - else - { - playerInfo = new InnerPlayerInfo(inner.Tag); - playerInfo.Deserialize(inner); - - if (!_allPlayers.TryAdd(playerInfo.PlayerId, playerInfo)) - { - throw new ImpostorException("Failed to add player to InnerGameData."); - } - } - } - } - - public override async ValueTask HandleRpcAsync(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader) - { - if (!await ValidateHost(call, sender)) - { - return false; - } - - switch (call) - { - case RpcCalls.SetTasks: - { - Rpc29SetTasks.Deserialize(reader, out var playerId, out var taskTypeIds); - SetTasks(playerId, taskTypeIds); - break; - } - - default: - return await base.HandleRpcAsync(sender, target, call, reader); - } - - return true; - } - - internal InnerPlayerInfo? AddPlayer(InnerPlayerControl control) - { - var playerId = control.PlayerId; - var playerInfo = new InnerPlayerInfo(control.PlayerId); - - if (_allPlayers.TryAdd(playerId, playerInfo)) - { - return playerInfo; - } - - return null; - } - - internal void RemovePlayer(InnerPlayerControl control) - { - _allPlayers.TryRemove(control.PlayerInfo.PlayerId, out _); - } - - private void SetTasks(byte playerId, ReadOnlyMemory taskTypeIds) - { - var player = GetPlayerById(playerId); - if (player == null) - { - _logger.LogTrace("Could not set tasks for playerId {0}.", playerId); - return; - } - - if (player.Disconnected) - { - return; - } - - player.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; - player.Tasks.Add(new TaskInfo(player, _eventManager, taskId++, taskType)); - } + _allPlayers.TryRemove(playerId, out _); } } } diff --git a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.Api.cs b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.Api.cs index 512a4f1a..b8b3cd8b 100644 --- a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.Api.cs +++ b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.Api.cs @@ -3,7 +3,7 @@ using Impostor.Api.Net.Inner.Objects; namespace Impostor.Server.Net.Inner.Objects { - internal partial class InnerPlayerInfo : IInnerPlayerInfo + internal partial class InnerPlayerInfo : InnerNetObject, IInnerPlayerInfo { IEnumerable IInnerPlayerInfo.Tasks => Tasks; } diff --git a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.cs b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.cs index 320cb5b0..498f8f6b 100644 --- a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.cs +++ b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.cs @@ -1,23 +1,33 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; 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.Utils; +using Impostor.Server.Net.State; +using Microsoft.Extensions.Logging; namespace Impostor.Server.Net.Inner.Objects { internal partial class InnerPlayerInfo { - public InnerPlayerInfo(byte playerId) + private readonly ILogger _logger; + + public InnerPlayerInfo(ICustomMessageManager customMessageManager, Game game, ILogger logger) : base(customMessageManager, game) { - PlayerId = playerId; + Components.Add(this); + _logger = logger; } public InnerPlayerControl? Controller { get; internal set; } - public byte PlayerId { get; } + public byte PlayerId { get; internal set; } + + public int ClientId { get; internal set; } public string PlayerName { get; internal set; } = string.Empty; @@ -32,6 +42,8 @@ namespace Impostor.Server.Net.Inner.Objects public RoleTypes? RoleType { get; internal set; } + public RoleTypes? RoleWhenAlive { get; internal set; } + public bool Disconnected { get; internal set; } public bool IsImpostor => RoleType is RoleTypes.Impostor or RoleTypes.Shapeshifter or RoleTypes.ImpostorGhost; @@ -71,13 +83,56 @@ namespace Impostor.Server.Net.Inner.Objects } } - public void Serialize(IMessageWriter writer) + public override ValueTask SerializeAsync(IMessageWriter writer, bool initialState) { - throw new NotImplementedException(); + writer.Write(PlayerId); + writer.WritePacked(ClientId); + + writer.Write((byte)Outfits.Count); + foreach (var outfit in Outfits) + { + writer.Write((byte)outfit.Key); + outfit.Value.Serialize(writer); + } + + writer.WritePacked(PlayerLevel); + + var flag = 0; + if (Disconnected) + { + flag = (byte)(flag | 1u); + } + + if (IsDead) + { + flag = (byte)(flag | 4u); + } + + writer.Write((byte)flag); + + writer.Write((ushort)(RoleType ?? 0)); + writer.Write(RoleWhenAlive.HasValue); + if (RoleWhenAlive.HasValue) + { + writer.Write((ushort)RoleWhenAlive.Value); + } + + writer.Write((byte)Tasks.Count); + for (var i = 0; i < Tasks.Count; i++) + { + Tasks[i].Serialize(writer); + } + + writer.Write(string.Empty); // FriendCode + writer.Write(string.Empty); // PUID + return new ValueTask(true); } - public void Deserialize(IMessageReader reader) + public override ValueTask DeserializeAsync(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState) { + PlayerId = reader.ReadByte(); + ClientId = reader.ReadPackedInt32(); + Outfits.Clear(); var b = reader.ReadByte(); for (var i = 0; i < b; i++) @@ -93,7 +148,15 @@ namespace Impostor.Server.Net.Inner.Objects Disconnected = (flag & 1) != 0; IsDead = (flag & 4) != 0; - _ = (RoleTypes)reader.ReadUInt16(); // ignore the RoleType here and only trust the SetRole rpc + // Ignore the RoleType here and only trust the SetRole RPC, as + // RoleType is not nullable in vanilla, while Impostor checks game + // starts based on assigned roles. + _ = (RoleTypes)reader.ReadUInt16(); + + if (reader.ReadBoolean()) + { + RoleWhenAlive = (RoleTypes)reader.ReadUInt16(); + } var taskCount = reader.ReadByte(); @@ -106,6 +169,12 @@ namespace Impostor.Server.Net.Inner.Objects { Tasks[i].Deserialize(reader); } + + // Impostor doesn't expose fields that aren't properly validated + reader.ReadString(); // FriendCode + reader.ReadString(); // PUID + + return ValueTask.CompletedTask; } } } diff --git a/src/Impostor.Server/Net/State/Game.Data.cs b/src/Impostor.Server/Net/State/Game.Data.cs index 5f0e56cc..4e429fa3 100644 --- a/src/Impostor.Server/Net/State/Game.Data.cs +++ b/src/Impostor.Server/Net/State/Game.Data.cs @@ -35,7 +35,6 @@ namespace Impostor.Server.Net.State [0] = typeof(InnerSkeldShipStatus), [1] = typeof(InnerMeetingHud), [2] = typeof(InnerLobbyBehaviour), - [3] = typeof(InnerGameData), [4] = typeof(InnerPlayerControl), [5] = typeof(InnerMiraShipStatus), [6] = typeof(InnerPolusShipStatus), @@ -43,6 +42,8 @@ namespace Impostor.Server.Net.State [8] = typeof(InnerAirshipStatus), [9] = typeof(InnerHideAndSeekManager), [10] = typeof(InnerNormalGameManager), + [11] = typeof(InnerPlayerInfo), + [12] = typeof(InnerVoteBanSystem), [13] = typeof(InnerFungleShipStatus), }; @@ -326,9 +327,17 @@ namespace Impostor.Server.Net.State break; } - case InnerGameData data: + case InnerPlayerInfo playerInfo: { - GameNet.GameData = data; + if (!GameNet.GameData.AddPlayer(playerInfo)) + { + _logger.LogWarning( + "Could not add PlayerInfo for playerId {PlayerId} with NetId {newId}, already have NetId {oldNetId}", + playerInfo.PlayerId, + playerInfo.NetId, + GameNet.GameData.GetPlayerById(playerInfo.PlayerId)?.NetId); + } + break; } @@ -358,7 +367,7 @@ namespace Impostor.Server.Net.State } // Hook up InnerPlayerControl <-> InnerPlayerControl.PlayerInfo. - var playerInfo = GameNet.GameData!.GetPlayerById(control.PlayerId) ?? GameNet.GameData.AddPlayer(control); + var playerInfo = GameNet.GameData.GetPlayerById(control.PlayerId); if (playerInfo != null) { @@ -402,12 +411,6 @@ namespace Impostor.Server.Net.State break; } - case InnerGameData: - { - GameNet.GameData = null; - break; - } - case InnerVoteBanSystem: { GameNet.VoteBan = null; @@ -420,13 +423,18 @@ namespace Impostor.Server.Net.State break; } - case InnerPlayerControl control: + case InnerPlayerInfo playerInfo: { if (GameState != GameStates.Started && GameState != GameStates.Starting) { - GameNet.GameData?.RemovePlayer(control); + GameNet.GameData.RemovePlayer(playerInfo.PlayerId); } + break; + } + + case InnerPlayerControl control: + { // Remove InnerPlayerControl <-> IClientPlayer. if (TryGetPlayer(control.OwnerId, out var player)) { diff --git a/src/Impostor.Server/Net/State/GameNet.Api.cs b/src/Impostor.Server/Net/State/GameNet.Api.cs index 68d4c261..d57ec1db 100644 --- a/src/Impostor.Server/Net/State/GameNet.Api.cs +++ b/src/Impostor.Server/Net/State/GameNet.Api.cs @@ -12,7 +12,7 @@ namespace Impostor.Server.Net.State IInnerLobbyBehaviour? IGameNet.LobbyBehaviour => LobbyBehaviour; - IInnerGameData? IGameNet.GameData => GameData; + IInnerGameData IGameNet.GameData => GameData; IInnerVoteBanSystem? IGameNet.VoteBan => VoteBan; diff --git a/src/Impostor.Server/Net/State/GameNet.cs b/src/Impostor.Server/Net/State/GameNet.cs index 66f69f0f..25efec69 100644 --- a/src/Impostor.Server/Net/State/GameNet.cs +++ b/src/Impostor.Server/Net/State/GameNet.cs @@ -11,7 +11,7 @@ namespace Impostor.Server.Net.State public InnerLobbyBehaviour? LobbyBehaviour { get; internal set; } - public InnerGameData? GameData { get; internal set; } + public InnerGameData GameData { get; internal set; } = new(); public InnerVoteBanSystem? VoteBan { get; internal set; } From 85296d2d46dffc3f9435deb31facaa87398d3ba6 Mon Sep 17 00:00:00 2001 From: miniduikboot Date: Sun, 28 Jul 2024 13:38:03 +0200 Subject: [PATCH 09/21] Serialize Color -1 as 0 in PlayerOutfit Official servers send 0 instead of -1 when the outfit is still incomplete to prevent some client issues. --- .../Innersloth/Customization/PlayerOutfit.cs | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/src/Impostor.Api/Innersloth/Customization/PlayerOutfit.cs b/src/Impostor.Api/Innersloth/Customization/PlayerOutfit.cs index a93fc5e8..dbe698bb 100644 --- a/src/Impostor.Api/Innersloth/Customization/PlayerOutfit.cs +++ b/src/Impostor.Api/Innersloth/Customization/PlayerOutfit.cs @@ -2,8 +2,13 @@ namespace Impostor.Api.Innersloth.Customization { public class PlayerOutfit { + // Magic value that is assigned by default when the outfit is initially created. This is not + // `null` because existing API already exposed this magic value. It's also not 0 (which is + // the value exposed when serializing) because SetColor anticheat checks exist. + private const ColorType UnknownColor = (ColorType)(-1); + private string _playerName = string.Empty; - private ColorType _color = (ColorType)(-1); + private ColorType _color = UnknownColor; private string _hatId = "missing"; private string _petId = "missing"; private string _skinId = "missing"; @@ -64,23 +69,25 @@ namespace Impostor.Api.Innersloth.Customization private byte NamePlateSequenceId { get; set; } = 0; - public bool IsIncomplete - { - get - { - if (!string.IsNullOrEmpty(PlayerName) && Color != (ColorType)(-1) && HatId != "missing" && PetId != "missing" && SkinId != "missing" && VisorId != "missing") - { - return NamePlateId == "missing"; - } - - return true; - } - } + /// + /// Gets a value indicating whether a player outfit is complete or whether it needs to receive additional cosmetics. + /// + /// true if the outfit is incomplete, false if the outfit is complete. + public bool IsIncomplete => string.IsNullOrEmpty(PlayerName) + || Color == UnknownColor + || HatId == "missing" + || PetId == "missing" + || SkinId == "missing" + || VisorId == "missing" + || NamePlateId == "missing"; public void Serialize(IMessageWriter writer) { writer.Write(PlayerName); - writer.WritePacked((int)Color); + + // Follow officials by not sending a color value that is out of range. Sending -1 can cause exceptions on the client side. + writer.WritePacked(Color == UnknownColor ? 0 : (int)Color); + writer.Write(HatId); writer.Write(PetId); writer.Write(SkinId); From 372ed8511fb9cd5bbc9ac2781357e3f9e75a29b3 Mon Sep 17 00:00:00 2001 From: miniduikboot Date: Sun, 21 Jul 2024 23:16:16 +0200 Subject: [PATCH 10/21] Set RoleWhenAlive when set to a Ghost role --- .../Net/Inner/Objects/InnerPlayerControl.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs index 92020de4..ff8d9bc8 100644 --- a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs +++ b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs @@ -384,6 +384,13 @@ namespace Impostor.Server.Net.Inner.Objects } Rpc44SetRole.Deserialize(reader, out var role, out var _); + + if (role is RoleTypes.ImpostorGhost or RoleTypes.CrewmateGhost or RoleTypes.GuardianAngel) + { + PlayerInfo.RoleWhenAlive = PlayerInfo.RoleType; + PlayerInfo.IsDead = true; + } + PlayerInfo.RoleType = role; if (Game.GameState == GameStates.Starting && Game.Players.All(clientPlayer => clientPlayer.Character?.PlayerInfo.RoleType != null)) From b118af12be15e107e3659a71034aaf8ac760ae0e Mon Sep 17 00:00:00 2001 From: miniduikboot Date: Thu, 20 Jun 2024 18:37:10 +0200 Subject: [PATCH 11/21] Add NetId to SetName and SetColor 2024.6.18 adds a NetId field *in front* of the existing fields to help link back to the PlayerInfo. We don't actually need this NetId and assume it's called on the correct PlayerControl, so we ignore it. --- src/Impostor.Api/Net/Messages/Rpcs/Rpc06SetName.cs | 6 ++++-- src/Impostor.Api/Net/Messages/Rpcs/Rpc08SetColor.cs | 6 ++++-- .../Net/Inner/Objects/InnerPlayerControl.Api.cs | 2 +- src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs | 4 ++-- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Impostor.Api/Net/Messages/Rpcs/Rpc06SetName.cs b/src/Impostor.Api/Net/Messages/Rpcs/Rpc06SetName.cs index e2e8ae48..3aca0ef3 100644 --- a/src/Impostor.Api/Net/Messages/Rpcs/Rpc06SetName.cs +++ b/src/Impostor.Api/Net/Messages/Rpcs/Rpc06SetName.cs @@ -2,13 +2,15 @@ namespace Impostor.Api.Net.Messages.Rpcs { public static class Rpc06SetName { - public static void Serialize(IMessageWriter writer, string name) + public static void Serialize(IMessageWriter writer, uint netId, string name) { + writer.Write(netId); writer.Write(name); } - public static void Deserialize(IMessageReader reader, out string name) + public static void Deserialize(IMessageReader reader, out uint netId, out string name) { + netId = reader.ReadUInt32(); name = reader.ReadString(); } } diff --git a/src/Impostor.Api/Net/Messages/Rpcs/Rpc08SetColor.cs b/src/Impostor.Api/Net/Messages/Rpcs/Rpc08SetColor.cs index 50ea3d69..6cab399e 100644 --- a/src/Impostor.Api/Net/Messages/Rpcs/Rpc08SetColor.cs +++ b/src/Impostor.Api/Net/Messages/Rpcs/Rpc08SetColor.cs @@ -4,13 +4,15 @@ namespace Impostor.Api.Net.Messages.Rpcs { public static class Rpc08SetColor { - public static void Serialize(IMessageWriter writer, ColorType color) + public static void Serialize(IMessageWriter writer, uint netId, ColorType color) { + writer.Write(netId); writer.Write((byte)color); } - public static void Deserialize(IMessageReader reader, out ColorType color) + public static void Deserialize(IMessageReader reader, out uint netId, out ColorType color) { + netId = reader.ReadUInt32(); color = (ColorType)reader.ReadByte(); } } diff --git a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.Api.cs b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.Api.cs index e4bd50a0..bd711618 100644 --- a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.Api.cs +++ b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.Api.cs @@ -32,7 +32,7 @@ namespace Impostor.Server.Net.Inner.Objects PlayerInfo.CurrentOutfit.Color = color; using var writer = Game.StartRpc(NetId, RpcCalls.SetColor); - Rpc08SetColor.Serialize(writer, color); + Rpc08SetColor.Serialize(writer, PlayerInfo.NetId, color); 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 ff8d9bc8..16fa42ed 100644 --- a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs +++ b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs @@ -167,7 +167,7 @@ namespace Impostor.Server.Net.Inner.Objects return false; } - Rpc06SetName.Deserialize(reader, out var name); + Rpc06SetName.Deserialize(reader, out var _, out var name); return await HandleSetName(sender, name); } @@ -191,7 +191,7 @@ namespace Impostor.Server.Net.Inner.Objects return false; } - Rpc08SetColor.Deserialize(reader, out var color); + Rpc08SetColor.Deserialize(reader, out var _, out var color); return await HandleSetColor(sender, color); } From 09cadcb63b3b38455dc93d66afa9b76273d3c91e Mon Sep 17 00:00:00 2001 From: miniduikboot Date: Thu, 20 Jun 2024 18:14:02 +0200 Subject: [PATCH 12/21] Spawn PlayerInfo's if host is not authoritive This PlayerInfo also needs to be synced over if it is server owned --- .../Net/Inner/Objects/InnerGameData.cs | 26 +++++++- src/Impostor.Server/Net/State/Game.Data.cs | 64 +++++++++++++++++++ .../Net/State/Game.Outgoing.cs | 22 +++++++ 3 files changed, 110 insertions(+), 2 deletions(-) diff --git a/src/Impostor.Server/Net/Inner/Objects/InnerGameData.cs b/src/Impostor.Server/Net/Inner/Objects/InnerGameData.cs index 26ec23bc..b1796f15 100644 --- a/src/Impostor.Server/Net/Inner/Objects/InnerGameData.cs +++ b/src/Impostor.Server/Net/Inner/Objects/InnerGameData.cs @@ -7,11 +7,14 @@ namespace Impostor.Server.Net.Inner.Objects internal partial class InnerGameData : IInnerGameData { private readonly ConcurrentDictionary _allPlayers = new(); + private readonly ConcurrentDictionary _allPlayersByClientId = new(); public int PlayerCount => _allPlayers.Count; public IReadOnlyDictionary Players => _allPlayers; + internal IReadOnlyDictionary PlayersByClientId => _allPlayersByClientId; + public InnerPlayerInfo? GetPlayerById(byte id) { if (id == byte.MaxValue) @@ -24,12 +27,31 @@ namespace Impostor.Server.Net.Inner.Objects internal bool AddPlayer(InnerPlayerInfo playerInfo) { - return _allPlayers.TryAdd(playerInfo.PlayerId, playerInfo); + return _allPlayers.TryAdd(playerInfo.PlayerId, playerInfo) && + _allPlayersByClientId.TryAdd(playerInfo.ClientId, playerInfo); } internal void RemovePlayer(byte playerId) { - _allPlayers.TryRemove(playerId, out _); + _allPlayers.TryRemove(playerId, out var player); + + if (player != null) + { + _allPlayersByClientId.TryRemove(player.ClientId, out _); + } + } + + internal byte GetNextAvailablePlayerId() + { + for (byte i = 0; i < byte.MaxValue; i++) + { + if (!Players.ContainsKey(i)) + { + return i; + } + } + + return byte.MaxValue; } } } diff --git a/src/Impostor.Server/Net/State/Game.Data.cs b/src/Impostor.Server/Net/State/Game.Data.cs index 4e429fa3..1ae179ca 100644 --- a/src/Impostor.Server/Net/State/Game.Data.cs +++ b/src/Impostor.Server/Net/State/Game.Data.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Impostor.Api; using Impostor.Api.Innersloth; @@ -30,6 +31,16 @@ namespace Impostor.Server.Net.State /// private const int CurrentClient = -3; + /// + /// Used to list objects that are managed by the game server. + /// + private const int ServerOwned = -4; + + /// + /// The first NetId that is considered as a server owned Network ID that the client will not allocate by default. + /// + private const int MinServerNetId = 100000; + private static readonly Dictionary SpawnableObjects = new() { [0] = typeof(InnerSkeldShipStatus), @@ -47,10 +58,14 @@ namespace Impostor.Server.Net.State [13] = typeof(InnerFungleShipStatus), }; + private static readonly Dictionary SpawnableObjectIds = SpawnableObjects.ToDictionary((i) => i.Value, (i) => i.Key); + private readonly List _allObjects = new List(); private readonly Dictionary _allObjectsFast = new Dictionary(); + private uint _nextNetId = MinServerNetId; + public T? FindObjectByNetId(uint netId) where T : IInnerNetObject { @@ -256,6 +271,10 @@ namespace Impostor.Server.Net.State sender.Scene = scene; _logger.LogTrace("> Scene {0} to {1}", clientId, sender.Scene); + + await SyncServerObjectsAsync(sender); + await SpawnPlayerInfoAsync(sender); + break; } @@ -447,6 +466,51 @@ namespace Impostor.Server.Net.State } } + private async ValueTask SyncServerObjectsAsync(ClientPlayer sender) + { + foreach (var obj in _allObjectsFast.Values) + { + if (obj.OwnerId == ServerOwned) + { + _logger.LogTrace("Syncing {Type} {NetId}", obj.GetType(), obj.NetId); + await SendObjectSpawnAsync(obj, sender.Client.Id); + } + } + } + + private async ValueTask SpawnPlayerInfoAsync(ClientPlayer sender) + { + // Hosts spawn PlayerInfo objects if they requested authority + if (IsHostAuthoritive) + { + return; + } + + // Only spawn a new PlayerInfo if one has not yet been spawned + if (GameNet.GameData.PlayersByClientId.ContainsKey(sender.Client.Id)) + { + return; + } + + var playerInfo = (InnerPlayerInfo)ActivatorUtilities.CreateInstance(_serviceProvider, typeof(InnerPlayerInfo), this); + playerInfo.SpawnFlags = SpawnFlags.None; + playerInfo.NetId = _nextNetId++; + playerInfo.OwnerId = ServerOwned; + playerInfo.ClientId = sender.Client.Id; + playerInfo.PlayerId = GameNet.GameData.GetNextAvailablePlayerId(); + + if (!AddNetObject(playerInfo)) + { + _logger.LogError("Couldn't spawn PlayerInfo for {Name} ({ClientId})", sender.Client.Name, sender.Client.Id); + playerInfo.NetId = uint.MaxValue; + return; + } + + _logger.LogTrace("Spawning PlayerInfo (netId {Netid})", playerInfo.NetId); + await OnSpawnAsync(sender, playerInfo); + await SendObjectSpawnAsync(playerInfo); + } + private bool AddNetObject(InnerNetObject obj) { if (_allObjectsFast.ContainsKey(obj.NetId)) diff --git a/src/Impostor.Server/Net/State/Game.Outgoing.cs b/src/Impostor.Server/Net/State/Game.Outgoing.cs index 4c2f31fc..1ab0086e 100644 --- a/src/Impostor.Server/Net/State/Game.Outgoing.cs +++ b/src/Impostor.Server/Net/State/Game.Outgoing.cs @@ -112,5 +112,27 @@ namespace Impostor.Server.Net.State { Message12WaitForHostS2C.Serialize(message, clear, Code, player.Client.Id); } + + private async ValueTask SendObjectSpawnAsync(InnerNetObject obj, int? targetClientId = null) + { + using var writer = StartGameData(targetClientId); + writer.StartMessage(GameDataTag.SpawnFlag); + writer.WritePacked(SpawnableObjectIds[obj.GetType()]); + writer.WritePacked(obj.OwnerId); + writer.Write((byte)obj.SpawnFlags); + + var components = obj.GetComponentsInChildren(); + writer.WritePacked(components.Count); + foreach (var component in components) + { + writer.WritePacked(obj.NetId); + writer.StartMessage(1); + await component.SerializeAsync(writer, true); + writer.EndMessage(); + } + + writer.EndMessage(); + await FinishGameDataAsync(writer, targetClientId); + } } } From 44e9dfe28d66f9754a071969a984e91749059ae6 Mon Sep 17 00:00:00 2001 From: miniduikboot Date: Tue, 13 Aug 2024 20:46:02 +0200 Subject: [PATCH 13/21] Bump to 2024.8.13 Network side seems unchanged and officials allow crossplay --- src/Impostor.Server/Net/Manager/CompatibilityManager.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Impostor.Server/Net/Manager/CompatibilityManager.cs b/src/Impostor.Server/Net/Manager/CompatibilityManager.cs index c6fa7efd..e88622ff 100644 --- a/src/Impostor.Server/Net/Manager/CompatibilityManager.cs +++ b/src/Impostor.Server/Net/Manager/CompatibilityManager.cs @@ -18,6 +18,7 @@ internal class CompatibilityManager : ICompatibilityManager new[] { new GameVersion(2024, 3, 1), // 2024.6.18 + new GameVersion(2024, 4, 1), // 2024.8.13 }, }; From ccd36b6571b96d05b37d80b8a993b6e5e32915bc Mon Sep 17 00:00:00 2001 From: miniduikboot Date: Fri, 21 Jun 2024 23:05:07 +0200 Subject: [PATCH 14/21] 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 9e1abe07ef79c22876733644610798417f8f7e57 Mon Sep 17 00:00:00 2001 From: miniduikboot Date: Sun, 18 Aug 2024 11:16:44 +0200 Subject: [PATCH 15/21] 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 1a9de063..66410d6f 100644 --- a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.cs +++ b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.cs @@ -34,7 +34,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 fc79bd1536887a03ac8aa425505ed0c295ffe09c Mon Sep 17 00:00:00 2001 From: miniduikboot Date: Sun, 30 Jun 2024 23:29:53 +0200 Subject: [PATCH 16/21] Add Message24EndGameHostMigration serialization Looks like Innersloth added this but hasn't actually used this. Weird.. When sent it makes the new host perform a full rejoin, which gums up Impostor's spawning logic. Fixing that causes the client to gum itself up. Sigh. After some testing it seems like host migration still works fine, so this investigation wasn't needed. Committing for posterity. Co-Authored-By: js6pak --- src/Impostor.Api/Net/Messages/MessageFlags.cs | 1 + .../S2C/Message24EndGameHostMigrationS2C.cs | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 src/Impostor.Api/Net/Messages/S2C/Message24EndGameHostMigrationS2C.cs diff --git a/src/Impostor.Api/Net/Messages/MessageFlags.cs b/src/Impostor.Api/Net/Messages/MessageFlags.cs index 8be5a581..a1a9a11a 100644 --- a/src/Impostor.Api/Net/Messages/MessageFlags.cs +++ b/src/Impostor.Api/Net/Messages/MessageFlags.cs @@ -28,6 +28,7 @@ namespace Impostor.Api.Net.Messages public const byte SetActivePodType = 21; public const byte QueryPlatformIds = 22; public const byte QueryLobbyInfo = 23; + public const byte EndGameHostMigration = 24; private static readonly Dictionary FlagCache; diff --git a/src/Impostor.Api/Net/Messages/S2C/Message24EndGameHostMigrationS2C.cs b/src/Impostor.Api/Net/Messages/S2C/Message24EndGameHostMigrationS2C.cs new file mode 100644 index 00000000..18f1d241 --- /dev/null +++ b/src/Impostor.Api/Net/Messages/S2C/Message24EndGameHostMigrationS2C.cs @@ -0,0 +1,21 @@ +using Impostor.Api.Games; + +namespace Impostor.Api.Net.Messages.S2C +{ + public class Message24EndGameHostMigrationS2C + { + public static void Serialize(IMessageWriter writer, GameCode gameCode, int hostClientId) + { + writer.StartMessage(MessageFlags.EndGameHostMigration); + gameCode.Serialize(writer); + writer.Write(hostClientId); + writer.EndMessage(); + } + + public static void Deserialize(IMessageReader reader, out GameCode gameCode, out int hostClientId) + { + gameCode = reader.ReadInt32(); + hostClientId = reader.ReadInt32(); + } + } +} From 01e9cccdf5ac974aca9427f15f0d76390e2ea267 Mon Sep 17 00:00:00 2001 From: miniduikboot Date: Fri, 2 Aug 2024 11:25:52 +0200 Subject: [PATCH 17/21] 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 113226d6..b132b99d 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)) From 219b72a3d1eccac57a66084463d0df7577a9fdc2 Mon Sep 17 00:00:00 2001 From: miniduikboot Date: Thu, 20 Jun 2024 22:26:16 +0200 Subject: [PATCH 18/21] 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 1ae179ca..9d0fb550 100644 --- a/src/Impostor.Server/Net/State/Game.Data.cs +++ b/src/Impostor.Server/Net/State/Game.Data.cs @@ -511,6 +511,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..a8efd3b4 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.ToArray()) + { + 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..f30271f8 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 async ValueTask SendObjectDespawnAsync(InnerNetObject obj, int? targetClientId = null) + { + using var writer = StartGameData(targetClientId); + writer.StartMessage(GameDataTag.DespawnFlag); + writer.WritePacked(obj.NetId); + writer.EndMessage(); + await 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 ed9f6e31344cea8d814d69f5ef06761c1ec23373 Mon Sep 17 00:00:00 2001 From: miniduikboot Date: Sun, 21 Jul 2024 23:13:25 +0200 Subject: [PATCH 19/21] 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 66410d6f..650289da 100644 --- a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.cs +++ b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.cs @@ -51,7 +51,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 83bf1c4368ae9edc39bcb58970e5980593143fab 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 20/21] 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..eb7af15f --- /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) + { + 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 b132b99d..c3d88727 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 4d6c060c7cccbe16b72365705c92c0e593e4755d 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 21/21] Add handler for phantom rpcs --- .../Inner/Objects/InnerPlayerControl.Api.cs | 14 ++++ .../Net/Inner/Objects/InnerPlayerControl.cs | 68 +++++++++++++++++-- 2 files changed, 76 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 46bb7094..34163f6e 100644 --- a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.Api.cs +++ b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.Api.cs @@ -148,5 +148,19 @@ 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); + Rpc63StartVanish.Serialize(writer); + await Game.FinishRpcAsync(writer); + } + + public async ValueTask StartAppearAsync(bool shouldAnimate) + { + using var writer = Game.StartRpc(NetId, RpcCalls.StartAppear); + Rpc65StartAppear.Serialize(writer, 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 c3d88727..60340ce1 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: @@ -1071,5 +1071,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 that the player unvanished before the maximum duration is reached + // 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; + } } }