Merge pull request #609 from Impostor/feature/2024.6.18-prepare

Prepare for 2024.6.18 update
This commit is contained in:
miniduikboot authored and GitHub committed 2024-08-20 21:50:45 +02:00
commit f6318c7040
46 files changed
+984 -278

No files matched your search

+1 -1
View File
@@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>11</LangVersion>
<VersionPrefix>1.9.3</VersionPrefix>
<VersionPrefix>1.10.0</VersionPrefix>
<VersionSuffix>dev</VersionSuffix>
<EnforceCodeStyleInBuild Condition="$(MSBuildProjectName)!='Hazel'">true</EnforceCodeStyleInBuild>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
@@ -79,6 +79,8 @@ public sealed class SourceGenerator : IIncrementalGenerator
enumGenerator.Generate("GameOverReason", underlyingType: CSharpEnumUnderlyingType.Byte);
enumGenerator.Generate("Platforms");
enumGenerator.Generate("RoleTypes", underlyingType: CSharpEnumUnderlyingType.UnsignedShort);
enumGenerator.Generate("RulesPresets", underlyingType: CSharpEnumUnderlyingType.Byte);
enumGenerator.Generate("SpecialGameModes", underlyingType: CSharpEnumUnderlyingType.Byte);
enumGenerator.Generate("StringNames");
enumGenerator.Generate("SystemTypes", underlyingType: CSharpEnumUnderlyingType.Byte);
enumGenerator.Generate("Language", sourceName: "SupportedLangs");
@@ -2,42 +2,102 @@ namespace Impostor.Api.Innersloth.Customization
{
public class PlayerOutfit
{
public ColorType Color { get; internal set; } = (ColorType)(-1);
// Magic value that is assigned by default when the outfit is initially created. This is not
// `null` because existing API already exposed this magic value. It's also not 0 (which is
// the value exposed when serializing) because SetColor anticheat checks exist.
private const ColorType UnknownColor = (ColorType)(-1);
public string HatId { get; internal set; } = "missing";
private string _playerName = string.Empty;
private ColorType _color = UnknownColor;
private string _hatId = "missing";
private string _petId = "missing";
private string _skinId = "missing";
private string _visorId = "missing";
private string _namePlateId = "missing";
public string PetId { get; internal set; } = "missing";
public bool IsDirty { get; internal set; }
public string SkinId { get; internal set; } = "missing";
public string VisorId { get; internal set; } = "missing";
public string NamePlateId { get; internal set; } = "missing";
public string PlayerName { get; internal set; } = "missing";
public bool IsIncomplete
public string PlayerName
{
get
{
if (!string.IsNullOrEmpty(PlayerName) && Color != (ColorType)(-1) && HatId != "missing" && PetId != "missing" && SkinId != "missing" && VisorId != "missing")
{
return NamePlateId == "missing";
}
return true;
}
get => _playerName;
internal set => SetField(ref _playerName, value);
}
public ColorType Color
{
get => _color;
internal set => SetField(ref _color, value);
}
public string HatId
{
get => _hatId;
internal set => SetField(ref _hatId, value);
}
public string PetId
{
get => _petId;
internal set => SetField(ref _petId, value);
}
public string SkinId
{
get => _skinId;
internal set => SetField(ref _skinId, value);
}
public string VisorId
{
get => _visorId;
internal set => SetField(ref _visorId, value);
}
public string NamePlateId
{
get => _namePlateId;
internal set => SetField(ref _namePlateId, value);
}
private byte HatSequenceId { get; set; } = 0;
private byte PetSequenceId { get; set; } = 0;
private byte SkinSequenceId { get; set; } = 0;
private byte VisorSequenceId { get; set; } = 0;
private byte NamePlateSequenceId { get; set; } = 0;
/// <summary>
/// Gets a value indicating whether a player outfit is complete or whether it needs to receive additional cosmetics.
/// </summary>
/// <returns>true if the outfit is incomplete, false if the outfit is complete.</returns>
public bool IsIncomplete => string.IsNullOrEmpty(PlayerName)
|| Color == UnknownColor
|| HatId == "missing"
|| PetId == "missing"
|| SkinId == "missing"
|| VisorId == "missing"
|| NamePlateId == "missing";
public void Serialize(IMessageWriter writer)
{
writer.Write(PlayerName);
writer.WritePacked((int)Color);
// Follow officials by not sending a color value that is out of range. Sending -1 can cause exceptions on the client side.
writer.WritePacked(Color == UnknownColor ? 0 : (int)Color);
writer.Write(HatId);
writer.Write(PetId);
writer.Write(SkinId);
writer.Write(VisorId);
writer.Write(NamePlateId);
writer.Write(HatSequenceId);
writer.Write(PetSequenceId);
writer.Write(SkinSequenceId);
writer.Write(VisorSequenceId);
writer.Write(NamePlateSequenceId);
}
public void Deserialize(IMessageReader reader)
@@ -49,6 +109,17 @@ namespace Impostor.Api.Innersloth.Customization
SkinId = reader.ReadString();
VisorId = reader.ReadString();
NamePlateId = reader.ReadString();
HatSequenceId = reader.ReadByte();
PetSequenceId = reader.ReadByte();
SkinSequenceId = reader.ReadByte();
VisorSequenceId = reader.ReadByte();
NamePlateSequenceId = reader.ReadByte();
}
private void SetField<T>(ref T field, T value)
{
field = value;
IsDirty = true;
}
}
}
@@ -2,7 +2,6 @@
"0": "SkeldShipStatus",
"1": "MeetingHud",
"2": "LobbyBehaviour",
"3": "GameData",
"4": "PlayerControl",
"5": "MiraShipStatus",
"6": "PolusShipStatus",
@@ -10,5 +9,7 @@
"8": "AirshipStatus",
"9": "HideAndSeekManager",
"10": "NormalGameManager",
"11": "NetworkedPlayerInfo",
"12": "VoteBanSystem",
"13": "FungleShipStatus"
}
@@ -6,5 +6,8 @@
"GuardianAngel": 4,
"Shapeshifter": 5,
"CrewmateGhost": 6,
"ImpostorGhost": 7
"ImpostorGhost": 7,
"Noisemaker": 8,
"Phantom": 9,
"Tracker": 10
}
@@ -55,5 +55,9 @@
"CheckShapeshift": 55,
"RejectShapeshift": 56,
"LobbyTimeExpiring": 60,
"ExtendLobbyTimer": 61
"ExtendLobbyTimer": 61,
"CheckVanish": 62,
"StartVanish": 63,
"CheckAppear": 64,
"StartAppear": 65
}
@@ -0,0 +1,6 @@
{
"Standard": 0,
"StandardRoles": 1,
"Flashlight": 2,
"Custom": 100
}
@@ -0,0 +1,23 @@
{
"None": 0,
"ImpersonationCeleb": 1,
"Spamming": 2,
"InappropriateNameUnsportsmanlike": 3,
"UnsportsmanlikeConduct": 4,
"ImpersonationDevelopers": 5,
"InappropriateChatPersonalInfo": 6,
"InappropriateNameDerogatory": 7,
"InappropriateNameNsfw": 8,
"Bullying": 9,
"CheatingHacking": 10,
"InappropriateChatDating": 11,
"WeaponizingRules": 12,
"RepeatOffender3": 13,
"SexualMisconduct": 14,
"Doxing": 15,
"IllegalActivity": 16,
"Harassment": 17,
"SelfHarmPromotion": 18,
"RepeatOffender10": 19,
"InvalidUser": 20
}
@@ -0,0 +1,4 @@
{
"None": 0,
"AprilFools": 1
}
@@ -570,10 +570,39 @@
"RolesHelp_Engineer_02": 1563,
"RolesHelp_GuardianAngel_01": 1564,
"RolesHelp_GuardianAngel_02": 1565,
"RolesHelp_Noisemaker_01": 1566,
"RolesHelp_Noisemaker_02": 1567,
"RolesHelp_Tracker_01": 1568,
"RolesHelp_Tracker_02": 1569,
"RolesHelp_Shapeshifter_01": 1600,
"RolesHelp_Shapeshifter_02": 1601,
"RolesHelp_Phantom_01": 1602,
"RolesHelp_Phantom_02": 1603,
"RolesHelpOutro_01": 1650,
"RolesHelpOutro_02": 1651,
"NoisemakerRole": 1660,
"NoisemakerBlurb": 1661,
"NoisemakerBlurbMed": 1662,
"NoisemakerBlurbLong": 1663,
"NoisemakerImpostorAlert": 1664,
"NoisemakerAlertDuration": 1665,
"PhantomAbility": 1670,
"PhantomRole": 1671,
"PhantomBlurb": 1672,
"PhantomBlurbMed": 1673,
"PhantomBlurbLong": 1674,
"PhantomCooldown": 1675,
"PhantomDuration": 1676,
"PhantomAbilityUndo": 1677,
"TrackerAbility": 1680,
"TrackerRole": 1681,
"TrackerBlurb": 1682,
"TrackerBlurbMed": 1683,
"TrackerBlurbLong": 1684,
"TrackerCooldown": 1685,
"TrackerDuration": 1686,
"TrackerAbilityUndo": 1687,
"TrackerDelay": 1688,
"SanctionDuration": 1700,
"SanctionPermanent": 1701,
"SanctionConduct": 1702,
@@ -1304,6 +1333,24 @@
"ConfirmDeleteAccountsEmpty": 3209,
"AccountRequestDelete": 3210,
"HasBeenKilled": 3211,
"RoomSettingsLabel": 3280,
"ViewLabel": 3281,
"RoomCodeLabel": 3282,
"ModeLabel": 3283,
"PrivacyLabel": 3284,
"CapacityLabel": 3285,
"GameSettingsLabel": 3286,
"RoleSettingsLabel": 3287,
"GamePresetsLabel": 3288,
"GamePresetsDescription": 3289,
"StandardRulesPresetDescription": 3290,
"GameModeStandard": 3291,
"GameModeStandardRoles": 3292,
"GameModeCustom": 3293,
"ReturnToLobby": 3294,
"GameModeFlashlight": 3295,
"HostNounLabel": 3296,
"RoleSettingsAll": 3297,
"ScrollList": 3300,
"ScrollNews": 3301,
"AmongUsAnnouncements": 3302,
@@ -1331,6 +1378,33 @@
"ReportNotificationBody": 3324,
"MissingGuardianEmailText": 3325,
"AprilFoolsModeToggle": 3326,
"LobbyChangeSettingNotification": 3327,
"ImpostorsCategory": 3350,
"RolesCategory": 3351,
"MeetingsCategory": 3352,
"PlayersCategory": 3353,
"TasksCategory": 3354,
"GameSettingsDescription": 3355,
"RoleSettingsDescription": 3356,
"InfoLabel": 3357,
"RoleQuotaLabel": 3358,
"OverviewCategory": 3359,
"CrewmateRolesHeader": 3360,
"ImpostorRolesHeader": 3361,
"ViewingLabel": 3362,
"LocalLobbyLabel": 3363,
"NumberRoleHeader": 3364,
"ChanceRoleHeader": 3365,
"WaitingForPlayers": 3366,
"HideAndSeekModeDescription": 3367,
"RolesRulesPresetDescription": 3368,
"FlashlightRulesPresetDescription": 3369,
"RoomCodeCopied": 3370,
"FinalHideCategory": 3371,
"LobbyChangeSettingNotificationRole": 3372,
"StandardRulesPresetDescriptionHideAndSeek": 3373,
"GameModeStandardSeek": 3374,
"HostYouLabel": 3375,
"MiningPit": 3400,
"Jungle": 3401,
"BuildSandcastle": 3402,
@@ -1399,5 +1473,11 @@
"HttpErrorBadGateway": 3531,
"HttpErrorServiceUnavailable": 3532,
"HttpErrorGatewayTimeout": 3533,
"UserVersionError": 3534
"UserVersionError": 3534,
"QCLobbyRoleNoisemakers": 3535,
"QCLobbyRoleTrackers": 3536,
"QCLobbyRolePhantoms": 3537,
"QCSelfWasTracking": 3538,
"QCSelfGoingToNoisemaker": 3539,
"QCSelfSawRoleInLocation": 3540
}
+2 -2
View File
@@ -1,5 +1,5 @@
{
"dumpostorVersion": "1.0.0",
"gameVersion": "2024.3.5",
"dumpostorVersion": "1.0.1",
"gameVersion": "2024.6.18",
"platformType": "StandaloneItch"
}
@@ -2,7 +2,7 @@ namespace Impostor.Api.Innersloth.GameOptions;
public class HideNSeekGameOptions : IGameOptions
{
public const int LatestVersion = 7;
public const int LatestVersion = 8;
public HideNSeekGameOptions(byte version = LatestVersion)
{
@@ -16,6 +16,12 @@ public class HideNSeekGameOptions : IGameOptions
/// <inheritdoc />
public GameModes GameMode => GameModes.HideNSeek;
/// <inheritdoc />
public SpecialGameModes SpecialMode { get; set; } = SpecialGameModes.None;
/// <inheritdoc />
public RulesPresets RulesPreset { get; set; } = RulesPresets.Custom;
/// <inheritdoc />
public byte MaxPlayers { get; set; } = 15;
@@ -96,6 +102,12 @@ public class HideNSeekGameOptions : IGameOptions
public void Deserialize(IMessageReader reader)
{
if (Version >= 8)
{
SpecialMode = (SpecialGameModes)reader.ReadByte();
RulesPreset = (RulesPresets)reader.ReadByte();
}
MaxPlayers = reader.ReadByte();
Keywords = (GameKeywords)reader.ReadInt32();
Map = (MapTypes)reader.ReadByte();
@@ -121,7 +133,7 @@ public class HideNSeekGameOptions : IGameOptions
MaxPingTime = reader.ReadSingle();
CrewmateTimeInVent = reader.ReadSingle();
if (Version > 7)
if (Version > LatestVersion)
{
IGameOptions.ThrowUnknownVersion<HideNSeekGameOptions>(Version);
}
@@ -129,6 +141,12 @@ public class HideNSeekGameOptions : IGameOptions
public void Serialize(IMessageWriter writer)
{
if (Version >= 8)
{
writer.Write((byte)SpecialMode);
writer.Write((byte)RulesPreset);
}
writer.Write(MaxPlayers);
writer.Write((uint)Keywords);
writer.Write((byte)Map);
@@ -154,7 +172,7 @@ public class HideNSeekGameOptions : IGameOptions
writer.Write(MaxPingTime);
writer.Write(CrewmateTimeInVent);
if (Version > 7)
if (Version > LatestVersion)
{
IGameOptions.ThrowUnknownVersion<HideNSeekGameOptions>(Version);
}
@@ -10,10 +10,20 @@ public interface IGameOptions
public byte Version { get; }
/// <summary>
/// Gets the currently active gamemode.
/// Gets the currently active gamemode. This is currently used for the Normal and HideAndSeek gamemodes.
/// </summary>
public GameModes GameMode { get; }
/// <summary>
/// Gets the currently active special gamemode. This is currently used for the AprilFools gamemode.
/// </summary>
public SpecialGameModes SpecialMode { get; }
/// <summary>
/// Gets the rule preset.
/// </summary>
public RulesPresets RulesPreset { get; }
/// <summary>
/// Gets or sets the maximum amount of players for this lobby.
/// </summary>
@@ -21,6 +21,12 @@ public class LegacyGameOptionsData : IGameOptions
public GameModes GameMode => GameModes.Normal;
/// <inheritdoc />
public SpecialGameModes SpecialMode { get; } = SpecialGameModes.None;
/// <inheritdoc />
public RulesPresets RulesPreset { get; } = RulesPresets.Custom;
/// <summary>
/// Gets or sets the maximum amount of players for this lobby.
/// </summary>
@@ -4,7 +4,7 @@ namespace Impostor.Api.Innersloth.GameOptions;
public class NormalGameOptions : IGameOptions
{
public const int LatestVersion = 7;
public const int LatestVersion = 8;
public NormalGameOptions(byte version = LatestVersion)
{
@@ -19,6 +19,12 @@ public class NormalGameOptions : IGameOptions
/// <inheritdoc />
public GameModes GameMode => GameModes.Normal;
/// <inheritdoc />
public SpecialGameModes SpecialMode { get; set; } = SpecialGameModes.None;
/// <inheritdoc />
public RulesPresets RulesPreset { get; set; } = RulesPresets.Custom;
/// <inheritdoc />
public byte MaxPlayers { get; set; } = 10;
@@ -133,6 +139,12 @@ public class NormalGameOptions : IGameOptions
public void Deserialize(IMessageReader reader)
{
if (Version >= 8)
{
SpecialMode = (SpecialGameModes)reader.ReadByte();
RulesPreset = (RulesPresets)reader.ReadByte();
}
MaxPlayers = reader.ReadByte();
Keywords = (GameKeywords)reader.ReadUInt32();
Map = (MapTypes)reader.ReadByte();
@@ -163,7 +175,7 @@ public class NormalGameOptions : IGameOptions
RoleOptions.Deserialize(reader);
if (Version > 7)
if (Version > LatestVersion)
{
IGameOptions.ThrowUnknownVersion<NormalGameOptions>(Version);
}
@@ -171,6 +183,12 @@ public class NormalGameOptions : IGameOptions
public void Serialize(IMessageWriter writer)
{
if (Version >= 8)
{
writer.Write((byte)SpecialMode);
writer.Write((byte)RulesPreset);
}
writer.Write(MaxPlayers);
writer.Write((uint)Keywords);
writer.Write((byte)Map);
@@ -201,7 +219,7 @@ public class NormalGameOptions : IGameOptions
RoleOptions.Serialize(writer);
if (Version > 7)
if (Version > LatestVersion)
{
IGameOptions.ThrowUnknownVersion<NormalGameOptions>(Version);
}
@@ -0,0 +1,33 @@
namespace Impostor.Api.Innersloth.GameOptions.RoleOptions;
public class NoisemakerRoleOptions : IRoleOptions
{
public NoisemakerRoleOptions(byte version)
{
Version = version;
}
public byte Version { get; }
public RoleTypes Type => RoleTypes.Noisemaker;
public bool ImpostorAlert { get; set; } = true;
public byte AlertDuration { get; set; } = 10;
public static NoisemakerRoleOptions Deserialize(IMessageReader reader, byte version)
{
var options = new NoisemakerRoleOptions(version);
options.AlertDuration = reader.ReadByte();
options.ImpostorAlert = reader.ReadBoolean();
return options;
}
public void Serialize(IMessageWriter writer)
{
writer.Write(AlertDuration);
writer.Write(ImpostorAlert);
}
}
@@ -0,0 +1,33 @@
namespace Impostor.Api.Innersloth.GameOptions.RoleOptions;
public class PhantomRoleOptions : IRoleOptions
{
public PhantomRoleOptions(byte version)
{
Version = version;
}
public byte Version { get; }
public RoleTypes Type => RoleTypes.Phantom;
public byte Cooldown { get; set; } = 15;
public byte Duration { get; set; } = 30;
public static PhantomRoleOptions Deserialize(IMessageReader reader, byte version)
{
var options = new PhantomRoleOptions(version);
options.Cooldown = reader.ReadByte();
options.Duration = reader.ReadByte();
return options;
}
public void Serialize(IMessageWriter writer)
{
writer.Write(Cooldown);
writer.Write(Duration);
}
}
@@ -31,6 +31,9 @@ public class RoleOptionsCollection
RoleTypes.Engineer => EngineerRoleOptions.Deserialize(roleOptionsReader, Version),
RoleTypes.GuardianAngel => GuardianAngelRoleOptions.Deserialize(roleOptionsReader, Version),
RoleTypes.Shapeshifter => ShapeshifterRoleOptions.Deserialize(roleOptionsReader, Version),
RoleTypes.Noisemaker => NoisemakerRoleOptions.Deserialize(roleOptionsReader, Version),
RoleTypes.Phantom => PhantomRoleOptions.Deserialize(roleOptionsReader, Version),
RoleTypes.Tracker => TrackerRoleOptions.Deserialize(roleOptionsReader, Version),
_ => throw new ArgumentOutOfRangeException(nameof(roleType), roleType, null),
};
@@ -0,0 +1,37 @@
namespace Impostor.Api.Innersloth.GameOptions.RoleOptions;
public class TrackerRoleOptions : IRoleOptions
{
public TrackerRoleOptions(byte version)
{
Version = version;
}
public byte Version { get; }
public RoleTypes Type => RoleTypes.Tracker;
public byte Cooldown { get; set; } = 15;
public byte Duration { get; set; } = 30;
public byte Delay { get; set; } = 1;
public static TrackerRoleOptions Deserialize(IMessageReader reader, byte version)
{
var options = new TrackerRoleOptions(version);
options.Cooldown = reader.ReadByte();
options.Duration = reader.ReadByte();
options.Delay = reader.ReadByte();
return options;
}
public void Serialize(IMessageWriter writer)
{
writer.Write(Cooldown);
writer.Write(Duration);
writer.Write(Delay);
}
}
@@ -1,6 +1,6 @@
namespace Impostor.Api.Net.Inner.Objects
{
public interface IInnerGameData : IInnerNetObject
public interface IInnerGameData
{
}
}
@@ -28,6 +28,7 @@ namespace Impostor.Api.Net.Messages
public const byte SetActivePodType = 21;
public const byte QueryPlatformIds = 22;
public const byte QueryLobbyInfo = 23;
public const byte EndGameHostMigration = 24;
private static readonly Dictionary<byte, string> FlagCache;
@@ -2,13 +2,15 @@ namespace Impostor.Api.Net.Messages.Rpcs
{
public static class Rpc06SetName
{
public static void Serialize(IMessageWriter writer, string name)
public static void Serialize(IMessageWriter writer, uint netId, string name)
{
writer.Write(netId);
writer.Write(name);
}
public static void Deserialize(IMessageReader reader, out string name)
public static void Deserialize(IMessageReader reader, out uint netId, out string name)
{
netId = reader.ReadUInt32();
name = reader.ReadString();
}
}
@@ -4,13 +4,15 @@ namespace Impostor.Api.Net.Messages.Rpcs
{
public static class Rpc08SetColor
{
public static void Serialize(IMessageWriter writer, ColorType color)
public static void Serialize(IMessageWriter writer, uint netId, ColorType color)
{
writer.Write(netId);
writer.Write((byte)color);
}
public static void Deserialize(IMessageReader reader, out ColorType color)
public static void Deserialize(IMessageReader reader, out uint netId, out ColorType color)
{
netId = reader.ReadUInt32();
color = (ColorType)reader.ReadByte();
}
}
@@ -4,15 +4,13 @@ namespace Impostor.Api.Net.Messages.Rpcs
{
public static class Rpc29SetTasks
{
public static void Serialize(IMessageWriter writer, byte playerId, ReadOnlyMemory<byte> taskTypeIds)
public static void Serialize(IMessageWriter writer, ReadOnlyMemory<byte> taskTypeIds)
{
writer.Write(playerId);
writer.Write(taskTypeIds);
}
public static void Deserialize(IMessageReader reader, out byte playerId, out ReadOnlyMemory<byte> taskTypeIds)
public static void Deserialize(IMessageReader reader, out ReadOnlyMemory<byte> taskTypeIds)
{
playerId = reader.ReadByte();
taskTypeIds = reader.ReadBytesAndSize();
}
}
@@ -4,14 +4,16 @@ namespace Impostor.Api.Net.Messages.Rpcs
{
public static class Rpc44SetRole
{
public static void Serialize(IMessageWriter writer, RoleTypes role)
public static void Serialize(IMessageWriter writer, RoleTypes role, bool canOverrideRole)
{
writer.Write((ushort)role);
writer.Write(canOverrideRole);
}
public static void Deserialize(IMessageReader reader, out RoleTypes role)
public static void Deserialize(IMessageReader reader, out RoleTypes role, out bool canOverrideRole)
{
role = (RoleTypes)reader.ReadUInt16();
canOverrideRole = reader.ReadBoolean();
}
}
}
@@ -0,0 +1,15 @@
namespace Impostor.Api.Net.Messages.Rpcs
{
public static class Rpc62CheckVanish
{
public static void Serialize(IMessageWriter writer, float maxDuration)
{
writer.Write(maxDuration);
}
public static void Deserialize(IMessageReader reader, out float maxDuration)
{
maxDuration = reader.ReadSingle();
}
}
}
@@ -0,0 +1,13 @@
namespace Impostor.Api.Net.Messages.Rpcs
{
public static class Rpc63StartVanish
{
public static void Serialize(IMessageWriter writer)
{
}
public static void Deserialize(IMessageReader reader)
{
}
}
}
@@ -0,0 +1,15 @@
namespace Impostor.Api.Net.Messages.Rpcs
{
public static class Rpc64CheckAppear
{
public static void Serialize(IMessageWriter writer, bool shouldAnimate)
{
writer.Write(shouldAnimate);
}
public static void Deserialize(IMessageReader reader, out bool shouldAnimate)
{
shouldAnimate = reader.ReadBoolean();
}
}
}
@@ -0,0 +1,15 @@
namespace Impostor.Api.Net.Messages.Rpcs
{
public static class Rpc65StartAppear
{
public static void Serialize(IMessageWriter writer, bool shouldAnimate)
{
writer.Write(shouldAnimate);
}
public static void Deserialize(IMessageReader reader, out bool shouldAnimate)
{
shouldAnimate = reader.ReadBoolean();
}
}
}
@@ -0,0 +1,21 @@
using Impostor.Api.Games;
namespace Impostor.Api.Net.Messages.S2C
{
public class Message24EndGameHostMigrationS2C
{
public static void Serialize(IMessageWriter writer, GameCode gameCode, int hostClientId)
{
writer.StartMessage(MessageFlags.EndGameHostMigration);
gameCode.Serialize(writer);
writer.Write(hostClientId);
writer.EndMessage();
}
public static void Deserialize(IMessageReader reader, out GameCode gameCode, out int hostClientId)
{
gameCode = reader.ReadInt32();
hostClientId = reader.ReadInt32();
}
}
}
@@ -21,6 +21,7 @@ namespace Impostor.Server.Net.Inner.Objects.Components
{
_logger = logger;
_votes = new Dictionary<int, int[]>();
Components.Add(this);
}
public override ValueTask<bool> SerializeAsync(IMessageWriter writer, bool initialState)
@@ -1,39 +0,0 @@
using System.Threading.Tasks;
using Impostor.Api;
using Impostor.Api.Net.Inner;
using Impostor.Api.Net.Messages.Rpcs;
using Impostor.Server.Events.Player;
namespace Impostor.Server.Net.Inner.Objects
{
internal partial class InnerGameData
{
public partial class TaskInfo
{
public async ValueTask CompleteAsync()
{
if (_playerInfo.Controller == null)
{
throw new ImpostorException("Can't complete a task that doesn't have a player assigned");
}
var player = _playerInfo.Controller;
if (Complete)
{
throw new ImpostorException("Can't complete a task that is already completed");
}
Complete = true;
// Send RPC.
using var writer = player.Game.StartRpc(player.NetId, RpcCalls.CompleteTask);
Rpc01CompleteTask.Serialize(writer, Id);
await player.Game.FinishRpcAsync(writer);
// Notify plugins.
await _eventManager.CallAsync(new PlayerCompletedTaskEvent(player.Game, player.Game.GetClientPlayer(player.OwnerId)!, player, this));
}
}
}
}
@@ -1,41 +0,0 @@
using Impostor.Api.Events.Managers;
using Impostor.Api.Innersloth.Maps;
using Impostor.Api.Net.Inner.Objects;
namespace Impostor.Server.Net.Inner.Objects
{
internal partial class InnerGameData
{
public partial class TaskInfo : ITaskInfo
{
private readonly InnerPlayerInfo _playerInfo;
private readonly IEventManager _eventManager;
public TaskInfo(InnerPlayerInfo playerInfo, IEventManager eventManager, uint id, TaskData? task)
{
_playerInfo = playerInfo;
_eventManager = eventManager;
Id = id;
Task = task;
}
public uint Id { get; internal set; }
public TaskData? Task { get; internal set; }
public bool Complete { get; internal set; }
public void Serialize(IMessageWriter writer)
{
writer.WritePacked(Id);
writer.Write(Complete);
}
public void Deserialize(IMessageReader reader)
{
Id = reader.ReadPackedUInt32();
Complete = reader.ReadBoolean();
}
}
}
}
@@ -1,41 +1,20 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading.Tasks;
using Impostor.Api;
using Impostor.Api.Events.Managers;
using Impostor.Api.Net;
using Impostor.Api.Net.Custom;
using Impostor.Api.Net.Inner;
using Impostor.Api.Net.Inner.Objects;
using Impostor.Api.Net.Messages.Rpcs;
using Impostor.Server.Net.Inner.Objects.Components;
using Impostor.Server.Net.State;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Impostor.Server.Net.Inner.Objects
{
internal partial class InnerGameData : InnerNetObject, IInnerGameData
internal partial class InnerGameData : IInnerGameData
{
private readonly ILogger<InnerGameData> _logger;
private readonly IEventManager _eventManager;
private readonly ConcurrentDictionary<byte, InnerPlayerInfo> _allPlayers;
public InnerGameData(ICustomMessageManager<ICustomRpc> customMessageManager, Game game, ILogger<InnerGameData> logger, IEventManager eventManager, IServiceProvider serviceProvider) : base(customMessageManager, game)
{
_logger = logger;
_eventManager = eventManager;
_allPlayers = new ConcurrentDictionary<byte, InnerPlayerInfo>();
Components.Add(this);
Components.Add(ActivatorUtilities.CreateInstance<InnerVoteBanSystem>(serviceProvider, game));
}
private readonly ConcurrentDictionary<byte, InnerPlayerInfo> _allPlayers = new();
private readonly ConcurrentDictionary<int, InnerPlayerInfo> _allPlayersByClientId = new();
public int PlayerCount => _allPlayers.Count;
public IReadOnlyDictionary<byte, InnerPlayerInfo> Players => _allPlayers;
internal IReadOnlyDictionary<int, InnerPlayerInfo> PlayersByClientId => _allPlayersByClientId;
public InnerPlayerInfo? GetPlayerById(byte id)
{
if (id == byte.MaxValue)
@@ -46,103 +25,33 @@ namespace Impostor.Server.Net.Inner.Objects
return _allPlayers.TryGetValue(id, out var player) ? player : null;
}
public override ValueTask<bool> SerializeAsync(IMessageWriter writer, bool initialState)
internal bool AddPlayer(InnerPlayerInfo playerInfo)
{
throw new NotImplementedException();
return _allPlayers.TryAdd(playerInfo.PlayerId, playerInfo) &&
_allPlayersByClientId.TryAdd(playerInfo.ClientId, playerInfo);
}
public override async ValueTask DeserializeAsync(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState)
internal void RemovePlayer(byte playerId)
{
if (!await ValidateHost(CheatContext.Deserialize, sender))
_allPlayers.TryRemove(playerId, out var player);
if (player != null)
{
return;
_allPlayersByClientId.TryRemove(player.ClientId, out _);
}
}
while (reader.Position < reader.Length)
internal byte GetNextAvailablePlayerId()
{
for (byte i = 0; i < byte.MaxValue; i++)
{
var inner = reader.ReadMessage();
var playerInfo = this.GetPlayerById(inner.Tag);
if (playerInfo != null)
if (!Players.ContainsKey(i))
{
playerInfo.Deserialize(inner);
}
else
{
playerInfo = new InnerPlayerInfo(inner.Tag);
playerInfo.Deserialize(inner);
if (!_allPlayers.TryAdd(playerInfo.PlayerId, playerInfo))
{
throw new ImpostorException("Failed to add player to InnerGameData.");
}
return i;
}
}
}
public override async ValueTask<bool> HandleRpcAsync(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader)
{
if (!await ValidateHost(call, sender))
{
return false;
}
switch (call)
{
case RpcCalls.SetTasks:
{
Rpc29SetTasks.Deserialize(reader, out var playerId, out var taskTypeIds);
SetTasks(playerId, taskTypeIds);
break;
}
default:
return await base.HandleRpcAsync(sender, target, call, reader);
}
return true;
}
internal InnerPlayerInfo? AddPlayer(InnerPlayerControl control)
{
var playerId = control.PlayerId;
var playerInfo = new InnerPlayerInfo(control.PlayerId);
if (_allPlayers.TryAdd(playerId, playerInfo))
{
return playerInfo;
}
return null;
}
internal void RemovePlayer(InnerPlayerControl control)
{
_allPlayers.TryRemove(control.PlayerInfo.PlayerId, out _);
}
private void SetTasks(byte playerId, ReadOnlyMemory<byte> taskTypeIds)
{
var player = GetPlayerById(playerId);
if (player == null)
{
_logger.LogTrace("Could not set tasks for playerId {0}.", playerId);
return;
}
if (player.Disconnected)
{
return;
}
player.Tasks = new List<TaskInfo>(taskTypeIds.Length);
var taskId = 0u;
foreach (var taskTypeId in taskTypeIds.Span)
{
var mapTasks = Game.GameNet!.ShipStatus?.Data.Tasks;
var taskType = (mapTasks != null && mapTasks.ContainsKey(taskTypeId)) ? mapTasks[taskTypeId] : null;
player.Tasks.Add(new TaskInfo(player, _eventManager, taskId++, taskType));
}
return byte.MaxValue;
}
}
}
@@ -20,7 +20,7 @@ namespace Impostor.Server.Net.Inner.Objects
public async ValueTask SetNameAsync(string name)
{
PlayerInfo.PlayerName = name;
PlayerInfo.CurrentOutfit.PlayerName = name;
using var writer = Game.StartRpc(NetId, RpcCalls.SetName);
writer.Write(name);
@@ -32,7 +32,7 @@ namespace Impostor.Server.Net.Inner.Objects
PlayerInfo.CurrentOutfit.Color = color;
using var writer = Game.StartRpc(NetId, RpcCalls.SetColor);
Rpc08SetColor.Serialize(writer, color);
Rpc08SetColor.Serialize(writer, PlayerInfo.NetId, color);
await Game.FinishRpcAsync(writer);
}
@@ -148,5 +148,19 @@ namespace Impostor.Server.Net.Inner.Objects
// Notify plugins.
await _eventManager.CallAsync(new PlayerExileEvent(Game, Game.GetClientPlayer(OwnerId)!, this));
}
public async ValueTask StartVanishAsync()
{
using var writer = Game.StartRpc(NetId, RpcCalls.StartVanish);
Rpc63StartVanish.Serialize(writer);
await Game.FinishRpcAsync(writer);
}
public async ValueTask StartAppearAsync(bool shouldAnimate)
{
using var writer = Game.StartRpc(NetId, RpcCalls.StartAppear);
Rpc65StartAppear.Serialize(writer, shouldAnimate);
await Game.FinishRpcAsync(writer);
}
}
}
@@ -167,7 +167,7 @@ namespace Impostor.Server.Net.Inner.Objects
return false;
}
Rpc06SetName.Deserialize(reader, out var name);
Rpc06SetName.Deserialize(reader, out var _, out var name);
return await HandleSetName(sender, name);
}
@@ -191,7 +191,7 @@ namespace Impostor.Server.Net.Inner.Objects
return false;
}
Rpc08SetColor.Deserialize(reader, out var color);
Rpc08SetColor.Deserialize(reader, out var _, out var color);
return await HandleSetColor(sender, color);
}
@@ -251,7 +251,7 @@ namespace Impostor.Server.Net.Inner.Objects
}
Rpc38SetLevel.Deserialize(reader, out var level);
return true;
return await HandleSetLevel(sender, level);
}
case RpcCalls.ReportDeadBody:
@@ -383,7 +383,14 @@ namespace Impostor.Server.Net.Inner.Objects
return false;
}
Rpc44SetRole.Deserialize(reader, out var role);
Rpc44SetRole.Deserialize(reader, out var role, out var _);
if (role is RoleTypes.ImpostorGhost or RoleTypes.CrewmateGhost or RoleTypes.GuardianAngel)
{
PlayerInfo.RoleWhenAlive = PlayerInfo.RoleType;
PlayerInfo.IsDead = true;
}
PlayerInfo.RoleType = role;
if (Game.GameState == GameStates.Starting && Game.Players.All(clientPlayer => clientPlayer.Character?.PlayerInfo.RoleType != null))
@@ -504,7 +511,7 @@ namespace Impostor.Server.Net.Inner.Objects
return false;
}
Rpc55CheckShapeshift.Deserialize(reader, Game, out var playerControl, out var shouldAnimate);
Rpc55CheckShapeshift.Deserialize(reader, Game, out var playerControl, out _);
break;
}
@@ -519,6 +526,56 @@ namespace Impostor.Server.Net.Inner.Objects
break;
}
case RpcCalls.CheckVanish:
{
if (!await ValidateOwnership(call, sender) ||
!await ValidateRole(call, sender, PlayerInfo, RoleTypes.Phantom) ||
!await ValidateCmd(call, sender, target))
{
return false;
}
Rpc62CheckVanish.Deserialize(reader, out var maxDuration);
return await HandleCheckVanish(sender, maxDuration);
}
case RpcCalls.StartVanish:
{
if (!await ValidateHost(call, sender) ||
!await ValidateBroadcast(call, sender, target))
{
return false;
}
Rpc63StartVanish.Deserialize(reader);
return await HandleStartVanish(sender);
}
case RpcCalls.CheckAppear:
{
if (!await ValidateOwnership(call, sender) ||
!await ValidateRole(call, sender, PlayerInfo, RoleTypes.Phantom) ||
!await ValidateCmd(call, sender, target))
{
return false;
}
Rpc64CheckAppear.Deserialize(reader, out var shouldAnimate);
return await HandleCheckAppear(sender, shouldAnimate);
}
case RpcCalls.StartAppear:
{
if (!await ValidateHost(call, sender) ||
!await ValidateBroadcast(call, sender, target))
{
return false;
}
Rpc65StartAppear.Deserialize(reader, out var shouldAnimate);
return await HandleStartAppear(sender, shouldAnimate);
}
default:
return await base.HandleRpcAsync(sender, target, call, reader);
}
@@ -679,7 +736,7 @@ namespace Impostor.Server.Net.Inner.Objects
}
}
PlayerInfo.PlayerName = name;
PlayerInfo.CurrentOutfit.PlayerName = name;
return true;
}
@@ -827,6 +884,19 @@ namespace Impostor.Server.Net.Inner.Objects
return true;
}
private async ValueTask<bool> HandleSetLevel(ClientPlayer sender, uint level)
{
if (Game.GameState == GameStates.Started &&
await sender.Client.ReportCheatAsync(RpcCalls.SetLevel, CheatCategory.GameFlow, "Client tried to set level while not in game"))
{
return false;
}
PlayerInfo.PlayerLevel = level;
return true;
}
private async ValueTask<bool> HandleCheckMurder(ClientPlayer sender, InnerPlayerControl? target)
{
if (!PlayerInfo.CanMurder(Game, _dateTimeProvider))
@@ -1001,5 +1071,61 @@ namespace Impostor.Server.Net.Inner.Objects
return true;
}
private async ValueTask<bool> HandleCheckVanish(ClientPlayer sender, float maxDuration)
{
// TODO: Check if operation is taking place during lobby/meetings
// TODO: Check that the player unvanished before the maximum duration is reached
// If the game is host authoritive, the RPC is handled by the host, otherwise by the server
if (_game.IsHostAuthoritive)
{
return true;
}
else
{
await StartVanishAsync();
return false;
}
}
private async ValueTask<bool> HandleStartVanish(ClientPlayer sender)
{
if (!_game.IsHostAuthoritive)
{
if (await sender.Client.ReportCheatAsync(RpcCalls.StartVanish, CheatCategory.GameFlow, "Client tried to send StartVanish directly"))
{
return false;
}
}
return true;
}
private async ValueTask<bool> HandleCheckAppear(ClientPlayer sender, bool shouldAnimate)
{
// If the game is host authoritive, the RPC is handled by the host, otherwise by the server
if (_game.IsHostAuthoritive)
{
return true;
}
else
{
await StartAppearAsync(shouldAnimate);
return false;
}
}
private async ValueTask<bool> HandleStartAppear(ClientPlayer sender, bool shouldAnimate)
{
if (!_game.IsHostAuthoritive)
{
if (await sender.Client.ReportCheatAsync(RpcCalls.StartAppear, CheatCategory.GameFlow, "Client tried to send StartAppear directly"))
{
return false;
}
}
return true;
}
}
}
@@ -3,7 +3,7 @@ using Impostor.Api.Net.Inner.Objects;
namespace Impostor.Server.Net.Inner.Objects
{
internal partial class InnerPlayerInfo : IInnerPlayerInfo
internal partial class InnerPlayerInfo : InnerNetObject, IInnerPlayerInfo
{
IEnumerable<ITaskInfo> IInnerPlayerInfo.Tasks => Tasks;
}
@@ -1,25 +1,40 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Impostor.Api.Events.Managers;
using Impostor.Api.Games;
using Impostor.Api.Innersloth;
using Impostor.Api.Innersloth.Customization;
using Impostor.Api.Innersloth.GameOptions;
using Impostor.Api.Net;
using Impostor.Api.Net.Custom;
using Impostor.Api.Net.Inner;
using Impostor.Api.Net.Messages.Rpcs;
using Impostor.Api.Utils;
using Impostor.Server.Net.State;
using Microsoft.Extensions.Logging;
namespace Impostor.Server.Net.Inner.Objects
{
internal partial class InnerPlayerInfo
{
public InnerPlayerInfo(byte playerId)
private readonly IEventManager _eventManager;
private readonly ILogger<InnerPlayerInfo> _logger;
public InnerPlayerInfo(ICustomMessageManager<ICustomRpc> customMessageManager, IEventManager eventManager, Game game, ILogger<InnerPlayerInfo> logger) : base(customMessageManager, game)
{
PlayerId = playerId;
Components.Add(this);
_eventManager = eventManager;
_logger = logger;
}
public InnerPlayerControl? Controller { get; internal set; }
public byte PlayerId { get; }
public byte PlayerId { get; internal set; }
public string PlayerName { get; internal set; } = string.Empty;
public int ClientId { get; internal set; }
public string PlayerName => CurrentOutfit.PlayerName;
public Dictionary<PlayerOutfitType, PlayerOutfit> Outfits { get; } = new()
{
@@ -32,9 +47,11 @@ namespace Impostor.Server.Net.Inner.Objects
public RoleTypes? RoleType { get; internal set; }
public RoleTypes? RoleWhenAlive { get; internal set; }
public bool Disconnected { get; internal set; }
public bool IsImpostor => RoleType is RoleTypes.Impostor or RoleTypes.Shapeshifter or RoleTypes.ImpostorGhost;
public bool IsImpostor => RoleType is RoleTypes.Impostor or RoleTypes.Shapeshifter or RoleTypes.ImpostorGhost or RoleTypes.Phantom;
public bool CanVent => RoleType is RoleTypes.Impostor or RoleTypes.Shapeshifter or RoleTypes.Engineer;
@@ -42,7 +59,7 @@ namespace Impostor.Server.Net.Inner.Objects
public DeathReason LastDeathReason { get; internal set; }
public List<InnerGameData.TaskInfo> Tasks { get; internal set; } = new List<InnerGameData.TaskInfo>(0);
public List<TaskInfo> Tasks { get; internal set; } = new List<TaskInfo>(0);
public DateTimeOffset LastMurder { get; set; }
@@ -71,13 +88,56 @@ namespace Impostor.Server.Net.Inner.Objects
}
}
public void Serialize(IMessageWriter writer)
public override ValueTask<bool> SerializeAsync(IMessageWriter writer, bool initialState)
{
throw new NotImplementedException();
writer.Write(PlayerId);
writer.WritePacked(ClientId);
writer.Write((byte)Outfits.Count);
foreach (var outfit in Outfits)
{
writer.Write((byte)outfit.Key);
outfit.Value.Serialize(writer);
}
writer.WritePacked(PlayerLevel);
var flag = 0;
if (Disconnected)
{
flag = (byte)(flag | 1u);
}
if (IsDead)
{
flag = (byte)(flag | 4u);
}
writer.Write((byte)flag);
writer.Write((ushort)(RoleType ?? 0));
writer.Write(RoleWhenAlive.HasValue);
if (RoleWhenAlive.HasValue)
{
writer.Write((ushort)RoleWhenAlive.Value);
}
writer.Write((byte)Tasks.Count);
for (var i = 0; i < Tasks.Count; i++)
{
Tasks[i].Serialize(writer);
}
writer.Write(string.Empty); // FriendCode
writer.Write(string.Empty); // PUID
return new ValueTask<bool>(true);
}
public void Deserialize(IMessageReader reader)
public override ValueTask DeserializeAsync(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState)
{
PlayerId = reader.ReadByte();
ClientId = reader.ReadPackedInt32();
Outfits.Clear();
var b = reader.ReadByte();
for (var i = 0; i < b; i++)
@@ -93,7 +153,15 @@ namespace Impostor.Server.Net.Inner.Objects
Disconnected = (flag & 1) != 0;
IsDead = (flag & 4) != 0;
_ = (RoleTypes)reader.ReadUInt16(); // ignore the RoleType here and only trust the SetRole rpc
// Ignore the RoleType here and only trust the SetRole RPC, as
// RoleType is not nullable in vanilla, while Impostor checks game
// starts based on assigned roles.
_ = (RoleTypes)reader.ReadUInt16();
if (reader.ReadBoolean())
{
RoleWhenAlive = (RoleTypes)reader.ReadUInt16();
}
var taskCount = reader.ReadByte();
@@ -106,6 +174,46 @@ namespace Impostor.Server.Net.Inner.Objects
{
Tasks[i].Deserialize(reader);
}
// Impostor doesn't expose fields that aren't properly validated
reader.ReadString(); // FriendCode
reader.ReadString(); // PUID
return ValueTask.CompletedTask;
}
public override async ValueTask<bool> HandleRpcAsync(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader)
{
switch (call)
{
case RpcCalls.SetTasks:
Rpc29SetTasks.Deserialize(reader, out var taskTypeIds);
SetTasks(taskTypeIds);
break;
default:
return await base.HandleRpcAsync(sender, target, call, reader);
}
return true;
}
private void SetTasks(ReadOnlyMemory<byte> taskTypeIds)
{
if (Disconnected)
{
return;
}
Tasks = new List<TaskInfo>(taskTypeIds.Length);
var taskId = 0u;
foreach (var taskTypeId in taskTypeIds.Span)
{
var mapTasks = Game.GameNet!.ShipStatus?.Data.Tasks;
var taskType = (mapTasks != null && mapTasks.ContainsKey(taskTypeId)) ? mapTasks[taskTypeId] : null;
Tasks.Add(new TaskInfo(this, _eventManager, taskId++, taskType));
}
}
}
}
@@ -0,0 +1,68 @@
using System.Threading.Tasks;
using Impostor.Api;
using Impostor.Api.Events.Managers;
using Impostor.Api.Innersloth.Maps;
using Impostor.Api.Net.Inner;
using Impostor.Api.Net.Inner.Objects;
using Impostor.Api.Net.Messages.Rpcs;
using Impostor.Server.Events.Player;
namespace Impostor.Server.Net.Inner.Objects
{
internal class TaskInfo : ITaskInfo
{
private readonly InnerPlayerInfo _playerInfo;
private readonly IEventManager _eventManager;
public TaskInfo(InnerPlayerInfo playerInfo, IEventManager eventManager, uint id, TaskData? task)
{
_playerInfo = playerInfo;
_eventManager = eventManager;
Id = id;
Task = task;
}
public uint Id { get; internal set; }
public TaskData? Task { get; internal set; }
public bool Complete { get; internal set; }
public void Serialize(IMessageWriter writer)
{
writer.WritePacked(Id);
writer.Write(Complete);
}
public void Deserialize(IMessageReader reader)
{
Id = reader.ReadPackedUInt32();
Complete = reader.ReadBoolean();
}
public async ValueTask CompleteAsync()
{
if (_playerInfo.Controller == null)
{
throw new ImpostorException("Can't complete a task that doesn't have a player assigned");
}
var player = _playerInfo.Controller;
if (Complete)
{
throw new ImpostorException("Can't complete a task that is already completed");
}
Complete = true;
// Send RPC.
using var writer = player.Game.StartRpc(player.NetId, RpcCalls.CompleteTask);
Rpc01CompleteTask.Serialize(writer, Id);
await player.Game.FinishRpcAsync(writer);
// Notify plugins.
await _eventManager.CallAsync(new PlayerCompletedTaskEvent(player.Game, player.Game.GetClientPlayer(player.OwnerId)!, player, this));
}
}
}
@@ -17,13 +17,8 @@ internal class CompatibilityManager : ICompatibilityManager
{
new[]
{
new GameVersion(2023, 10, 1), // 2023.10.24
new GameVersion(2023, 11, 15), // 2023.11.28
},
new[]
{
new GameVersion(2024, 2, 1), // 2024.3.5
new GameVersion(2024, 2, 3), // 2024.6.4
new GameVersion(2024, 3, 1), // 2024.6.18
new GameVersion(2024, 4, 1), // 2024.8.13
},
};
+96 -12
View File
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Impostor.Api;
using Impostor.Api.Innersloth;
@@ -30,12 +31,21 @@ namespace Impostor.Server.Net.State
/// </summary>
private const int CurrentClient = -3;
/// <summary>
/// Used to list objects that are managed by the game server.
/// </summary>
private const int ServerOwned = -4;
/// <summary>
/// The first NetId that is considered as a server owned Network ID that the client will not allocate by default.
/// </summary>
private const int MinServerNetId = 100000;
private static readonly Dictionary<uint, Type> SpawnableObjects = new()
{
[0] = typeof(InnerSkeldShipStatus),
[1] = typeof(InnerMeetingHud),
[2] = typeof(InnerLobbyBehaviour),
[3] = typeof(InnerGameData),
[4] = typeof(InnerPlayerControl),
[5] = typeof(InnerMiraShipStatus),
[6] = typeof(InnerPolusShipStatus),
@@ -43,13 +53,19 @@ namespace Impostor.Server.Net.State
[8] = typeof(InnerAirshipStatus),
[9] = typeof(InnerHideAndSeekManager),
[10] = typeof(InnerNormalGameManager),
[11] = typeof(InnerPlayerInfo),
[12] = typeof(InnerVoteBanSystem),
[13] = typeof(InnerFungleShipStatus),
};
private static readonly Dictionary<Type, uint> SpawnableObjectIds = SpawnableObjects.ToDictionary((i) => i.Value, (i) => i.Key);
private readonly List<InnerNetObject> _allObjects = new List<InnerNetObject>();
private readonly Dictionary<uint, InnerNetObject> _allObjectsFast = new Dictionary<uint, InnerNetObject>();
private uint _nextNetId = MinServerNetId;
public T? FindObjectByNetId<T>(uint netId)
where T : IInnerNetObject
{
@@ -255,6 +271,10 @@ namespace Impostor.Server.Net.State
sender.Scene = scene;
_logger.LogTrace("> Scene {0} to {1}", clientId, sender.Scene);
await SyncServerObjectsAsync(sender);
await SpawnPlayerInfoAsync(sender);
break;
}
@@ -326,9 +346,17 @@ namespace Impostor.Server.Net.State
break;
}
case InnerGameData data:
case InnerPlayerInfo playerInfo:
{
GameNet.GameData = data;
if (!GameNet.GameData.AddPlayer(playerInfo))
{
_logger.LogWarning(
"Could not add PlayerInfo for playerId {PlayerId} with NetId {newId}, already have NetId {oldNetId}",
playerInfo.PlayerId,
playerInfo.NetId,
GameNet.GameData.GetPlayerById(playerInfo.PlayerId)?.NetId);
}
break;
}
@@ -358,7 +386,7 @@ namespace Impostor.Server.Net.State
}
// Hook up InnerPlayerControl <-> InnerPlayerControl.PlayerInfo.
var playerInfo = GameNet.GameData!.GetPlayerById(control.PlayerId) ?? GameNet.GameData.AddPlayer(control);
var playerInfo = GameNet.GameData.GetPlayerById(control.PlayerId);
if (playerInfo != null)
{
@@ -402,12 +430,6 @@ namespace Impostor.Server.Net.State
break;
}
case InnerGameData:
{
GameNet.GameData = null;
break;
}
case InnerVoteBanSystem:
{
GameNet.VoteBan = null;
@@ -420,13 +442,18 @@ namespace Impostor.Server.Net.State
break;
}
case InnerPlayerControl control:
case InnerPlayerInfo playerInfo:
{
if (GameState != GameStates.Started && GameState != GameStates.Starting)
{
GameNet.GameData?.RemovePlayer(control);
GameNet.GameData.RemovePlayer(playerInfo.PlayerId);
}
break;
}
case InnerPlayerControl control:
{
// Remove InnerPlayerControl <-> IClientPlayer.
if (TryGetPlayer(control.OwnerId, out var player))
{
@@ -439,6 +466,63 @@ namespace Impostor.Server.Net.State
}
}
private async ValueTask SyncServerObjectsAsync(ClientPlayer sender)
{
foreach (var obj in _allObjectsFast.Values)
{
if (obj.OwnerId == ServerOwned)
{
_logger.LogTrace("Syncing {Type} {NetId}", obj.GetType(), obj.NetId);
await SendObjectSpawnAsync(obj, sender.Client.Id);
}
}
}
private async ValueTask SpawnPlayerInfoAsync(ClientPlayer sender)
{
// Hosts spawn PlayerInfo objects if they requested authority
if (IsHostAuthoritive)
{
return;
}
// Only spawn a new PlayerInfo if one has not yet been spawned
if (GameNet.GameData.PlayersByClientId.ContainsKey(sender.Client.Id))
{
return;
}
var playerInfo = (InnerPlayerInfo)ActivatorUtilities.CreateInstance(_serviceProvider, typeof(InnerPlayerInfo), this);
playerInfo.SpawnFlags = SpawnFlags.None;
playerInfo.NetId = _nextNetId++;
playerInfo.OwnerId = ServerOwned;
playerInfo.ClientId = sender.Client.Id;
playerInfo.PlayerId = GameNet.GameData.GetNextAvailablePlayerId();
if (!AddNetObject(playerInfo))
{
_logger.LogError("Couldn't spawn PlayerInfo for {Name} ({ClientId})", sender.Client.Name, sender.Client.Id);
playerInfo.NetId = uint.MaxValue;
return;
}
_logger.LogTrace("Spawning PlayerInfo (netId {Netid})", playerInfo.NetId);
await OnSpawnAsync(sender, playerInfo);
await SendObjectSpawnAsync(playerInfo);
}
private async ValueTask DespawnPlayerInfoAsync(InnerPlayerInfo playerInfo)
{
if (playerInfo.OwnerId == ServerOwned)
{
_logger.LogDebug("Despawning PlayerInfo {nid}", playerInfo.NetId);
GameNet.GameData.RemovePlayer(playerInfo.PlayerId);
RemoveNetObject(playerInfo);
await SendObjectDespawnAsync(playerInfo);
}
}
private bool AddNetObject(InnerNetObject obj)
{
if (_allObjectsFast.ContainsKey(obj.NetId))
@@ -1,4 +1,5 @@
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Impostor.Api.Games;
@@ -43,6 +44,12 @@ namespace Impostor.Server.Net.State
player.Value.Limbo = LimboStates.PreSpawn;
}
// Delete all PlayerInfo objects
foreach (var playerInfo in GameNet.GameData.Players.Values.ToArray())
{
await DespawnPlayerInfoAsync(playerInfo);
}
await _eventManager.CallAsync(new GameEndedEvent(this, gameOverReason));
}
@@ -112,5 +112,36 @@ namespace Impostor.Server.Net.State
{
Message12WaitForHostS2C.Serialize(message, clear, Code, player.Client.Id);
}
private async ValueTask SendObjectSpawnAsync(InnerNetObject obj, int? targetClientId = null)
{
using var writer = StartGameData(targetClientId);
writer.StartMessage(GameDataTag.SpawnFlag);
writer.WritePacked(SpawnableObjectIds[obj.GetType()]);
writer.WritePacked(obj.OwnerId);
writer.Write((byte)obj.SpawnFlags);
var components = obj.GetComponentsInChildren<InnerNetObject>();
writer.WritePacked(components.Count);
foreach (var component in components)
{
writer.WritePacked(obj.NetId);
writer.StartMessage(1);
await component.SerializeAsync(writer, true);
writer.EndMessage();
}
writer.EndMessage();
await FinishGameDataAsync(writer, targetClientId);
}
private async ValueTask SendObjectDespawnAsync(InnerNetObject obj, int? targetClientId = null)
{
using var writer = StartGameData(targetClientId);
writer.StartMessage(GameDataTag.DespawnFlag);
writer.WritePacked(obj.NetId);
writer.EndMessage();
await FinishGameDataAsync(writer, targetClientId);
}
}
}
@@ -85,6 +85,13 @@ namespace Impostor.Server.Net.State
}
});
// Clean up the PlayerInfo if we own it and we're still in the lobby
if (GameState == GameStates.NotStarted)
{
var playerInfo = GameNet.GameData.PlayersByClientId[playerId];
await DespawnPlayerInfoAsync(playerInfo);
}
return true;
}
+1 -1
View File
@@ -12,7 +12,7 @@ namespace Impostor.Server.Net.State
IInnerLobbyBehaviour? IGameNet.LobbyBehaviour => LobbyBehaviour;
IInnerGameData? IGameNet.GameData => GameData;
IInnerGameData IGameNet.GameData => GameData;
IInnerVoteBanSystem? IGameNet.VoteBan => VoteBan;
+1 -1
View File
@@ -11,7 +11,7 @@ namespace Impostor.Server.Net.State
public InnerLobbyBehaviour? LobbyBehaviour { get; internal set; }
public InnerGameData? GameData { get; internal set; }
public InnerGameData GameData { get; internal set; } = new();
public InnerVoteBanSystem? VoteBan { get; internal set; }