Merge pull request #501 from Impostor/feature/2022.12.8

This commit is contained in:
js6pak authored and GitHub committed 2022-12-29 18:02:15 +01:00
commit 11bb2dec47
75 files changed
+1707 -525

No files matched your search

+1 -1
View File
@@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<LangVersion>11</LangVersion>
<VersionPrefix>1.7.3</VersionPrefix>
<VersionPrefix>1.8.0</VersionPrefix>
<VersionSuffix>dev</VersionSuffix>
<EnforceCodeStyleInBuild Condition="$(MSBuildProjectName)!='Hazel'">true</EnforceCodeStyleInBuild>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
+5 -2
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,9 @@ namespace Impostor.Api.Games
{
public interface IGame
{
GameOptionsData Options { get; }
IGameOptions Options { get; }
GameFilterOptions FilterOptions { get; }
GameCode Code { get; }
@@ -52,7 +55,7 @@ namespace Impostor.Api.Games
void BanIp(IPAddress ipAddress);
/// <summary>
/// Syncs the internal <see cref="GameOptionsData" /> to all players.
/// Syncs the internal <see cref="IGameOptions" /> to all players.
/// Necessary to do if you modified it, otherwise it won't be used.
/// </summary>
/// <returns>A <see cref="ValueTask" /> representing the asynchronous operation.</returns>
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Impostor.Api.Innersloth;
using Impostor.Api.Innersloth.GameOptions;
namespace Impostor.Api.Games.Managers
{
@@ -14,8 +15,9 @@ namespace Impostor.Api.Games.Managers
/// Creates a new game.
/// </summary>
/// <param name="options">Game options.</param>
/// <param name="filterOptions">Filter 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, GameFilterOptions filterOptions);
}
}
@@ -0,0 +1,17 @@
namespace Impostor.Api.Innersloth;
public enum CrossplayFlags
{
Unknown = 0,
StandaloneEpicPC = 1 << 1,
StandaloneSteamPC = 1 << 2,
StandaloneMac = 1 << 3,
StandaloneWin10 = 1 << 4,
StandaloneItch = 1 << 5,
IPhone = 1 << 6,
Android = 1 << 7,
Switch = 1 << 8,
Xbox = 1 << 9,
Playstation = 1 << 10,
All = int.MaxValue,
}
@@ -0,0 +1,41 @@
using System.Collections.Generic;
namespace Impostor.Api.Innersloth;
public class GameFilterOptions
{
public HashSet<string> FilterTags { get; } = new HashSet<string>();
public static GameFilterOptions CreateDefault()
{
return new GameFilterOptions
{
FilterTags =
{
"Beginner",
},
};
}
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((byte)NumCommonTasks);
writer.Write((byte)NumLongTasks);
writer.Write((byte)NumShortTasks);
writer.Write(NumEmergencyMeetings);
writer.Write((byte)NumImpostors);
writer.Write((byte)KillDistance);
writer.Write(DiscussionTime);
writer.Write(VotingTime);
writer.Write(IsDefaults);
writer.Write((byte)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;
}
}
}
}
+2 -6
View File
@@ -38,9 +38,7 @@
Sabotage = 17,
/// <summary>
/// Decontam on Mira and bottom decontam on Polus
/// </summary>
/// <remarks>The only decontamination on Mira and bottom decontamination on Polus.</remarks>
Decontamination = 18,
Launchpad = 19,
@@ -57,9 +55,7 @@
Dropship = 25,
/// <summary>
/// Top decontam on Polus
/// </summary>
/// <remarks>Top decontamination on Polus.</remarks>
Decontamination2 = 26,
Outside = 27,
+3
View File
@@ -1,4 +1,5 @@
using Impostor.Api.Net.Inner.Objects;
using Impostor.Api.Net.Inner.Objects.GameManager;
using Impostor.Api.Net.Inner.Objects.ShipStatus;
namespace Impostor.Api.Net.Inner
@@ -8,6 +9,8 @@ namespace Impostor.Api.Net.Inner
/// </summary>
public interface IGameNet
{
IInnerGameManager? GameManager { get; }
IInnerLobbyBehaviour? LobbyBehaviour { get; }
IInnerGameData? GameData { get; }
@@ -0,0 +1,5 @@
namespace Impostor.Api.Net.Inner.Objects.GameManager;
public interface IInnerGameManager
{
}
@@ -0,0 +1,8 @@
using Impostor.Api.Net.Inner.Objects.GameManager.Logic.HideAndSeek;
namespace Impostor.Api.Net.Inner.Objects.GameManager;
public interface IInnerHideAndSeekManager : IInnerGameManager
{
ILogicGameFlowHnS LogicFlowHnS { get; }
}
@@ -0,0 +1,5 @@
namespace Impostor.Api.Net.Inner.Objects.GameManager;
public interface IInnerNormalGameManager : IInnerGameManager
{
}
@@ -0,0 +1,8 @@
namespace Impostor.Api.Net.Inner.Objects.GameManager.Logic.HideAndSeek;
public interface ILogicGameFlowHnS
{
float CurrentFinalHideTime { get; }
float CurrentHideTime { get; }
}
+2
View File
@@ -45,5 +45,7 @@
Shapeshift = 46,
CheckMurder = 47,
CheckProtect = 48,
Pet = 49,
CancelPet = 50,
}
}
@@ -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);
}
}
}
+20 -1
View File
@@ -1,4 +1,8 @@
namespace Impostor.Api.Net.Messages
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Impostor.Api.Net.Messages
{
public static class MessageFlags
{
@@ -24,5 +28,20 @@
public const byte SetActivePodType = 21;
public const byte QueryPlatformIds = 22;
public const byte QueryLobbyInfo = 23;
private static readonly Dictionary<byte, string> FlagCache;
static MessageFlags()
{
FlagCache = typeof(MessageFlags)
.GetFields(BindingFlags.Public | BindingFlags.Static)
.Where(fi => fi.IsLiteral && !fi.IsInitOnly)
.ToDictionary(x => (byte)x.GetValue(null)!, y => y.Name);
}
public static string FlagToString(byte flag)
{
return FlagCache[flag];
}
}
}
@@ -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);
}
}
}
@@ -0,0 +1,19 @@
using System.Numerics;
namespace Impostor.Api.Net.Messages.Rpcs
{
public static class Rpc49Pet
{
public static void Serialize(IMessageWriter writer, Vector2 position, Vector2 petPosition)
{
writer.Write(position);
writer.Write(petPosition);
}
public static void Deserialize(IMessageReader reader, out Vector2 position, out Vector2 petPosition)
{
position = reader.ReadVector2();
petPosition = reader.ReadVector2();
}
}
}
@@ -0,0 +1,13 @@
namespace Impostor.Api.Net.Messages.Rpcs
{
public static class Rpc50CancelPet
{
public static void Serialize(IMessageWriter writer)
{
}
public static void Deserialize(IMessageReader reader)
{
}
}
}
@@ -1,20 +1,19 @@
using System;
using Impostor.Api.Innersloth;
using Impostor.Api.Games;
namespace Impostor.Api.Net.Messages.S2C
{
public static class Message00HostGameS2C
{
public static void Serialize(IMessageWriter writer, int gameCode)
public static void Serialize(IMessageWriter writer, GameCode gameCode)
{
writer.StartMessage(MessageFlags.HostGame);
writer.Write(gameCode);
gameCode.Serialize(writer);
writer.EndMessage();
}
public static GameOptionsData Deserialize(IMessageReader reader)
public static void Deserialize(IMessageReader reader, out GameCode gameCode)
{
throw new NotImplementedException();
gameCode = reader.ReadInt32();
}
}
}
+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, GameFilterOptions.CreateDefault());
// TODO: ObjectPool for MessageReaders
using (var connection = new UdpClientConnection(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 22023), null))
@@ -1,6 +1,7 @@
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 +23,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(), GameFilterOptions.CreateDefault());
if (game == null)
{
_logger.LogWarning("Example game creation was cancelled");
@@ -19,21 +19,16 @@ namespace Impostor.Plugins.Example.Handlers
{
_logger.LogInformation("Game creation requested by {client}", e.Client == null ? "a plugin" : e.Client.Name);
if (e.Client != null)
{
var gameCode = GameCode.From(e.Client.Name);
if (!gameCode.IsInvalid)
{
e.GameCode = gameCode;
}
if (e.Client.Name == "dima")
{
e.IsCancelled = true;
e.Client.DisconnectAsync(DisconnectReason.Custom, "No you dont >:(");
}
}
// TODO: Code below causes the lobby to stop loading and close after 5 secs.
// if (e.Client != null)
// {
// var gameCode = GameCode.From(e.Client.Name);
//
// if (!gameCode.IsInvalid)
// {
// e.GameCode = gameCode;
// }
// }
}
[EventListener]
@@ -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();
}
+10 -11
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;
@@ -58,17 +59,17 @@ namespace Impostor.Server.Net
{
var flag = reader.Tag;
_logger.LogTrace("[{0}] Server got {1}.", Id, flag);
_logger.LogTrace("[{0}] Server got {1}.", Id, MessageFlags.FlagToString(flag));
switch (flag)
{
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, gameFilterOptions);
if (game == null)
{
@@ -259,8 +260,8 @@ namespace Impostor.Server.Net
case MessageFlags.GetGameListV2:
{
Message16GetGameListC2S.Deserialize(reader, out var options, out _);
await OnRequestGameListAsync(options);
Message16GetGameListC2S.Deserialize(reader, out var options, out _, out _, out var filterOptions);
await OnRequestGameListAsync(options, filterOptions);
break;
}
@@ -354,15 +355,13 @@ namespace Impostor.Server.Net
/// <summary>
/// Triggered when the connected client requests the game listing.
/// </summary>
/// <param name="options">
/// All options given.
/// At this moment, the client can only specify the map, impostor count and chat language.
/// </param>
private ValueTask OnRequestGameListAsync(GameOptionsData options)
/// <param name="options">Options specific to the game mode. At this moment, the client can only specify the map, impostor count and chat language.</param>
/// <param name="filterOptions">Filter options not specific to the game mode.</param>
private ValueTask OnRequestGameListAsync(IGameOptions options, GameFilterOptions filterOptions)
{
using var message = MessageWriter.Get(MessageType.Reliable);
var games = _gameManager.FindListings((MapFlags)options.Map, options.NumImpostors, options.Keywords, this.GameVersion);
var games = _gameManager.FindListings((MapFlags)options.Map, options.NumImpostors, options.Keywords, this.GameVersion, filterOptions.FilterTags);
Message16GetGameListS2C.Serialize(message, games);
@@ -108,6 +108,18 @@ namespace Impostor.Server.Net.Inner.Objects.Components
Rpc31ClimbLadder.Deserialize(reader, out var ladderId, out var lastClimbLadderSid);
break;
case RpcCalls.Pet:
{
Rpc49Pet.Deserialize(reader, out var position, out var petPosition);
break;
}
case RpcCalls.CancelPet:
{
Rpc50CancelPet.Deserialize(reader);
break;
}
default:
return await base.HandleRpcAsync(sender, target, call, reader);
}
@@ -0,0 +1,98 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Impostor.Api.Net;
using Impostor.Api.Net.Custom;
using Impostor.Api.Net.Inner;
using Impostor.Api.Net.Inner.Objects.GameManager;
using Impostor.Server.Net.Inner.Objects.GameManager.Logic;
using Impostor.Server.Net.State;
using Microsoft.Extensions.Logging;
namespace Impostor.Server.Net.Inner.Objects.GameManager;
internal abstract class InnerGameManager : InnerNetObject, IInnerGameManager
{
private readonly ILogger<InnerGameManager> _logger;
private readonly List<GameLogicComponent> _logicComponents = new();
public InnerGameManager(ICustomMessageManager<ICustomRpc> customMessageManager, Game game, ILogger<InnerGameManager> logger) : base(customMessageManager, game)
{
_logger = logger;
Components.Add(this);
}
public LogicGameFlow LogicFlow { get; protected init; } = null!;
public LogicMinigame LogicMinigame { get; protected init; } = null!;
public LogicRoleSelection LogicRoleSelection { get; protected init; } = null!;
public LogicUsables LogicUsables { get; protected init; } = null!;
public LogicOptions LogicOptions { get; protected init; } = null!;
protected T AddGameLogic<T>(T logic)
where T : GameLogicComponent
{
_logicComponents.Add(logic);
return logic;
}
/// <summary>
/// Finds the tag of the registered <see cref="GameLogicComponent"/>.
/// </summary>
/// <param name="logic">Instance to search for.</param>
/// <typeparam name="T">Intance type to search for.</typeparam>
/// <returns>Tag of the registered <see cref="GameLogicComponent"/>, or -1 if not found.</returns>
internal int GetGameLogicTag<T>(T logic)
where T : GameLogicComponent
{
return _logicComponents.IndexOf(logic);
}
public override ValueTask<bool> HandleRpcAsync(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader)
{
foreach (var logicComponent in _logicComponents)
{
logicComponent.HandleRpc(call, reader);
}
return ValueTask.FromResult(true);
}
public override ValueTask<bool> SerializeAsync(IMessageWriter writer, bool initialState)
{
throw new System.NotImplementedException();
}
public override ValueTask DeserializeAsync(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState)
{
while (reader.Position < reader.Length)
{
var innerReader = reader.ReadMessage();
var tag = (int)innerReader.Tag;
if (tag < 0 || tag > this._logicComponents.Count)
{
_logger.LogError("Out of bounds in DeserializeAsync of InnerGameManager");
continue;
}
var component = this._logicComponents[tag];
component.Deserialize(innerReader, initialState);
if (innerReader.Position < innerReader.Length)
{
_logger.LogWarning(
"Server did not consume all bytes from {0} component {1} ({2} < {3}).",
nameof(InnerGameManager),
component.GetType().FullName,
innerReader.Position,
innerReader.Length);
}
}
return ValueTask.CompletedTask;
}
}
@@ -0,0 +1,9 @@
using Impostor.Api.Net.Inner.Objects.GameManager;
using Impostor.Api.Net.Inner.Objects.GameManager.Logic.HideAndSeek;
namespace Impostor.Server.Net.Inner.Objects.GameManager;
internal partial class InnerHideAndSeekManager
{
ILogicGameFlowHnS IInnerHideAndSeekManager.LogicFlowHnS => LogicFlowHnS;
}
@@ -0,0 +1,37 @@
using Impostor.Api.Net.Custom;
using Impostor.Api.Net.Inner.Objects.GameManager;
using Impostor.Server.Net.Inner.Objects.GameManager.Logic.HideAndSeek;
using Impostor.Server.Net.State;
using Microsoft.Extensions.Logging;
namespace Impostor.Server.Net.Inner.Objects.GameManager;
internal partial class InnerHideAndSeekManager : InnerGameManager, IInnerHideAndSeekManager
{
public InnerHideAndSeekManager(ICustomMessageManager<ICustomRpc> customMessageManager, Game game, ILogger<InnerGameManager> logger) : base(customMessageManager, game, logger)
{
LogicMusic = AddGameLogic(new LogicHnSMusic());
LogicMinigame = AddGameLogic(new LogicMinigameHnS());
LogicFlowHnS = new LogicGameFlowHnS();
LogicFlow = AddGameLogic(LogicFlowHnS);
LogicUsables = AddGameLogic(new LogicUsablesHnS());
LogicRoleSelection = AddGameLogic(new LogicRoleSelectionHnS());
LogicOptionsHnS = new LogicOptionsHnS(game);
LogicOptions = AddGameLogic(this.LogicOptionsHnS);
LogicDangerLevel = AddGameLogic(new LogicHnSDangerLevel());
LogicPing = AddGameLogic(new LogicPingsHnS());
LogicDeathPopup = AddGameLogic(new LogicHnSDeathPopup());
}
public LogicHnSMusic LogicMusic { get; private set; }
public LogicGameFlowHnS LogicFlowHnS { get; private set; }
public LogicOptionsHnS LogicOptionsHnS { get; private set; }
public LogicHnSDangerLevel LogicDangerLevel { get; private set; }
public LogicPingsHnS LogicPing { get; private set; }
public LogicHnSDeathPopup LogicDeathPopup { get; private set; }
}
@@ -0,0 +1,20 @@
using Impostor.Api.Net.Custom;
using Impostor.Api.Net.Inner.Objects.GameManager;
using Impostor.Server.Net.Inner.Objects.GameManager.Logic;
using Impostor.Server.Net.Inner.Objects.GameManager.Logic.Normal;
using Impostor.Server.Net.State;
using Microsoft.Extensions.Logging;
namespace Impostor.Server.Net.Inner.Objects.GameManager;
internal class InnerNormalGameManager : InnerGameManager, IInnerNormalGameManager
{
public InnerNormalGameManager(ICustomMessageManager<ICustomRpc> customMessageManager, Game game, ILogger<InnerGameManager> logger) : base(customMessageManager, game, logger)
{
LogicFlow = AddGameLogic(new LogicGameFlowNormal());
LogicMinigame = AddGameLogic(new LogicMinigame());
LogicRoleSelection = AddGameLogic(new LogicRoleSelectionNormal());
LogicUsables = AddGameLogic(new LogicUsablesBasic());
LogicOptions = AddGameLogic(new LogicOptionsNormal(game));
}
}
@@ -0,0 +1,21 @@
using System;
using Impostor.Api.Net.Inner;
namespace Impostor.Server.Net.Inner.Objects.GameManager.Logic;
internal abstract class GameLogicComponent
{
public virtual void HandleRpc(RpcCalls callId, IMessageReader reader)
{
throw new NotImplementedException($"Unhandled RpcCall {callId}");
}
public virtual bool Serialize(IMessageWriter writer, bool initialState)
{
throw new NotImplementedException();
}
public virtual void Deserialize(IMessageReader reader, bool initialState)
{
}
}
@@ -0,0 +1,18 @@
using Impostor.Api.Net.Inner.Objects.GameManager.Logic.HideAndSeek;
namespace Impostor.Server.Net.Inner.Objects.GameManager.Logic.HideAndSeek;
internal class LogicGameFlowHnS : LogicGameFlow, ILogicGameFlowHnS
{
public float CurrentFinalHideTime { get; private set; }
public float CurrentHideTime { get; private set; }
public override void Deserialize(IMessageReader reader, bool initialState)
{
var num = reader.ReadSingle();
CurrentFinalHideTime = reader.ReadSingle();
CurrentHideTime = num;
}
}
@@ -0,0 +1,5 @@
namespace Impostor.Server.Net.Inner.Objects.GameManager.Logic.HideAndSeek;
internal class LogicHnSDangerLevel : GameLogicComponent
{
}
@@ -0,0 +1,5 @@
namespace Impostor.Server.Net.Inner.Objects.GameManager.Logic.HideAndSeek;
internal class LogicHnSDeathPopup : GameLogicComponent
{
}
@@ -0,0 +1,5 @@
namespace Impostor.Server.Net.Inner.Objects.GameManager.Logic.HideAndSeek;
internal class LogicHnSMusic : GameLogicComponent
{
}
@@ -0,0 +1,5 @@
namespace Impostor.Server.Net.Inner.Objects.GameManager.Logic.HideAndSeek;
internal class LogicMinigameHnS : LogicMinigame
{
}
@@ -0,0 +1,10 @@
using Impostor.Server.Net.State;
namespace Impostor.Server.Net.Inner.Objects.GameManager.Logic.HideAndSeek;
internal class LogicOptionsHnS : LogicOptions
{
public LogicOptionsHnS(Game game) : base(game)
{
}
}
@@ -0,0 +1,5 @@
namespace Impostor.Server.Net.Inner.Objects.GameManager.Logic.HideAndSeek;
internal class LogicPingsHnS : GameLogicComponent
{
}
@@ -0,0 +1,5 @@
namespace Impostor.Server.Net.Inner.Objects.GameManager.Logic.HideAndSeek;
internal class LogicRoleSelectionHnS : LogicRoleSelection
{
}
@@ -0,0 +1,5 @@
namespace Impostor.Server.Net.Inner.Objects.GameManager.Logic.HideAndSeek;
internal class LogicUsablesHnS : LogicUsables
{
}
@@ -0,0 +1,5 @@
namespace Impostor.Server.Net.Inner.Objects.GameManager.Logic;
internal abstract class LogicGameFlow : GameLogicComponent
{
}
@@ -0,0 +1,5 @@
namespace Impostor.Server.Net.Inner.Objects.GameManager.Logic;
internal class LogicMinigame : GameLogicComponent
{
}
@@ -0,0 +1,25 @@
using Impostor.Api.Innersloth.GameOptions;
using Impostor.Server.Net.State;
namespace Impostor.Server.Net.Inner.Objects.GameManager.Logic;
internal abstract class LogicOptions : GameLogicComponent
{
private readonly Game _game;
protected LogicOptions(Game game)
{
_game = game;
}
public override bool Serialize(IMessageWriter writer, bool initialState)
{
GameOptionsFactory.Serialize(writer, _game.Options);
return true;
}
public override void Deserialize(IMessageReader reader, bool initialState)
{
GameOptionsFactory.DeserializeInto(reader, _game.Options);
}
}
@@ -0,0 +1,5 @@
namespace Impostor.Server.Net.Inner.Objects.GameManager.Logic;
internal abstract class LogicRoleSelection : GameLogicComponent
{
}
@@ -0,0 +1,5 @@
namespace Impostor.Server.Net.Inner.Objects.GameManager.Logic;
internal abstract class LogicUsables : GameLogicComponent
{
}
@@ -0,0 +1,5 @@
namespace Impostor.Server.Net.Inner.Objects.GameManager.Logic.Normal;
internal class LogicGameFlowNormal : LogicGameFlow
{
}
@@ -0,0 +1,10 @@
using Impostor.Server.Net.State;
namespace Impostor.Server.Net.Inner.Objects.GameManager.Logic.Normal;
internal class LogicOptionsNormal : LogicOptions
{
public LogicOptionsNormal(Game game) : base(game)
{
}
}
@@ -0,0 +1,5 @@
namespace Impostor.Server.Net.Inner.Objects.GameManager.Logic.Normal;
internal class LogicRoleSelectionNormal : LogicRoleSelection
{
}
@@ -0,0 +1,5 @@
namespace Impostor.Server.Net.Inner.Objects.GameManager.Logic.Normal;
internal class LogicUsablesBasic : LogicUsables
{
}
@@ -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)
@@ -21,14 +21,8 @@ namespace Impostor.Server.Net.Manager
// NOTE: when updating this array, keep the versions ordered from old to new, otherwise the version compare logic doesn't work properly
private static readonly int[] SupportedVersions =
{
GameVersion.GetVersion(2021, 11, 9), // 2021.11.9
GameVersion.GetVersion(2021, 12, 14), // 2021.12.14
GameVersion.GetVersion(2022, 1, 10), // 2021.2.23
GameVersion.GetVersion(2022, 2, 2), // 2022.3.29 and 2022.4.19
GameVersion.GetVersion(2022, 4, 20), // 2022.6.21 and 2022.7.12
GameVersion.GetVersion(2022, 5, 12), // 2022.8.23
GameVersion.GetVersion(2022, 7, 25), // 2022.10.18
GameVersion.GetVersion(2022, 9, 2), // 2022.10.25
GameVersion.GetVersion(2022, 11, 1), // 2022.12.8
GameVersion.GetVersion(2022, 11, 9), // 2022.12.14
};
private readonly ILogger<ClientManager> _logger;
+15 -9
View File
@@ -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;
@@ -50,7 +51,7 @@ namespace Impostor.Server.Net.Manager
return game;
}
public IEnumerable<Game> FindListings(MapFlags map, int impostorCount, GameKeywords language, int gameVersion, int count = 10)
public IEnumerable<Game> FindListings(MapFlags map, int impostorCount, GameKeywords language, int gameVersion, HashSet<string> filterTags, int count = 10)
{
var results = 0;
@@ -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)))
@@ -77,6 +78,11 @@ namespace Impostor.Server.Net.Manager
continue;
}
if (!game.FilterOptions.FilterTags.SetEquals(filterTags))
{
continue;
}
// Add to result.
yield return game;
@@ -110,7 +116,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, GameFilterOptions filterOptions)
{
var @event = new GameCreationEvent(this, owner);
await _eventManager.CallAsync(@event);
@@ -120,11 +126,11 @@ namespace Impostor.Server.Net.Manager
return null;
}
var (success, game) = await TryCreateAsync(options, @event.GameCode);
var (success, game) = await TryCreateAsync(options, filterOptions, @event.GameCode);
for (var i = 0; i < 10 && !success; i++)
{
(success, game) = await TryCreateAsync(options);
(success, game) = await TryCreateAsync(options, filterOptions);
}
if (!success || game == null)
@@ -135,15 +141,15 @@ namespace Impostor.Server.Net.Manager
return game;
}
public ValueTask<IGame?> CreateAsync(GameOptionsData options)
public ValueTask<IGame?> CreateAsync(IGameOptions options, GameFilterOptions filterOptions)
{
return CreateAsync(null, options);
return CreateAsync(null, options, filterOptions);
}
private async ValueTask<(bool Success, Game? Game)> TryCreateAsync(GameOptionsData options, GameCode? desiredGameCode = null)
private async ValueTask<(bool Success, Game? Game)> TryCreateAsync(IGameOptions options, GameFilterOptions filterOptions, GameCode? desiredGameCode = null)
{
var gameCode = desiredGameCode ?? _gameCodeFactory.Create();
var game = ActivatorUtilities.CreateInstance<Game>(_serviceProvider, _publicIp, gameCode, options);
var game = ActivatorUtilities.CreateInstance<Game>(_serviceProvider, _publicIp, gameCode, options, filterOptions);
if (!_games.TryAdd(gameCode, game))
{
+33 -17
View File
@@ -1,12 +1,12 @@
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;
using Impostor.Hazel;
using Impostor.Server.Net.Inner;
namespace Impostor.Server.Net.State
{
@@ -25,24 +25,40 @@ namespace Impostor.Server.Net.State
{
if (Host?.Character == null)
{
throw new ImpostorException("Attempted to set infected when the host was not spawned.");
throw new ImpostorException("Attempted to change settings when the host was not spawned.");
}
using (var writer = StartRpc(Host.Character.NetId, RpcCalls.SyncSettings))
if (GameNet.GameManager == null)
{
// Someone will probably forget to do this, so we include it here.
// 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());
}
await FinishRpcAsync(writer);
throw new ImpostorException("Attempted to change options when the game manager was not spawned.");
}
var gameOptionsTag = GameNet.GameManager.GetGameLogicTag(GameNet.GameManager.LogicOptions);
if (gameOptionsTag == -1)
{
throw new ImpostorException("Attempted to change options when the LogicOptions was not spawned.");
}
// Someone will probably forget to do this, so we include it here.
// If this is not done, the host will overwrite changes later with the defaults.
Options.IsDefaults = false;
using var writer = MessageWriter.Get(MessageType.Reliable);
writer.StartMessage(MessageFlags.GameData);
Code.Serialize(writer);
writer.StartMessage(GameDataTag.DataFlag);
writer.WritePacked(GameNet.GameManager.NetId);
writer.StartMessage((byte)gameOptionsTag);
GameNet.GameManager.LogicOptions.Serialize(writer, false);
writer.EndMessage();
writer.EndMessage();
writer.EndMessage();
await SendToAllAsync(writer);
}
public async ValueTask SetPrivacyAsync(bool isPublic)
@@ -10,6 +10,7 @@ using Impostor.Server.Events.Player;
using Impostor.Server.Net.Inner;
using Impostor.Server.Net.Inner.Objects;
using Impostor.Server.Net.Inner.Objects.Components;
using Impostor.Server.Net.Inner.Objects.GameManager;
using Impostor.Server.Net.Inner.Objects.ShipStatus;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@@ -40,6 +41,8 @@ namespace Impostor.Server.Net.State
typeof(InnerPolusShipStatus),
typeof(InnerSkeldShipStatus), // April fools skeld
typeof(InnerAirshipStatus),
typeof(InnerHideAndSeekManager),
typeof(InnerNormalGameManager),
};
private readonly List<InnerNetObject> _allObjects = new List<InnerNetObject>();
@@ -287,6 +290,12 @@ namespace Impostor.Server.Net.State
{
switch (netObj)
{
case InnerGameManager innerGameManager:
{
GameNet.GameManager = innerGameManager;
break;
}
case InnerLobbyBehaviour lobby:
{
GameNet.LobbyBehaviour = lobby;
+7 -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,8 @@ namespace Impostor.Server.Net.State
GameManager gameManager,
IPEndPoint publicIp,
GameCode code,
GameOptionsData options,
IGameOptions options,
GameFilterOptions filterOptions,
ClientManager clientManager,
IEventManager eventManager,
IOptions<CompatibilityConfig> compatibilityConfig)
@@ -53,6 +55,7 @@ namespace Impostor.Server.Net.State
GameState = GameStates.NotStarted;
GameNet = new GameNet();
Options = options;
FilterOptions = filterOptions;
_clientManager = clientManager;
_eventManager = eventManager;
_compatibilityConfig = compatibilityConfig.Value;
@@ -71,7 +74,9 @@ namespace Impostor.Server.Net.State
public GameStates GameState { get; private set; }
public GameOptionsData Options { get; }
public IGameOptions Options { get; }
public GameFilterOptions FilterOptions { get; }
public IDictionary<object, object> Items { get; }
@@ -1,5 +1,6 @@
using Impostor.Api.Net.Inner;
using Impostor.Api.Net.Inner.Objects;
using Impostor.Api.Net.Inner.Objects.GameManager;
using Impostor.Api.Net.Inner.Objects.ShipStatus;
namespace Impostor.Server.Net.State
@@ -7,6 +8,8 @@ namespace Impostor.Server.Net.State
/// <inheritdoc />
internal partial class GameNet : IGameNet
{
IInnerGameManager? IGameNet.GameManager => GameManager;
IInnerLobbyBehaviour? IGameNet.LobbyBehaviour => LobbyBehaviour;
IInnerGameData? IGameNet.GameData => GameData;
+3
View File
@@ -1,11 +1,14 @@
using Impostor.Server.Net.Inner.Objects;
using Impostor.Server.Net.Inner.Objects.Components;
using Impostor.Server.Net.Inner.Objects.GameManager;
using Impostor.Server.Net.Inner.Objects.ShipStatus;
namespace Impostor.Server.Net.State
{
internal partial class GameNet
{
public InnerGameManager? GameManager { get; internal set; }
public InnerLobbyBehaviour? LobbyBehaviour { get; internal set; }
public InnerGameData? GameData { get; internal set; }
+6 -4
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))
{
@@ -212,7 +214,7 @@ namespace Impostor.Tools.ServerReplay
case RecordedPacketType.GameCreated:
_gameCodeFactory.Result = GameCode.From(reader.ReadString());
await _gameManager.CreateAsync(Connections[clientId].Client, GameOptions[clientId]);
await _gameManager.CreateAsync(Connections[clientId].Client, GameOptions[clientId], GameFilterOptions.CreateDefault());
GameOptions.Remove(clientId);
break;