Implement new GameOptions system

Co-authored-by: miniduikboot <mini@duikbo.at>
This commit is contained in:
js6pakandminiduikboot authored and AeonLucid committed 2022-12-18 22:45:56 +01:00
1 parent d1ac9301be
commit 8dfa38b4b5
36 files changed
+1189 -471

No files matched your search

+2 -1
View File
@@ -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; }
@@ -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
/// <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);
}
}
@@ -0,0 +1,7 @@
namespace Impostor.Api.Innersloth;
public enum CrossplayFlags
{
Default = 4,
All = int.MaxValue,
}
@@ -0,0 +1,30 @@
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);
}
}
}
+9
View File
@@ -0,0 +1,9 @@
namespace Impostor.Api.Innersloth
{
public enum GameModes : byte
{
None,
Normal,
HideNSeek,
}
}
@@ -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);
}
}
}
@@ -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<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);
}
}
}
@@ -0,0 +1,57 @@
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}");
}
}
@@ -0,0 +1,263 @@
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}");
}
}
}
@@ -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<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);
}
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -0,0 +1,8 @@
namespace Impostor.Api.Innersloth.GameOptions.RoleOptions;
public interface IRoleOptions
{
RoleTypes Type { get; }
void Serialize(IMessageWriter writer);
}
@@ -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<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);
}
}
@@ -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<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);
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -1,278 +0,0 @@
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}.");
}
}
}
}
@@ -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<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;
}
}
}
}
@@ -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();
}
/// <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);
}
}
}
@@ -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);
}
}
}
@@ -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);
}
}
}
@@ -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();
}
+2 -1
View File
@@ -65,6 +65,7 @@
"resolved": "1.2.0.435",
"contentHash": "ouwPWZxbOV3SmCZxIRqHvljkSzkCyi1tDoMzQtDb/bRP8ctASV/iRJr+A2Gdj0QLaLmWnqTWDrH82/iP+X80Lg=="
}
}
},
".NETStandard,Version=v2.1/linux-x64": {}
}
}
+3 -2
View File
@@ -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))
@@ -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");
@@ -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();
}
+5 -4
View File
@@ -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.
/// </param>
private ValueTask OnRequestGameListAsync(GameOptionsData options)
private ValueTask OnRequestGameListAsync(IGameOptions options)
{
using var message = MessageWriter.Get(MessageType.Reliable);
@@ -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)
{
@@ -117,7 +117,7 @@ namespace Impostor.Server.Net.Inner.Objects
return false;
}
Rpc02SyncSettings.Deserialize(reader, Game.Options);
Rpc02SyncSettings.DeserializeInto(reader, Game.Options);
break;
}
@@ -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)
@@ -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<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);
@@ -135,12 +136,12 @@ namespace Impostor.Server.Net.Manager
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);
+4 -9
View File
@@ -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);
}
+3 -2
View File
@@ -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> 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<object, object> Items { get; }
+5 -3
View File
@@ -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<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;
@@ -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))
{