mirror of
https://github.com/Impostor/Impostor.git
synced 2026-10-20 11:39:41 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb980096ff | ||
|
|
58a8bda8e7 | ||
|
|
a7c37b6bcf | ||
|
|
792c9626e4 | ||
|
|
e4f81b4a30 | ||
|
|
2603f34b89 | ||
|
|
9b27edbb11 | ||
|
|
2f9406cb6a | ||
|
|
d5f48aa798 | ||
|
|
ff5501aab7 | ||
|
|
acc4da1210 | ||
|
|
a0f0a65cbf | ||
|
|
6c91280125 | ||
|
|
2ddbb8891c | ||
|
|
8c52b13af4 | ||
|
|
f6318c7040 | ||
|
|
4d6c060c7c | ||
|
|
83bf1c4368 | ||
|
|
ed9f6e3134 | ||
|
|
219b72a3d1 | ||
|
|
01e9cccdf5 | ||
|
|
fc79bd1536 | ||
|
|
9e1abe07ef | ||
|
|
ccd36b6571 | ||
|
|
44e9dfe28d | ||
|
|
09cadcb63b | ||
|
|
b118af12be | ||
|
|
372ed8511f | ||
|
|
85296d2d46 | ||
|
|
c43fdd2a69 | ||
|
|
615469c6c8 | ||
|
|
482b8c2b72 | ||
|
|
2eca9b4e9f | ||
|
|
079f58a7b7 | ||
|
|
c315ef9c28 | ||
|
|
0e7372d9d9 | ||
|
|
bf5a2c2be5 | ||
|
|
066eca09ee | ||
|
|
35c66cda5f | ||
|
|
5d4b3d34f5 | ||
|
|
d94cf78a98 | ||
|
|
cfa66fa5cb | ||
|
|
2d82e1af19 | ||
|
|
c6bad15346 | ||
|
|
b26f451a07 | ||
|
|
4f7ef58b00 | ||
|
|
fe3ea67510 | ||
|
|
c522dd9f88 | ||
|
|
46caeb76c2 | ||
|
|
b218079f9e | ||
|
|
a61524842f | ||
|
|
34c14f69a4 | ||
|
|
da0ee81b5d | ||
|
|
468a71d01e | ||
|
|
6f05946689 | ||
|
|
b2a9bab125 |
No files matched your search
@@ -1,7 +1,5 @@
|
||||
# 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.
|
||||
|
||||
## Options
|
||||
|
||||
### Required Server Configuration
|
||||
@@ -39,17 +37,17 @@ Impostor has an Anticheat that makes it possible to kick cheaters from games aut
|
||||
| **EnableOwnershipChecks** | `true` | Enables checks that check if players are allowed to perform a certain action on themself or another player. |
|
||||
| **EnableRoleChecks** | `true` | Enables checks that check if players have the correct role when performing certain role abilities like venting or murdering. |
|
||||
| **EnableTargetChecks** | `true` | Enables checks that check if certain packets to everyone that should only have been sent to certain players or vice versa. This includes sending votes or network objects. |
|
||||
|
||||
| **ForbidProtocolExtensions** | `true` | If disabled allows players to send network packets that go beyond the network packets sent by the vanilla game. This is necessary for most mods that need all players to install it. |
|
||||
|
||||
### 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,11 +97,11 @@ 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 `config.json` file contains all the configurable options.
|
||||
|
||||
### Environment variables
|
||||
|
||||
If you're unable to edit the config.json file, you can instead set Environment variables to configure Impostor. The general pattern for each variable is `IMPOSTOR_SectionName__VariableName`. For example, to disable the anticheat, you can set the environment variable `IMPOSTOR_AntiCheat__Enabled=false`. Here are some more examples:
|
||||
If you're unable to edit the `config.json` file, you can instead set Environment variables to configure Impostor. The general pattern for each variable is `IMPOSTOR_SectionName__VariableName`. For example, to disable the anticheat, you can set the environment variable `IMPOSTOR_AntiCheat__Enabled=false`. Here are some more examples:
|
||||
|
||||
```
|
||||
IMPOSTOR_Server__PublicIp=127.0.0.1
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<LangVersion>11</LangVersion>
|
||||
<VersionPrefix>1.9.2</VersionPrefix>
|
||||
<VersionPrefix>1.10.2</VersionPrefix>
|
||||
<VersionSuffix>dev</VersionSuffix>
|
||||
<EnforceCodeStyleInBuild Condition="$(MSBuildProjectName)!='Hazel'">true</EnforceCodeStyleInBuild>
|
||||
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all" />
|
||||
<PackageReference Include="PolySharp" Version="1.14.1" PrivateAssets="all" />
|
||||
|
||||
<PackageReference Include="System.Text.Json" Version="8.0.0" PrivateAssets="all" GeneratePathProperty="true" />
|
||||
<PackageReference Include="System.Text.Json" Version="8.0.4" PrivateAssets="all" GeneratePathProperty="true" />
|
||||
<PackageReference Include="System.Text.Encodings.Web" Version="8.0.0" PrivateAssets="all" GeneratePathProperty="true" />
|
||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0" PrivateAssets="all" GeneratePathProperty="true" />
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -60,9 +60,9 @@
|
||||
},
|
||||
"System.Text.Json": {
|
||||
"type": "Direct",
|
||||
"requested": "[8.0.0, )",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "OdrZO2WjkiEG6ajEFRABTRCi/wuXQPxeV6g8xvUJqdxMvvuCCEk86zPla8UiIQJz3durtUEbNyY/3lIhS0yZvQ==",
|
||||
"requested": "[8.0.4, )",
|
||||
"resolved": "8.0.4",
|
||||
"contentHash": "bAkhgDJ88XTsqczoxEMliSrpijKZHhbJQldhAmObj/RbrN3sU5dcokuXmWJWsdQAhiMJ9bTayWsL1C9fbbCRhw==",
|
||||
"dependencies": {
|
||||
"Microsoft.Bcl.AsyncInterfaces": "8.0.0",
|
||||
"System.Buffers": "4.5.1",
|
||||
|
||||
@@ -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.";
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ namespace Impostor.Api.Config
|
||||
|
||||
public ushort PublicPort { get; set; } = 22023;
|
||||
|
||||
public string ListenIp { get; set; } = "127.0.0.1";
|
||||
public string ListenIp { get; set; } = "0.0.0.0";
|
||||
|
||||
public ushort ListenPort { get; set; } = 22023;
|
||||
|
||||
|
||||
@@ -101,22 +101,41 @@ namespace Impostor.Api.Games
|
||||
/// <returns>A <see cref="ValueTask" /> representing the asynchronous operation.</returns>
|
||||
ValueTask SendToAsync(IMessageWriter writer, int id);
|
||||
|
||||
/// <summary>
|
||||
/// Start a GameData(To) message.
|
||||
/// </summary>
|
||||
/// <remarks>Use with caution, recommended only for advanced developers.</remarks>
|
||||
/// <param name="targetClientId">The client to target if needed, `null` if the message should be sent to all players.</param>
|
||||
/// <param name="type">Message type of the message.</param>
|
||||
/// <returns>A <see cref="IMessageWriter" /> that you can fill and send using <see cref="FinishGameDataAsync"/>.</returns>
|
||||
IMessageWriter StartGameData(int? targetClientId = null, MessageType type = MessageType.Reliable);
|
||||
|
||||
/// <summary>
|
||||
/// Finishes GameData message and sends it to either the target or all players.
|
||||
/// </summary>
|
||||
/// <remarks>Use with caution, recommended only for advanced developers.</remarks>
|
||||
/// <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>
|
||||
ValueTask FinishGameDataAsync(IMessageWriter writer, int? targetClientId = null);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a MessageWriter with GameData header.
|
||||
/// </summary>
|
||||
/// <remarks>Use with caution, recommended only for advanced developers.</remarks>
|
||||
/// <param name="targetNetId">Net id of the InnerNetObject.</param>
|
||||
/// <param name="callId">Rpc id of the message.</param>
|
||||
/// <param name="targetClientId">Client id of GameDataTo's target.</param>
|
||||
/// <param name="targetClientId">The client to target if needed, `null` if the message should be sent to all players.</param>
|
||||
/// <param name="type">Message type of the message.</param>
|
||||
/// <returns>A <see cref="IMessageWriter" /> that you can fill with rpc data and send using <see cref="FinishRpcAsync"/>.</returns>
|
||||
IMessageWriter StartRpc(uint targetNetId, RpcCalls callId, int? targetClientId, MessageType type = MessageType.Reliable);
|
||||
IMessageWriter StartRpc(uint targetNetId, RpcCalls callId, int? targetClientId = null, MessageType type = MessageType.Reliable);
|
||||
|
||||
/// <summary>
|
||||
/// Finishes rpc message and sends it to the target.
|
||||
/// Finishes rpc message and sends it to either the target or all players.
|
||||
/// </summary>
|
||||
/// <remarks>Use with caution, recommended only for advanced developers.</remarks>
|
||||
/// <param name="writer">Message writer of the rpc.</param>
|
||||
/// <param name="targetClientId">Id of the receiving client.</param>
|
||||
/// <param name="targetClientId">Same target client id passed to <see cref="StartRpc"/>.</param>
|
||||
/// <returns>A <see cref="ValueTask" /> representing the asynchronous operation.</returns>
|
||||
ValueTask FinishRpcAsync(IMessageWriter writer, int? targetClientId = null);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
internal byte HatSequenceId { get; set; } = 0;
|
||||
|
||||
internal byte PetSequenceId { get; set; } = 0;
|
||||
|
||||
internal byte SkinSequenceId { get; set; } = 0;
|
||||
|
||||
internal byte VisorSequenceId { get; set; } = 0;
|
||||
|
||||
internal 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,
|
||||
@@ -1365,6 +1439,9 @@
|
||||
"CookFish": 3431,
|
||||
"PrepVegetables": 3432,
|
||||
"MushroomMixupName": 3433,
|
||||
"EosAccountSilentMergeFail": 3434,
|
||||
"AskForEosAccount": 3435,
|
||||
"KwsCreateFail": 3436,
|
||||
"GameType": 3500,
|
||||
"GameTypeError": 3501,
|
||||
"GameTypeClassic": 3502,
|
||||
@@ -1399,5 +1476,21 @@
|
||||
"HttpErrorBadGateway": 3531,
|
||||
"HttpErrorServiceUnavailable": 3532,
|
||||
"HttpErrorGatewayTimeout": 3533,
|
||||
"UserVersionError": 3534
|
||||
"UserVersionError": 3534,
|
||||
"QCLobbyRoleNoisemakers": 3535,
|
||||
"QCLobbyRoleTrackers": 3536,
|
||||
"QCLobbyRolePhantoms": 3537,
|
||||
"QCSelfWasTracking": 3538,
|
||||
"QCSelfGoingToNoisemaker": 3539,
|
||||
"QCSelfSawRoleInLocation": 3540,
|
||||
"QCCoreSettings": 3541,
|
||||
"QCRolesGalore": 3542,
|
||||
"QCPitchDark": 3543,
|
||||
"QCFlashlight": 3544,
|
||||
"QCPleaseWait": 3545,
|
||||
"QCWaitMinute": 3546,
|
||||
"QCLobbyClosing": 3547,
|
||||
"QCChangeCosmetics": 3548,
|
||||
"QCWaitingPlayers": 3549,
|
||||
"QCLetsWaitPlayers": 3550
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"dumpostorVersion": "1.0.0",
|
||||
"gameVersion": "2024.3.5",
|
||||
"dumpostorVersion": "1.0.1",
|
||||
"gameVersion": "2024.10.29",
|
||||
"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);
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ namespace Impostor.Api.Net
|
||||
/// <summary>
|
||||
/// Represents a connected game client.
|
||||
/// </summary>
|
||||
public interface IClient
|
||||
public interface IClient : IEquatable<IClient>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the unique ID of the client.
|
||||
|
||||
@@ -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,7 @@ namespace Impostor.Api.Net.Messages
|
||||
|
||||
public static string FlagToString(byte flag)
|
||||
{
|
||||
return FlagCache[flag];
|
||||
return FlagCache.TryGetValue(flag, out var res) ? res : $"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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,16 @@ namespace Impostor.Api.Net.Messages.Rpcs
|
||||
{
|
||||
public static class Rpc39SetHatStr
|
||||
{
|
||||
public static void Serialize(IMessageWriter writer, string hat)
|
||||
public static void Serialize(IMessageWriter writer, string hat, byte nextRpcSequenceId)
|
||||
{
|
||||
writer.Write(hat);
|
||||
writer.Write(nextRpcSequenceId);
|
||||
}
|
||||
|
||||
public static void Deserialize(IMessageReader reader, out string hat)
|
||||
public static void Deserialize(IMessageReader reader, out string hat, out byte nextRpcSequenceId)
|
||||
{
|
||||
hat = reader.ReadString();
|
||||
nextRpcSequenceId = reader.ReadByte();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,14 +2,16 @@ namespace Impostor.Api.Net.Messages.Rpcs
|
||||
{
|
||||
public static class Rpc40SetSkinStr
|
||||
{
|
||||
public static void Serialize(IMessageWriter writer, string skin)
|
||||
public static void Serialize(IMessageWriter writer, string skin, byte nextRpcSequenceId)
|
||||
{
|
||||
writer.Write(skin);
|
||||
writer.Write(nextRpcSequenceId);
|
||||
}
|
||||
|
||||
public static void Deserialize(IMessageReader reader, out string skin)
|
||||
public static void Deserialize(IMessageReader reader, out string skin, out byte nextRpcSequenceId)
|
||||
{
|
||||
skin = reader.ReadString();
|
||||
nextRpcSequenceId = reader.ReadByte();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,14 +2,16 @@ namespace Impostor.Api.Net.Messages.Rpcs
|
||||
{
|
||||
public static class Rpc41SetPetStr
|
||||
{
|
||||
public static void Serialize(IMessageWriter writer, string pet)
|
||||
public static void Serialize(IMessageWriter writer, string pet, byte nextRpcSequenceId)
|
||||
{
|
||||
writer.Write(pet);
|
||||
writer.Write(nextRpcSequenceId);
|
||||
}
|
||||
|
||||
public static void Deserialize(IMessageReader reader, out string pet)
|
||||
public static void Deserialize(IMessageReader reader, out string pet, out byte nextRpcSequenceId)
|
||||
{
|
||||
pet = reader.ReadString();
|
||||
nextRpcSequenceId = reader.ReadByte();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,14 +2,16 @@ namespace Impostor.Api.Net.Messages.Rpcs
|
||||
{
|
||||
public static class Rpc42SetVisorStr
|
||||
{
|
||||
public static void Serialize(IMessageWriter writer, string visor)
|
||||
public static void Serialize(IMessageWriter writer, string visor, byte nextRpcSequenceId)
|
||||
{
|
||||
writer.Write(visor);
|
||||
writer.Write(nextRpcSequenceId);
|
||||
}
|
||||
|
||||
public static void Deserialize(IMessageReader reader, out string visor)
|
||||
public static void Deserialize(IMessageReader reader, out string visor, out byte nextRpcSequenceId)
|
||||
{
|
||||
visor = reader.ReadString();
|
||||
nextRpcSequenceId = reader.ReadByte();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,14 +2,16 @@ namespace Impostor.Api.Net.Messages.Rpcs
|
||||
{
|
||||
public static class Rpc43SetNamePlateStr
|
||||
{
|
||||
public static void Serialize(IMessageWriter writer, string namePlate)
|
||||
public static void Serialize(IMessageWriter writer, string namePlate, byte nextRpcSequenceId)
|
||||
{
|
||||
writer.Write(namePlate);
|
||||
writer.Write(nextRpcSequenceId);
|
||||
}
|
||||
|
||||
public static void Deserialize(IMessageReader reader, out string namePlate)
|
||||
public static void Deserialize(IMessageReader reader, out string namePlate, out byte nextRpcSequenceId)
|
||||
{
|
||||
namePlate = reader.ReadString();
|
||||
nextRpcSequenceId = reader.ReadByte();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,14 +82,27 @@ namespace Impostor.Server.Net
|
||||
return false;
|
||||
}
|
||||
|
||||
_logger.LogWarning("Client {Name} ({Id}) was caught cheating: [{Context}-{Category}] {Message}", Name, Id, context.Name, category, message);
|
||||
var supportCode = Random.Shared.Next(0, 999_999).ToString("000-000");
|
||||
|
||||
if (_antiCheatConfig.BanIpFromGame)
|
||||
_logger.LogWarning("Client {Name} ({Id}) was caught cheating: [{SupportCode}] [{Context}-{Category}] {Message}", Name, Id, supportCode, context.Name, category, message);
|
||||
|
||||
if (Player is { } player)
|
||||
{
|
||||
Player?.Game.BanIp(Connection.EndPoint.Address);
|
||||
if (_antiCheatConfig.BanIpFromGame)
|
||||
{
|
||||
player.Game.BanIp(Connection.EndPoint.Address);
|
||||
}
|
||||
|
||||
await player.Game.HandleRemovePlayer(Id, DisconnectReason.Hacking);
|
||||
}
|
||||
|
||||
await DisconnectAsync(DisconnectReason.Hacking, context.Name + ": " + message);
|
||||
var disconnectMessage =
|
||||
$"""
|
||||
You have been caught cheating and were {(_antiCheatConfig.BanIpFromGame ? "banned" : "kicked")} from the lobby.
|
||||
For questions, contact your server admin and share the following code: {supportCode}.
|
||||
""";
|
||||
|
||||
await DisconnectAsync(DisconnectReason.Custom, disconnectMessage);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -348,7 +361,9 @@ namespace Impostor.Server.Net
|
||||
{
|
||||
if (Player != null)
|
||||
{
|
||||
await Player.Game.HandleRemovePlayer(Id, DisconnectReason.ExitGame);
|
||||
// The client never sends over the real disconnect reason so we always assume ExitGame
|
||||
var isRemote = reason == "The remote sent a disconnect request";
|
||||
await Player.Game.HandleRemovePlayer(Id, isRemote ? DisconnectReason.ExitGame : DisconnectReason.Error);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -358,6 +373,7 @@ namespace Impostor.Server.Net
|
||||
|
||||
_logger.LogInformation("Client {0} disconnecting, reason: {1}", Id, reason);
|
||||
_clientManager.Remove(this);
|
||||
await _gameManager.OnClientDisconnectAsync(this);
|
||||
}
|
||||
|
||||
private bool IsPacketAllowed(IMessageReader message, bool hostOnly)
|
||||
|
||||
@@ -59,5 +59,20 @@ namespace Impostor.Server.Net
|
||||
{
|
||||
await Connection.CustomDisconnectAsync(reason, message);
|
||||
}
|
||||
|
||||
public bool Equals(IClient? other)
|
||||
{
|
||||
return other != null && Id == other.Id;
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return Equals(obj as ClientBase);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Id;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,6 +69,11 @@ namespace Impostor.Server.Net.Hazel
|
||||
{
|
||||
await Client.HandleMessageAsync(message, e.Type);
|
||||
}
|
||||
|
||||
if (!IsConnected)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -53,12 +53,20 @@ internal abstract class InnerGameManager : InnerNetObject, IInnerGameManager
|
||||
|
||||
public override async ValueTask<bool> HandleRpcAsync(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader)
|
||||
{
|
||||
// Let all logic components process this RPC. If at least one component handles it, return true.
|
||||
var result = false;
|
||||
foreach (var logicComponent in _logicComponents)
|
||||
{
|
||||
await logicComponent.HandleRpcAsync(call, reader);
|
||||
result |= await logicComponent.HandleRpcAsync(call, reader);
|
||||
}
|
||||
|
||||
return true;
|
||||
// If no component accepted it, try and find a custom RPC that can deal with it.
|
||||
if (!result)
|
||||
{
|
||||
result |= await base.HandleRpcAsync(sender, target, call, reader);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override ValueTask<bool> SerializeAsync(IMessageWriter writer, bool initialState)
|
||||
|
||||
@@ -6,9 +6,9 @@ namespace Impostor.Server.Net.Inner.Objects.GameManager.Logic;
|
||||
|
||||
internal abstract class GameLogicComponent
|
||||
{
|
||||
public virtual ValueTask HandleRpcAsync(RpcCalls callId, IMessageReader reader)
|
||||
public virtual ValueTask<bool> HandleRpcAsync(RpcCalls callId, IMessageReader reader)
|
||||
{
|
||||
throw new NotImplementedException($"Unhandled RpcCall {callId}");
|
||||
return ValueTask.FromResult(false);
|
||||
}
|
||||
|
||||
public virtual 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -289,7 +289,7 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
if (exiled != null)
|
||||
{
|
||||
exiled.Die(DeathReason.Exile);
|
||||
exiled.PlayerInfo.LastDeathReason = DeathReason.Exile;
|
||||
await _eventManager.CallAsync(new PlayerExileEvent(Game, Game.GetClientPlayer(exiled!.OwnerId)!, exiled));
|
||||
}
|
||||
|
||||
|
||||
@@ -20,10 +20,10 @@ 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);
|
||||
Rpc06SetName.Serialize(writer, PlayerInfo.NetId, name);
|
||||
await Game.FinishRpcAsync(writer);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -40,8 +40,8 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
{
|
||||
PlayerInfo.CurrentOutfit.HatId = hatId;
|
||||
|
||||
using var writer = Game.StartRpc(NetId, RpcCalls.SetHat);
|
||||
Rpc39SetHatStr.Serialize(writer, hatId);
|
||||
using var writer = Game.StartRpc(NetId, RpcCalls.SetHatStr);
|
||||
Rpc39SetHatStr.Serialize(writer, hatId, PlayerInfo.GetNextRpcSequenceId(RpcCalls.SetHatStr));
|
||||
await Game.FinishRpcAsync(writer);
|
||||
}
|
||||
|
||||
@@ -49,8 +49,8 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
{
|
||||
PlayerInfo.CurrentOutfit.PetId = petId;
|
||||
|
||||
using var writer = Game.StartRpc(NetId, RpcCalls.SetPet);
|
||||
Rpc41SetPetStr.Serialize(writer, petId);
|
||||
using var writer = Game.StartRpc(NetId, RpcCalls.SetPetStr);
|
||||
Rpc41SetPetStr.Serialize(writer, petId, PlayerInfo.GetNextRpcSequenceId(RpcCalls.SetPetStr));
|
||||
await Game.FinishRpcAsync(writer);
|
||||
}
|
||||
|
||||
@@ -58,8 +58,26 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
{
|
||||
PlayerInfo.CurrentOutfit.SkinId = skinId;
|
||||
|
||||
using var writer = Game.StartRpc(NetId, RpcCalls.SetSkin);
|
||||
Rpc40SetSkinStr.Serialize(writer, skinId);
|
||||
using var writer = Game.StartRpc(NetId, RpcCalls.SetSkinStr);
|
||||
Rpc40SetSkinStr.Serialize(writer, skinId, PlayerInfo.GetNextRpcSequenceId(RpcCalls.SetSkinStr));
|
||||
await Game.FinishRpcAsync(writer);
|
||||
}
|
||||
|
||||
public async ValueTask SetVisorAsync(string visorId)
|
||||
{
|
||||
PlayerInfo.CurrentOutfit.VisorId = visorId;
|
||||
|
||||
using var writer = Game.StartRpc(NetId, RpcCalls.SetVisorStr);
|
||||
Rpc42SetVisorStr.Serialize(writer, visorId, PlayerInfo.GetNextRpcSequenceId(RpcCalls.SetVisorStr));
|
||||
await Game.FinishRpcAsync(writer);
|
||||
}
|
||||
|
||||
public async ValueTask SetNamePlateAsync(string nameplateId)
|
||||
{
|
||||
PlayerInfo.CurrentOutfit.NamePlateId = nameplateId;
|
||||
|
||||
using var writer = Game.StartRpc(NetId, RpcCalls.SetNamePlateStr);
|
||||
Rpc43SetNamePlateStr.Serialize(writer, nameplateId, PlayerInfo.GetNextRpcSequenceId(RpcCalls.SetNamePlateStr));
|
||||
await Game.FinishRpcAsync(writer);
|
||||
}
|
||||
|
||||
@@ -148,5 +166,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);
|
||||
}
|
||||
|
||||
@@ -203,8 +203,8 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
return false;
|
||||
}
|
||||
|
||||
Rpc39SetHatStr.Deserialize(reader, out var hat);
|
||||
return await HandleSetHat(sender, hat);
|
||||
Rpc39SetHatStr.Deserialize(reader, out var hat, out var nextRpcSequenceId);
|
||||
return await HandleSetHat(sender, hat, nextRpcSequenceId);
|
||||
}
|
||||
|
||||
case RpcCalls.SetSkinStr:
|
||||
@@ -215,8 +215,8 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
return false;
|
||||
}
|
||||
|
||||
Rpc40SetSkinStr.Deserialize(reader, out var skin);
|
||||
return await HandleSetSkin(sender, skin);
|
||||
Rpc40SetSkinStr.Deserialize(reader, out var skin, out var nextRpcSequenceId);
|
||||
return await HandleSetSkin(sender, skin, nextRpcSequenceId);
|
||||
}
|
||||
|
||||
case RpcCalls.SetVisorStr:
|
||||
@@ -227,8 +227,8 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
return false;
|
||||
}
|
||||
|
||||
Rpc42SetVisorStr.Deserialize(reader, out var visor);
|
||||
return await HandleSetVisor(sender, visor);
|
||||
Rpc42SetVisorStr.Deserialize(reader, out var visor, out var nextRpcSequenceId);
|
||||
return await HandleSetVisor(sender, visor, nextRpcSequenceId);
|
||||
}
|
||||
|
||||
case RpcCalls.SetNamePlateStr:
|
||||
@@ -238,8 +238,8 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
return false;
|
||||
}
|
||||
|
||||
Rpc43SetNamePlateStr.Deserialize(reader, out var namePlate);
|
||||
return await HandleSetNamePlate(sender, namePlate);
|
||||
Rpc43SetNamePlateStr.Deserialize(reader, out var namePlate, out var nextRpcSequenceId);
|
||||
return await HandleSetNamePlate(sender, namePlate, nextRpcSequenceId);
|
||||
}
|
||||
|
||||
case RpcCalls.SetLevel:
|
||||
@@ -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:
|
||||
@@ -335,8 +335,8 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
return false;
|
||||
}
|
||||
|
||||
Rpc41SetPetStr.Deserialize(reader, out var pet);
|
||||
return await HandleSetPet(sender, pet);
|
||||
Rpc41SetPetStr.Deserialize(reader, out var pet, out var nextRpcSequenceId);
|
||||
return await HandleSetPet(sender, pet, nextRpcSequenceId);
|
||||
}
|
||||
|
||||
case RpcCalls.SetStartCounter:
|
||||
@@ -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);
|
||||
}
|
||||
@@ -554,23 +611,6 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask HandleSetInfected(ReadOnlyMemory<byte> infectedIds)
|
||||
{
|
||||
for (var i = 0; i < infectedIds.Length; i++)
|
||||
{
|
||||
var player = Game.GameNet.GameData!.GetPlayerById(infectedIds.Span[i]);
|
||||
if (player != null)
|
||||
{
|
||||
// player.IsImpostor = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (Game.GameState == GameStates.Starting)
|
||||
{
|
||||
await Game.StartedAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask<bool> HandleCheckName(ClientPlayer sender, string name)
|
||||
{
|
||||
if (Game.GameState == GameStates.Started)
|
||||
@@ -643,6 +683,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 +704,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 +723,7 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
}
|
||||
}
|
||||
|
||||
PlayerInfo.PlayerName = name;
|
||||
PlayerInfo.CurrentOutfit.PlayerName = name;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -694,9 +738,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 +761,17 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
}
|
||||
}
|
||||
|
||||
if ((byte)color >= ColorsCount)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.SetColor, 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 +783,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 = requested; (byte)c < 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -775,54 +848,71 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
return true;
|
||||
}
|
||||
|
||||
private async ValueTask<bool> HandleSetHat(ClientPlayer sender, string hat)
|
||||
private async ValueTask<bool> HandleSetHat(ClientPlayer sender, string hat, byte nextRpcSequenceId)
|
||||
{
|
||||
if (Game.GameState == GameStates.Started &&
|
||||
await sender.Client.ReportCheatAsync(RpcCalls.SetHat, CheatCategory.GameFlow, "Client tried to change hat while not in lobby"))
|
||||
await sender.Client.ReportCheatAsync(RpcCalls.SetHatStr, CheatCategory.GameFlow, "Client tried to change hat while not in lobby"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
PlayerInfo.CurrentOutfit.HatId = hat;
|
||||
PlayerInfo.CurrentOutfit.HatSequenceId = nextRpcSequenceId;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async ValueTask<bool> HandleSetSkin(ClientPlayer sender, string skin)
|
||||
private async ValueTask<bool> HandleSetSkin(ClientPlayer sender, string skin, byte nextRpcSequenceId)
|
||||
{
|
||||
if (Game.GameState == GameStates.Started &&
|
||||
await sender.Client.ReportCheatAsync(RpcCalls.SetSkin, CheatCategory.GameFlow, "Client tried to change skin while not in lobby"))
|
||||
await sender.Client.ReportCheatAsync(RpcCalls.SetSkinStr, CheatCategory.GameFlow, "Client tried to change skin while not in lobby"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
PlayerInfo.CurrentOutfit.SkinId = skin;
|
||||
PlayerInfo.CurrentOutfit.SkinSequenceId = nextRpcSequenceId;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async ValueTask<bool> HandleSetVisor(ClientPlayer sender, string visor)
|
||||
private async ValueTask<bool> HandleSetVisor(ClientPlayer sender, string visor, byte nextRpcSequenceId)
|
||||
{
|
||||
if (Game.GameState == GameStates.Started &&
|
||||
await sender.Client.ReportCheatAsync(RpcCalls.SetVisor, CheatCategory.GameFlow, "Client tried to change visor while not in lobby"))
|
||||
await sender.Client.ReportCheatAsync(RpcCalls.SetVisorStr, CheatCategory.GameFlow, "Client tried to change visor while not in lobby"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
PlayerInfo.CurrentOutfit.VisorId = visor;
|
||||
PlayerInfo.CurrentOutfit.VisorSequenceId = nextRpcSequenceId;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async ValueTask<bool> HandleSetNamePlate(ClientPlayer sender, string skin)
|
||||
private async ValueTask<bool> HandleSetNamePlate(ClientPlayer sender, string namePlate, byte nextRpcSequenceId)
|
||||
{
|
||||
if (Game.GameState == GameStates.Started &&
|
||||
await sender.Client.ReportCheatAsync(RpcCalls.SetNamePlate, CheatCategory.GameFlow, "Client tried to change skin while not in lobby"))
|
||||
await sender.Client.ReportCheatAsync(RpcCalls.SetNamePlateStr, CheatCategory.GameFlow, "Client tried to change skin while not in lobby"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
PlayerInfo.CurrentOutfit.NamePlateId = skin;
|
||||
PlayerInfo.CurrentOutfit.NamePlateId = namePlate;
|
||||
PlayerInfo.CurrentOutfit.NamePlateSequenceId = nextRpcSequenceId;
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -949,7 +1039,7 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
if (!await ValidateRole(RpcCalls.ProtectPlayer, sender, PlayerInfo, RoleTypes.GuardianAngel))
|
||||
{
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
((InnerPlayerControl)target).Protect(this);
|
||||
@@ -971,15 +1061,16 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
await _eventManager.CallAsync(new PlayerStartMeetingEvent(Game, Game.GetClientPlayer(this.OwnerId)!, this, deadPlayer));
|
||||
}
|
||||
|
||||
private async ValueTask<bool> HandleSetPet(ClientPlayer sender, string pet)
|
||||
private async ValueTask<bool> HandleSetPet(ClientPlayer sender, string pet, byte nextRpcSequenceId)
|
||||
{
|
||||
if (Game.GameState == GameStates.Started &&
|
||||
await sender.Client.ReportCheatAsync(RpcCalls.SetPet, CheatCategory.GameFlow, "Client tried to change pet while not in lobby"))
|
||||
await sender.Client.ReportCheatAsync(RpcCalls.SetPetStr, CheatCategory.GameFlow, "Client tried to change pet while not in lobby"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
PlayerInfo.CurrentOutfit.PetId = pet;
|
||||
PlayerInfo.CurrentOutfit.PetSequenceId = nextRpcSequenceId;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1001,5 +1092,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,17 +47,19 @@ 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;
|
||||
public bool CanVent => RoleType is RoleTypes.Impostor or RoleTypes.Shapeshifter or RoleTypes.Phantom or RoleTypes.Engineer;
|
||||
|
||||
public bool IsDead { get; internal set; }
|
||||
|
||||
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,87 @@ 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));
|
||||
}
|
||||
}
|
||||
|
||||
public byte GetNextRpcSequenceId(RpcCalls rpc)
|
||||
{
|
||||
var defaultOutfit = this.Outfits[PlayerOutfitType.Default];
|
||||
|
||||
byte nextRpcSequenceId = 0;
|
||||
const byte SequenceIdAddition = 5;
|
||||
|
||||
// ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault
|
||||
switch (rpc)
|
||||
{
|
||||
case RpcCalls.SetHatStr:
|
||||
defaultOutfit.HatSequenceId += SequenceIdAddition;
|
||||
nextRpcSequenceId = defaultOutfit.HatSequenceId;
|
||||
break;
|
||||
case RpcCalls.SetSkinStr:
|
||||
defaultOutfit.SkinSequenceId += SequenceIdAddition;
|
||||
nextRpcSequenceId = defaultOutfit.SkinSequenceId;
|
||||
break;
|
||||
case RpcCalls.SetPetStr:
|
||||
defaultOutfit.PetSequenceId += SequenceIdAddition;
|
||||
nextRpcSequenceId = defaultOutfit.PetSequenceId;
|
||||
break;
|
||||
case RpcCalls.SetVisorStr:
|
||||
defaultOutfit.VisorSequenceId += SequenceIdAddition;
|
||||
nextRpcSequenceId = defaultOutfit.VisorSequenceId;
|
||||
break;
|
||||
case RpcCalls.SetNamePlateStr:
|
||||
defaultOutfit.NamePlateSequenceId += SequenceIdAddition;
|
||||
nextRpcSequenceId = defaultOutfit.NamePlateSequenceId;
|
||||
break;
|
||||
default:
|
||||
_logger.LogError(
|
||||
"{NextRpcSequenceIdName} was called for an RPC it doesn\'t know how to handle: {Rpc}",
|
||||
nameof(GetNextRpcSequenceId),
|
||||
rpc);
|
||||
break;
|
||||
}
|
||||
|
||||
return nextRpcSequenceId;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,12 +17,10 @@ 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, 3, 1), // 2024.6.18
|
||||
new GameVersion(2024, 4, 1), // 2024.8.13
|
||||
new GameVersion(2024, 4, 2), // 2024.9.4
|
||||
new GameVersion(2024, 8, 10), // 2024.10.29
|
||||
},
|
||||
};
|
||||
|
||||
@@ -62,6 +60,12 @@ internal class CompatibilityManager : ICompatibilityManager
|
||||
return null;
|
||||
}
|
||||
|
||||
private CompatibilityGroup GetCompatibilityGroupOrDefault(GameVersion clientVersion)
|
||||
{
|
||||
// If the compatibility group is not defined, we assume it is not compatible with anything else than itself
|
||||
return TryGetCompatibilityGroup(clientVersion) ?? new CompatibilityGroup(new[] { clientVersion.Normalize() });
|
||||
}
|
||||
|
||||
public VersionCompareResult CanConnectToServer(GameVersion clientVersion)
|
||||
{
|
||||
if (this.TryGetCompatibilityGroup(clientVersion) != null)
|
||||
@@ -90,13 +94,8 @@ internal class CompatibilityManager : ICompatibilityManager
|
||||
return GameJoinError.None;
|
||||
}
|
||||
|
||||
var hostCompatGroup = this.TryGetCompatibilityGroup(hostVersion);
|
||||
var playerCompatGroup = this.TryGetCompatibilityGroup(clientVersion);
|
||||
|
||||
if (hostCompatGroup == null || playerCompatGroup == null)
|
||||
{
|
||||
return GameJoinError.InvalidClient;
|
||||
}
|
||||
var hostCompatGroup = GetCompatibilityGroupOrDefault(hostVersion);
|
||||
var playerCompatGroup = GetCompatibilityGroupOrDefault(clientVersion);
|
||||
|
||||
if (hostCompatGroup != playerCompatGroup)
|
||||
{
|
||||
|
||||
@@ -31,6 +31,7 @@ namespace Impostor.Server.Net.Manager
|
||||
private readonly IEventManager _eventManager;
|
||||
private readonly IGameCodeFactory _gameCodeFactory;
|
||||
private readonly ICompatibilityManager _compatibilityManager;
|
||||
private readonly ConcurrentDictionary<IClient, Game?> _gamesCreatedBy;
|
||||
|
||||
public GameManager(
|
||||
ILogger<GameManager> logger,
|
||||
@@ -49,6 +50,7 @@ namespace Impostor.Server.Net.Manager
|
||||
_games = new ConcurrentDictionary<int, Game>();
|
||||
_compatibilityConfig = compatibilityConfig.Value;
|
||||
_compatibilityManager = compatibilityManager;
|
||||
_gamesCreatedBy = new ConcurrentDictionary<IClient, Game?>();
|
||||
}
|
||||
|
||||
IEnumerable<IGame> IGameManager.Games => _games.Select(kv => kv.Value);
|
||||
@@ -85,6 +87,12 @@ namespace Impostor.Server.Net.Manager
|
||||
|
||||
public async ValueTask<IGame?> CreateAsync(IClient? owner, IGameOptions options, GameFilterOptions filterOptions)
|
||||
{
|
||||
if (owner != null && !_gamesCreatedBy.TryAdd(owner, null))
|
||||
{
|
||||
_logger.LogWarning("Connection {Name}({ClientId}) has tried to create a second game, blocked", owner.Name, owner.Id);
|
||||
return null;
|
||||
}
|
||||
|
||||
var @event = new GameCreationEvent(this, owner);
|
||||
await _eventManager.CallAsync(@event);
|
||||
|
||||
@@ -100,6 +108,11 @@ namespace Impostor.Server.Net.Manager
|
||||
(success, game) = await TryCreateAsync(options, filterOptions, owner);
|
||||
}
|
||||
|
||||
if (owner != null)
|
||||
{
|
||||
_gamesCreatedBy[owner] = game;
|
||||
}
|
||||
|
||||
if (!success || game == null)
|
||||
{
|
||||
throw new ImpostorException("Could not create new game"); // TODO: Fix generic exception.
|
||||
@@ -129,5 +142,14 @@ namespace Impostor.Server.Net.Manager
|
||||
|
||||
return (true, game);
|
||||
}
|
||||
|
||||
internal async ValueTask OnClientDisconnectAsync(IClient client)
|
||||
{
|
||||
if (_gamesCreatedBy.TryRemove(client, out var game) && game is { PlayerCount: 0, GameState: not GameStates.Destroyed })
|
||||
{
|
||||
_logger.LogWarning("Client {Name}({ClientId}) left empty game open when disconnecting", client.Name, client.Id);
|
||||
await RemoveAsync(game.Code);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
@@ -245,15 +261,20 @@ namespace Impostor.Server.Net.State
|
||||
if (scene != "OnlineGame")
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"Player {0} ({1}) tried to send SceneChangeFlag with disallowed scene.",
|
||||
"Player {PlayerName} ({ClientId}) tried to send SceneChangeFlag with disallowed scene \"{Scene}\".",
|
||||
sender.Client.Name,
|
||||
sender.Client.Id);
|
||||
sender.Client.Id,
|
||||
scene);
|
||||
return false;
|
||||
}
|
||||
|
||||
sender.Scene = scene;
|
||||
|
||||
_logger.LogTrace("> Scene {0} to {1}", clientId, sender.Scene);
|
||||
|
||||
await SyncServerObjectsAsync(sender);
|
||||
await SpawnPlayerInfoAsync(sender);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -325,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;
|
||||
}
|
||||
|
||||
@@ -357,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)
|
||||
{
|
||||
@@ -401,12 +430,6 @@ namespace Impostor.Server.Net.State
|
||||
break;
|
||||
}
|
||||
|
||||
case InnerGameData:
|
||||
{
|
||||
GameNet.GameData = null;
|
||||
break;
|
||||
}
|
||||
|
||||
case InnerVoteBanSystem:
|
||||
{
|
||||
GameNet.VoteBan = null;
|
||||
@@ -419,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))
|
||||
{
|
||||
@@ -438,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));
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,23 @@ 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(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);
|
||||
}
|
||||
|
||||
public IMessageWriter StartGameData(int? targetClientId = null, MessageType type = MessageType.Reliable)
|
||||
{
|
||||
var writer = MessageWriter.Get(type);
|
||||
|
||||
@@ -54,17 +71,12 @@ 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)
|
||||
public ValueTask FinishGameDataAsync(IMessageWriter writer, int? targetClientId = null)
|
||||
{
|
||||
writer.EndMessage();
|
||||
writer.EndMessage();
|
||||
|
||||
return targetClientId.HasValue
|
||||
? SendToAsync(writer, targetClientId.Value)
|
||||
@@ -100,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,15 @@ namespace Impostor.Server.Net.State
|
||||
}
|
||||
});
|
||||
|
||||
// Clean up the PlayerInfo if we own it and we're still in the lobby
|
||||
if (GameState == GameStates.NotStarted)
|
||||
{
|
||||
if (GameNet.GameData.PlayersByClientId.TryGetValue(playerId, out var playerInfo))
|
||||
{
|
||||
await DespawnPlayerInfoAsync(playerInfo);
|
||||
}
|
||||
}
|
||||
|
||||
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; }
|
||||
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
{
|
||||
"Server": {
|
||||
"PublicIp": "127.0.0.1",
|
||||
"PublicPort": 22023,
|
||||
"ListenIp": "0.0.0.0",
|
||||
"ListenPort": 22023
|
||||
},
|
||||
"HttpServer": {
|
||||
"Enabled": true,
|
||||
"ListenIp": "0.0.0.0",
|
||||
"ListenPort": 22023
|
||||
},
|
||||
"AntiCheat": {
|
||||
"Enabled": true,
|
||||
"BanIpFromGame": true
|
||||
},
|
||||
"Timeout": {
|
||||
"SpawnTimeout": 2500,
|
||||
"ConnectionTimeout": 2500
|
||||
},
|
||||
"Compatibility": {
|
||||
"AllowFutureGameVersions": false,
|
||||
"AllowVersionMixing": false
|
||||
},
|
||||
"Debug": {
|
||||
"GameRecorderEnabled": true,
|
||||
"GameRecorderPath": ""
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
{
|
||||
"Server": {
|
||||
"PublicIp": "127.0.0.1",
|
||||
"PublicPort": 22023,
|
||||
@@ -12,6 +12,28 @@
|
||||
},
|
||||
"AntiCheat": {
|
||||
"Enabled": true,
|
||||
"BanIpFromGame": true
|
||||
"BanIpFromGame": true,
|
||||
"AllowCheatingHosts": "Never",
|
||||
"EnableGameFlowChecks": true,
|
||||
"EnableMustBeHostChecks": true,
|
||||
"EnableColorLimitChecks": true,
|
||||
"EnableNameLimitChecks": true,
|
||||
"EnableOwnershipChecks": true,
|
||||
"EnableRoleChecks": true,
|
||||
"EnableTargetChecks": true,
|
||||
"ForbidProtocolExtensions": true
|
||||
},
|
||||
"Timeout": {
|
||||
"SpawnTimeout": 2500,
|
||||
"ConnectionTimeout": 2500
|
||||
},
|
||||
"Compatibility": {
|
||||
"AllowFutureGameVersions": false,
|
||||
"AllowHostAuthority": false,
|
||||
"AllowVersionMixing": false
|
||||
},
|
||||
"Debug": {
|
||||
"GameRecorderEnabled": false,
|
||||
"GameRecorderPath": ""
|
||||
}
|
||||
}
|
||||
@@ -48,11 +48,23 @@ public sealed class CompatibilityManagerTests
|
||||
public static IEnumerable<object[]> CanJoinGameData =>
|
||||
new List<object[]>
|
||||
{
|
||||
// Same version -> can join
|
||||
new object[] { GameJoinError.None, new GameVersion(1, 0, 0), new GameVersion(1, 0, 0) },
|
||||
new object[] { GameJoinError.InvalidClient, new GameVersion(1, 0, 0), new GameVersion(100, 0, 0) },
|
||||
|
||||
// Compatible versions -> can join
|
||||
new object[] { GameJoinError.None, new GameVersion(2, 0, 0), new GameVersion(2, 1, 0) },
|
||||
|
||||
// Different incompatible versions -> show correct error
|
||||
new object[] { GameJoinError.ClientOutdated, new GameVersion(2, 0, 0), new GameVersion(1, 0, 0) },
|
||||
new object[] { GameJoinError.ClientTooNew, new GameVersion(1, 0, 0), new GameVersion(2, 0, 0) },
|
||||
new object[] { GameJoinError.None, new GameVersion(2, 0, 0), new GameVersion(2, 1, 0) },
|
||||
|
||||
// Versions unknown to Impostor -> also show correct errors
|
||||
new object[] { GameJoinError.ClientTooNew, new GameVersion(0, 0, 1), new GameVersion(1, 0, 0) },
|
||||
new object[] { GameJoinError.ClientOutdated, new GameVersion(1, 0, 0), new GameVersion(0, 0, 1) },
|
||||
new object[] { GameJoinError.ClientTooNew, new GameVersion(1, 0, 0), new GameVersion(100, 0, 0) },
|
||||
new object[] { GameJoinError.ClientOutdated, new GameVersion(100, 0, 0), new GameVersion(1, 0, 0) },
|
||||
new object[] { GameJoinError.ClientTooNew, new GameVersion(0, 0, 1), new GameVersion(100, 0, 0) },
|
||||
|
||||
};
|
||||
|
||||
[Theory]
|
||||
|
||||
Reference in new issue
Block a user