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;
{
public interface IGame
{
- GameOptionsData Options { get; }
+ IGameOptions Options { get; }
GameCode Code { get; }
using System.Collections.Generic;
using System.Threading.Tasks;
-using Impostor.Api.Innersloth;
+using Impostor.Api.Innersloth.GameOptions;
namespace Impostor.Api.Games.Managers
{
/// <param name="options">Game options.</param>
/// <returns>Created game or null if creation was cancelled by a plugin.</returns>
/// <exception cref="ImpostorException">Thrown when game creation failed.</exception>
- ValueTask<IGame?> CreateAsync(GameOptionsData options);
+ ValueTask<IGame?> CreateAsync(IGameOptions options);
}
}
--- /dev/null
+namespace Impostor.Api.Innersloth;
+
+public enum CrossplayFlags
+{
+ Default = 4,
+ All = int.MaxValue,
+}
--- /dev/null
+using System.Collections.Generic;
+
+namespace Impostor.Api.Innersloth;
+
+public class GameFilterOptions
+{
+ public HashSet<string> FilterTags { get; } = new HashSet<string>();
+
+ 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);
+ }
+ }
+}
--- /dev/null
+namespace Impostor.Api.Innersloth
+{
+ public enum GameModes : byte
+ {
+ None,
+ Normal,
+ HideNSeek,
+ }
+}
--- /dev/null
+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);
+ }
+ }
+}
--- /dev/null
+namespace Impostor.Api.Innersloth.GameOptions;
+
+public class HideNSeekGameOptions : IGameOptions
+{
+ public const int LatestVersion = 7;
+
+ public HideNSeekGameOptions(byte version = LatestVersion)
+ {
+ Version = version;
+ IGameOptions.EnsureVersionIsModular<HideNSeekGameOptions>(version);
+ }
+
+ /// <inheritdoc />
+ public byte Version { get; }
+
+ /// <inheritdoc />
+ public GameModes GameMode => GameModes.HideNSeek;
+
+ /// <inheritdoc />
+ public byte MaxPlayers { get; set; } = 15;
+
+ /// <inheritdoc />
+ public GameKeywords Keywords { get; set; } = GameKeywords.English;
+
+ /// <inheritdoc />
+ public MapTypes Map { get; set; } = MapTypes.Skeld;
+
+ /// <inheritdoc />
+ public int NumImpostors { get; set; } = 1;
+
+ /// <inheritdoc />
+ public bool IsDefaults { get; set; } = true;
+
+ /// <summary>
+ /// Gets or sets the Player speed modifier.
+ /// </summary>
+ public float PlayerSpeedMod { get; set; } = 1f;
+
+ /// <summary>
+ /// Gets or sets the Light modifier for the players that are members of the crew as a multiplier value.
+ /// </summary>
+ public float CrewLightMod { get; set; } = 1f;
+
+ /// <summary>
+ /// Gets or sets the Light modifier for the players that are Impostors as a multiplier value.
+ /// </summary>
+ public float ImpostorLightMod { get; set; } = 1f;
+
+ /// <summary>
+ /// Gets or sets the number of common tasks.
+ /// </summary>
+ public int NumCommonTasks { get; set; } = 1;
+
+ /// <summary>
+ /// Gets or sets the number of long tasks.
+ /// </summary>
+ public int NumLongTasks { get; set; } = 1;
+
+ /// <summary>
+ /// Gets or sets the number of short tasks.
+ /// </summary>
+ 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<HideNSeekGameOptions>(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<HideNSeekGameOptions>(Version);
+ }
+ }
+}
--- /dev/null
+using System.Diagnostics.CodeAnalysis;
+
+namespace Impostor.Api.Innersloth.GameOptions;
+
+public interface IGameOptions
+{
+ /// <summary>
+ /// Gets the version.
+ /// </summary>
+ public byte Version { get; }
+
+ /// <summary>
+ /// Gets the currently active gamemode.
+ /// </summary>
+ public GameModes GameMode { get; }
+
+ /// <summary>
+ /// Gets or sets the maximum amount of players for this lobby.
+ /// </summary>
+ public byte MaxPlayers { get; set; }
+
+ /// <summary>
+ /// Gets or sets the language of the lobby as per <see cref="GameKeywords" /> enum.
+ /// </summary>
+ public GameKeywords Keywords { get; set; }
+
+ /// <summary>
+ /// Gets or sets the Map selected for this lobby.
+ /// </summary>
+ public MapTypes Map { get; set; }
+
+ /// <summary>
+ /// Gets or sets the number of impostors for this lobby.
+ /// </summary>
+ public int NumImpostors { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether the GameOptions are the default ones.
+ /// </summary>
+ public bool IsDefaults { get; set; }
+
+ public void Serialize(IMessageWriter writer);
+
+ public static void EnsureVersionIsModular<TCaller>(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<TCaller>(byte version)
+ {
+ throw new ImpostorException($"Unknown {typeof(TCaller).Name} version {version}");
+ }
+}
--- /dev/null
+using Impostor.Api.Innersloth.GameOptions.RoleOptions;
+
+namespace Impostor.Api.Innersloth.GameOptions;
+
+public class LegacyGameOptionsData : IGameOptions
+{
+ /// <summary>
+ /// The latest major version of the game client.
+ /// </summary>
+ public const int LatestVersion = 5;
+
+ public LegacyGameOptionsData(byte version = LatestVersion)
+ {
+ Version = version;
+ }
+
+ /// <summary>
+ /// Gets or sets host's version of the game.
+ /// </summary>
+ public byte Version { get; set; }
+
+ public GameModes GameMode => GameModes.Normal;
+
+ /// <summary>
+ /// Gets or sets the maximum amount of players for this lobby.
+ /// </summary>
+ public byte MaxPlayers { get; set; } = 10;
+
+ /// <summary>
+ /// Gets or sets the language of the lobby as per <see cref="GameKeywords" /> enum.
+ /// </summary>
+ public GameKeywords Keywords { get; set; } = GameKeywords.English;
+
+ /// <summary>
+ /// Gets or sets the Map selected for this lobby.
+ /// </summary>
+ public MapTypes Map { get; set; } = MapTypes.Skeld;
+
+ /// <summary>
+ /// Gets or sets the Player speed modifier.
+ /// </summary>
+ public float PlayerSpeedMod { get; set; } = 1f;
+
+ /// <summary>
+ /// Gets or sets the Light modifier for the players that are members of the crew as a multiplier value.
+ /// </summary>
+ public float CrewLightMod { get; set; } = 1f;
+
+ /// <summary>
+ /// Gets or sets the Light modifier for the players that are Impostors as a multiplier value.
+ /// </summary>
+ public float ImpostorLightMod { get; set; } = 1f;
+
+ /// <summary>
+ /// Gets or sets the Impostor cooldown to kill in seconds.
+ /// </summary>
+ public float KillCooldown { get; set; } = 15f;
+
+ /// <summary>
+ /// Gets or sets the number of common tasks.
+ /// </summary>
+ public int NumCommonTasks { get; set; } = 1;
+
+ /// <summary>
+ /// Gets or sets the number of long tasks.
+ /// </summary>
+ public int NumLongTasks { get; set; } = 1;
+
+ /// <summary>
+ /// Gets or sets the number of short tasks.
+ /// </summary>
+ public int NumShortTasks { get; set; } = 2;
+
+ /// <summary>
+ /// Gets or sets the maximum amount of emergency meetings each player can call during the game in seconds.
+ /// </summary>
+ public int NumEmergencyMeetings { get; set; } = 1;
+
+ /// <summary>
+ /// Gets or sets the cooldown between each time any player can call an emergency meeting in seconds.
+ /// </summary>
+ public int EmergencyCooldown { get; set; } = 15;
+
+ /// <summary>
+ /// Gets or sets the number of impostors for this lobby.
+ /// </summary>
+ public int NumImpostors { get; set; } = 1;
+
+ /// <summary>
+ /// Gets or sets a value indicating whether ghosts (dead crew members) can do tasks.
+ /// </summary>
+ public bool GhostsDoTasks { get; set; } = true;
+
+ /// <summary>
+ /// Gets or sets the Kill as per values in <see cref="KillDistances" />.
+ /// </summary>
+ public KillDistances KillDistance { get; set; } = KillDistances.Normal;
+
+ /// <summary>
+ /// Gets or sets the time for discussion before voting time in seconds.
+ /// </summary>
+ public int DiscussionTime { get; set; } = 15;
+
+ /// <summary>
+ /// Gets or sets the time for voting in seconds.
+ /// </summary>
+ public int VotingTime { get; set; } = 120;
+
+ /// <summary>
+ /// Gets or sets a value indicating whether an ejected player is an impostor or not.
+ /// </summary>
+ public bool ConfirmImpostor { get; set; } = true;
+
+ /// <summary>
+ /// Gets or sets a value indicating whether players are able to see tasks being performed by other players.
+ /// </summary>
+ /// <remarks>
+ /// By being set to true, tasks such as Empty Garbage, Submit Scan, Clear asteroids, Prime shields execution will be visible to other players.
+ /// </remarks>
+ public bool VisualTasks { get; set; } = true;
+
+ /// <summary>
+ /// Gets or sets a value indicating whether the vote is anonymous.
+ /// </summary>
+ public bool AnonymousVotes { get; set; }
+
+ /// <summary>
+ /// Gets or sets the task bar update mode as per values in <see cref="Innersloth.TaskBarUpdate" />.
+ /// </summary>
+ public TaskBarUpdate TaskBarUpdate { get; set; } = TaskBarUpdate.Always;
+
+ /// <summary>
+ /// Gets or sets role options.
+ /// </summary>
+ public LegacyRoleOptionsData RoleOptions { get; set; } = new LegacyRoleOptionsData();
+
+ /// <summary>
+ /// Gets or sets a value indicating whether the GameOptions are the default ones.
+ /// </summary>
+ 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<LegacyGameOptionsData>(Version);
+ }
+ }
+
+ /// <summary>
+ /// Serializes this instance of GameOptionsData object to a specified BinaryWriter.
+ /// </summary>
+ /// <param name="writer">The stream to write the message to.</param>
+ 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}");
+ }
+ }
+}
--- /dev/null
+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<NormalGameOptions>(version);
+ RoleOptions = new RoleOptionsCollection(version);
+ }
+
+ /// <inheritdoc />
+ public byte Version { get; }
+
+ /// <inheritdoc />
+ public GameModes GameMode => GameModes.Normal;
+
+ /// <inheritdoc />
+ public byte MaxPlayers { get; set; } = 10;
+
+ /// <inheritdoc />
+ public GameKeywords Keywords { get; set; } = GameKeywords.English;
+
+ /// <inheritdoc />
+ public MapTypes Map { get; set; } = MapTypes.Skeld;
+
+ /// <inheritdoc />
+ public int NumImpostors { get; set; } = 1;
+
+ /// <inheritdoc />
+ public bool IsDefaults { get; set; } = true;
+
+ /// <summary>
+ /// Gets or sets the Player speed modifier.
+ /// </summary>
+ public float PlayerSpeedMod { get; set; } = 1f;
+
+ /// <summary>
+ /// Gets or sets the Light modifier for the players that are members of the crew as a multiplier value.
+ /// </summary>
+ public float CrewLightMod { get; set; } = 1f;
+
+ /// <summary>
+ /// Gets or sets the Light modifier for the players that are Impostors as a multiplier value.
+ /// </summary>
+ public float ImpostorLightMod { get; set; } = 1f;
+
+ /// <summary>
+ /// Gets or sets the Impostor cooldown to kill in seconds.
+ /// </summary>
+ public float KillCooldown { get; set; } = 15f;
+
+ /// <summary>
+ /// Gets or sets the number of common tasks.
+ /// </summary>
+ public int NumCommonTasks { get; set; } = 1;
+
+ /// <summary>
+ /// Gets or sets the number of long tasks.
+ /// </summary>
+ public int NumLongTasks { get; set; } = 1;
+
+ /// <summary>
+ /// Gets or sets the number of short tasks.
+ /// </summary>
+ public int NumShortTasks { get; set; } = 2;
+
+ /// <summary>
+ /// Gets or sets the maximum amount of emergency meetings each player can call during the game in seconds.
+ /// </summary>
+ public int NumEmergencyMeetings { get; set; } = 1;
+
+ /// <summary>
+ /// Gets or sets the cooldown between each time any player can call an emergency meeting in seconds.
+ /// </summary>
+ public int EmergencyCooldown { get; set; } = 15;
+
+ /// <summary>
+ /// Gets or sets a value indicating whether ghosts (dead crew members) can do tasks.
+ /// </summary>
+ public bool GhostsDoTasks { get; set; } = true;
+
+ /// <summary>
+ /// Gets or sets the Kill as per values in <see cref="KillDistances" />.
+ /// </summary>
+ public KillDistances KillDistance { get; set; } = KillDistances.Normal;
+
+ /// <summary>
+ /// Gets or sets the time for discussion before voting time in seconds.
+ /// </summary>
+ public int DiscussionTime { get; set; } = 15;
+
+ /// <summary>
+ /// Gets or sets the time for voting in seconds.
+ /// </summary>
+ public int VotingTime { get; set; } = 120;
+
+ /// <summary>
+ /// Gets or sets a value indicating whether an ejected player is an impostor or not.
+ /// </summary>
+ public bool ConfirmImpostor { get; set; } = true;
+
+ /// <summary>
+ /// Gets or sets a value indicating whether players are able to see tasks being performed by other players.
+ /// </summary>
+ /// <remarks>
+ /// By being set to true, tasks such as Empty Garbage, Submit Scan, Clear asteroids, Prime shields execution will be visible to other players.
+ /// </remarks>
+ public bool VisualTasks { get; set; } = true;
+
+ /// <summary>
+ /// Gets or sets a value indicating whether the vote is anonymous.
+ /// </summary>
+ public bool AnonymousVotes { get; set; }
+
+ /// <summary>
+ /// Gets or sets the task bar update mode as per values in <see cref="Innersloth.TaskBarUpdate" />.
+ /// </summary>
+ 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<NormalGameOptions>(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<NormalGameOptions>(Version);
+ }
+ }
+}
--- /dev/null
+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);
+ }
+}
--- /dev/null
+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);
+ }
+}
--- /dev/null
+namespace Impostor.Api.Innersloth.GameOptions.RoleOptions;
+
+public interface IRoleOptions
+{
+ RoleTypes Type { get; }
+
+ void Serialize(IMessageWriter writer);
+}
--- /dev/null
+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<RoleTypes, RoleRate> RoleRates { get; } = new Dictionary<RoleTypes, RoleRate>();
+
+ 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);
+ }
+}
--- /dev/null
+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<RoleTypes, RoleData> Roles { get; } = new Dictionary<RoleTypes, RoleData>();
+
+ 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);
+}
--- /dev/null
+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);
+ }
+}
--- /dev/null
+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);
+ }
+}
--- /dev/null
+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);
+ }
+}
+++ /dev/null
-using System;
-using System.IO;
-
-namespace Impostor.Api.Innersloth
-{
- public class GameOptionsData
- {
- /// <summary>
- /// The latest major version of the game client.
- /// </summary>
- public const int LatestVersion = 5;
-
- /// <summary>
- /// Gets or sets host's version of the game.
- /// </summary>
- public byte Version { get; set; } = LatestVersion;
-
- /// <summary>
- /// Gets or sets the maximum amount of players for this lobby.
- /// </summary>
- public byte MaxPlayers { get; set; } = 10;
-
- /// <summary>
- /// Gets or sets the language of the lobby as per <see cref="GameKeywords" /> enum.
- /// </summary>
- public GameKeywords Keywords { get; set; } = GameKeywords.English;
-
- /// <summary>
- /// Gets or sets the Map selected for this lobby.
- /// </summary>
- public MapTypes Map { get; set; } = MapTypes.Skeld;
-
- /// <summary>
- /// Gets or sets the Player speed modifier.
- /// </summary>
- public float PlayerSpeedMod { get; set; } = 1f;
-
- /// <summary>
- /// Gets or sets the Light modifier for the players that are members of the crew as a multiplier value.
- /// </summary>
- public float CrewLightMod { get; set; } = 1f;
-
- /// <summary>
- /// Gets or sets the Light modifier for the players that are Impostors as a multiplier value.
- /// </summary>
- public float ImpostorLightMod { get; set; } = 1f;
-
- /// <summary>
- /// Gets or sets the Impostor cooldown to kill in seconds.
- /// </summary>
- public float KillCooldown { get; set; } = 15f;
-
- /// <summary>
- /// Gets or sets the number of common tasks.
- /// </summary>
- public int NumCommonTasks { get; set; } = 1;
-
- /// <summary>
- /// Gets or sets the number of long tasks.
- /// </summary>
- public int NumLongTasks { get; set; } = 1;
-
- /// <summary>
- /// Gets or sets the number of short tasks.
- /// </summary>
- public int NumShortTasks { get; set; } = 2;
-
- /// <summary>
- /// Gets or sets the maximum amount of emergency meetings each player can call during the game in seconds.
- /// </summary>
- public int NumEmergencyMeetings { get; set; } = 1;
-
- /// <summary>
- /// Gets or sets the cooldown between each time any player can call an emergency meeting in seconds.
- /// </summary>
- public int EmergencyCooldown { get; set; } = 15;
-
- /// <summary>
- /// Gets or sets the number of impostors for this lobby.
- /// </summary>
- public int NumImpostors { get; set; } = 1;
-
- /// <summary>
- /// Gets or sets a value indicating whether ghosts (dead crew members) can do tasks.
- /// </summary>
- public bool GhostsDoTasks { get; set; } = true;
-
- /// <summary>
- /// Gets or sets the Kill as per values in <see cref="KillDistances" />.
- /// </summary>
- public KillDistances KillDistance { get; set; } = KillDistances.Normal;
-
- /// <summary>
- /// Gets or sets the time for discussion before voting time in seconds.
- /// </summary>
- public int DiscussionTime { get; set; } = 15;
-
- /// <summary>
- /// Gets or sets the time for voting in seconds.
- /// </summary>
- public int VotingTime { get; set; } = 120;
-
- /// <summary>
- /// Gets or sets a value indicating whether an ejected player is an impostor or not.
- /// </summary>
- public bool ConfirmImpostor { get; set; } = true;
-
- /// <summary>
- /// Gets or sets a value indicating whether players are able to see tasks being performed by other players.
- /// </summary>
- /// <remarks>
- /// By being set to true, tasks such as Empty Garbage, Submit Scan, Clear asteroids, Prime shields execution will be visible to other players.
- /// </remarks>
- public bool VisualTasks { get; set; } = true;
-
- /// <summary>
- /// Gets or sets a value indicating whether the vote is anonymous.
- /// </summary>
- public bool AnonymousVotes { get; set; }
-
- /// <summary>
- /// Gets or sets the task bar update mode as per values in <see cref="Innersloth.TaskBarUpdate" />.
- /// </summary>
- public TaskBarUpdate TaskBarUpdate { get; set; } = TaskBarUpdate.Always;
-
- /// <summary>
- /// Gets or sets role options.
- /// </summary>
- public RoleOptionsData RoleOptions { get; set; } = new RoleOptionsData();
-
- /// <summary>
- /// Gets or sets a value indicating whether the GameOptions are the default ones.
- /// </summary>
- public bool IsDefaults { get; set; } = true;
-
- /// <summary>
- /// Deserialize a packet/message to a new GameOptionsData object.
- /// </summary>
- /// <param name="reader">Message reader object containing the raw message.</param>
- /// <returns>GameOptionsData object.</returns>
- public static GameOptionsData DeserializeCreate(IMessageReader reader)
- {
- var options = new GameOptionsData();
- options.Deserialize(reader.ReadBytesAndSize());
- return options;
- }
-
- /// <summary>
- /// Serializes this instance of GameOptionsData object to a specified BinaryWriter.
- /// </summary>
- /// <param name="writer">The stream to write the message to.</param>
- /// <param name="version">The version of the game.</param>
- 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());
- }
-
- /// <summary>
- /// Deserialize a ReadOnlyMemory object to this instance of the GameOptionsData object.
- /// </summary>
- /// <param name="memory">Memory containing the message/packet.</param>
- public void Deserialize(ReadOnlyMemory<byte> 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}.");
- }
- }
- }
-}
+++ /dev/null
-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<RoleTypes, RoleRate> RoleRates { get; } = new Dictionary<RoleTypes, RoleRate>();
-
- public static RoleOptionsData Deserialize(ReadOnlySpan<byte> 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;
- }
- }
- }
-}
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();
}
- /// <summary>
- /// Deserialize a packet.
- /// </summary>
- /// <param name="reader"><see cref="IMessageReader" /> with <see cref="IMessageReader.Tag" /> 0.</param>
- /// <returns>Deserialized <see cref="GameOptionsData" />.</returns>
- 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);
}
}
}
using System;
using Impostor.Api.Innersloth;
+using Impostor.Api.Innersloth.GameOptions;
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)
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);
}
}
}
-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);
}
}
}
using System;
-using Impostor.Api.Innersloth;
+using Impostor.Api.Innersloth.GameOptions;
namespace Impostor.Api.Net.Messages.S2C
{
writer.EndMessage();
}
- public static GameOptionsData Deserialize(IMessageReader reader)
+ public static LegacyGameOptionsData Deserialize(IMessageReader reader)
{
throw new NotImplementedException();
}
"resolved": "1.2.0.435",
"contentHash": "ouwPWZxbOV3SmCZxIRqHvljkSzkCyi1tDoMzQtDb/bRP8ctASV/iRJr+A2Gdj0QLaLmWnqTWDrH82/iP+X80Lg=="
}
- }
+ },
+ ".NETStandard,Version=v2.1/linux-x64": {}
}
}
\ No newline at end of file
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;
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))
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;
{
_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");
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
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();
}
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;
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)
{
case MessageFlags.GetGameListV2:
{
- Message16GetGameListC2S.Deserialize(reader, out var options, out _);
+ Message16GetGameListC2S.Deserialize(reader, out var options, out _, out _, out _);
await OnRequestGameListAsync(options);
break;
}
/// All options given.
/// At this moment, the client can only specify the map, impostor count and chat language.
/// </param>
- private ValueTask OnRequestGameListAsync(GameOptionsData options)
+ private ValueTask OnRequestGameListAsync(IGameOptions options)
{
using var message = MessageWriter.Get(MessageType.Reliable);
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;
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)
{
return false;
}
- Rpc02SyncSettings.Deserialize(reader, Game.Options);
+ Rpc02SyncSettings.DeserializeInto(reader, Game.Options);
break;
}
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
// 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)
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;
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)))
await _eventManager.CallAsync(new GameDestroyedEvent(game));
}
- public async ValueTask<IGame?> CreateAsync(IClient? owner, GameOptionsData options)
+ public async ValueTask<IGame?> CreateAsync(IClient? owner, IGameOptions options)
{
var @event = new GameCreationEvent(this, owner);
await _eventManager.CallAsync(@event);
return game;
}
- public ValueTask<IGame?> CreateAsync(GameOptionsData options)
+ public ValueTask<IGame?> 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<Game>(_serviceProvider, _publicIp, gameCode, options);
-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
_bannedIps.Add(ipAddress);
}
+ // TODO This no longer does anything, it was replaced by LogicOptions
public async ValueTask SyncSettingsAsync()
{
if (Host?.Character == null)
// 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);
}
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;
GameManager gameManager,
IPEndPoint publicIp,
GameCode code,
- GameOptionsData options,
+ IGameOptions options,
ClientManager clientManager,
IEventManager eventManager,
IOptions<CompatibilityConfig> compatibilityConfig)
public GameStates GameState { get; private set; }
- public GameOptionsData Options { get; }
+ public IGameOptions Options { get; }
public IDictionary<object, object> Items { get; }
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;
{
private static readonly ILogger Logger = Log.ForContext(typeof(Program));
private static readonly Dictionary<int, IHazelConnection> Connections = new Dictionary<int, IHazelConnection>();
- private static readonly Dictionary<int, GameOptionsData> GameOptions = new Dictionary<int, GameOptionsData>();
+ private static readonly Dictionary<int, IGameOptions> GameOptions = new Dictionary<int, IGameOptions>();
private static ServiceProvider _serviceProvider;
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))
{