mirror of
https://github.com/Impostor/Impostor.git
synced 2026-10-20 11:39:41 +00:00
Compare commits
41
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ae24d549c | ||
|
|
99cf2243bd | ||
|
|
80c1d53337 | ||
|
|
276f634074 | ||
|
|
609c30ffb4 | ||
|
|
5afb2ca810 | ||
|
|
79a9f7d5dc | ||
|
|
97ed6cb83c | ||
|
|
b3865a27e5 | ||
|
|
d7744a2ec6 | ||
|
|
6d1d1f4ad6 | ||
|
|
a123690cdd | ||
|
|
8f6da3dc87 | ||
|
|
91bc4d33fa | ||
|
|
7e8fc850ff | ||
|
|
6fa8c965cf | ||
|
|
7d8974b622 | ||
|
|
988e3f71f0 | ||
|
|
76c2e3df1f | ||
|
|
4f5cf7837c | ||
|
|
eeab337c75 | ||
|
|
2860ef6d6f | ||
|
|
fd94b0f732 | ||
|
|
94f30ef87d | ||
|
|
5cac18723d | ||
|
|
19f4821c2a | ||
|
|
c48c6b3bde | ||
|
|
cceecb0c36 | ||
|
|
01eb7bb375 | ||
|
|
913b86df19 | ||
|
|
043549702f | ||
|
|
a072d5234a | ||
|
|
264ed110a5 | ||
|
|
ae50126729 | ||
|
|
19046daf24 | ||
|
|
beaf77fdbb | ||
|
|
0343235f0d | ||
|
|
3856aeed03 | ||
|
|
96dbd88ef7 | ||
|
|
753f17586a | ||
|
|
520e9080f4 |
No files matched your search
@@ -1,6 +1,6 @@
|
||||
# Server configuration
|
||||
|
||||
Some information about all the possible configurations. Click [here](https://github.com/Impostor/Impostor/blob/master/src/Impostor.Server/config-full.json) to see all the possible config options.
|
||||
Some information about all the possible configurations. Click [here](https://github.com/Impostor/Impostor/blob/master/src/Impostor.Server/full-config-example.json) to see all the possible config options.
|
||||
|
||||
## Options
|
||||
|
||||
@@ -44,12 +44,13 @@ Impostor has an Anticheat that makes it possible to kick cheaters from games aut
|
||||
|
||||
### Compatibility
|
||||
|
||||
Impostor has two compatibility options which allow some extra flexibility but may not work properly. Enabling either of these options is not recommended.
|
||||
Impostor has some compatibility options which allow some extra flexibility but may not work properly. Enabling any of these options is not recommended. When contacting support, please mention which of these options are enabled.
|
||||
|
||||
| Key | Default | Value |
|
||||
| ------------------------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| **AllowNewGameVersions** | `false` | Warning: Setting this option to `true` is unsupported and may cause issues when large updates to Among Us are released. Allow future versions of Among Us to join your server. |
|
||||
| **AllowVersionMixing** | `false` | Allow players using different game versions to play in one lobby. |
|
||||
| Key | Default | Value |
|
||||
|-----------------------------|---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **AllowFutureGameVersions** | `false` | Warning: Setting this option to `true` is unsupported and may cause issues when large updates to Among Us are released, but can be useful when a small patch is released. Allows future versions of Among Us to join your server. |
|
||||
| **AllowHostAuthority** | `false` | Certain Among Us mods allow disabling some server-authoritative features, which also changes some code paths in the client. These code paths have not undergone as much testing and contain bugs, which can't be fixed from the Impostor side. Use with caution. |
|
||||
| **AllowVersionMixing** | `false` | Allows players using different game versions to play in one lobby that have not been marked by the Impostor developers as compatible. |
|
||||
|
||||
### Debug
|
||||
|
||||
@@ -99,7 +100,7 @@ Other Serilog sinks are also supported, but are out of scope for this documentat
|
||||
|
||||
### File
|
||||
|
||||
The simplest option to configure is by using the `config.json` file next to the server executable. For all possible options see the [config-full.json](https://github.com/Impostor/Impostor/blob/dev/src/Impostor.Server/config-full.json) file.
|
||||
The simplest option to configure is by using the `config.json` file next to the server executable. For all possible options see the [full-config-example.json](https://github.com/Impostor/Impostor/blob/master/src/Impostor.Server/full-config-example.json) file.
|
||||
|
||||
### Environment variables
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -151,6 +151,7 @@
|
||||
"System.Runtime.CompilerServices.Unsafe": "4.5.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
".NETStandard,Version=v2.0/linux-x64": {}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,8 @@ namespace Impostor.Api.Config
|
||||
|
||||
public bool AllowFutureGameVersions { get; set; } = false;
|
||||
|
||||
public bool AllowHostAuthority { get; set; } = false;
|
||||
|
||||
public bool AllowVersionMixing { get; set; } = false;
|
||||
}
|
||||
}
|
||||
@@ -30,5 +30,7 @@
|
||||
Sorry, UDP Matchmaking is no longer supported.
|
||||
See <link={UpgradingDocsLink}#impostor-190>Impostor documentation</link> on how to migrate to HTTP Matchmaking
|
||||
""";
|
||||
|
||||
public const string HostAuthorityUnsupported = "Your client is requesting host authority, which is not enabled on this Impostor server.";
|
||||
}
|
||||
}
|
||||
@@ -2,19 +2,67 @@ namespace Impostor.Api.Innersloth.Customization
|
||||
{
|
||||
public class PlayerOutfit
|
||||
{
|
||||
public ColorType Color { get; internal set; } = (ColorType)(-1);
|
||||
private string _playerName = string.Empty;
|
||||
private ColorType _color = (ColorType)(-1);
|
||||
private string _hatId = "missing";
|
||||
private string _petId = "missing";
|
||||
private string _skinId = "missing";
|
||||
private string _visorId = "missing";
|
||||
private string _namePlateId = "missing";
|
||||
|
||||
public string HatId { get; internal set; } = "missing";
|
||||
public bool IsDirty { get; internal set; }
|
||||
|
||||
public string PetId { get; internal set; } = "missing";
|
||||
public string PlayerName
|
||||
{
|
||||
get => _playerName;
|
||||
set => SetField(ref _playerName, value);
|
||||
}
|
||||
|
||||
public string SkinId { get; internal set; } = "missing";
|
||||
public ColorType Color
|
||||
{
|
||||
get => _color;
|
||||
set => SetField(ref _color, value);
|
||||
}
|
||||
|
||||
public string VisorId { get; internal set; } = "missing";
|
||||
public string HatId
|
||||
{
|
||||
get => _hatId;
|
||||
set => SetField(ref _hatId, value);
|
||||
}
|
||||
|
||||
public string NamePlateId { get; internal set; } = "missing";
|
||||
public string PetId
|
||||
{
|
||||
get => _petId;
|
||||
set => SetField(ref _petId, value);
|
||||
}
|
||||
|
||||
public string PlayerName { get; internal set; } = "missing";
|
||||
public string SkinId
|
||||
{
|
||||
get => _skinId;
|
||||
set => SetField(ref _skinId, value);
|
||||
}
|
||||
|
||||
public string VisorId
|
||||
{
|
||||
get => _visorId;
|
||||
set => SetField(ref _visorId, value);
|
||||
}
|
||||
|
||||
public string NamePlateId
|
||||
{
|
||||
get => _namePlateId;
|
||||
set => SetField(ref _namePlateId, value);
|
||||
}
|
||||
|
||||
public byte HatSequenceId { get; internal set; } = 0;
|
||||
|
||||
public byte PetSequenceId { get; internal set; } = 0;
|
||||
|
||||
public byte SkinSequenceId { get; internal set; } = 0;
|
||||
|
||||
public byte VisorSequenceId { get; internal set; } = 0;
|
||||
|
||||
public byte NamePlateSequenceId { get; internal set; } = 0;
|
||||
|
||||
public bool IsIncomplete
|
||||
{
|
||||
@@ -38,6 +86,11 @@ namespace Impostor.Api.Innersloth.Customization
|
||||
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 +102,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
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,16 @@ public interface IGameOptions
|
||||
/// </summary>
|
||||
public GameModes GameMode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets if the AprilFools mode is enabled or not.
|
||||
/// </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;
|
||||
|
||||
@@ -41,7 +42,14 @@ namespace Impostor.Api.Net.Messages
|
||||
|
||||
public static string FlagToString(byte flag)
|
||||
{
|
||||
return FlagCache[flag];
|
||||
if (FlagCache.TryGetValue(flag, out var res))
|
||||
{
|
||||
return res;
|
||||
}
|
||||
else
|
||||
{
|
||||
return $"Unknown Flag {flag}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 = 0f)
|
||||
{
|
||||
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,20 @@
|
||||
using System;
|
||||
|
||||
namespace Impostor.Api.Net.Messages.S2C
|
||||
{
|
||||
public class Message24EndGameHostMigrationS2C
|
||||
{
|
||||
public static void Serialize(IMessageWriter writer, int gameCode, int hostClientId)
|
||||
{
|
||||
writer.StartMessage(MessageFlags.EndGameHostMigration);
|
||||
writer.Write(gameCode);
|
||||
writer.Write(hostClientId);
|
||||
writer.EndMessage();
|
||||
}
|
||||
|
||||
public static void Deserialize(IMessageReader reader)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,12 @@
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.NET.ILLink.Tasks": {
|
||||
"type": "Direct",
|
||||
"requested": "[8.0.6, )",
|
||||
"resolved": "8.0.6",
|
||||
"contentHash": "E+lDylsTeP4ZiDmnEkiJ5wobnGaIJzFhOgZppznJCb69UZgbh6G3cfv1pnLDLLBx6JAgl0kAlnINDeT3uCuczQ=="
|
||||
},
|
||||
"StyleCop.Analyzers": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.2.0-beta.556, )",
|
||||
@@ -117,6 +123,7 @@
|
||||
"resolved": "4.5.0",
|
||||
"contentHash": "Xg4G4Indi4dqP1iuAiMSwpiWS54ZghzR644OtsRCm/m/lBMG8dUBhLVN7hLm8NNrNTR+iGbshCPTwrvxZPlm4g=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"net8.0/linux-x64": {}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,16 @@
|
||||
namespace Impostor.Server.Net.Inner
|
||||
{
|
||||
public static class GameDataTag
|
||||
public enum GameDataTag : byte
|
||||
{
|
||||
public const byte DataFlag = 1;
|
||||
public const byte RpcFlag = 2;
|
||||
public const byte SpawnFlag = 4;
|
||||
public const byte DespawnFlag = 5;
|
||||
public const byte SceneChangeFlag = 6;
|
||||
public const byte ReadyFlag = 7;
|
||||
public const byte ChangeSettingsFlag = 8;
|
||||
public const byte ConsoleDeclareClientPlatformFlag = 205;
|
||||
public const byte PS4RoomRequest = 206;
|
||||
DataFlag = 1,
|
||||
RpcFlag = 2,
|
||||
SpawnFlag = 4,
|
||||
DespawnFlag = 5,
|
||||
SceneChangeFlag = 6,
|
||||
ReadyFlag = 7,
|
||||
ChangeSettingsFlag = 8,
|
||||
ConsoleDeclareClientPlatformFlag = 205,
|
||||
PS4RoomRequest = 206,
|
||||
XboxDeclareXuid = 207,
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,8 @@ namespace Impostor.Server.Net.Inner
|
||||
|
||||
public SpawnFlags SpawnFlags { get; internal set; }
|
||||
|
||||
public virtual bool IsDirty { get; internal set; }
|
||||
|
||||
public bool IsOwnedBy(IClientPlayer player)
|
||||
{
|
||||
return OwnerId == player.Client.Id ||
|
||||
|
||||
@@ -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 void AddPlayer(InnerPlayerInfo playerInfo)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
_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 < 255; 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 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -289,7 +289,6 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
if (exiled != null)
|
||||
{
|
||||
exiled.Die(DeathReason.Exile);
|
||||
await _eventManager.CallAsync(new PlayerExileEvent(Game, Game.GetClientPlayer(exiled!.OwnerId)!, exiled));
|
||||
}
|
||||
|
||||
|
||||
@@ -20,8 +20,9 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
public async ValueTask SetNameAsync(string name)
|
||||
{
|
||||
PlayerInfo.PlayerName = name;
|
||||
PlayerInfo.CurrentOutfit.PlayerName = name;
|
||||
|
||||
// TODO check playerinfo ownership
|
||||
using var writer = Game.StartRpc(NetId, RpcCalls.SetName);
|
||||
writer.Write(name);
|
||||
await Game.FinishRpcAsync(writer);
|
||||
@@ -32,7 +33,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 +149,18 @@ 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);
|
||||
await Game.FinishRpcAsync(writer);
|
||||
}
|
||||
|
||||
public async ValueTask StartAppearAsync(bool shouldAnimate)
|
||||
{
|
||||
using var writer = Game.StartRpc(NetId, RpcCalls.StartAppear);
|
||||
writer.Write(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);
|
||||
}
|
||||
|
||||
@@ -383,8 +383,16 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
return false;
|
||||
}
|
||||
|
||||
Rpc44SetRole.Deserialize(reader, out var role);
|
||||
Rpc44SetRole.Deserialize(reader, out var role, out var _);
|
||||
|
||||
if (role == RoleTypes.ImpostorGhost || role == RoleTypes.CrewmateGhost)
|
||||
{
|
||||
PlayerInfo.RoleWhenAlive = PlayerInfo.RoleType;
|
||||
PlayerInfo.IsDead = true;
|
||||
}
|
||||
|
||||
PlayerInfo.RoleType = role;
|
||||
PlayerInfo.IsDirty = true;
|
||||
|
||||
if (Game.GameState == GameStates.Starting && Game.Players.All(clientPlayer => clientPlayer.Character?.PlayerInfo.RoleType != null))
|
||||
{
|
||||
@@ -504,7 +512,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 +527,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);
|
||||
}
|
||||
@@ -530,6 +588,7 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
{
|
||||
PlayerInfo.IsDead = true;
|
||||
PlayerInfo.LastDeathReason = reason;
|
||||
PlayerInfo.IsDirty = true;
|
||||
}
|
||||
|
||||
internal void Protect(InnerPlayerControl guardianAngel)
|
||||
@@ -546,6 +605,7 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
if (task != null)
|
||||
{
|
||||
task.Complete = true;
|
||||
PlayerInfo.IsDirty = true;
|
||||
await _eventManager.CallAsync(new PlayerCompletedTaskEvent(Game, sender, this, task));
|
||||
}
|
||||
else
|
||||
@@ -643,6 +703,7 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
if (RequestedPlayerName.Any())
|
||||
{
|
||||
var expected = RequestedPlayerName.Dequeue();
|
||||
var requested = expected;
|
||||
|
||||
if (Game.Players.Any(x => x.Character != null && x.Character != this && x.Character.PlayerInfo.PlayerName == expected))
|
||||
{
|
||||
@@ -663,7 +724,10 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
if (name != expected)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.SetName, CheatCategory.NameLimits, "Client sent SetName with incorrect name"))
|
||||
if (await sender.Client.ReportCheatAsync(
|
||||
RpcCalls.SetName,
|
||||
CheatCategory.NameLimits,
|
||||
$"Client sent SetName with incorrect name, got '{name}', requested '{requested}', expected '{expected}'"))
|
||||
{
|
||||
await SetNameAsync(expected);
|
||||
return false;
|
||||
@@ -679,7 +743,7 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
}
|
||||
}
|
||||
|
||||
PlayerInfo.PlayerName = name;
|
||||
PlayerInfo.CurrentOutfit.PlayerName = name;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -694,9 +758,9 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
}
|
||||
}
|
||||
|
||||
if ((byte)color > ColorsCount)
|
||||
if ((byte)color >= ColorsCount)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.CheckColor, CheatCategory.ColorLimits, "Client sent invalid color"))
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.CheckColor, CheatCategory.ProtocolExtension, "Client sent unknown color"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -717,9 +781,17 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
}
|
||||
}
|
||||
|
||||
if ((byte)color >= ColorsCount)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.CheckColor, CheatCategory.ProtocolExtension, "Client sent unknown color"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (sender.IsOwner(this))
|
||||
{
|
||||
if (Game.Players.Any(x => x.Character != null && x.Character != this && x.Character.PlayerInfo.CurrentOutfit.Color == color))
|
||||
if (Game.IsColorUsed(color, this))
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.SetColor, CheatCategory.ColorLimits, "Client sent a color that is already used"))
|
||||
{
|
||||
@@ -731,33 +803,54 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
{
|
||||
if (RequestedColorId.Any())
|
||||
{
|
||||
var expected = RequestedColorId.Dequeue();
|
||||
var requested = RequestedColorId.Dequeue();
|
||||
|
||||
for (var colorOffset = 0; colorOffset <= ColorsCount; colorOffset++)
|
||||
if (Game.IsColorUsed(color, this))
|
||||
{
|
||||
var possibleColor = (ColorType)((byte)(expected + colorOffset) % ColorsCount);
|
||||
if (!Game.Players.Any(x => x.Character != null && x.Character != this && x.Character.PlayerInfo.CurrentOutfit.Color == possibleColor))
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.SetColor, CheatCategory.ColorLimits, "Client selected a color that is already used"))
|
||||
{
|
||||
expected = possibleColor;
|
||||
break;
|
||||
}
|
||||
|
||||
if (colorOffset == ColorsCount)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.SetColor, CheatCategory.ColorLimits, "Client sent SetColor but all colors are already in use"))
|
||||
{
|
||||
await SetColorAsync(expected);
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (color != expected)
|
||||
var startFrom = requested;
|
||||
|
||||
// Among Us wraps to the front if all colors between the requested and the final color are in use, check for this
|
||||
if (requested > color)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.SetColor, CheatCategory.ColorLimits, "Client sent SetColor with incorrect color"))
|
||||
// Among Us wrapped, so check all colors between requested and the final color
|
||||
for (var c = (ColorType)requested; c < (ColorType)ColorsCount; c++)
|
||||
{
|
||||
await SetColorAsync(expected);
|
||||
return false;
|
||||
if (!Game.IsColorUsed(c, this))
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(
|
||||
RpcCalls.SetColor,
|
||||
CheatCategory.ColorLimits,
|
||||
$"Client skipped color {c} that could be used, but wrapped instead. Player requested {requested} but was given {color}"))
|
||||
{
|
||||
await SetColorAsync(c);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Start checking from the first color, fallthrough to normal case
|
||||
startFrom = ColorType.Red;
|
||||
}
|
||||
|
||||
// Check all colors between the requested color and the assigned color
|
||||
for (var c = startFrom; c < color; c++)
|
||||
{
|
||||
if (!Game.IsColorUsed(c, this))
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(
|
||||
RpcCalls.SetColor,
|
||||
CheatCategory.ColorLimits,
|
||||
$"Client skipped color {c} that could be used. Player requested {requested} but was given {color}"))
|
||||
{
|
||||
await SetColorAsync(c);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -949,7 +1042,7 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
if (!await ValidateRole(RpcCalls.ProtectPlayer, sender, PlayerInfo, RoleTypes.GuardianAngel))
|
||||
{
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
((InnerPlayerControl)target).Protect(this);
|
||||
@@ -1001,5 +1094,71 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async ValueTask<bool> HandleCheckVanish(ClientPlayer sender, float maxDuration)
|
||||
{
|
||||
if (!await ValidateRole(RpcCalls.CheckVanish, sender, PlayerInfo, RoleTypes.Phantom))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: Check if operation is taking place during lobby/meetings
|
||||
// TODO: Check max duration
|
||||
// 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 (!await ValidateRole(RpcCalls.CheckAppear, sender, PlayerInfo, RoleTypes.Phantom))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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 bool Disconnected { get; internal set; }
|
||||
public RoleTypes? RoleWhenAlive { get; internal set; }
|
||||
|
||||
public bool IsImpostor => RoleType is RoleTypes.Impostor or RoleTypes.Shapeshifter or RoleTypes.ImpostorGhost;
|
||||
public bool Disconnected { get; internal set; } = false;
|
||||
|
||||
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,11 +59,17 @@ 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; }
|
||||
|
||||
public uint PlayerLevel { get; internal set; }
|
||||
public uint PlayerLevel { get; private set; }
|
||||
|
||||
public override bool IsDirty
|
||||
{
|
||||
get => CurrentOutfit.IsDirty;
|
||||
internal set => CurrentOutfit.IsDirty = value;
|
||||
}
|
||||
|
||||
public bool CanMurder(IGame game, IDateTimeProvider dateTimeProvider)
|
||||
{
|
||||
@@ -71,13 +94,57 @@ 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);
|
||||
writer.Write(string.Empty);
|
||||
return new ValueTask<bool>(true);
|
||||
}
|
||||
|
||||
public void Deserialize(IMessageReader reader)
|
||||
public override ValueTask DeserializeAsync(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState)
|
||||
{
|
||||
// TODO ac
|
||||
PlayerId = reader.ReadByte();
|
||||
ClientId = reader.ReadPackedInt32();
|
||||
|
||||
Outfits.Clear();
|
||||
var b = reader.ReadByte();
|
||||
for (var i = 0; i < b; i++)
|
||||
@@ -93,7 +160,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 +181,48 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
{
|
||||
Tasks[i].Deserialize(reader);
|
||||
}
|
||||
|
||||
// Impostor doesn't expose fields that aren't properly validated
|
||||
reader.ReadString();
|
||||
reader.ReadString();
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
IsDirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
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);
|
||||
_playerInfo.IsDirty = true;
|
||||
|
||||
// Notify plugins.
|
||||
await _eventManager.CallAsync(new PlayerCompletedTaskEvent(player.Game, player.Game.GetClientPlayer(player.OwnerId)!, player, this));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,28 @@ namespace Impostor.Server.Net.Manager
|
||||
_clients = new ConcurrentDictionary<int, ClientBase>();
|
||||
_compatibilityManager = compatibilityManager;
|
||||
_compatibilityConfig = compatibilityConfig.Value;
|
||||
|
||||
if (_compatibilityConfig.AllowFutureGameVersions
|
||||
|| _compatibilityConfig.AllowHostAuthority
|
||||
|| _compatibilityConfig.AllowVersionMixing)
|
||||
{
|
||||
_logger.LogWarning("One or more compatibility options were enabled, please mention these when seeking support:");
|
||||
|
||||
if (_compatibilityConfig.AllowFutureGameVersions)
|
||||
{
|
||||
_logger.LogWarning("AllowFutureGameVersions, which allows future Among Us versions to connect that were unknown at the time this Impostor was built");
|
||||
}
|
||||
|
||||
if (_compatibilityConfig.AllowHostAuthority)
|
||||
{
|
||||
_logger.LogWarning("AllowHostAuthority, which allows game hosts to control more game features, but it uses less well tested code on the client, which causes some bugs");
|
||||
}
|
||||
|
||||
if (_compatibilityConfig.AllowVersionMixing)
|
||||
{
|
||||
_logger.LogWarning("AllowVersionMixing, which allows players to join games created on different game versions that they may not be 100% compatible with");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<ClientBase> Clients => _clients.Values;
|
||||
@@ -79,6 +101,20 @@ namespace Impostor.Server.Net.Manager
|
||||
return;
|
||||
}
|
||||
|
||||
// Warn when players connect using the +25 flag that disables server authority. The host authority version
|
||||
// of 2024.6.18 contains various bugs that break the game, so print a message if this mode is in use
|
||||
if (clientVersion.HasDisableServerAuthorityFlag)
|
||||
{
|
||||
if (!_compatibilityConfig.AllowHostAuthority)
|
||||
{
|
||||
_logger.LogInformation("Player {Name} kicked because they requested host authority.", name);
|
||||
await connection.CustomDisconnectAsync(DisconnectReason.Custom, DisconnectMessages.HostAuthorityUnsupported);
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.LogWarning("Player {Name} connected with server authority disabled, this may cause issues as there are known bugs in this mode. Please mention that this mode is in use when asking for support.", name);
|
||||
}
|
||||
|
||||
if (name.Length > 10)
|
||||
{
|
||||
await connection.CustomDisconnectAsync(DisconnectReason.Custom, DisconnectMessages.UsernameLength);
|
||||
|
||||
@@ -17,13 +17,7 @@ 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
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ namespace Impostor.Server.Net.State
|
||||
writer.StartMessage(MessageFlags.GameData);
|
||||
Code.Serialize(writer);
|
||||
|
||||
writer.StartMessage(GameDataTag.DataFlag);
|
||||
writer.StartMessage((byte)GameDataTag.DataFlag);
|
||||
writer.WritePacked(GameNet.GameManager.NetId);
|
||||
|
||||
writer.StartMessage((byte)gameOptionsTag);
|
||||
|
||||
@@ -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,13 @@ namespace Impostor.Server.Net.State
|
||||
/// </summary>
|
||||
private const int CurrentClient = -3;
|
||||
|
||||
private const int ServerOwned = -4;
|
||||
|
||||
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 +45,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 = 100000;
|
||||
|
||||
public T? FindObjectByNetId<T>(uint netId)
|
||||
where T : IInnerNetObject
|
||||
{
|
||||
@@ -83,13 +91,16 @@ namespace Impostor.Server.Net.State
|
||||
{
|
||||
using var reader = parent.ReadMessage();
|
||||
|
||||
switch (reader.Tag)
|
||||
_logger.LogTrace("Client {SenderId} sent GameData {Tag}", sender.Client.Id, (GameDataTag)reader.Tag);
|
||||
|
||||
switch ((GameDataTag)reader.Tag)
|
||||
{
|
||||
case GameDataTag.DataFlag:
|
||||
{
|
||||
var netId = reader.ReadPackedUInt32();
|
||||
if (_allObjectsFast.TryGetValue(netId, out var obj))
|
||||
{
|
||||
_logger.LogTrace("Received Data for {NetId}, which is of type {Type}", netId, obj.GetType().Name);
|
||||
await obj.DeserializeAsync(sender, target, reader, false);
|
||||
}
|
||||
else
|
||||
@@ -105,7 +116,15 @@ namespace Impostor.Server.Net.State
|
||||
var netId = reader.ReadPackedUInt32();
|
||||
if (_allObjectsFast.TryGetValue(netId, out var obj))
|
||||
{
|
||||
if (!await obj.HandleRpcAsync(sender, target, (RpcCalls)reader.ReadByte(), reader))
|
||||
var call = (RpcCalls)reader.ReadByte();
|
||||
_logger.LogTrace(
|
||||
"Client {SenderId} called Rpc {Call} on NetId {CallerId} and sent it to {Target}",
|
||||
sender.Client.Id,
|
||||
call,
|
||||
obj.NetId,
|
||||
target?.Client.Id.ToString() ?? "everyone");
|
||||
|
||||
if (!await obj.HandleRpcAsync(sender, target, call, reader))
|
||||
{
|
||||
parent.RemoveMessage(reader);
|
||||
continue;
|
||||
@@ -254,6 +273,9 @@ namespace Impostor.Server.Net.State
|
||||
sender.Scene = scene;
|
||||
|
||||
_logger.LogTrace("> Scene {0} to {1}", clientId, sender.Scene);
|
||||
|
||||
await CheckPlayerInfos(sender);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -306,6 +328,17 @@ namespace Impostor.Server.Net.State
|
||||
}
|
||||
}
|
||||
|
||||
// Check for dirty netobjects
|
||||
foreach (var netObject in _allObjectsFast.Values)
|
||||
{
|
||||
if (netObject.IsDirty && netObject.OwnerId == ServerOwned)
|
||||
{
|
||||
_logger.LogTrace("Sending over {Type} {NetId}", netObject.GetType().Name, netObject.NetId);
|
||||
await SendObjectData(netObject);
|
||||
netObject.IsDirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -325,9 +358,9 @@ namespace Impostor.Server.Net.State
|
||||
break;
|
||||
}
|
||||
|
||||
case InnerGameData data:
|
||||
case InnerPlayerInfo playerInfo:
|
||||
{
|
||||
GameNet.GameData = data;
|
||||
GameNet.GameData.AddPlayer(playerInfo);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -357,12 +390,19 @@ 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)
|
||||
{
|
||||
playerInfo.Controller = control;
|
||||
control.PlayerInfo = playerInfo;
|
||||
|
||||
// Reset PlayerInfo
|
||||
playerInfo.RoleType = null;
|
||||
playerInfo.RoleWhenAlive = null;
|
||||
playerInfo.Tasks.Clear();
|
||||
playerInfo.IsDead = false;
|
||||
playerInfo.IsDirty = true;
|
||||
}
|
||||
|
||||
if (player != null)
|
||||
@@ -401,12 +441,6 @@ namespace Impostor.Server.Net.State
|
||||
break;
|
||||
}
|
||||
|
||||
case InnerGameData:
|
||||
{
|
||||
GameNet.GameData = null;
|
||||
break;
|
||||
}
|
||||
|
||||
case InnerVoteBanSystem:
|
||||
{
|
||||
GameNet.VoteBan = null;
|
||||
@@ -419,13 +453,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))
|
||||
{
|
||||
@@ -438,6 +477,41 @@ namespace Impostor.Server.Net.State
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask CheckPlayerInfos(ClientPlayer sender)
|
||||
{
|
||||
// Sync all server-owned objects
|
||||
foreach (var obj in _allObjectsFast.Values)
|
||||
{
|
||||
if (obj.OwnerId == -4)
|
||||
{
|
||||
_logger.LogTrace("Sharing {Type} {NetId}", obj.GetType(), obj.NetId);
|
||||
await SendObjectSpawn(obj, sender.Client.Id);
|
||||
}
|
||||
}
|
||||
|
||||
// If the host doesn't disable server authority, spawn a player info for it
|
||||
if (!IsHostAuthoritive && !GameNet.GameData.PlayersByClientId.ContainsKey(sender.Client.Id))
|
||||
{
|
||||
var playerInfo = (InnerPlayerInfo)ActivatorUtilities.CreateInstance(_serviceProvider, typeof(InnerPlayerInfo), this);
|
||||
playerInfo.SpawnFlags = SpawnFlags.None;
|
||||
playerInfo.NetId = _nextNetId++;
|
||||
playerInfo.OwnerId = -4;
|
||||
playerInfo.ClientId = sender.Client.Id;
|
||||
playerInfo.PlayerId = GameNet.GameData.GetNextAvailablePlayerId();
|
||||
|
||||
if (!AddNetObject(playerInfo))
|
||||
{
|
||||
_logger.LogTrace("Couldn't spawn PlayerInfo");
|
||||
playerInfo.NetId = uint.MaxValue;
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.LogTrace("Spawning PlayerInfo (netId {Netid})", playerInfo.NetId);
|
||||
await OnSpawnAsync(sender, playerInfo);
|
||||
await SendObjectSpawn(playerInfo);
|
||||
}
|
||||
}
|
||||
|
||||
private bool AddNetObject(InnerNetObject obj)
|
||||
{
|
||||
if (_allObjectsFast.ContainsKey(obj.NetId))
|
||||
|
||||
@@ -39,6 +39,27 @@ namespace Impostor.Server.Net.State
|
||||
}
|
||||
|
||||
public IMessageWriter StartRpc(uint targetNetId, RpcCalls callId, int? targetClientId = null, MessageType type = MessageType.Reliable)
|
||||
{
|
||||
var writer = StartGameData(targetClientId, type);
|
||||
|
||||
writer.StartMessage((byte)GameDataTag.RpcFlag);
|
||||
writer.WritePacked(targetNetId);
|
||||
writer.Write((byte)callId);
|
||||
|
||||
return writer;
|
||||
}
|
||||
|
||||
public ValueTask FinishRpcAsync(IMessageWriter writer, int? targetClientId = null)
|
||||
{
|
||||
writer.EndMessage();
|
||||
return FinishGameDataAsync(writer, targetClientId);
|
||||
}
|
||||
|
||||
/// <summary>Start a GameData(To) message.</summary>
|
||||
/// <param name="targetClientId">The client to target if needed, `null` otherwise.</param>
|
||||
/// <param name="type">The type of message to send, defaults to reliable.</param>
|
||||
/// <returns>MessageWriter that should be handed back to <see cref="FinishGameDataAsync"/>.</returns>
|
||||
private IMessageWriter StartGameData(int? targetClientId = null, MessageType type = MessageType.Reliable)
|
||||
{
|
||||
var writer = MessageWriter.Get(type);
|
||||
|
||||
@@ -54,17 +75,16 @@ namespace Impostor.Server.Net.State
|
||||
writer.WritePacked(targetClientId.Value);
|
||||
}
|
||||
|
||||
writer.StartMessage(GameDataTag.RpcFlag);
|
||||
writer.WritePacked(targetNetId);
|
||||
writer.Write((byte)callId);
|
||||
|
||||
return writer;
|
||||
}
|
||||
|
||||
public ValueTask FinishRpcAsync(IMessageWriter writer, int? targetClientId = null)
|
||||
/// <summary>Finalize and send a GameData packet.</summary>
|
||||
/// <param name="writer">MessageWriter received from <see cref="StartGameData"/>.</param>
|
||||
/// <param name="targetClientId">Same target ClientId passed to StartGameData.</param>
|
||||
/// <returns>Task that sends the packet.</returns>
|
||||
private ValueTask FinishGameDataAsync(IMessageWriter writer, int? targetClientId = null)
|
||||
{
|
||||
writer.EndMessage();
|
||||
writer.EndMessage();
|
||||
|
||||
return targetClientId.HasValue
|
||||
? SendToAsync(writer, targetClientId.Value)
|
||||
@@ -100,5 +120,46 @@ namespace Impostor.Server.Net.State
|
||||
{
|
||||
Message12WaitForHostS2C.Serialize(message, clear, Code, player.Client.Id);
|
||||
}
|
||||
|
||||
private ValueTask SendObjectSpawn(InnerNetObject obj, int? targetClientId = null)
|
||||
{
|
||||
using var writer = StartGameData(targetClientId);
|
||||
writer.StartMessage((byte)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 comp in components)
|
||||
{
|
||||
writer.WritePacked(obj.NetId);
|
||||
writer.StartMessage(1);
|
||||
comp.SerializeAsync(writer, true);
|
||||
writer.EndMessage();
|
||||
}
|
||||
|
||||
writer.EndMessage();
|
||||
return FinishGameDataAsync(writer, targetClientId);
|
||||
}
|
||||
|
||||
private ValueTask SendObjectDespawn(InnerNetObject obj, int? targetClientId = null)
|
||||
{
|
||||
using var writer = StartGameData(targetClientId);
|
||||
writer.StartMessage((byte)GameDataTag.DespawnFlag);
|
||||
writer.WritePacked(obj.NetId);
|
||||
writer.EndMessage();
|
||||
return FinishGameDataAsync(writer, targetClientId);
|
||||
}
|
||||
|
||||
private async ValueTask SendObjectData(InnerNetObject obj, int? targetClientId = null)
|
||||
{
|
||||
using var writer = StartGameData(targetClientId);
|
||||
writer.StartMessage((byte)GameDataTag.DataFlag);
|
||||
writer.WritePacked(obj.NetId);
|
||||
await obj.SerializeAsync(writer, false);
|
||||
writer.EndMessage();
|
||||
await FinishGameDataAsync(writer, targetClientId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -85,6 +85,24 @@ namespace Impostor.Server.Net.State
|
||||
}
|
||||
});
|
||||
|
||||
// Clean up the PlayerInfo if we own it
|
||||
foreach (var playerInfo in GameNet.GameData.Players.Values)
|
||||
{
|
||||
if (playerInfo.ClientId == playerId)
|
||||
{
|
||||
if (playerInfo.OwnerId == ServerOwned)
|
||||
{
|
||||
_logger.LogInformation("Destroying PlayerInfo {nid}", playerInfo.NetId);
|
||||
GameNet.GameData.RemovePlayer(playerInfo.PlayerId);
|
||||
RemoveNetObject(playerInfo);
|
||||
|
||||
await SendObjectDespawn(playerInfo);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,10 @@ using Impostor.Api.Config;
|
||||
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.Inner.Objects;
|
||||
using Impostor.Api.Net.Manager;
|
||||
using Impostor.Api.Net.Messages.S2C;
|
||||
using Impostor.Server.Events;
|
||||
@@ -131,6 +133,15 @@ namespace Impostor.Server.Net.State
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Check if there are players using a color.</summary>
|
||||
/// <param name="color">The color to check for.</param>
|
||||
/// <param name="exceptBy">Exempt a player from being checked.</param>
|
||||
/// <returns>True if there is player other than exceptBy that uses that color.</returns>
|
||||
internal bool IsColorUsed(ColorType color, IInnerPlayerControl? exceptBy = null)
|
||||
{
|
||||
return Players.Any(p => p.Character != null && p.Character != exceptBy && p.Character.PlayerInfo.CurrentOutfit.Color == color);
|
||||
}
|
||||
|
||||
private ValueTask BroadcastJoinMessage(IMessageWriter message, bool clear, ClientPlayer player)
|
||||
{
|
||||
Message01JoinGameS2C.SerializeJoin(message, clear, Code, player, HostId);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
},
|
||||
"Compatibility": {
|
||||
"AllowFutureGameVersions": false,
|
||||
"AllowHostAuthority": false,
|
||||
"AllowVersionMixing": false
|
||||
},
|
||||
"Debug": {
|
||||
@@ -56,6 +56,12 @@
|
||||
"resolved": "8.0.1",
|
||||
"contentHash": "X21b8D6z58EG34OMLH955750mFaEaGs5xLGe9Vyi3059Cgb3sk+p2k/JlQXaieEBRrk+SVlFzDei3Q6PdVzJOA=="
|
||||
},
|
||||
"Microsoft.NET.ILLink.Tasks": {
|
||||
"type": "Direct",
|
||||
"requested": "[8.0.6, )",
|
||||
"resolved": "8.0.6",
|
||||
"contentHash": "E+lDylsTeP4ZiDmnEkiJ5wobnGaIJzFhOgZppznJCb69UZgbh6G3cfv1pnLDLLBx6JAgl0kAlnINDeT3uCuczQ=="
|
||||
},
|
||||
"Serilog.Extensions.Hosting": {
|
||||
"type": "Direct",
|
||||
"requested": "[8.0.0, )",
|
||||
|
||||
Reference in new issue
Block a user