From 8dfa38b4b50d37caefdd9a1dc7b17fec59009e95 Mon Sep 17 00:00:00 2001 From: js6pak Date: Sun, 11 Dec 2022 18:02:26 +0100 Subject: [PATCH] Implement new GameOptions system Co-authored-by: miniduikboot --- src/Impostor.Api/Games/IGame.cs | 3 +- .../Games/Managers/IGameManager.cs | 4 +- src/Impostor.Api/Innersloth/CrossplayFlags.cs | 7 + .../Innersloth/GameFilterOptions.cs | 30 ++ src/Impostor.Api/Innersloth/GameModes.cs | 9 + .../GameOptions/GameOptionsFactory.cs | 74 +++++ .../GameOptions/HideNSeekGameOptions.cs | 162 ++++++++++ .../Innersloth/GameOptions/IGameOptions.cs | 57 ++++ .../GameOptions/LegacyGameOptionsData.cs | 263 +++++++++++++++++ .../GameOptions/NormalGameOptions.cs | 209 +++++++++++++ .../RoleOptions/EngineerRoleOptions.cs | 33 +++ .../RoleOptions/GuardianAngelRoleOptions.cs | 37 +++ .../GameOptions/RoleOptions/IRoleOptions.cs | 8 + .../RoleOptions/LegacyRoleOptionsData.cs | 73 +++++ .../RoleOptions/RoleOptionsCollection.cs | 55 ++++ .../GameOptions/RoleOptions/RoleRate.cs | 15 + .../RoleOptions/ScientistRoleOptions.cs | 33 +++ .../RoleOptions/ShapeshifterRoleOptions.cs | 37 +++ .../Innersloth/GameOptionsData.cs | 278 ------------------ .../Innersloth/RoleOptionsData.cs | 135 --------- .../Net/Messages/C2S/Message00HostGameC2S.cs | 22 +- .../Messages/C2S/Message16GetGameListC2S.cs | 8 +- .../Net/Messages/Rpcs/Rpc02SyncSettings.cs | 15 +- .../Net/Messages/S2C/Message00HostGameS2C.cs | 4 +- src/Impostor.Api/packages.lock.json | 3 +- src/Impostor.Client.App/Program.cs | 5 +- src/Impostor.Plugins.Example/ExamplePlugin.cs | 4 +- .../Handlers/PlayerEventListener.cs | 9 +- src/Impostor.Server/Net/Client.cs | 9 +- .../Net/Inner/Objects/InnerMeetingHud.cs | 11 +- .../Net/Inner/Objects/InnerPlayerControl.cs | 2 +- .../Net/Inner/Objects/InnerPlayerInfo.cs | 11 +- .../Net/Manager/GameManager.cs | 9 +- src/Impostor.Server/Net/State/Game.Api.cs | 13 +- src/Impostor.Server/Net/State/Game.cs | 5 +- src/Impostor.Tools.ServerReplay/Program.cs | 8 +- 36 files changed, 1189 insertions(+), 471 deletions(-) create mode 100644 src/Impostor.Api/Innersloth/CrossplayFlags.cs create mode 100644 src/Impostor.Api/Innersloth/GameFilterOptions.cs create mode 100644 src/Impostor.Api/Innersloth/GameModes.cs create mode 100644 src/Impostor.Api/Innersloth/GameOptions/GameOptionsFactory.cs create mode 100644 src/Impostor.Api/Innersloth/GameOptions/HideNSeekGameOptions.cs create mode 100644 src/Impostor.Api/Innersloth/GameOptions/IGameOptions.cs create mode 100644 src/Impostor.Api/Innersloth/GameOptions/LegacyGameOptionsData.cs create mode 100644 src/Impostor.Api/Innersloth/GameOptions/NormalGameOptions.cs create mode 100644 src/Impostor.Api/Innersloth/GameOptions/RoleOptions/EngineerRoleOptions.cs create mode 100644 src/Impostor.Api/Innersloth/GameOptions/RoleOptions/GuardianAngelRoleOptions.cs create mode 100644 src/Impostor.Api/Innersloth/GameOptions/RoleOptions/IRoleOptions.cs create mode 100644 src/Impostor.Api/Innersloth/GameOptions/RoleOptions/LegacyRoleOptionsData.cs create mode 100644 src/Impostor.Api/Innersloth/GameOptions/RoleOptions/RoleOptionsCollection.cs create mode 100644 src/Impostor.Api/Innersloth/GameOptions/RoleOptions/RoleRate.cs create mode 100644 src/Impostor.Api/Innersloth/GameOptions/RoleOptions/ScientistRoleOptions.cs create mode 100644 src/Impostor.Api/Innersloth/GameOptions/RoleOptions/ShapeshifterRoleOptions.cs delete mode 100644 src/Impostor.Api/Innersloth/GameOptionsData.cs delete mode 100644 src/Impostor.Api/Innersloth/RoleOptionsData.cs diff --git a/src/Impostor.Api/Games/IGame.cs b/src/Impostor.Api/Games/IGame.cs index f76382b..a10da63 100644 --- a/src/Impostor.Api/Games/IGame.cs +++ b/src/Impostor.Api/Games/IGame.cs @@ -2,6 +2,7 @@ using System.Net; using System.Threading.Tasks; using Impostor.Api.Innersloth; +using Impostor.Api.Innersloth.GameOptions; using Impostor.Api.Net; using Impostor.Api.Net.Inner; @@ -9,7 +10,7 @@ namespace Impostor.Api.Games { public interface IGame { - GameOptionsData Options { get; } + IGameOptions Options { get; } GameCode Code { get; } diff --git a/src/Impostor.Api/Games/Managers/IGameManager.cs b/src/Impostor.Api/Games/Managers/IGameManager.cs index f417d31..c418b72 100644 --- a/src/Impostor.Api/Games/Managers/IGameManager.cs +++ b/src/Impostor.Api/Games/Managers/IGameManager.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; using System.Threading.Tasks; -using Impostor.Api.Innersloth; +using Impostor.Api.Innersloth.GameOptions; namespace Impostor.Api.Games.Managers { @@ -16,6 +16,6 @@ namespace Impostor.Api.Games.Managers /// Game options. /// Created game or null if creation was cancelled by a plugin. /// Thrown when game creation failed. - ValueTask CreateAsync(GameOptionsData options); + ValueTask CreateAsync(IGameOptions options); } } diff --git a/src/Impostor.Api/Innersloth/CrossplayFlags.cs b/src/Impostor.Api/Innersloth/CrossplayFlags.cs new file mode 100644 index 0000000..1b251fe --- /dev/null +++ b/src/Impostor.Api/Innersloth/CrossplayFlags.cs @@ -0,0 +1,7 @@ +namespace Impostor.Api.Innersloth; + +public enum CrossplayFlags +{ + Default = 4, + All = int.MaxValue, +} diff --git a/src/Impostor.Api/Innersloth/GameFilterOptions.cs b/src/Impostor.Api/Innersloth/GameFilterOptions.cs new file mode 100644 index 0000000..e84cb52 --- /dev/null +++ b/src/Impostor.Api/Innersloth/GameFilterOptions.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; + +namespace Impostor.Api.Innersloth; + +public class GameFilterOptions +{ + public HashSet FilterTags { get; } = new HashSet(); + + public static GameFilterOptions Deserialize(IMessageReader reader) + { + var options = new GameFilterOptions(); + + var count = reader.ReadPackedInt32(); + for (var i = 0; i < count; i++) + { + options.FilterTags.Add(reader.ReadString()); + } + + return options; + } + + public void Serialize(IMessageWriter writer) + { + writer.WritePacked(FilterTags.Count); + foreach (var filterTag in FilterTags) + { + writer.Write(filterTag); + } + } +} diff --git a/src/Impostor.Api/Innersloth/GameModes.cs b/src/Impostor.Api/Innersloth/GameModes.cs new file mode 100644 index 0000000..0c55518 --- /dev/null +++ b/src/Impostor.Api/Innersloth/GameModes.cs @@ -0,0 +1,9 @@ +namespace Impostor.Api.Innersloth +{ + public enum GameModes : byte + { + None, + Normal, + HideNSeek, + } +} diff --git a/src/Impostor.Api/Innersloth/GameOptions/GameOptionsFactory.cs b/src/Impostor.Api/Innersloth/GameOptions/GameOptionsFactory.cs new file mode 100644 index 0000000..a7625aa --- /dev/null +++ b/src/Impostor.Api/Innersloth/GameOptions/GameOptionsFactory.cs @@ -0,0 +1,74 @@ +using System; + +namespace Impostor.Api.Innersloth.GameOptions; + +public static class GameOptionsFactory +{ + public const byte ModularOptionsDataVersion = 7; + + public static void Serialize(IMessageWriter writer, IGameOptions gameOptions) + { + if (gameOptions.Version < ModularOptionsDataVersion) + { + throw new NotSupportedException(); + } + + // In theory we should use WriteBytesAndSize here, but it's costly and requires computing the size + // Base game does similar hacks and completely ignores this value for modular game options so we can just write 0 here + writer.WritePacked(0); + + writer.Write(gameOptions.Version); + writer.StartMessage(0); + writer.Write((byte)gameOptions.GameMode); + gameOptions.Serialize(writer); + writer.EndMessage(); + } + + public static IGameOptions Deserialize(IMessageReader reader) + { + reader.ReadPackedInt32(); + var version = reader.ReadByte(); + + if (version < ModularOptionsDataVersion) + { + return LegacyGameOptionsData.Deserialize(reader, version); + } + + var optionsReader = reader.ReadMessage(); + var gameMode = (GameModes)optionsReader.ReadByte(); + + return gameMode switch + { + GameModes.Normal => NormalGameOptions.Deserialize(optionsReader, version), + GameModes.HideNSeek => HideNSeekGameOptions.Deserialize(optionsReader, version), + _ => throw new ArgumentOutOfRangeException(), + }; + } + + public static void DeserializeInto(IMessageReader reader, IGameOptions gameOptions) + { + reader.ReadPackedInt32(); + var version = reader.ReadByte(); + + if (version < ModularOptionsDataVersion) + { + ((LegacyGameOptionsData)gameOptions).Deserialize(reader); + return; + } + + var optionsReader = reader.ReadMessage(); + var gameMode = (GameModes)optionsReader.ReadByte(); + + switch (gameMode) + { + case GameModes.Normal: + ((NormalGameOptions)gameOptions).Deserialize(optionsReader); + break; + case GameModes.HideNSeek: + ((HideNSeekGameOptions)gameOptions).Deserialize(optionsReader); + break; + default: + throw new ArgumentOutOfRangeException(nameof(gameMode), gameMode, null); + } + } +} diff --git a/src/Impostor.Api/Innersloth/GameOptions/HideNSeekGameOptions.cs b/src/Impostor.Api/Innersloth/GameOptions/HideNSeekGameOptions.cs new file mode 100644 index 0000000..6d061a9 --- /dev/null +++ b/src/Impostor.Api/Innersloth/GameOptions/HideNSeekGameOptions.cs @@ -0,0 +1,162 @@ +namespace Impostor.Api.Innersloth.GameOptions; + +public class HideNSeekGameOptions : IGameOptions +{ + public const int LatestVersion = 7; + + public HideNSeekGameOptions(byte version = LatestVersion) + { + Version = version; + IGameOptions.EnsureVersionIsModular(version); + } + + /// + public byte Version { get; } + + /// + public GameModes GameMode => GameModes.HideNSeek; + + /// + public byte MaxPlayers { get; set; } = 15; + + /// + public GameKeywords Keywords { get; set; } = GameKeywords.English; + + /// + public MapTypes Map { get; set; } = MapTypes.Skeld; + + /// + public int NumImpostors { get; set; } = 1; + + /// + public bool IsDefaults { get; set; } = true; + + /// + /// Gets or sets the Player speed modifier. + /// + public float PlayerSpeedMod { get; set; } = 1f; + + /// + /// Gets or sets the Light modifier for the players that are members of the crew as a multiplier value. + /// + public float CrewLightMod { get; set; } = 1f; + + /// + /// Gets or sets the Light modifier for the players that are Impostors as a multiplier value. + /// + public float ImpostorLightMod { get; set; } = 1f; + + /// + /// Gets or sets the number of common tasks. + /// + public int NumCommonTasks { get; set; } = 1; + + /// + /// Gets or sets the number of long tasks. + /// + public int NumLongTasks { get; set; } = 1; + + /// + /// Gets or sets the number of short tasks. + /// + public int NumShortTasks { get; set; } = 2; + + public int CrewmateVentUses { get; set; } = 1; + + public float CrewmateTimeInVent { get; set; } = 3f; + + public float HidingTime { get; set; } = 200f; + + public float CrewmateFlashlightSize { get; set; } = 0.35f; + + public float ImpostorFlashlightSize { get; set; } = 0.25f; + + public bool UseFlashlight { get; set; } = true; + + public bool FinalHideSeekMap { get; set; } = true; + + public float FinalHideTime { get; set; } = 50f; + + public float FinalSeekerSpeed { get; set; } = 1.2f; + + public bool FinalHidePings { get; set; } = true; + + public bool ShowNames { get; set; } = true; + + public uint SeekerPlayerId { get; set; } = 0xFFFFFFFF; + + public float MaxPingTime { get; set; } = 6f; + + public static HideNSeekGameOptions Deserialize(IMessageReader reader, byte version) + { + var options = new HideNSeekGameOptions(version); + options.Deserialize(reader); + return options; + } + + public void Deserialize(IMessageReader reader) + { + MaxPlayers = reader.ReadByte(); + Keywords = (GameKeywords)reader.ReadInt32(); + Map = (MapTypes)reader.ReadByte(); + PlayerSpeedMod = reader.ReadSingle(); + CrewLightMod = reader.ReadSingle(); + ImpostorLightMod = reader.ReadSingle(); + NumCommonTasks = reader.ReadByte(); + NumLongTasks = reader.ReadByte(); + NumShortTasks = reader.ReadByte(); + IsDefaults = reader.ReadBoolean(); + + CrewmateVentUses = reader.ReadInt32(); + HidingTime = reader.ReadSingle(); + CrewmateFlashlightSize = reader.ReadSingle(); + ImpostorFlashlightSize = reader.ReadSingle(); + UseFlashlight = reader.ReadBoolean(); + FinalHideSeekMap = reader.ReadBoolean(); + FinalHideTime = reader.ReadSingle(); + FinalSeekerSpeed = reader.ReadSingle(); + FinalHidePings = reader.ReadBoolean(); + ShowNames = reader.ReadBoolean(); + SeekerPlayerId = reader.ReadUInt32(); + MaxPingTime = reader.ReadSingle(); + CrewmateTimeInVent = reader.ReadSingle(); + + if (Version > 7) + { + IGameOptions.ThrowUnknownVersion(Version); + } + } + + public void Serialize(IMessageWriter writer) + { + writer.Write(MaxPlayers); + writer.Write((uint)Keywords); + writer.Write((byte)Map); + writer.Write(PlayerSpeedMod); + writer.Write(CrewLightMod); + writer.Write(ImpostorLightMod); + writer.Write((byte)NumCommonTasks); + writer.Write((byte)NumLongTasks); + writer.Write((byte)NumShortTasks); + writer.Write(IsDefaults); + + writer.Write(CrewmateVentUses); + writer.Write(HidingTime); + writer.Write(CrewmateFlashlightSize); + writer.Write(ImpostorFlashlightSize); + writer.Write(UseFlashlight); + writer.Write(FinalHideSeekMap); + writer.Write(FinalHideTime); + writer.Write(FinalSeekerSpeed); + writer.Write(FinalHidePings); + writer.Write(ShowNames); + writer.Write(SeekerPlayerId); + writer.Write(MaxPingTime); + writer.Write(CrewmateTimeInVent); + + if (Version > 7) + { + IGameOptions.ThrowUnknownVersion(Version); + } + } +} diff --git a/src/Impostor.Api/Innersloth/GameOptions/IGameOptions.cs b/src/Impostor.Api/Innersloth/GameOptions/IGameOptions.cs new file mode 100644 index 0000000..10200c1 --- /dev/null +++ b/src/Impostor.Api/Innersloth/GameOptions/IGameOptions.cs @@ -0,0 +1,57 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Impostor.Api.Innersloth.GameOptions; + +public interface IGameOptions +{ + /// + /// Gets the version. + /// + public byte Version { get; } + + /// + /// Gets the currently active gamemode. + /// + public GameModes GameMode { get; } + + /// + /// Gets or sets the maximum amount of players for this lobby. + /// + public byte MaxPlayers { get; set; } + + /// + /// Gets or sets the language of the lobby as per enum. + /// + public GameKeywords Keywords { get; set; } + + /// + /// Gets or sets the Map selected for this lobby. + /// + public MapTypes Map { get; set; } + + /// + /// Gets or sets the number of impostors for this lobby. + /// + public int NumImpostors { get; set; } + + /// + /// Gets or sets a value indicating whether the GameOptions are the default ones. + /// + public bool IsDefaults { get; set; } + + public void Serialize(IMessageWriter writer); + + public static void EnsureVersionIsModular(byte version) + { + if (version < GameOptionsFactory.ModularOptionsDataVersion) + { + throw new ImpostorException($"{typeof(TCaller).Name} didn't exist before version 7, did you mean {nameof(LegacyGameOptionsData)}?"); + } + } + + [DoesNotReturn] + public static void ThrowUnknownVersion(byte version) + { + throw new ImpostorException($"Unknown {typeof(TCaller).Name} version {version}"); + } +} diff --git a/src/Impostor.Api/Innersloth/GameOptions/LegacyGameOptionsData.cs b/src/Impostor.Api/Innersloth/GameOptions/LegacyGameOptionsData.cs new file mode 100644 index 0000000..1ed7ecc --- /dev/null +++ b/src/Impostor.Api/Innersloth/GameOptions/LegacyGameOptionsData.cs @@ -0,0 +1,263 @@ +using Impostor.Api.Innersloth.GameOptions.RoleOptions; + +namespace Impostor.Api.Innersloth.GameOptions; + +public class LegacyGameOptionsData : IGameOptions +{ + /// + /// The latest major version of the game client. + /// + public const int LatestVersion = 5; + + public LegacyGameOptionsData(byte version = LatestVersion) + { + Version = version; + } + + /// + /// Gets or sets host's version of the game. + /// + public byte Version { get; set; } + + public GameModes GameMode => GameModes.Normal; + + /// + /// Gets or sets the maximum amount of players for this lobby. + /// + public byte MaxPlayers { get; set; } = 10; + + /// + /// Gets or sets the language of the lobby as per enum. + /// + public GameKeywords Keywords { get; set; } = GameKeywords.English; + + /// + /// Gets or sets the Map selected for this lobby. + /// + public MapTypes Map { get; set; } = MapTypes.Skeld; + + /// + /// Gets or sets the Player speed modifier. + /// + public float PlayerSpeedMod { get; set; } = 1f; + + /// + /// Gets or sets the Light modifier for the players that are members of the crew as a multiplier value. + /// + public float CrewLightMod { get; set; } = 1f; + + /// + /// Gets or sets the Light modifier for the players that are Impostors as a multiplier value. + /// + public float ImpostorLightMod { get; set; } = 1f; + + /// + /// Gets or sets the Impostor cooldown to kill in seconds. + /// + public float KillCooldown { get; set; } = 15f; + + /// + /// Gets or sets the number of common tasks. + /// + public int NumCommonTasks { get; set; } = 1; + + /// + /// Gets or sets the number of long tasks. + /// + public int NumLongTasks { get; set; } = 1; + + /// + /// Gets or sets the number of short tasks. + /// + public int NumShortTasks { get; set; } = 2; + + /// + /// Gets or sets the maximum amount of emergency meetings each player can call during the game in seconds. + /// + public int NumEmergencyMeetings { get; set; } = 1; + + /// + /// Gets or sets the cooldown between each time any player can call an emergency meeting in seconds. + /// + public int EmergencyCooldown { get; set; } = 15; + + /// + /// Gets or sets the number of impostors for this lobby. + /// + public int NumImpostors { get; set; } = 1; + + /// + /// Gets or sets a value indicating whether ghosts (dead crew members) can do tasks. + /// + public bool GhostsDoTasks { get; set; } = true; + + /// + /// Gets or sets the Kill as per values in . + /// + public KillDistances KillDistance { get; set; } = KillDistances.Normal; + + /// + /// Gets or sets the time for discussion before voting time in seconds. + /// + public int DiscussionTime { get; set; } = 15; + + /// + /// Gets or sets the time for voting in seconds. + /// + public int VotingTime { get; set; } = 120; + + /// + /// Gets or sets a value indicating whether an ejected player is an impostor or not. + /// + public bool ConfirmImpostor { get; set; } = true; + + /// + /// Gets or sets a value indicating whether players are able to see tasks being performed by other players. + /// + /// + /// By being set to true, tasks such as Empty Garbage, Submit Scan, Clear asteroids, Prime shields execution will be visible to other players. + /// + public bool VisualTasks { get; set; } = true; + + /// + /// Gets or sets a value indicating whether the vote is anonymous. + /// + public bool AnonymousVotes { get; set; } + + /// + /// Gets or sets the task bar update mode as per values in . + /// + public TaskBarUpdate TaskBarUpdate { get; set; } = TaskBarUpdate.Always; + + /// + /// Gets or sets role options. + /// + public LegacyRoleOptionsData RoleOptions { get; set; } = new LegacyRoleOptionsData(); + + /// + /// Gets or sets a value indicating whether the GameOptions are the default ones. + /// + public bool IsDefaults { get; set; } = true; + + public static LegacyGameOptionsData Deserialize(IMessageReader reader, byte version) + { + var options = new LegacyGameOptionsData(version); + options.Deserialize(reader); + return options; + } + + public void Deserialize(IMessageReader reader) + { + Version = reader.ReadByte(); + MaxPlayers = reader.ReadByte(); + Keywords = (GameKeywords)reader.ReadUInt32(); + Map = (MapTypes)reader.ReadByte(); + PlayerSpeedMod = reader.ReadSingle(); + + CrewLightMod = reader.ReadSingle(); + ImpostorLightMod = reader.ReadSingle(); + KillCooldown = reader.ReadSingle(); + + NumCommonTasks = reader.ReadByte(); + NumLongTasks = reader.ReadByte(); + NumShortTasks = reader.ReadByte(); + + NumEmergencyMeetings = reader.ReadInt32(); + + NumImpostors = reader.ReadByte(); + KillDistance = (KillDistances)reader.ReadByte(); + DiscussionTime = reader.ReadInt32(); + VotingTime = reader.ReadInt32(); + + IsDefaults = reader.ReadBoolean(); + + if (Version >= 2) + { + EmergencyCooldown = reader.ReadByte(); + } + + if (Version >= 3) + { + ConfirmImpostor = reader.ReadBoolean(); + VisualTasks = reader.ReadBoolean(); + } + + if (Version >= 4) + { + AnonymousVotes = reader.ReadBoolean(); + TaskBarUpdate = (TaskBarUpdate)reader.ReadByte(); + } + + if (Version >= 5) + { + RoleOptions = LegacyRoleOptionsData.Deserialize(reader); + } + + if (Version >= 6) + { + // Nothing was changed in V6 + } + + if (Version > 6) + { + IGameOptions.ThrowUnknownVersion(Version); + } + } + + /// + /// Serializes this instance of GameOptionsData object to a specified BinaryWriter. + /// + /// The stream to write the message to. + public void Serialize(IMessageWriter writer) + { + writer.Write((byte)Version); + writer.Write((byte)MaxPlayers); + writer.Write((uint)Keywords); + writer.Write((byte)Map); + writer.Write((float)PlayerSpeedMod); + writer.Write((float)CrewLightMod); + writer.Write((float)ImpostorLightMod); + writer.Write((float)KillCooldown); + writer.Write((byte)NumCommonTasks); + writer.Write((byte)NumLongTasks); + writer.Write((byte)NumShortTasks); + writer.Write((int)NumEmergencyMeetings); + writer.Write((byte)NumImpostors); + writer.Write((byte)KillDistance); + writer.Write((uint)DiscussionTime); + writer.Write((uint)VotingTime); + writer.Write((bool)IsDefaults); + + if (Version >= 2) + { + writer.Write((byte)EmergencyCooldown); + } + + if (Version >= 3) + { + writer.Write((bool)ConfirmImpostor); + writer.Write((bool)VisualTasks); + } + + if (Version >= 4) + { + writer.Write((bool)AnonymousVotes); + writer.Write((byte)TaskBarUpdate); + } + + if (Version >= 5) + { + RoleOptions.Serialize(writer); + } + + if (Version >= 6) + { + // Nothing was changed in V6 + } + + if (Version > 6) + { + throw new ImpostorException($"Unknown {nameof(LegacyGameOptionsData)} version {Version}"); + } + } +} diff --git a/src/Impostor.Api/Innersloth/GameOptions/NormalGameOptions.cs b/src/Impostor.Api/Innersloth/GameOptions/NormalGameOptions.cs new file mode 100644 index 0000000..e91cb42 --- /dev/null +++ b/src/Impostor.Api/Innersloth/GameOptions/NormalGameOptions.cs @@ -0,0 +1,209 @@ +using Impostor.Api.Innersloth.GameOptions.RoleOptions; + +namespace Impostor.Api.Innersloth.GameOptions; + +public class NormalGameOptions : IGameOptions +{ + public const int LatestVersion = 7; + + public NormalGameOptions(byte version = LatestVersion) + { + Version = version; + IGameOptions.EnsureVersionIsModular(version); + RoleOptions = new RoleOptionsCollection(version); + } + + /// + public byte Version { get; } + + /// + public GameModes GameMode => GameModes.Normal; + + /// + public byte MaxPlayers { get; set; } = 10; + + /// + public GameKeywords Keywords { get; set; } = GameKeywords.English; + + /// + public MapTypes Map { get; set; } = MapTypes.Skeld; + + /// + public int NumImpostors { get; set; } = 1; + + /// + public bool IsDefaults { get; set; } = true; + + /// + /// Gets or sets the Player speed modifier. + /// + public float PlayerSpeedMod { get; set; } = 1f; + + /// + /// Gets or sets the Light modifier for the players that are members of the crew as a multiplier value. + /// + public float CrewLightMod { get; set; } = 1f; + + /// + /// Gets or sets the Light modifier for the players that are Impostors as a multiplier value. + /// + public float ImpostorLightMod { get; set; } = 1f; + + /// + /// Gets or sets the Impostor cooldown to kill in seconds. + /// + public float KillCooldown { get; set; } = 15f; + + /// + /// Gets or sets the number of common tasks. + /// + public int NumCommonTasks { get; set; } = 1; + + /// + /// Gets or sets the number of long tasks. + /// + public int NumLongTasks { get; set; } = 1; + + /// + /// Gets or sets the number of short tasks. + /// + public int NumShortTasks { get; set; } = 2; + + /// + /// Gets or sets the maximum amount of emergency meetings each player can call during the game in seconds. + /// + public int NumEmergencyMeetings { get; set; } = 1; + + /// + /// Gets or sets the cooldown between each time any player can call an emergency meeting in seconds. + /// + public int EmergencyCooldown { get; set; } = 15; + + /// + /// Gets or sets a value indicating whether ghosts (dead crew members) can do tasks. + /// + public bool GhostsDoTasks { get; set; } = true; + + /// + /// Gets or sets the Kill as per values in . + /// + public KillDistances KillDistance { get; set; } = KillDistances.Normal; + + /// + /// Gets or sets the time for discussion before voting time in seconds. + /// + public int DiscussionTime { get; set; } = 15; + + /// + /// Gets or sets the time for voting in seconds. + /// + public int VotingTime { get; set; } = 120; + + /// + /// Gets or sets a value indicating whether an ejected player is an impostor or not. + /// + public bool ConfirmImpostor { get; set; } = true; + + /// + /// Gets or sets a value indicating whether players are able to see tasks being performed by other players. + /// + /// + /// By being set to true, tasks such as Empty Garbage, Submit Scan, Clear asteroids, Prime shields execution will be visible to other players. + /// + public bool VisualTasks { get; set; } = true; + + /// + /// Gets or sets a value indicating whether the vote is anonymous. + /// + public bool AnonymousVotes { get; set; } + + /// + /// Gets or sets the task bar update mode as per values in . + /// + public TaskBarUpdate TaskBarUpdate { get; set; } = TaskBarUpdate.Always; + + public RoleOptionsCollection RoleOptions { get; set; } + + public static NormalGameOptions Deserialize(IMessageReader reader, byte version) + { + var options = new NormalGameOptions(version); + options.Deserialize(reader); + return options; + } + + public void Deserialize(IMessageReader reader) + { + MaxPlayers = reader.ReadByte(); + Keywords = (GameKeywords)reader.ReadUInt32(); + Map = (MapTypes)reader.ReadByte(); + PlayerSpeedMod = reader.ReadSingle(); + + CrewLightMod = reader.ReadSingle(); + ImpostorLightMod = reader.ReadSingle(); + KillCooldown = reader.ReadSingle(); + + NumCommonTasks = reader.ReadByte(); + NumLongTasks = reader.ReadByte(); + NumShortTasks = reader.ReadByte(); + + NumEmergencyMeetings = reader.ReadInt32(); + + NumImpostors = reader.ReadByte(); + KillDistance = (KillDistances)reader.ReadByte(); + DiscussionTime = reader.ReadInt32(); + VotingTime = reader.ReadInt32(); + + IsDefaults = reader.ReadBoolean(); + + EmergencyCooldown = reader.ReadByte(); + ConfirmImpostor = reader.ReadBoolean(); + VisualTasks = reader.ReadBoolean(); + AnonymousVotes = reader.ReadBoolean(); + TaskBarUpdate = (TaskBarUpdate)reader.ReadByte(); + + RoleOptions.Deserialize(reader); + + if (Version > 7) + { + IGameOptions.ThrowUnknownVersion(Version); + } + } + + public void Serialize(IMessageWriter writer) + { + writer.Write(MaxPlayers); + writer.Write((uint)Keywords); + writer.Write((byte)Map); + writer.Write(PlayerSpeedMod); + + writer.Write(CrewLightMod); + writer.Write(ImpostorLightMod); + writer.Write(KillCooldown); + + writer.Write(NumCommonTasks); + writer.Write(NumLongTasks); + writer.Write(NumShortTasks); + + writer.Write(NumEmergencyMeetings); + + writer.Write(NumImpostors); + writer.Write((byte)KillDistance); + writer.Write(DiscussionTime); + writer.Write(VotingTime); + + writer.Write(IsDefaults); + + writer.Write(EmergencyCooldown); + writer.Write(ConfirmImpostor); + writer.Write(VisualTasks); + writer.Write(AnonymousVotes); + writer.Write((byte)TaskBarUpdate); + + RoleOptions.Serialize(writer); + + if (Version > 7) + { + IGameOptions.ThrowUnknownVersion(Version); + } + } +} diff --git a/src/Impostor.Api/Innersloth/GameOptions/RoleOptions/EngineerRoleOptions.cs b/src/Impostor.Api/Innersloth/GameOptions/RoleOptions/EngineerRoleOptions.cs new file mode 100644 index 0000000..45c7b74 --- /dev/null +++ b/src/Impostor.Api/Innersloth/GameOptions/RoleOptions/EngineerRoleOptions.cs @@ -0,0 +1,33 @@ +namespace Impostor.Api.Innersloth.GameOptions.RoleOptions; + +public class EngineerRoleOptions : IRoleOptions +{ + public EngineerRoleOptions(byte version) + { + Version = version; + } + + public byte Version { get; } + + public RoleTypes Type => RoleTypes.Engineer; + + public byte Cooldown { get; set; } = 30; + + public byte InVentMaxTime { get; set; } = 15; + + public static EngineerRoleOptions Deserialize(IMessageReader reader, byte version) + { + var options = new EngineerRoleOptions(version); + + options.Cooldown = reader.ReadByte(); + options.InVentMaxTime = reader.ReadByte(); + + return options; + } + + public void Serialize(IMessageWriter writer) + { + writer.Write(Cooldown); + writer.Write(InVentMaxTime); + } +} diff --git a/src/Impostor.Api/Innersloth/GameOptions/RoleOptions/GuardianAngelRoleOptions.cs b/src/Impostor.Api/Innersloth/GameOptions/RoleOptions/GuardianAngelRoleOptions.cs new file mode 100644 index 0000000..d46c57d --- /dev/null +++ b/src/Impostor.Api/Innersloth/GameOptions/RoleOptions/GuardianAngelRoleOptions.cs @@ -0,0 +1,37 @@ +namespace Impostor.Api.Innersloth.GameOptions.RoleOptions; + +public class GuardianAngelRoleOptions : IRoleOptions +{ + public GuardianAngelRoleOptions(byte version) + { + Version = version; + } + + public byte Version { get; } + + public RoleTypes Type => RoleTypes.GuardianAngel; + + public byte Cooldown { get; set; } = 60; + + public byte ProtectionDurationSeconds { get; set; } = 10; + + public bool ImpostorsCanSeeProtect { get; set; } + + public static GuardianAngelRoleOptions Deserialize(IMessageReader reader, byte version) + { + var options = new GuardianAngelRoleOptions(version); + + options.Cooldown = reader.ReadByte(); + options.ProtectionDurationSeconds = reader.ReadByte(); + options.ImpostorsCanSeeProtect = reader.ReadBoolean(); + + return options; + } + + public void Serialize(IMessageWriter writer) + { + writer.Write(Cooldown); + writer.Write(ProtectionDurationSeconds); + writer.Write(ImpostorsCanSeeProtect); + } +} diff --git a/src/Impostor.Api/Innersloth/GameOptions/RoleOptions/IRoleOptions.cs b/src/Impostor.Api/Innersloth/GameOptions/RoleOptions/IRoleOptions.cs new file mode 100644 index 0000000..0fe0bac --- /dev/null +++ b/src/Impostor.Api/Innersloth/GameOptions/RoleOptions/IRoleOptions.cs @@ -0,0 +1,8 @@ +namespace Impostor.Api.Innersloth.GameOptions.RoleOptions; + +public interface IRoleOptions +{ + RoleTypes Type { get; } + + void Serialize(IMessageWriter writer); +} diff --git a/src/Impostor.Api/Innersloth/GameOptions/RoleOptions/LegacyRoleOptionsData.cs b/src/Impostor.Api/Innersloth/GameOptions/RoleOptions/LegacyRoleOptionsData.cs new file mode 100644 index 0000000..5a5381f --- /dev/null +++ b/src/Impostor.Api/Innersloth/GameOptions/RoleOptions/LegacyRoleOptionsData.cs @@ -0,0 +1,73 @@ +using System.Collections.Generic; + +namespace Impostor.Api.Innersloth.GameOptions.RoleOptions; + +public class LegacyRoleOptionsData +{ + public bool ShapeshifterLeaveSkin { get; set; } + + public byte ShapeshifterCooldown { get; set; } = 10; + + public byte ShapeshifterDuration { get; set; } = 30; + + public byte ScientistCooldown { get; set; } = 15; + + public byte ScientistBatteryCharge { get; set; } = 5; + + public byte GuardianAngelCooldown { get; set; } = 60; + + public bool ImpostorsCanSeeProtect { get; set; } + + public byte ProtectionDurationSeconds { get; set; } = 10; + + public byte EngineerCooldown { get; set; } = 30; + + public byte EngineerInVentMaxTime { get; set; } = 15; + + public Dictionary RoleRates { get; } = new Dictionary(); + + public static LegacyRoleOptionsData Deserialize(IMessageReader reader) + { + var roleOptionsData = new LegacyRoleOptionsData(); + var num = reader.ReadPackedInt32(); + for (var i = 0; i < num; i++) + { + var key = (RoleTypes)reader.ReadInt16(); + var roleRate = RoleRate.Deserialize(reader); + roleOptionsData.RoleRates[key] = roleRate; + } + + roleOptionsData.ShapeshifterLeaveSkin = reader.ReadBoolean(); + roleOptionsData.ShapeshifterCooldown = reader.ReadByte(); + roleOptionsData.ShapeshifterDuration = reader.ReadByte(); + roleOptionsData.ScientistCooldown = reader.ReadByte(); + roleOptionsData.GuardianAngelCooldown = reader.ReadByte(); + roleOptionsData.EngineerCooldown = reader.ReadByte(); + roleOptionsData.EngineerInVentMaxTime = reader.ReadByte(); + roleOptionsData.ScientistBatteryCharge = reader.ReadByte(); + roleOptionsData.ProtectionDurationSeconds = reader.ReadByte(); + roleOptionsData.ImpostorsCanSeeProtect = reader.ReadBoolean(); + return roleOptionsData; + } + + public void Serialize(IMessageWriter writer) + { + writer.WritePacked(RoleRates.Count); + foreach (var roleRate in RoleRates) + { + writer.Write((ushort)roleRate.Key); + roleRate.Value.Serialize(writer); + } + + writer.Write(ShapeshifterLeaveSkin); + writer.Write(ShapeshifterCooldown); + writer.Write(ShapeshifterDuration); + writer.Write(ScientistCooldown); + writer.Write(GuardianAngelCooldown); + writer.Write(EngineerCooldown); + writer.Write(EngineerInVentMaxTime); + writer.Write(ScientistBatteryCharge); + writer.Write(ProtectionDurationSeconds); + writer.Write(ImpostorsCanSeeProtect); + } +} diff --git a/src/Impostor.Api/Innersloth/GameOptions/RoleOptions/RoleOptionsCollection.cs b/src/Impostor.Api/Innersloth/GameOptions/RoleOptions/RoleOptionsCollection.cs new file mode 100644 index 0000000..d8b7076 --- /dev/null +++ b/src/Impostor.Api/Innersloth/GameOptions/RoleOptions/RoleOptionsCollection.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; + +namespace Impostor.Api.Innersloth.GameOptions.RoleOptions; + +public class RoleOptionsCollection +{ + public RoleOptionsCollection(byte version) + { + Version = version; + } + + public byte Version { get; } + + public Dictionary Roles { get; } = new Dictionary(); + + public void Deserialize(IMessageReader reader) + { + Roles.Clear(); + + var count = reader.ReadPackedInt32(); + Roles.EnsureCapacity(count); + for (var i = 0; i < count; i++) + { + var roleType = (RoleTypes)reader.ReadInt16(); + var roleRate = RoleRate.Deserialize(reader); + var roleOptionsReader = reader.ReadMessage(); + IRoleOptions roleOptions = roleType switch + { + RoleTypes.Scientist => ScientistRoleOptions.Deserialize(roleOptionsReader, Version), + RoleTypes.Engineer => EngineerRoleOptions.Deserialize(roleOptionsReader, Version), + RoleTypes.GuardianAngel => GuardianAngelRoleOptions.Deserialize(roleOptionsReader, Version), + RoleTypes.Shapeshifter => ShapeshifterRoleOptions.Deserialize(roleOptionsReader, Version), + _ => throw new ArgumentOutOfRangeException(nameof(roleType), roleType, null), + }; + + Roles.Add(roleType, new RoleData(roleType, roleOptions, roleRate)); + } + } + + public void Serialize(IMessageWriter writer) + { + writer.WritePacked(Roles.Count); + foreach (var (key, roleData) in Roles) + { + writer.Write((ushort)key); + roleData.Rate.Serialize(writer); + writer.StartMessage(0); + roleData.RoleOptions.Serialize(writer); + writer.EndMessage(); + } + } + + public readonly record struct RoleData(RoleTypes Type, IRoleOptions RoleOptions, RoleRate Rate); +} diff --git a/src/Impostor.Api/Innersloth/GameOptions/RoleOptions/RoleRate.cs b/src/Impostor.Api/Innersloth/GameOptions/RoleOptions/RoleRate.cs new file mode 100644 index 0000000..352db4f --- /dev/null +++ b/src/Impostor.Api/Innersloth/GameOptions/RoleOptions/RoleRate.cs @@ -0,0 +1,15 @@ +namespace Impostor.Api.Innersloth.GameOptions.RoleOptions; + +public readonly record struct RoleRate(byte MaxCount, byte Chance) +{ + public static RoleRate Deserialize(IMessageReader reader) + { + return new RoleRate(reader.ReadByte(), reader.ReadByte()); + } + + public void Serialize(IMessageWriter writer) + { + writer.Write(MaxCount); + writer.Write(Chance); + } +} diff --git a/src/Impostor.Api/Innersloth/GameOptions/RoleOptions/ScientistRoleOptions.cs b/src/Impostor.Api/Innersloth/GameOptions/RoleOptions/ScientistRoleOptions.cs new file mode 100644 index 0000000..4a38953 --- /dev/null +++ b/src/Impostor.Api/Innersloth/GameOptions/RoleOptions/ScientistRoleOptions.cs @@ -0,0 +1,33 @@ +namespace Impostor.Api.Innersloth.GameOptions.RoleOptions; + +public class ScientistRoleOptions : IRoleOptions +{ + public ScientistRoleOptions(byte version) + { + Version = version; + } + + public byte Version { get; } + + public RoleTypes Type => RoleTypes.Scientist; + + public byte Cooldown { get; set; } = 15; + + public byte BatteryCharge { get; set; } = 5; + + public static ScientistRoleOptions Deserialize(IMessageReader reader, byte version) + { + var options = new ScientistRoleOptions(version); + + options.Cooldown = reader.ReadByte(); + options.BatteryCharge = reader.ReadByte(); + + return options; + } + + public void Serialize(IMessageWriter writer) + { + writer.Write(Cooldown); + writer.Write(BatteryCharge); + } +} diff --git a/src/Impostor.Api/Innersloth/GameOptions/RoleOptions/ShapeshifterRoleOptions.cs b/src/Impostor.Api/Innersloth/GameOptions/RoleOptions/ShapeshifterRoleOptions.cs new file mode 100644 index 0000000..8202b7d --- /dev/null +++ b/src/Impostor.Api/Innersloth/GameOptions/RoleOptions/ShapeshifterRoleOptions.cs @@ -0,0 +1,37 @@ +namespace Impostor.Api.Innersloth.GameOptions.RoleOptions; + +public class ShapeshifterRoleOptions : IRoleOptions +{ + public ShapeshifterRoleOptions(byte version) + { + Version = version; + } + + public byte Version { get; } + + public RoleTypes Type => RoleTypes.Shapeshifter; + + public bool LeaveSkin { get; set; } + + public byte Cooldown { get; set; } = 10; + + public byte Duration { get; set; } = 30; + + public static ShapeshifterRoleOptions Deserialize(IMessageReader reader, byte version) + { + var options = new ShapeshifterRoleOptions(version); + + options.LeaveSkin = reader.ReadBoolean(); + options.Cooldown = reader.ReadByte(); + options.Duration = reader.ReadByte(); + + return options; + } + + public void Serialize(IMessageWriter writer) + { + writer.Write(LeaveSkin); + writer.Write(Cooldown); + writer.Write(Duration); + } +} diff --git a/src/Impostor.Api/Innersloth/GameOptionsData.cs b/src/Impostor.Api/Innersloth/GameOptionsData.cs deleted file mode 100644 index 71a6e43..0000000 --- a/src/Impostor.Api/Innersloth/GameOptionsData.cs +++ /dev/null @@ -1,278 +0,0 @@ -using System; -using System.IO; - -namespace Impostor.Api.Innersloth -{ - public class GameOptionsData - { - /// - /// The latest major version of the game client. - /// - public const int LatestVersion = 5; - - /// - /// Gets or sets host's version of the game. - /// - public byte Version { get; set; } = LatestVersion; - - /// - /// Gets or sets the maximum amount of players for this lobby. - /// - public byte MaxPlayers { get; set; } = 10; - - /// - /// Gets or sets the language of the lobby as per enum. - /// - public GameKeywords Keywords { get; set; } = GameKeywords.English; - - /// - /// Gets or sets the Map selected for this lobby. - /// - public MapTypes Map { get; set; } = MapTypes.Skeld; - - /// - /// Gets or sets the Player speed modifier. - /// - public float PlayerSpeedMod { get; set; } = 1f; - - /// - /// Gets or sets the Light modifier for the players that are members of the crew as a multiplier value. - /// - public float CrewLightMod { get; set; } = 1f; - - /// - /// Gets or sets the Light modifier for the players that are Impostors as a multiplier value. - /// - public float ImpostorLightMod { get; set; } = 1f; - - /// - /// Gets or sets the Impostor cooldown to kill in seconds. - /// - public float KillCooldown { get; set; } = 15f; - - /// - /// Gets or sets the number of common tasks. - /// - public int NumCommonTasks { get; set; } = 1; - - /// - /// Gets or sets the number of long tasks. - /// - public int NumLongTasks { get; set; } = 1; - - /// - /// Gets or sets the number of short tasks. - /// - public int NumShortTasks { get; set; } = 2; - - /// - /// Gets or sets the maximum amount of emergency meetings each player can call during the game in seconds. - /// - public int NumEmergencyMeetings { get; set; } = 1; - - /// - /// Gets or sets the cooldown between each time any player can call an emergency meeting in seconds. - /// - public int EmergencyCooldown { get; set; } = 15; - - /// - /// Gets or sets the number of impostors for this lobby. - /// - public int NumImpostors { get; set; } = 1; - - /// - /// Gets or sets a value indicating whether ghosts (dead crew members) can do tasks. - /// - public bool GhostsDoTasks { get; set; } = true; - - /// - /// Gets or sets the Kill as per values in . - /// - public KillDistances KillDistance { get; set; } = KillDistances.Normal; - - /// - /// Gets or sets the time for discussion before voting time in seconds. - /// - public int DiscussionTime { get; set; } = 15; - - /// - /// Gets or sets the time for voting in seconds. - /// - public int VotingTime { get; set; } = 120; - - /// - /// Gets or sets a value indicating whether an ejected player is an impostor or not. - /// - public bool ConfirmImpostor { get; set; } = true; - - /// - /// Gets or sets a value indicating whether players are able to see tasks being performed by other players. - /// - /// - /// By being set to true, tasks such as Empty Garbage, Submit Scan, Clear asteroids, Prime shields execution will be visible to other players. - /// - public bool VisualTasks { get; set; } = true; - - /// - /// Gets or sets a value indicating whether the vote is anonymous. - /// - public bool AnonymousVotes { get; set; } - - /// - /// Gets or sets the task bar update mode as per values in . - /// - public TaskBarUpdate TaskBarUpdate { get; set; } = TaskBarUpdate.Always; - - /// - /// Gets or sets role options. - /// - public RoleOptionsData RoleOptions { get; set; } = new RoleOptionsData(); - - /// - /// Gets or sets a value indicating whether the GameOptions are the default ones. - /// - public bool IsDefaults { get; set; } = true; - - /// - /// Deserialize a packet/message to a new GameOptionsData object. - /// - /// Message reader object containing the raw message. - /// GameOptionsData object. - public static GameOptionsData DeserializeCreate(IMessageReader reader) - { - var options = new GameOptionsData(); - options.Deserialize(reader.ReadBytesAndSize()); - return options; - } - - /// - /// Serializes this instance of GameOptionsData object to a specified BinaryWriter. - /// - /// The stream to write the message to. - /// The version of the game. - public void Serialize(BinaryWriter writer, byte version = LatestVersion) - { - writer.Write((byte)version); - writer.Write((byte)MaxPlayers); - writer.Write((uint)Keywords); - writer.Write((byte)Map); - writer.Write((float)PlayerSpeedMod); - writer.Write((float)CrewLightMod); - writer.Write((float)ImpostorLightMod); - writer.Write((float)KillCooldown); - writer.Write((byte)NumCommonTasks); - writer.Write((byte)NumLongTasks); - writer.Write((byte)NumShortTasks); - writer.Write((int)NumEmergencyMeetings); - writer.Write((byte)NumImpostors); - writer.Write((byte)KillDistance); - writer.Write((uint)DiscussionTime); - writer.Write((uint)VotingTime); - writer.Write((bool)IsDefaults); - - if (version >= 2) - { - writer.Write((byte)EmergencyCooldown); - } - - if (version >= 3) - { - writer.Write((bool)ConfirmImpostor); - writer.Write((bool)VisualTasks); - } - - if (version >= 4) - { - writer.Write((bool)AnonymousVotes); - writer.Write((byte)TaskBarUpdate); - } - - if (version >= 5) - { - RoleOptions.Serialize(writer); - } - - if (version >= 6) - { - // Nothing was changed in V6 - } - - if (version > 6) - { - throw new ImpostorException($"Unknown GameOptionsData version {Version}."); - } - } - - public void Serialize(IMessageWriter writer) - { - using var memory = new MemoryStream(); - using var writerBin = new BinaryWriter(memory); - Serialize(writerBin); - writer.WriteBytesAndSize(memory.ToArray()); - } - - /// - /// Deserialize a ReadOnlyMemory object to this instance of the GameOptionsData object. - /// - /// Memory containing the message/packet. - public void Deserialize(ReadOnlyMemory memory) - { - var bytes = memory.Span; - - Version = bytes.ReadByte(); - MaxPlayers = bytes.ReadByte(); - Keywords = (GameKeywords)bytes.ReadUInt32(); - Map = (MapTypes)bytes.ReadByte(); - PlayerSpeedMod = bytes.ReadSingle(); - - CrewLightMod = bytes.ReadSingle(); - ImpostorLightMod = bytes.ReadSingle(); - KillCooldown = bytes.ReadSingle(); - - NumCommonTasks = bytes.ReadByte(); - NumLongTasks = bytes.ReadByte(); - NumShortTasks = bytes.ReadByte(); - - NumEmergencyMeetings = bytes.ReadInt32(); - - NumImpostors = bytes.ReadByte(); - KillDistance = (KillDistances)bytes.ReadByte(); - DiscussionTime = bytes.ReadInt32(); - VotingTime = bytes.ReadInt32(); - - IsDefaults = bytes.ReadBoolean(); - - if (Version >= 2) - { - EmergencyCooldown = bytes.ReadByte(); - } - - if (Version >= 3) - { - ConfirmImpostor = bytes.ReadBoolean(); - VisualTasks = bytes.ReadBoolean(); - } - - if (Version >= 4) - { - AnonymousVotes = bytes.ReadBoolean(); - TaskBarUpdate = (TaskBarUpdate)bytes.ReadByte(); - } - - if (Version >= 5) - { - RoleOptions = RoleOptionsData.Deserialize(bytes); - } - - if (Version >= 6) - { - // Nothing was changed in V6 - } - - if (Version > 6) - { - throw new ImpostorException($"Unknown GameOptionsData version {Version}."); - } - } - } -} diff --git a/src/Impostor.Api/Innersloth/RoleOptionsData.cs b/src/Impostor.Api/Innersloth/RoleOptionsData.cs deleted file mode 100644 index 85356cf..0000000 --- a/src/Impostor.Api/Innersloth/RoleOptionsData.cs +++ /dev/null @@ -1,135 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; - -namespace Impostor.Api.Innersloth -{ - public class RoleOptionsData - { - public bool ShapeshifterLeaveSkin { get; set; } - - public byte ShapeshifterCooldown { get; set; } = 10; - - public byte ShapeshifterDuration { get; set; } = 30; - - public byte ScientistCooldown { get; set; } = 15; - - public byte ScientistBatteryCharge { get; set; } = 5; - - public byte GuardianAngelCooldown { get; set; } = 60; - - public bool ImpostorsCanSeeProtect { get; set; } - - public byte ProtectionDurationSeconds { get; set; } = 10; - - public byte EngineerCooldown { get; set; } = 30; - - public byte EngineerInVentMaxTime { get; set; } = 15; - - public Dictionary RoleRates { get; } = new Dictionary(); - - public static RoleOptionsData Deserialize(ReadOnlySpan span) - { - var roleOptionsData = new RoleOptionsData(); - var num = span.ReadInt32(); - for (var i = 0; i < num; i++) - { - var key = (RoleTypes)span.ReadInt16(); - var roleRate = new RoleRate(span.ReadByte(), span.ReadByte()); - roleOptionsData.RoleRates[key] = roleRate; - } - - roleOptionsData.ShapeshifterLeaveSkin = span.ReadBoolean(); - roleOptionsData.ShapeshifterCooldown = span.ReadByte(); - roleOptionsData.ShapeshifterDuration = span.ReadByte(); - roleOptionsData.ScientistCooldown = span.ReadByte(); - roleOptionsData.GuardianAngelCooldown = span.ReadByte(); - roleOptionsData.EngineerCooldown = span.ReadByte(); - roleOptionsData.EngineerInVentMaxTime = span.ReadByte(); - roleOptionsData.ScientistBatteryCharge = span.ReadByte(); - roleOptionsData.ProtectionDurationSeconds = span.ReadByte(); - roleOptionsData.ImpostorsCanSeeProtect = span.ReadBoolean(); - return roleOptionsData; - } - - public static RoleOptionsData Deserialize(IMessageReader reader) - { - var roleOptionsData = new RoleOptionsData(); - var num = reader.ReadPackedInt32(); - for (var i = 0; i < num; i++) - { - var key = (RoleTypes)reader.ReadInt16(); - var roleRate = new RoleRate(reader.ReadByte(), reader.ReadByte()); - roleOptionsData.RoleRates[key] = roleRate; - } - - roleOptionsData.ShapeshifterLeaveSkin = reader.ReadBoolean(); - roleOptionsData.ShapeshifterCooldown = reader.ReadByte(); - roleOptionsData.ShapeshifterDuration = reader.ReadByte(); - roleOptionsData.ScientistCooldown = reader.ReadByte(); - roleOptionsData.GuardianAngelCooldown = reader.ReadByte(); - roleOptionsData.EngineerCooldown = reader.ReadByte(); - roleOptionsData.EngineerInVentMaxTime = reader.ReadByte(); - roleOptionsData.ScientistBatteryCharge = reader.ReadByte(); - roleOptionsData.ProtectionDurationSeconds = reader.ReadByte(); - roleOptionsData.ImpostorsCanSeeProtect = reader.ReadBoolean(); - return roleOptionsData; - } - - public void Serialize(IMessageWriter writer) - { - writer.WritePacked(RoleRates.Count); - foreach (var roleRate in RoleRates) - { - writer.Write((ushort)roleRate.Key); - writer.Write((byte)roleRate.Value.MaxCount); - writer.Write((byte)roleRate.Value.Chance); - } - - writer.Write(ShapeshifterLeaveSkin); - writer.Write(ShapeshifterCooldown); - writer.Write(ShapeshifterDuration); - writer.Write(ScientistCooldown); - writer.Write(GuardianAngelCooldown); - writer.Write(EngineerCooldown); - writer.Write(EngineerInVentMaxTime); - writer.Write(ScientistBatteryCharge); - writer.Write(ProtectionDurationSeconds); - writer.Write(ImpostorsCanSeeProtect); - } - - public void Serialize(BinaryWriter writer) - { - writer.Write(RoleRates.Count); - foreach (var roleRate in RoleRates) - { - writer.Write((ushort)roleRate.Key); - writer.Write((byte)roleRate.Value.MaxCount); - writer.Write((byte)roleRate.Value.Chance); - } - - writer.Write(ShapeshifterLeaveSkin); - writer.Write(ShapeshifterCooldown); - writer.Write(ShapeshifterDuration); - writer.Write(ScientistCooldown); - writer.Write(GuardianAngelCooldown); - writer.Write(EngineerCooldown); - writer.Write(EngineerInVentMaxTime); - writer.Write(ScientistBatteryCharge); - writer.Write(ProtectionDurationSeconds); - writer.Write(ImpostorsCanSeeProtect); - } - - public readonly struct RoleRate - { - public readonly int MaxCount; - public readonly int Chance; - - public RoleRate(int maxCount, int chance) - { - MaxCount = maxCount; - Chance = chance; - } - } - } -} diff --git a/src/Impostor.Api/Net/Messages/C2S/Message00HostGameC2S.cs b/src/Impostor.Api/Net/Messages/C2S/Message00HostGameC2S.cs index 87f32e9..8b802c2 100644 --- a/src/Impostor.Api/Net/Messages/C2S/Message00HostGameC2S.cs +++ b/src/Impostor.Api/Net/Messages/C2S/Message00HostGameC2S.cs @@ -1,28 +1,24 @@ using Impostor.Api.Innersloth; +using Impostor.Api.Innersloth.GameOptions; namespace Impostor.Api.Net.Messages.C2S { public static class Message00HostGameC2S { - public static void Serialize(IMessageWriter writer, GameOptionsData gameOptionsData) + public static void Serialize(IMessageWriter writer, IGameOptions gameOptions, CrossplayFlags crossplayFlags, GameFilterOptions gameFilterOptions) { writer.StartMessage(MessageFlags.HostGame); - gameOptionsData.Serialize(writer); - writer.Write(int.MaxValue); // crossplayFlags + GameOptionsFactory.Serialize(writer, gameOptions); + writer.Write((int)crossplayFlags); + gameFilterOptions.Serialize(writer); writer.EndMessage(); } - /// - /// Deserialize a packet. - /// - /// with 0. - /// Deserialized . - public static GameOptionsData Deserialize(IMessageReader reader) + public static void Deserialize(IMessageReader reader, out IGameOptions gameOptions, out CrossplayFlags crossplayFlags, out GameFilterOptions gameFilterOptions) { - var gameOptionsData = GameOptionsData.DeserializeCreate(reader); - reader.ReadInt32(); // crossplayFlags, not used yet - - return gameOptionsData; + gameOptions = GameOptionsFactory.Deserialize(reader); + crossplayFlags = (CrossplayFlags)reader.ReadInt32(); + gameFilterOptions = GameFilterOptions.Deserialize(reader); } } } diff --git a/src/Impostor.Api/Net/Messages/C2S/Message16GetGameListC2S.cs b/src/Impostor.Api/Net/Messages/C2S/Message16GetGameListC2S.cs index 13c02ae..9ba3b6c 100644 --- a/src/Impostor.Api/Net/Messages/C2S/Message16GetGameListC2S.cs +++ b/src/Impostor.Api/Net/Messages/C2S/Message16GetGameListC2S.cs @@ -1,5 +1,6 @@ using System; using Impostor.Api.Innersloth; +using Impostor.Api.Innersloth.GameOptions; namespace Impostor.Api.Net.Messages.C2S { @@ -10,7 +11,7 @@ namespace Impostor.Api.Net.Messages.C2S throw new NotImplementedException(); } - public static void Deserialize(IMessageReader reader, out GameOptionsData options, out QuickChatModes chatMode) + public static void Deserialize(IMessageReader reader, out IGameOptions options, out QuickChatModes chatMode, out CrossplayFlags crossplayFlags, out GameFilterOptions gameFilterOptions) { var version = reader.ReadPackedInt32(); if (version != 2) @@ -18,9 +19,10 @@ namespace Impostor.Api.Net.Messages.C2S throw new NotSupportedException($"Version {version} of {nameof(Message16GetGameListC2S)} is not supported"); } - options = GameOptionsData.DeserializeCreate(reader); + options = GameOptionsFactory.Deserialize(reader); chatMode = (QuickChatModes)reader.ReadByte(); - reader.ReadInt32(); // crossplayFlags, not used yet + crossplayFlags = (CrossplayFlags)reader.ReadInt32(); + gameFilterOptions = GameFilterOptions.Deserialize(reader); } } } diff --git a/src/Impostor.Api/Net/Messages/Rpcs/Rpc02SyncSettings.cs b/src/Impostor.Api/Net/Messages/Rpcs/Rpc02SyncSettings.cs index 047d21f..3541e9d 100644 --- a/src/Impostor.Api/Net/Messages/Rpcs/Rpc02SyncSettings.cs +++ b/src/Impostor.Api/Net/Messages/Rpcs/Rpc02SyncSettings.cs @@ -1,17 +1,22 @@ -using Impostor.Api.Innersloth; +using Impostor.Api.Innersloth.GameOptions; namespace Impostor.Api.Net.Messages.Rpcs { public static class Rpc02SyncSettings { - public static void Serialize(IMessageWriter writer, GameOptionsData gameOptionsData) + public static void Serialize(IMessageWriter writer, IGameOptions gameOptionsData) { - gameOptionsData.Serialize(writer); + GameOptionsFactory.Serialize(writer, gameOptionsData); } - public static void Deserialize(IMessageReader reader, GameOptionsData gameOptionsData) + public static void Deserialize(IMessageReader reader, out IGameOptions gameOptionsData) { - gameOptionsData.Deserialize(reader.ReadBytesAndSize()); + gameOptionsData = GameOptionsFactory.Deserialize(reader); + } + + public static void DeserializeInto(IMessageReader reader, IGameOptions gameOptionsData) + { + GameOptionsFactory.DeserializeInto(reader, gameOptionsData); } } } diff --git a/src/Impostor.Api/Net/Messages/S2C/Message00HostGameS2C.cs b/src/Impostor.Api/Net/Messages/S2C/Message00HostGameS2C.cs index 8fef225..effd745 100644 --- a/src/Impostor.Api/Net/Messages/S2C/Message00HostGameS2C.cs +++ b/src/Impostor.Api/Net/Messages/S2C/Message00HostGameS2C.cs @@ -1,5 +1,5 @@ using System; -using Impostor.Api.Innersloth; +using Impostor.Api.Innersloth.GameOptions; namespace Impostor.Api.Net.Messages.S2C { @@ -12,7 +12,7 @@ namespace Impostor.Api.Net.Messages.S2C writer.EndMessage(); } - public static GameOptionsData Deserialize(IMessageReader reader) + public static LegacyGameOptionsData Deserialize(IMessageReader reader) { throw new NotImplementedException(); } diff --git a/src/Impostor.Api/packages.lock.json b/src/Impostor.Api/packages.lock.json index d7113bf..0fa398c 100644 --- a/src/Impostor.Api/packages.lock.json +++ b/src/Impostor.Api/packages.lock.json @@ -65,6 +65,7 @@ "resolved": "1.2.0.435", "contentHash": "ouwPWZxbOV3SmCZxIRqHvljkSzkCyi1tDoMzQtDb/bRP8ctASV/iRJr+A2Gdj0QLaLmWnqTWDrH82/iP+X80Lg==" } - } + }, + ".NETStandard,Version=v2.1/linux-x64": {} } } \ No newline at end of file diff --git a/src/Impostor.Client.App/Program.cs b/src/Impostor.Client.App/Program.cs index 057677b..eeb328e 100644 --- a/src/Impostor.Client.App/Program.cs +++ b/src/Impostor.Client.App/Program.cs @@ -2,6 +2,7 @@ using System.Threading; using System.Threading.Tasks; using Impostor.Api.Innersloth; +using Impostor.Api.Innersloth.GameOptions; using Impostor.Api.Net.Messages.C2S; using Impostor.Hazel; using Impostor.Hazel.Abstractions; @@ -27,11 +28,11 @@ namespace Impostor.Client.App var writeGameCreate = MessageWriter.Get(MessageType.Reliable); - Message00HostGameC2S.Serialize(writeGameCreate, new GameOptionsData + Message00HostGameC2S.Serialize(writeGameCreate, new LegacyGameOptionsData { MaxPlayers = 4, NumImpostors = 2, - }); + }, CrossplayFlags.All, new GameFilterOptions()); // TODO: ObjectPool for MessageReaders using (var connection = new UdpClientConnection(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 22023), null)) diff --git a/src/Impostor.Plugins.Example/ExamplePlugin.cs b/src/Impostor.Plugins.Example/ExamplePlugin.cs index 07f4d86..e5c91b0 100644 --- a/src/Impostor.Plugins.Example/ExamplePlugin.cs +++ b/src/Impostor.Plugins.Example/ExamplePlugin.cs @@ -1,6 +1,6 @@ using System.Threading.Tasks; using Impostor.Api.Games.Managers; -using Impostor.Api.Innersloth; +using Impostor.Api.Innersloth.GameOptions; using Impostor.Api.Plugins; using Microsoft.Extensions.Logging; @@ -22,7 +22,7 @@ namespace Impostor.Plugins.Example { _logger.LogInformation("Example is being enabled."); - var game = await _gameManager.CreateAsync(new GameOptionsData()); + var game = await _gameManager.CreateAsync(new NormalGameOptions()); if (game == null) { _logger.LogWarning("Example game creation was cancelled"); diff --git a/src/Impostor.Plugins.Example/Handlers/PlayerEventListener.cs b/src/Impostor.Plugins.Example/Handlers/PlayerEventListener.cs index cdadd88..6370e5f 100644 --- a/src/Impostor.Plugins.Example/Handlers/PlayerEventListener.cs +++ b/src/Impostor.Plugins.Example/Handlers/PlayerEventListener.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using Impostor.Api.Events; using Impostor.Api.Events.Player; using Impostor.Api.Innersloth.Customization; +using Impostor.Api.Innersloth.GameOptions; using Microsoft.Extensions.Logging; namespace Impostor.Plugins.Example.Handlers @@ -65,9 +66,13 @@ namespace Impostor.Plugins.Example.Handlers if (e.Message == "test") { - e.Game.Options.KillCooldown = 0; e.Game.Options.NumImpostors = 2; - e.Game.Options.PlayerSpeedMod = 5; + + if (e.Game.Options is NormalGameOptions normalGameOptions) + { + normalGameOptions.KillCooldown = 0; + normalGameOptions.PlayerSpeedMod = 5; + } await e.Game.SyncSettingsAsync(); } diff --git a/src/Impostor.Server/Net/Client.cs b/src/Impostor.Server/Net/Client.cs index 31f5a04..49488e8 100644 --- a/src/Impostor.Server/Net/Client.cs +++ b/src/Impostor.Server/Net/Client.cs @@ -5,6 +5,7 @@ using Impostor.Api; using Impostor.Api.Config; using Impostor.Api.Games; using Impostor.Api.Innersloth; +using Impostor.Api.Innersloth.GameOptions; using Impostor.Api.Net; using Impostor.Api.Net.Custom; using Impostor.Api.Net.Messages; @@ -65,10 +66,10 @@ namespace Impostor.Server.Net case MessageFlags.HostGame: { // Read game settings. - var gameInfo = Message00HostGameC2S.Deserialize(reader); + Message00HostGameC2S.Deserialize(reader, out var gameOptions, out _, out var gameFilterOptions); // Create game. - var game = await _gameManager.CreateAsync(this, gameInfo); + var game = await _gameManager.CreateAsync(this, gameOptions); if (game == null) { @@ -259,7 +260,7 @@ namespace Impostor.Server.Net case MessageFlags.GetGameListV2: { - Message16GetGameListC2S.Deserialize(reader, out var options, out _); + Message16GetGameListC2S.Deserialize(reader, out var options, out _, out _, out _); await OnRequestGameListAsync(options); break; } @@ -358,7 +359,7 @@ namespace Impostor.Server.Net /// All options given. /// At this moment, the client can only specify the map, impostor count and chat language. /// - private ValueTask OnRequestGameListAsync(GameOptionsData options) + private ValueTask OnRequestGameListAsync(IGameOptions options) { using var message = MessageWriter.Get(MessageType.Reliable); diff --git a/src/Impostor.Server/Net/Inner/Objects/InnerMeetingHud.cs b/src/Impostor.Server/Net/Inner/Objects/InnerMeetingHud.cs index c36278f..79af789 100644 --- a/src/Impostor.Server/Net/Inner/Objects/InnerMeetingHud.cs +++ b/src/Impostor.Server/Net/Inner/Objects/InnerMeetingHud.cs @@ -8,6 +8,7 @@ using Impostor.Api; using Impostor.Api.Events.Managers; using Impostor.Api.Events.Player; using Impostor.Api.Innersloth; +using Impostor.Api.Innersloth.GameOptions; using Impostor.Api.Net; using Impostor.Api.Net.Custom; using Impostor.Api.Net.Inner; @@ -43,7 +44,15 @@ namespace Impostor.Server.Net.Inner.Objects try { const float AnimationTime = 0.25f + 0.5f + 0.4f + 3f + 0.75f + 5f; - await Task.Delay(TimeSpan.FromSeconds(AnimationTime + Game.Options.DiscussionTime + Game.Options.VotingTime), _timerToken.Token); + if (Game.Options.GameMode == GameModes.Normal) + { + var options = (NormalGameOptions)Game.Options; + await Task.Delay(TimeSpan.FromSeconds(AnimationTime + options.DiscussionTime + options.VotingTime), _timerToken.Token); + } + else + { + throw new NotImplementedException(); + } } catch (TaskCanceledException) { diff --git a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs index 793d9ce..e02f998 100644 --- a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs +++ b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs @@ -117,7 +117,7 @@ namespace Impostor.Server.Net.Inner.Objects return false; } - Rpc02SyncSettings.Deserialize(reader, Game.Options); + Rpc02SyncSettings.DeserializeInto(reader, Game.Options); break; } diff --git a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.cs b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.cs index 12e2186..02a079a 100644 --- a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.cs +++ b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using Impostor.Api.Games; using Impostor.Api.Innersloth; using Impostor.Api.Innersloth.Customization; +using Impostor.Api.Innersloth.GameOptions; using Impostor.Api.Utils; namespace Impostor.Server.Net.Inner.Objects @@ -59,7 +60,15 @@ namespace Impostor.Server.Net.Inner.Objects // the impostor has a cooldown of half the usual duration. As a workaround we always assume the kill was // prevented and the impostor only has half of its cooldown. // FIXME when the base game improved their implementation. - return dateTimeProvider.UtcNow.Subtract(LastMurder).TotalSeconds >= game.Options.KillCooldown / 2; + if (game.Options.GameMode == GameModes.Normal) + { + var options = (NormalGameOptions)game.Options; + return dateTimeProvider.UtcNow.Subtract(LastMurder).TotalSeconds >= options.KillCooldown / 2; + } + else + { + return true; + } } public void Serialize(IMessageWriter writer) diff --git a/src/Impostor.Server/Net/Manager/GameManager.cs b/src/Impostor.Server/Net/Manager/GameManager.cs index ec87434..a4bf36a 100644 --- a/src/Impostor.Server/Net/Manager/GameManager.cs +++ b/src/Impostor.Server/Net/Manager/GameManager.cs @@ -10,6 +10,7 @@ using Impostor.Api.Events.Managers; using Impostor.Api.Games; using Impostor.Api.Games.Managers; using Impostor.Api.Innersloth; +using Impostor.Api.Innersloth.GameOptions; using Impostor.Api.Net; using Impostor.Server.Events; using Impostor.Server.Net.State; @@ -59,7 +60,7 @@ namespace Impostor.Server.Net.Manager x.Value.IsPublic && x.Value.GameState == GameStates.NotStarted && x.Value.PlayerCount < x.Value.Options.MaxPlayers && - (_compatibilityConfig.AllowVersionMixing == true || x.Value.Host?.Client.GameVersion == gameVersion))) + (_compatibilityConfig.AllowVersionMixing || x.Value.Host == null || x.Value.Host.Client.GameVersion == gameVersion))) { // Check for options. if (!map.HasFlag((MapFlags)(1 << (byte)game.Options.Map))) @@ -110,7 +111,7 @@ namespace Impostor.Server.Net.Manager await _eventManager.CallAsync(new GameDestroyedEvent(game)); } - public async ValueTask CreateAsync(IClient? owner, GameOptionsData options) + public async ValueTask CreateAsync(IClient? owner, IGameOptions options) { var @event = new GameCreationEvent(this, owner); await _eventManager.CallAsync(@event); @@ -135,12 +136,12 @@ namespace Impostor.Server.Net.Manager return game; } - public ValueTask CreateAsync(GameOptionsData options) + public ValueTask CreateAsync(IGameOptions options) { return CreateAsync(null, options); } - private async ValueTask<(bool Success, Game? Game)> TryCreateAsync(GameOptionsData options, GameCode? desiredGameCode = null) + private async ValueTask<(bool Success, Game? Game)> TryCreateAsync(IGameOptions options, GameCode? desiredGameCode = null) { var gameCode = desiredGameCode ?? _gameCodeFactory.Create(); var game = ActivatorUtilities.CreateInstance(_serviceProvider, _publicIp, gameCode, options); diff --git a/src/Impostor.Server/Net/State/Game.Api.cs b/src/Impostor.Server/Net/State/Game.Api.cs index 62a7ef1..f685ca1 100644 --- a/src/Impostor.Server/Net/State/Game.Api.cs +++ b/src/Impostor.Server/Net/State/Game.Api.cs @@ -1,11 +1,10 @@ -using System.IO; -using System.Net; +using System.Net; using System.Threading.Tasks; using Impostor.Api; using Impostor.Api.Games; -using Impostor.Api.Innersloth; using Impostor.Api.Net; using Impostor.Api.Net.Inner; +using Impostor.Api.Net.Messages.Rpcs; using Impostor.Hazel; namespace Impostor.Server.Net.State @@ -21,6 +20,7 @@ namespace Impostor.Server.Net.State _bannedIps.Add(ipAddress); } + // TODO This no longer does anything, it was replaced by LogicOptions public async ValueTask SyncSettingsAsync() { if (Host?.Character == null) @@ -34,12 +34,7 @@ namespace Impostor.Server.Net.State // If this is not done, the host will overwrite changes later with the defaults. Options.IsDefaults = false; - await using (var memory = new MemoryStream()) - await using (var writerBin = new BinaryWriter(memory)) - { - Options.Serialize(writerBin, GameOptionsData.LatestVersion); - writer.WriteBytesAndSize(memory.ToArray()); - } + Rpc02SyncSettings.Serialize(writer, Options); await FinishRpcAsync(writer); } diff --git a/src/Impostor.Server/Net/State/Game.cs b/src/Impostor.Server/Net/State/Game.cs index ed701f0..2f800e7 100644 --- a/src/Impostor.Server/Net/State/Game.cs +++ b/src/Impostor.Server/Net/State/Game.cs @@ -10,6 +10,7 @@ using Impostor.Api.Config; using Impostor.Api.Events.Managers; using Impostor.Api.Games; using Impostor.Api.Innersloth; +using Impostor.Api.Innersloth.GameOptions; using Impostor.Api.Net; using Impostor.Api.Net.Messages.S2C; using Impostor.Server.Events; @@ -36,7 +37,7 @@ namespace Impostor.Server.Net.State GameManager gameManager, IPEndPoint publicIp, GameCode code, - GameOptionsData options, + IGameOptions options, ClientManager clientManager, IEventManager eventManager, IOptions compatibilityConfig) @@ -71,7 +72,7 @@ namespace Impostor.Server.Net.State public GameStates GameState { get; private set; } - public GameOptionsData Options { get; } + public IGameOptions Options { get; } public IDictionary Items { get; } diff --git a/src/Impostor.Tools.ServerReplay/Program.cs b/src/Impostor.Tools.ServerReplay/Program.cs index dff5615..990de64 100644 --- a/src/Impostor.Tools.ServerReplay/Program.cs +++ b/src/Impostor.Tools.ServerReplay/Program.cs @@ -8,13 +8,14 @@ using Impostor.Api.Events.Managers; using Impostor.Api.Games; using Impostor.Api.Games.Managers; using Impostor.Api.Innersloth; +using Impostor.Api.Innersloth.GameOptions; using Impostor.Api.Net; using Impostor.Api.Net.Custom; using Impostor.Api.Net.Messages; +using Impostor.Hazel.Abstractions; using Impostor.Api.Net.Messages.C2S; using Impostor.Api.Utils; using Impostor.Hazel; -using Impostor.Hazel.Abstractions; using Impostor.Hazel.Extensions; using Impostor.Server; using Impostor.Server.Events; @@ -37,7 +38,7 @@ namespace Impostor.Tools.ServerReplay { private static readonly ILogger Logger = Log.ForContext(typeof(Program)); private static readonly Dictionary Connections = new Dictionary(); - private static readonly Dictionary GameOptions = new Dictionary(); + private static readonly Dictionary GameOptions = new Dictionary(); private static ServiceProvider _serviceProvider; @@ -199,7 +200,8 @@ namespace Impostor.Tools.ServerReplay if (tag == MessageFlags.HostGame) { - GameOptions.Add(clientId, Message00HostGameC2S.Deserialize(message)); + Message00HostGameC2S.Deserialize(message, out var gameOptions, out _, out _); + GameOptions.Add(clientId, gameOptions); } else if (Connections.TryGetValue(clientId, out var client)) { -- 2.39.5