mirror of
https://github.com/Impostor/Impostor.git
synced 2026-10-21 21:43:21 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
703915f06f | ||
|
|
bf52365a1a | ||
|
|
e97c28574b | ||
|
|
8ee5796d89 | ||
|
|
7b77165421 | ||
|
|
196c01d5e3 | ||
|
|
fd40e469c5 | ||
|
|
27b2fc5876 | ||
|
|
8bbe6be4b0 | ||
|
|
4f372ecabb | ||
|
|
b09c40b7e1 | ||
|
|
5ee0fdb61f | ||
|
|
44ebc0c6b0 | ||
|
|
681126dfb5 | ||
|
|
9f627b7b01 | ||
|
|
510ed07389 | ||
|
|
8f41030faf | ||
|
|
f23dd8aa25 | ||
|
|
857e04dbc3 | ||
|
|
b78f86f0f3 | ||
|
|
db9c6ab66c | ||
|
|
a161aa7045 | ||
|
|
caab877c80 | ||
|
|
202b955db1 | ||
|
|
1cd298608a | ||
|
|
f0689ac2e2 | ||
|
|
2cad4ffa14 | ||
|
|
e9437e781b | ||
|
|
06f4f8bf00 | ||
|
|
2cb9c648f3 | ||
|
|
b30775c2fc | ||
|
|
764c42eae3 | ||
|
|
4244f39640 | ||
|
|
069ec446cd | ||
|
|
98783e5b42 | ||
|
|
fd12cd1a39 | ||
|
|
139f2334a2 | ||
|
|
60371d538c | ||
|
|
6e68747fe0 | ||
|
|
3736b37831 | ||
|
|
eeb85e4abc | ||
|
|
fd33a892cd | ||
|
|
fbdb708941 | ||
|
|
9a60ce3d56 | ||
|
|
d7601e8057 | ||
|
|
0e9454ecdf | ||
|
|
ce5a491cdb | ||
|
|
16c8acc849 | ||
|
|
195d31cad1 | ||
|
|
18846d3dd4 | ||
|
|
eb231dc393 | ||
|
|
a00b9e63d8 | ||
|
|
d7e5594cf6 | ||
|
|
8ba89d9844 | ||
|
|
ebd7f99438 | ||
|
|
2197434bdc | ||
|
|
8f54566245 | ||
|
|
a70e875a4d | ||
|
|
e473e4cdb4 | ||
|
|
9169fab67d | ||
|
|
565bf26ff8 | ||
|
|
8fb996b314 | ||
|
|
46a6005ba5 | ||
|
|
0ac0b1d403 | ||
|
|
e1bd85313a | ||
|
|
fff9713697 | ||
|
|
be0a0b917c | ||
|
|
c672e19e99 | ||
|
|
104376b43f | ||
|
|
92ca5aa9d7 | ||
|
|
67e94e8efc | ||
|
|
da8353655d | ||
|
|
dab8751f1d | ||
|
|
723495e390 | ||
|
|
cd220530ac | ||
|
|
fc2679a8d2 | ||
|
|
28e933092a | ||
|
|
06289d46c0 | ||
|
|
6bd13c0399 | ||
|
|
7e85419b82 | ||
|
|
3e2c3f1525 | ||
|
|
61cbd5c4d8 | ||
|
|
495dc01948 | ||
|
|
a448ece73e | ||
|
|
e7b98f1842 | ||
|
|
d65e99fe45 |
No files matched your search
@@ -8,6 +8,14 @@ The Impostor project is a reverse engineered and open source server for the game
|
||||
|
||||
Yes, Impostor can be used with both the Android and iOS versions of the game. You can add a custom server via a link, you can do this using the [site](https://impostor.github.io/Impostor)
|
||||
|
||||
## Can this be used with the console version of the game?
|
||||
|
||||
Sadly not, as these don't allow adding custom servers.
|
||||
|
||||
## Can this be used with Among Us 3D/VR?
|
||||
|
||||
No, it's an entirely different game with a completely different network stack. So we'd need to develop another server completely from scratch. We're currently not interested in doing this.
|
||||
|
||||
## How can I get started?
|
||||
|
||||
See [Setting up your Server](Running-the-server.md) for more information on running the server and [Client Setup](https://impostor.github.io/Impostor/) for helping your friends join in!
|
||||
|
||||
+25
-10
@@ -1,19 +1,13 @@
|
||||
# HTTP Server
|
||||
|
||||
Since Impostor 1.9.0 a HTTP service is included for matchmaking. Recent versions of Among Us require this service in order to connect correctly and not kick players after a round.
|
||||
Since Impostor 1.9.0 a HTTP service is included for matchmaking, which is required for clients to connect to the Impostor server.
|
||||
|
||||
Depending on whether you want to support mobile players, you can set up the HTTP server in one of two ways:
|
||||
|
||||
- Directly expose the HTTP server. Simpler, but only works if you don't want to support mobile players.
|
||||
- Use a reverse proxy to expose the HTTP server. More complex, but allows mobile players to connect.
|
||||
|
||||
## Directly expose the HTTP server.
|
||||
|
||||
In config.json, set "ListenIp" to "0.0.0.0" in the "HttpServer" section. If you don't have this section, look in the example [config.json](https://github.com/Impostor/Impostor/blob/master/src/Impostor.Server/config.json)
|
||||
Starting from Among Us 16.0.5, you need to set up HTTPS for players to connect to your server.
|
||||
Unfortunately Impostor doesn't handle SSL certificates, so you need to set up a reverse proxy.
|
||||
|
||||
## Use a reverse proxy
|
||||
|
||||
A reverse proxy allows you to forward HTTP requests from users to multiple services. If you already have one, you should configure it to add Impostor. This page contains an Nginx configuration you can use for reference.
|
||||
A reverse proxy allows you to forward HTTP requests from users to multiple services. If you already have one, you should configure it to add Impostor.
|
||||
|
||||
If you have never set up a reverse proxy before, we recommend you to set up [Caddy](https://caddyserver.com/). It is easy to set up and comes with support for requesting SSL certificates out of the box.
|
||||
|
||||
@@ -65,3 +59,24 @@ server {
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## Stop exposing Impostor's HTTP server to the internet directly
|
||||
|
||||
To only allow connections to Impostor's HTTP servers via the reverse proxy, we recommend to set the ListenIp of the HTTP server to 127.0.0.1:
|
||||
|
||||
```json
|
||||
{
|
||||
// NOTE: merge this snippet into the rest of your configuration
|
||||
"HttpServer": {
|
||||
"ListenIp": "127.0.0.1"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Or if you're configuring Impostor with environment variables:
|
||||
|
||||
```sh
|
||||
IMPOSTOR_HttpServer__ListenIp=127.0.0.1
|
||||
```
|
||||
|
||||
For more information on server configuration, see [Server-configuration.md](Server Configuration).
|
||||
@@ -10,7 +10,9 @@ To connect to the server, you need to configure and install a region file on htt
|
||||
|
||||
Among Us connects to the server using two network services: the (TCP) HTTP service points Among Us to the UDP service, then the UDP service hosts the actual game traffic. Because of this, Impostor uses port 22023 using **both** the TCP and UDP protocols.
|
||||
|
||||
As the phone version of Among Us requires this HTTP connection to be secure, we recommend using a HTTP reverse proxy to terminate SSL on the HTTP service. Setup instructions for this are in the [Http Server documentation](Http-server.md).
|
||||
To connect to your Among Us version from another PC, you need to set up a HTTP reverse proxy to support HTTPS. If you're just running a version of Impostor for testing Setup instructions for this are in the [Http Server documentation](Http-server.md).
|
||||
|
||||
If you want to test if your Among Us HTTP server is working, open `http://localhost:22023` (assuming you're using the default settings, change the IP and port respectively) in a browser
|
||||
|
||||
Depending on your host you may also need to port forward Impostor to the internet or pass Impostor traffic by your firewall. Port 22023 UDP needs to be accessible for everyone that wants to play on the server, then you also need to portforward your HTTP reverse proxy or port 22023 TCP if you don't use a reverse proxy. As port forwarding changes per host or router configuration, port forwarding is not covered by this guide.
|
||||
|
||||
@@ -21,8 +23,9 @@ Depending on your host you may also need to port forward Impostor to the interne
|
||||
3. Extract the zip.
|
||||
4. Modify `config.json` to your liking. Documentation can be found [here](Server-configuration.md). You need to at least change `PublicIp` to the address people will connect to your server to.
|
||||
5. Run `Impostor.Server` (Linux/macOS) or `Impostor.Server.exe` (Windows)
|
||||
6. (OPTIONAL - Linux) Configure a systemd definition file and enable the service to start on boot, see [systemd configuration](Server-configuration.md#systemd)
|
||||
7. (OPTIONAL) Set up a reverse proxy to support HTTPS, so mobile phones can connect, see [reverse proxy configuration](Http-server.md)
|
||||
6. Set up a reverse proxy to support HTTPS, so you can connect to your server from another device. See [reverse proxy configuration](Http-server.md)
|
||||
7. (OPTIONAL - Linux) Configure a systemd definition file and enable the service to start on boot, see [systemd configuration](Server-configuration.md#systemd)
|
||||
|
||||
|
||||
## Using Docker
|
||||
|
||||
@@ -34,7 +37,7 @@ Docker is a program that allows you to run programs like Impostor in a container
|
||||
After installing Docker, you can just start a Docker container with `docker run`:
|
||||
|
||||
```
|
||||
docker run -p 22023:22023/tcp -p 22023:22023/udp -e IMPOSTOR_Server__PublicIp=your.public.ip.here aeonlucid/impostor:nightly
|
||||
docker run -p 127.0.0.1:22023:22023/tcp -p 22023:22023/udp -e IMPOSTOR_Server__PublicIp=your.public.ip.here aeonlucid/impostor:nightly
|
||||
```
|
||||
|
||||
Please replace `your.public.ip.here` with the public IP address of your server. This is the address Among Us will try to reach your server at.
|
||||
@@ -43,7 +46,7 @@ To configure the docker container, either use environment variables or mount con
|
||||
|
||||
## Using Docker Compose
|
||||
|
||||
Docker Compose allows you to start a Docker container using a prefined configuration. This is an example configuration you can continue on:
|
||||
Docker Compose allows you to start a Docker container using a predefined configuration. This is an example configuration you can continue on:
|
||||
|
||||
```
|
||||
version: '3.4'
|
||||
@@ -53,12 +56,12 @@ services:
|
||||
image: aeonlucid/impostor:nightly
|
||||
container_name: impostor
|
||||
ports:
|
||||
- 22023:22023/tcp # Add "127.0.0.1:" if you're using a reverse proxy to terminate HTTPS
|
||||
- 127.0.0.1:22023:22023/tcp # Set up your own reverse proxy to terminate HTTPS
|
||||
- 22023:22023/udp
|
||||
environment: # Either configure Impostor using environment variables or mount a copy of config.json
|
||||
environment: # Either configure Impostor using environment variables or mount config.json in your container
|
||||
- IMPOSTOR_Server__PublicIp=your.public.ip.here
|
||||
volumes:
|
||||
- /path/to/local/config.json:/app/config.json # For easy editing of the config
|
||||
- /path/to/local/config.json:/app/config.json # Mount config.json
|
||||
- /path/to/local/plugins:/app/plugins # Only needed if using plugins
|
||||
- /path/to/local/libraries:/app/libraries # Only needed if using external libraries
|
||||
- /path/to/local/libraries:/app/libraries # Only needed if using external libraries (some plugins may need this)
|
||||
```
|
||||
@@ -25,19 +25,25 @@ Impostor has an Http Server that is used by recent versions of Among Us to conne
|
||||
|
||||
Impostor has an Anticheat that makes it possible to kick cheaters from games automatically. Note that the anticheat is tuned on the vanilla version of the game, so client-side modifications could trigger the Anticheat if you're playing with them.
|
||||
|
||||
| Key | Default | Value |
|
||||
|----------------------------|-----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **Enabled** | `true` | Whether the anticheat should be enabled. |
|
||||
| **BanIpFromGame** | `true` | When anticheat is enabled and a player is caught hacking, they will be kicked from the server. If this value is set to `true`, the player will be banned instead and will not be able to rejoin that specific game. |
|
||||
| **AllowCheatingHosts** | `"Never"` | Configure whether hosts are allowed to cheat. "Never" forbids it, "Always" allows it. "IfRequested" allows hosts to cheat if they connect with the DisableServerAuthorityFlag set. |
|
||||
| **EnableGameFlowChecks** | `true` | Enable checks that check if certain actions are done in the appropriate order or at the appropriate moment in the game. This includes changing cosmetics while in game or murdering too fast. |
|
||||
| **EnableMustBeHostChecks** | `true` | Enables checks that check if players are the host before they can do actions that require them to be host of the game. This includes starting the game and spawning objects. |
|
||||
| **EnableColorLimitChecks** | `true` | Enables checks that checks if players request colors that are already in use. |
|
||||
| **EnableNameLimitChecks** | `true` | Enables checks that checks if player names have a length that is possible to set using the user interface. |
|
||||
| **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. |
|
||||
| Key | Default | Value |
|
||||
|-------------------------------|---------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **Enabled** | `true` | Whether the anticheat should be enabled. |
|
||||
| **BanIpFromGame** | `true` | When anticheat is enabled and a player is caught hacking, they will be kicked from the server. If this value is set to `true`, the player will be banned instead and will not be able to rejoin that specific game. |
|
||||
| **AllowCheatingHosts** | `"Never"` | Configure whether hosts are allowed to cheat. "Never" forbids it, "Always" allows it. "IfRequested" allows hosts to cheat if they connect with the DisableServerAuthorityFlag set. |
|
||||
| **AllowHostOnlyExtensions** | "IfRequested" | Configure whether [Host-Only extensions](https://github.com/Innersloth-LLC/AmongUsModdingInformation#host-only-mods) are allowed in all games ("Always"), +25 games only ("IfRequested") or by nobody ("Never") |
|
||||
| **EnableGameFlowChecks** | `true` | Enable checks that check if certain actions are done in the appropriate order or at the appropriate moment in the game. This includes changing cosmetics while in game or murdering too fast. |
|
||||
| **EnableInvalidObjectChecks** | `true` | Enables checks that check if network objects are spawned properly. Disabling this option also implies disabling EnableRoleChecks. |
|
||||
| **EnableMustBeHostChecks** | `true` | Enables checks that check if players are the host before they can do actions that require them to be host of the game. This includes starting the game and spawning objects. |
|
||||
| **EnableColorLimitChecks** | `true` | Enables checks that checks if players request colors that are already in use. |
|
||||
| **EnableNameLimitChecks** | `true` | Enables checks that checks if player names have a length that is possible to set using the user interface. |
|
||||
| **EnableItemLimitChecks** | `true` | Enables checks for item counts that cannot be exceeded in the official clients. |
|
||||
| **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. |
|
||||
| **EnableIdentifierChecks** | `true` | Enables checks that verify if the friendcode and puid a host claims for a player in their PlayerInfo match the ones known for that player. |
|
||||
| **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. |
|
||||
| **EnablePacketSizeChecks** | `true` | Enables checks that verify if network messages exceed the maximum allowed size. |
|
||||
| **PacketSizeLimit** | `1203` | The maximum allowed size (in bytes) for a network message. The official servers of Among Us requires that a Hazel packet is < 1204 bytes, excluding headers. |
|
||||
|
||||
### Compatibility
|
||||
|
||||
|
||||
@@ -47,6 +47,12 @@ Also check if the port Impostor (ListenPort) is listening on is correctly port-f
|
||||
Please check that you only have **working** plugins in the `plugins` folder.
|
||||
This error can be caused by non-plugin files or plugins that are not working correctly.
|
||||
|
||||
## Disconnected with `You disconnected from the server. If this happens often, check your network strength. This may also be a server issue`
|
||||
|
||||
Usually this means that Among Us couldn't connect to Impostor's HTTP server.
|
||||
|
||||
Open the address of your server in a web browser, it should show you a small page confirming that Impostor is available.
|
||||
|
||||
## My question is not yet answered and I'm still having problems!
|
||||
|
||||
That's unfortunate. Join the [Impostor Discord](https://discord.gg/Mk3w6Tb), ask your question there and we'll try to help you out. Note that we're not always available, so it may take some time to get an answer. To make answering your question easier, please add the following details:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<LangVersion>11</LangVersion>
|
||||
<VersionPrefix>1.10.2</VersionPrefix>
|
||||
<VersionPrefix>1.10.9</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.4" PrivateAssets="all" GeneratePathProperty="true" />
|
||||
<PackageReference Include="System.Text.Json" Version="8.0.5" 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" />
|
||||
|
||||
|
||||
@@ -85,6 +85,14 @@ public sealed class SourceGenerator : IIncrementalGenerator
|
||||
enumGenerator.Generate("SystemTypes", underlyingType: CSharpEnumUnderlyingType.Byte);
|
||||
enumGenerator.Generate("Language", sourceName: "SupportedLangs");
|
||||
enumGenerator.Generate("TaskTypes");
|
||||
enumGenerator.Generate("BoolOptionNames", "Impostor.Api.Innersloth.GameFilters");
|
||||
enumGenerator.Generate("ByteOptionNames", "Impostor.Api.Innersloth.GameFilters");
|
||||
enumGenerator.Generate("CategorizedOptionNames", "Impostor.Api.Innersloth.GameFilters");
|
||||
enumGenerator.Generate("FloatArrayOptionNames", "Impostor.Api.Innersloth.GameFilters");
|
||||
enumGenerator.Generate("FloatOptionNames", "Impostor.Api.Innersloth.GameFilters");
|
||||
enumGenerator.Generate("Int32ArrayOptionNames", "Impostor.Api.Innersloth.GameFilters");
|
||||
enumGenerator.Generate("Int32OptionNames", "Impostor.Api.Innersloth.GameFilters");
|
||||
enumGenerator.Generate("UInt32OptionNames", "Impostor.Api.Innersloth.GameFilters");
|
||||
|
||||
enumGenerator.Generate("RpcCalls", "Impostor.Api.Net.Inner", underlyingType: CSharpEnumUnderlyingType.Byte);
|
||||
|
||||
|
||||
@@ -60,9 +60,9 @@
|
||||
},
|
||||
"System.Text.Json": {
|
||||
"type": "Direct",
|
||||
"requested": "[8.0.4, )",
|
||||
"resolved": "8.0.4",
|
||||
"contentHash": "bAkhgDJ88XTsqczoxEMliSrpijKZHhbJQldhAmObj/RbrN3sU5dcokuXmWJWsdQAhiMJ9bTayWsL1C9fbbCRhw==",
|
||||
"requested": "[8.0.5, )",
|
||||
"resolved": "8.0.5",
|
||||
"contentHash": "0f1B50Ss7rqxXiaBJyzUu9bWFOO2/zSlifZ/UNMdiIpDYe4cY4LQQicP4nirK1OS31I43rn062UIJ1Q9bpmHpg==",
|
||||
"dependencies": {
|
||||
"Microsoft.Bcl.AsyncInterfaces": "8.0.0",
|
||||
"System.Buffers": "4.5.1",
|
||||
@@ -151,6 +151,7 @@
|
||||
"System.Runtime.CompilerServices.Unsafe": "4.5.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
".NETStandard,Version=v2.0/win-x64": {}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,14 @@
|
||||
namespace Impostor.Api;
|
||||
|
||||
// NOTE: when adding items to this enum, add them to the bottom for API stability
|
||||
public enum CheatCategory
|
||||
{
|
||||
/// <summary>A packet used a part of the network protocol that is unknown to Impostor, like a custom RPC.</summary>
|
||||
ProtocolExtension,
|
||||
|
||||
/// <summary>A host-only mod extension is used outside of host-authoritive mode.</summary>
|
||||
HostOnlyExtension,
|
||||
|
||||
/// <summary>A packet was sent at an inappropriate moment.</summary>
|
||||
GameFlow,
|
||||
|
||||
@@ -26,6 +30,18 @@ public enum CheatCategory
|
||||
/// <summary>A packet was sent to a player that should be broadcasted, or vice versa.</summary>
|
||||
Target,
|
||||
|
||||
/// <summary>A packet was sent on an invalid network object, like a PlayerControl without PlayerInfo.</summary>
|
||||
InvalidObject,
|
||||
|
||||
/// <summary>A packet was sent that exceeded the maximum allowed RPC size.</summary>
|
||||
PacketSize,
|
||||
|
||||
/// <summary>Legacy category for unsorted anticheat checks.</summary>
|
||||
Other,
|
||||
|
||||
/// <summary>A packet was sent with more items than possible in the game.</summary>
|
||||
ItemLimits,
|
||||
|
||||
/// <summary>A packet contained an identifier (friendcode or puid) that does not match the one known for that player.</summary>
|
||||
Identifiers,
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Impostor.Api.Config
|
||||
namespace Impostor.Api.Config
|
||||
{
|
||||
public class AntiCheatConfig
|
||||
{
|
||||
@@ -10,20 +10,32 @@
|
||||
|
||||
public CheatingHostMode AllowCheatingHosts { get; set; } = CheatingHostMode.Never;
|
||||
|
||||
public CheatingHostMode AllowHostOnlyExtensions { get; set; } = CheatingHostMode.IfRequested;
|
||||
|
||||
public bool EnableGameFlowChecks { get; set; } = true;
|
||||
|
||||
public bool EnableMustBeHostChecks { get; set; } = true;
|
||||
|
||||
public bool EnableInvalidObjectChecks { get; set; } = true;
|
||||
|
||||
public bool EnableColorLimitChecks { get; set; } = true;
|
||||
|
||||
public bool EnableNameLimitChecks { get; set; } = true;
|
||||
|
||||
public bool EnableItemLimitChecks { get; set; } = true;
|
||||
|
||||
public bool EnableOwnershipChecks { get; set; } = true;
|
||||
|
||||
public bool EnableRoleChecks { get; set; } = true;
|
||||
|
||||
public bool EnableTargetChecks { get; set; } = true;
|
||||
|
||||
public bool EnableIdentifierChecks { get; set; } = true;
|
||||
|
||||
public bool ForbidProtocolExtensions { get; set; } = true;
|
||||
|
||||
public bool EnablePacketSizeChecks { get; set; } = true;
|
||||
|
||||
public int PacketSizeLimit { get; set; } = 1203;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using Impostor.Api.Net.Inner.Objects;
|
||||
using Impostor.Api.Net.Inner.Objects;
|
||||
|
||||
namespace Impostor.Api.Events.Meeting
|
||||
{
|
||||
@@ -13,5 +13,15 @@ namespace Impostor.Api.Events.Meeting
|
||||
/// Gets a value indicating whether meeting result is a tie.
|
||||
/// </summary>
|
||||
bool IsTie { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether a Judge overruled the vote result.
|
||||
/// </summary>
|
||||
bool WasOverruled { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the nonce of the winning Judge overrule, or 0 if the meeting wasn't overruled.
|
||||
/// </summary>
|
||||
ushort OverrideId { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Impostor.Api.Events.Player
|
||||
namespace Impostor.Api.Events.Player
|
||||
{
|
||||
public interface IPlayerChatEvent : IPlayerEvent, IEventCancelable
|
||||
{
|
||||
@@ -6,5 +6,12 @@
|
||||
/// Gets the message sent by the player.
|
||||
/// </summary>
|
||||
string Message { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the message is sent to
|
||||
/// all players (true) or only to the host (false).
|
||||
/// To not send this message at all, cancel this event instead.
|
||||
/// </summary>
|
||||
bool SendToAllPlayers { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Impostor.Api.Innersloth;
|
||||
@@ -47,6 +48,11 @@ namespace Impostor.Api.Games
|
||||
/// </remarks>
|
||||
bool IsHostAuthoritive { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the mod GUID that the game was registered with through the AMCI.
|
||||
/// </summary>
|
||||
Guid? ModGuid { get; }
|
||||
|
||||
IClientPlayer? GetClientPlayer(int clientId);
|
||||
|
||||
T? FindObjectByNetId<T>(uint netId)
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556" PrivateAssets="all" />
|
||||
<PackageReference Include="Impostor.Hazel.Abstractions" Version="1.0.0" />
|
||||
<PackageReference Include="Impostor.Hazel.Abstractions" Version="1.0.1" />
|
||||
|
||||
<ProjectReference Include="..\Impostor.Api.Innersloth.Generator\Impostor.Api.Innersloth.Generator.csproj" ReferenceOutputAssembly="false" OutputItemType="Analyzer" />
|
||||
<AdditionalFiles Include="Innersloth/Data/**/*.json" />
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -5,7 +5,7 @@
|
||||
"4": "PlayerControl",
|
||||
"5": "MiraShipStatus",
|
||||
"6": "PolusShipStatus",
|
||||
"7": "ShipStatus",
|
||||
"7": "SkeldShipStatus",
|
||||
"8": "AirshipStatus",
|
||||
"9": "HideAndSeekManager",
|
||||
"10": "NormalGameManager",
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"Invalid": 0,
|
||||
"VisualTasks": 1,
|
||||
"GhostsDoTasks": 2,
|
||||
"ConfirmImpostor": 3,
|
||||
"AnonymousVotes": 4,
|
||||
"IsDefaults": 5,
|
||||
"UseFlashlight": 6,
|
||||
"SeekerFinalVents": 7,
|
||||
"SeekerFinalMap": 8,
|
||||
"SeekerPings": 9,
|
||||
"ShowCrewmateNames": 10,
|
||||
"Roles": 11,
|
||||
"ShapeshifterLeaveSkin": 1000,
|
||||
"ImpostorsCanSeeProtect": 1100,
|
||||
"NoisemakerImpostorAlert": 1300
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"Invalid": 0,
|
||||
"MapId": 1
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"KillCooldown": 0,
|
||||
"PlayerSpeed": 1,
|
||||
"EmergencyMeetingCount": 2,
|
||||
"EmergencyMeetingCooldown": 3,
|
||||
"DiscussionTime": 4,
|
||||
"VotingTime": 5,
|
||||
"CrewmateVision": 6,
|
||||
"HidingTime": 7,
|
||||
"MaxTimeInVent": 8,
|
||||
"ImposterVision": 9,
|
||||
"FinalHideTime": 10,
|
||||
"FinalHideImposterSpeed": 11
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"Invalid": 0,
|
||||
"KillDistances": 1
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"Invalid": 0,
|
||||
"KillCooldown": 1,
|
||||
"PlayerSpeedMod": 2,
|
||||
"ImpostorLightMod": 3,
|
||||
"CrewLightMod": 4,
|
||||
"CrewmateTimeInVent": 100,
|
||||
"FinalEscapeTime": 101,
|
||||
"EscapeTime": 102,
|
||||
"SeekerFinalSpeed": 103,
|
||||
"MaxPingTime": 104,
|
||||
"CrewmateFlashlightSize": 105,
|
||||
"ImpostorFlashlightSize": 106,
|
||||
"ShapeshifterCooldown": 1000,
|
||||
"ShapeshifterDuration": 1001,
|
||||
"ProtectionDurationSeconds": 1100,
|
||||
"GuardianAngelCooldown": 1101,
|
||||
"ScientistCooldown": 1200,
|
||||
"ScientistBatteryCharge": 1201,
|
||||
"EngineerCooldown": 1300,
|
||||
"EngineerInVentMaxTime": 1301,
|
||||
"PhantomCooldown": 1500,
|
||||
"PhantomDuration": 1501,
|
||||
"TrackerCooldown": 1550,
|
||||
"TrackerDuration": 1551,
|
||||
"TrackerDelay": 1552,
|
||||
"NoisemakerAlertDuration": 1600,
|
||||
"ViperDissolveTime": 1601,
|
||||
"DetectiveSuspectLimit": 1602,
|
||||
"JudgeTaskRequirementPercentage": 1620,
|
||||
"SpiritGuideCooldownSeconds": 1640
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"HumansByVote": 0,
|
||||
"HumansByTask": 1,
|
||||
"ImpostorByVote": 2,
|
||||
"ImpostorByKill": 3,
|
||||
"ImpostorBySabotage": 4,
|
||||
"CrewmatesByVote": 0,
|
||||
"CrewmatesByTask": 1,
|
||||
"ImpostorsByVote": 2,
|
||||
"ImpostorsByKill": 3,
|
||||
"ImpostorsBySabotage": 4,
|
||||
"ImpostorDisconnect": 5,
|
||||
"HumansDisconnect": 6,
|
||||
"HideAndSeek_ByTimer": 7,
|
||||
"HideAndSeek_ByKills": 8
|
||||
"CrewmateDisconnect": 6,
|
||||
"HideAndSeek_CrewmatesByTimer": 7,
|
||||
"HideAndSeek_ImpostorsByKills": 8
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"Invalid": 0,
|
||||
"MaxImpostors": 1,
|
||||
"MinPlayers": 2
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"Invalid": 0,
|
||||
"NumImpostors": 1,
|
||||
"KillDistance": 2,
|
||||
"NumEmergencyMeetings": 3,
|
||||
"EmergencyCooldown": 4,
|
||||
"DiscussionTime": 5,
|
||||
"VotingTime": 6,
|
||||
"MaxImpostors": 7,
|
||||
"MinPlayers": 8,
|
||||
"MaxPlayers": 9,
|
||||
"NumCommonTasks": 10,
|
||||
"NumShortTasks": 11,
|
||||
"NumLongTasks": 12,
|
||||
"TaskBarMode": 13,
|
||||
"Tag": 14,
|
||||
"CrewmatesRemainingForVitals": 100,
|
||||
"CrewmateVentUses": 101,
|
||||
"ImpostorPlayerID": 102,
|
||||
"RulePreset": 200
|
||||
}
|
||||
@@ -9,5 +9,9 @@
|
||||
"ImpostorGhost": 7,
|
||||
"Noisemaker": 8,
|
||||
"Phantom": 9,
|
||||
"Tracker": 10
|
||||
"Tracker": 10,
|
||||
"Detective": 12,
|
||||
"Viper": 18,
|
||||
"Judge": 19,
|
||||
"SpiritGuide": 21
|
||||
}
|
||||
@@ -8,15 +8,15 @@
|
||||
"SetName": 6,
|
||||
"CheckColor": 7,
|
||||
"SetColor": 8,
|
||||
"SetHat": 9,
|
||||
"SetSkin": 10,
|
||||
"SetHat_Deprecated": 9,
|
||||
"SetSkin_Deprecated": 10,
|
||||
"ReportDeadBody": 11,
|
||||
"MurderPlayer": 12,
|
||||
"SendChat": 13,
|
||||
"StartMeeting": 14,
|
||||
"SetScanner": 15,
|
||||
"SendChatNote": 16,
|
||||
"SetPet": 17,
|
||||
"SetPet_Deprecated": 17,
|
||||
"SetStartCounter": 18,
|
||||
"EnterVent": 19,
|
||||
"ExitVent": 20,
|
||||
@@ -33,8 +33,8 @@
|
||||
"SendQuickChat": 33,
|
||||
"BootFromVent": 34,
|
||||
"UpdateSystem": 35,
|
||||
"SetVisor": 36,
|
||||
"SetNamePlate": 37,
|
||||
"SetVisor_Deprecated": 36,
|
||||
"SetNamePlate_Deprecated": 37,
|
||||
"SetLevel": 38,
|
||||
"SetHatStr": 39,
|
||||
"SetSkinStr": 40,
|
||||
@@ -59,5 +59,7 @@
|
||||
"CheckVanish": 62,
|
||||
"StartVanish": 63,
|
||||
"CheckAppear": 64,
|
||||
"StartAppear": 65
|
||||
"StartAppear": 65,
|
||||
"QueueOverruleVotes": 66,
|
||||
"SpiritGuideMessage": 67
|
||||
}
|
||||
@@ -626,6 +626,14 @@
|
||||
"SanctionSelfHarmPromotion": 1720,
|
||||
"SanctionRepeatOffender10": 1721,
|
||||
"SanctionUnknown": 1722,
|
||||
"ReplicatorAbility": 1800,
|
||||
"ReplicatorRole": 1801,
|
||||
"ReplicatorBlurb": 1802,
|
||||
"ReplicatorBlurbMed": 1803,
|
||||
"ReplicatorBlurbLong": 1804,
|
||||
"ReplicatorCooldown": 1805,
|
||||
"ReplicatorDuration": 1806,
|
||||
"ReplicatorQuantity": 1807,
|
||||
"ScreenShakeOption": 1900,
|
||||
"FeaturedItems": 1901,
|
||||
"FeaturedBundles": 1902,
|
||||
@@ -1379,6 +1387,9 @@
|
||||
"MissingGuardianEmailText": 3325,
|
||||
"AprilFoolsModeToggle": 3326,
|
||||
"LobbyChangeSettingNotification": 3327,
|
||||
"LoadingBarLoading": 3328,
|
||||
"LoadingBarGameStart": 3329,
|
||||
"LoadingBarGameStartWaitingPlayers": 3330,
|
||||
"ImpostorsCategory": 3350,
|
||||
"RolesCategory": 3351,
|
||||
"MeetingsCategory": 3352,
|
||||
@@ -1405,6 +1416,10 @@
|
||||
"StandardRulesPresetDescriptionHideAndSeek": 3373,
|
||||
"GameModeStandardSeek": 3374,
|
||||
"HostYouLabel": 3375,
|
||||
"GamePresetsPopupTitle": 3376,
|
||||
"GamePresetsPopupText": 3377,
|
||||
"GamePresetsPopupContinue": 3378,
|
||||
"GamePresetsPopupBack": 3379,
|
||||
"MiningPit": 3400,
|
||||
"Jungle": 3401,
|
||||
"BuildSandcastle": 3402,
|
||||
@@ -1492,5 +1507,182 @@
|
||||
"QCLobbyClosing": 3547,
|
||||
"QCChangeCosmetics": 3548,
|
||||
"QCWaitingPlayers": 3549,
|
||||
"QCLetsWaitPlayers": 3550
|
||||
"QCLetsWaitPlayers": 3550,
|
||||
"ServerLabel": 3551,
|
||||
"JoinLobby": 3552,
|
||||
"ChatLabel": 3553,
|
||||
"ToolTipDefault": 3554,
|
||||
"LevelLabel": 3555,
|
||||
"NoneLowercaseLabel": 3556,
|
||||
"ToolTipClassic": 3557,
|
||||
"ToolTipHide": 3558,
|
||||
"ToolTipMap": 3559,
|
||||
"ToolTipCapacity": 3560,
|
||||
"ToolTipLevel": 3561,
|
||||
"ToolTipServer": 3562,
|
||||
"ToolTipCrossplay": 3563,
|
||||
"ToolTipNoCrossplay": 3564,
|
||||
"ToolTipLanguage": 3565,
|
||||
"ConfirmSettings": 3566,
|
||||
"ConfirmSettingsTooltip": 3567,
|
||||
"EnterCodeElipsis": 3568,
|
||||
"ToolTipChatTab": 3569,
|
||||
"DropshipsLabel": 3570,
|
||||
"FiltersActive": 3571,
|
||||
"ClearFilters": 3572,
|
||||
"MoreLabel": 3573,
|
||||
"HostNounEmpty": 3574,
|
||||
"ToolTipSkeld": 3575,
|
||||
"ToolTipMIRA": 3576,
|
||||
"ToolTipPolus": 3577,
|
||||
"ToolTipAirship": 3578,
|
||||
"ToolTipFungle": 3579,
|
||||
"FilterLow": 3580,
|
||||
"FilterMed": 3581,
|
||||
"FilterHigh": 3582,
|
||||
"FilterVeryHigh": 3583,
|
||||
"FilterLong": 3584,
|
||||
"FilterNone": 3585,
|
||||
"FilterPlayerLevel": 3586,
|
||||
"RefreshLabel": 3587,
|
||||
"JoinGameLabel": 3588,
|
||||
"FilterClearText": 3589,
|
||||
"MatchesFoundLabel": 3590,
|
||||
"FilterGameTooltip": 3591,
|
||||
"FilterMapTooltip": 3592,
|
||||
"FilterImpostorTooltip": 3593,
|
||||
"FilterRoleTooltip": 3594,
|
||||
"FilterSpeedTooltip": 3595,
|
||||
"FilterKillCDTooltip": 3596,
|
||||
"FilterVotingTooltip": 3597,
|
||||
"FilterVisualTaskTooltip": 3598,
|
||||
"FilterAnonVotesTooltip": 3599,
|
||||
"FilterConfirmEjectTooltip": 3600,
|
||||
"FilterLevelTooltip": 3601,
|
||||
"FilterCrossplayTooltip": 3602,
|
||||
"FilterDefaultTooltip": 3603,
|
||||
"FilterFlashlightTooltip": 3604,
|
||||
"FilterHidingTimeTooltip": 3605,
|
||||
"FilterFinalTimeTooltip": 3606,
|
||||
"FilterMaxVentTooltip": 3607,
|
||||
"FilterBeginner": 3608,
|
||||
"FilterIntermediate": 3609,
|
||||
"FilterExpert": 3610,
|
||||
"FilterTag": 3611,
|
||||
"FilterTagTooltip": 3612,
|
||||
"TooltipTag": 3613,
|
||||
"SelectedLabel": 3614,
|
||||
"TooltipSpeedInfo": 3615,
|
||||
"TooltipKillCDInfo": 3616,
|
||||
"TooltipVotingInfo": 3617,
|
||||
"TooptipHidingTimeInfo": 3618,
|
||||
"TooltipFinalTimeInfo": 3619,
|
||||
"FilterNoMaps": 3620,
|
||||
"MaxFilters": 3621,
|
||||
"XboxCommsMultiplayerError": 3700,
|
||||
"XboxCommsUGError": 3701,
|
||||
"XboxCommsCreateGameError": 3702,
|
||||
"XboxCommsSearchError": 3703,
|
||||
"XboxCommsJoinError": 3704,
|
||||
"WatchAdBody": 3800,
|
||||
"WatchAdTitle": 3801,
|
||||
"WatchAd": 3802,
|
||||
"RewardAdTitle": 3803,
|
||||
"RewardAdBody1": 3804,
|
||||
"RewardAdBody2": 3805,
|
||||
"WatchAdErrorBody": 3806,
|
||||
"WatchAdErrorTitle": 3807,
|
||||
"DetectiveRole": 4000,
|
||||
"DetectiveAbility": 4001,
|
||||
"DetectiveAbilityNotes": 4002,
|
||||
"DetectiveBlurb": 4003,
|
||||
"DetectiveBlurbMed": 4004,
|
||||
"DetectiveBlurbLong": 4005,
|
||||
"DetectiveNotesEmpty": 4006,
|
||||
"DetectiveNotesFoundDead": 4007,
|
||||
"DetectiveNotesSuspectInfo": 4008,
|
||||
"DetectiveNearLocation": 4009,
|
||||
"DetectiveNotesKiller": 4010,
|
||||
"StatsDetectiveQuestioned": 4011,
|
||||
"StatsDetectiveCasesFilled": 4012,
|
||||
"DetectiveSuspectLimit": 4013,
|
||||
"RolesHelp_Detective_01": 4014,
|
||||
"RolesHelp_Detective_02": 4015,
|
||||
"ViperRole": 4037,
|
||||
"ViperBlurb": 4038,
|
||||
"ViperBlurbMed": 4039,
|
||||
"ViperBlurbLong": 4040,
|
||||
"ViperDissolveTime": 4041,
|
||||
"ViperAbility": 4042,
|
||||
"StatsViperBodiesDissolved": 4043,
|
||||
"RolesHelp_Viper_01": 4044,
|
||||
"RolesHelp_Viper_02": 4045,
|
||||
"QCWhereWasDead": 4060,
|
||||
"QCInterrogateCrew": 4061,
|
||||
"QCInterrogated": 4062,
|
||||
"QCBodyDissolved": 4063,
|
||||
"QCBodyVeryDissolved": 4064,
|
||||
"QCBodyAlmostDissolved": 4065,
|
||||
"QCDetectives": 4066,
|
||||
"QCVipers": 4067,
|
||||
"DetectiveNotesTitleVictim": 4068,
|
||||
"DetectiveNotesTitleFoundDead": 4069,
|
||||
"DetectiveNotesTitleKiller": 4070,
|
||||
"DetectiveNotesTitleSuspects": 4071,
|
||||
"DetectiveNotesTitleLocationsAtTimeOfDeath": 4072,
|
||||
"DetectiveNotesPromptPreposition": 4073,
|
||||
"DetectiveNotesPromptLocation": 4074,
|
||||
"DetectiveNotesPromptImpostor": 4075,
|
||||
"DetectiveNotesPromptSuspect": 4076,
|
||||
"DetectiveNotesPrepositionIn": 4077,
|
||||
"DetectiveNotesPrepositionBy": 4078,
|
||||
"DetectiveNotesPrepositionMaybe": 4079,
|
||||
"DetectiveNotesTitlePostIt": 4080,
|
||||
"DetectiveNotesPostIt": 4081,
|
||||
"DetectiveNotesSuspectNumber": 4082,
|
||||
"AprilFoolsMode": 4083,
|
||||
"MatchInfoGuideTitle": 4084,
|
||||
"MatchInfoGuidePlayersLabel": 4085,
|
||||
"MatchInfoGuideNoRolesMessage": 4086,
|
||||
"JudgeRole": 4090,
|
||||
"JudgeAbility": 4091,
|
||||
"JudgeBlurb": 4092,
|
||||
"JudgeBlurbMed": 4093,
|
||||
"JudgeBlurbLong": 4094,
|
||||
"JudgeTaskRequirement": 4095,
|
||||
"RolesHelp_Judge_01": 4096,
|
||||
"RolesHelp_Judge_02": 4097,
|
||||
"JudgeAbilityUnlocked": 4098,
|
||||
"JudgeAbilityProgress": 4099,
|
||||
"JudgeCourtAdjourned": 4100,
|
||||
"JudgeHasSpoken": 4101,
|
||||
"StatsJudgeSuccessfulOverrules": 4102,
|
||||
"StatsJudgeFailedOverrules": 4103,
|
||||
"QCJudges": 4104,
|
||||
"QCJudgeOverrule": 4105,
|
||||
"QCJudgePlease": 4106,
|
||||
"QCJudgeWillOverrule": 4107,
|
||||
"JudgeBeatYouToIt": 4108,
|
||||
"SpiritGuideRole": 4120,
|
||||
"SpiritGuideAbility": 4121,
|
||||
"SpiritGuideBlurb": 4122,
|
||||
"SpiritGuideBlurbMed": 4123,
|
||||
"SpiritGuideBlurbLong": 4124,
|
||||
"SpiritGuideCooldown": 4125,
|
||||
"SpiritGuideAbilityCancel": 4126,
|
||||
"SpiritGuideAbilitySend": 4127,
|
||||
"SpiritGuideAbilityRefresh": 4128,
|
||||
"StatsSpiritGuideSentMessage": 4129,
|
||||
"RolesHelp_SpiritGuide_01": 4130,
|
||||
"RolesHelp_SpiritGuide_02": 4131,
|
||||
"QCInfluencerMessage": 4132,
|
||||
"QCBecauseInfluencer": 4133,
|
||||
"QCAnyInfluencerMessage": 4134,
|
||||
"QCInfluencers": 4135,
|
||||
"RedeemCodeLabel": 4200,
|
||||
"CodeLengthLabel": 4201,
|
||||
"CodeErrorLabel": 4202,
|
||||
"CodeAlreadyRedeemedLabel": 4203,
|
||||
"CodeRedeemPopupLabel": 4204,
|
||||
"HorseModeToggle": 4300
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"Invalid": 0,
|
||||
"Keywords": 1
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"dumpostorVersion": "1.0.1",
|
||||
"gameVersion": "2024.10.29",
|
||||
"dumpostorVersion": "1.0.2-dev",
|
||||
"gameVersion": "2026.9.29",
|
||||
"platformType": "StandaloneItch"
|
||||
}
|
||||
@@ -1,93 +1,93 @@
|
||||
{
|
||||
"0": {
|
||||
"room": "Cafeteria",
|
||||
"position": {
|
||||
"x": 6.3936,
|
||||
"y": 1.33848
|
||||
}
|
||||
},
|
||||
"1": {
|
||||
"room": "Storage",
|
||||
"position": {
|
||||
"x": 5.268,
|
||||
"y": -14.2884
|
||||
}
|
||||
},
|
||||
"2": {
|
||||
"room": "UpperEngine",
|
||||
"position": {
|
||||
"x": 16.930801,
|
||||
"y": -2.1276002
|
||||
}
|
||||
},
|
||||
"3": {
|
||||
"room": "Cafeteria",
|
||||
"position": {
|
||||
"x": 0.708,
|
||||
"y": -4.992
|
||||
}
|
||||
},
|
||||
"4": {
|
||||
"room": "LowerEngine",
|
||||
"position": {
|
||||
"x": 14.6808,
|
||||
"y": -11.472
|
||||
}
|
||||
},
|
||||
"5": {
|
||||
"room": "UpperEngine",
|
||||
"position": {
|
||||
"x": 14.676002,
|
||||
"y": 1.3406401
|
||||
}
|
||||
},
|
||||
"6": {
|
||||
"room": "Security",
|
||||
"position": {
|
||||
"x": 14.734801,
|
||||
"y": -5.1417603
|
||||
}
|
||||
},
|
||||
"7": {
|
||||
"room": "Storage",
|
||||
"position": {
|
||||
"x": -1.1316001,
|
||||
"y": -11.98128
|
||||
}
|
||||
},
|
||||
"8": {
|
||||
"room": "Cafeteria",
|
||||
"position": {
|
||||
"x": -5.1432004,
|
||||
"y": 1.3355999
|
||||
}
|
||||
},
|
||||
"9": {
|
||||
"1": {
|
||||
"room": "Cafeteria",
|
||||
"position": {
|
||||
"x": 0.708,
|
||||
"y": -4.992
|
||||
}
|
||||
},
|
||||
"2": {
|
||||
"room": "Cafeteria",
|
||||
"position": {
|
||||
"x": 6.3936,
|
||||
"y": 1.33848
|
||||
}
|
||||
},
|
||||
"3": {
|
||||
"room": "Electrical",
|
||||
"position": {
|
||||
"x": 9.53616,
|
||||
"y": -13.416001
|
||||
}
|
||||
},
|
||||
"10": {
|
||||
"room": "MedBay",
|
||||
"4": {
|
||||
"room": "Storage",
|
||||
"position": {
|
||||
"x": 9.1452,
|
||||
"y": -0.4439999
|
||||
"x": -1.1316001,
|
||||
"y": -11.98128
|
||||
}
|
||||
},
|
||||
"11": {
|
||||
"5": {
|
||||
"room": "Storage",
|
||||
"position": {
|
||||
"x": 0.70919997,
|
||||
"y": -8.560801
|
||||
}
|
||||
},
|
||||
"6": {
|
||||
"room": "Storage",
|
||||
"position": {
|
||||
"x": 5.268,
|
||||
"y": -14.2884
|
||||
}
|
||||
},
|
||||
"7": {
|
||||
"room": "LowerEngine",
|
||||
"position": {
|
||||
"x": 14.6808,
|
||||
"y": -11.472
|
||||
}
|
||||
},
|
||||
"8": {
|
||||
"room": "LowerEngine",
|
||||
"position": {
|
||||
"x": 16.930801,
|
||||
"y": -8.9664
|
||||
}
|
||||
},
|
||||
"12": {
|
||||
"room": "Storage",
|
||||
"9": {
|
||||
"room": "UpperEngine",
|
||||
"position": {
|
||||
"x": 0.70919997,
|
||||
"y": -8.560801
|
||||
"x": 14.676002,
|
||||
"y": 1.3406401
|
||||
}
|
||||
},
|
||||
"10": {
|
||||
"room": "UpperEngine",
|
||||
"position": {
|
||||
"x": 16.930801,
|
||||
"y": -2.1276002
|
||||
}
|
||||
},
|
||||
"11": {
|
||||
"room": "Security",
|
||||
"position": {
|
||||
"x": 14.734801,
|
||||
"y": -5.1417603
|
||||
}
|
||||
},
|
||||
"12": {
|
||||
"room": "MedBay",
|
||||
"position": {
|
||||
"x": 9.1452,
|
||||
"y": -0.4439999
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"initialSpawnCenter": {
|
||||
"x": -0.72,
|
||||
"y": 0.62
|
||||
"x": 0.87,
|
||||
"y": 0.69
|
||||
},
|
||||
"meetingSpawnCenter": {
|
||||
"x": -0.72,
|
||||
"y": 0.62
|
||||
"x": 0.87,
|
||||
"y": 0.69
|
||||
},
|
||||
"meetingSpawnCenter2": {
|
||||
"x": 0,
|
||||
|
||||
@@ -476,5 +476,138 @@
|
||||
"usableDistance": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
"29": {
|
||||
"type": "NormalPlayerTask",
|
||||
"taskType": "VentCleaning",
|
||||
"length": "Short",
|
||||
"consoles": [
|
||||
{
|
||||
"id": 0,
|
||||
"room": "Admin",
|
||||
"position": {
|
||||
"x": -2.544,
|
||||
"y": -9.955201
|
||||
},
|
||||
"usableDistance": 1
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"room": "Cafeteria",
|
||||
"position": {
|
||||
"x": -4.2588,
|
||||
"y": -0.27600002
|
||||
},
|
||||
"usableDistance": 1
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"room": "Nav",
|
||||
"position": {
|
||||
"x": -16.008001,
|
||||
"y": -6.3840003
|
||||
},
|
||||
"usableDistance": 1
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"room": "Nav",
|
||||
"position": {
|
||||
"x": -16.008001,
|
||||
"y": -3.1680002
|
||||
},
|
||||
"usableDistance": 1
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"room": "Weapons",
|
||||
"position": {
|
||||
"x": -8.820001,
|
||||
"y": 3.3240001
|
||||
},
|
||||
"usableDistance": 1
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"room": "Shields",
|
||||
"position": {
|
||||
"x": -9.5232,
|
||||
"y": -14.337601
|
||||
},
|
||||
"usableDistance": 1
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"room": "Hallway",
|
||||
"position": {
|
||||
"x": -9.384,
|
||||
"y": -6.438
|
||||
},
|
||||
"usableDistance": 1
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"room": "Electrical",
|
||||
"position": {
|
||||
"x": 9.7764,
|
||||
"y": -8.034
|
||||
},
|
||||
"usableDistance": 1
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"room": "Reactor",
|
||||
"position": {
|
||||
"x": 20.796001,
|
||||
"y": -6.9528003
|
||||
},
|
||||
"usableDistance": 1
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"room": "Reactor",
|
||||
"position": {
|
||||
"x": 21.876,
|
||||
"y": -3.0516002
|
||||
},
|
||||
"usableDistance": 1
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"room": "LowerEngine",
|
||||
"position": {
|
||||
"x": 15.2508,
|
||||
"y": -13.656001
|
||||
},
|
||||
"usableDistance": 1
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"room": "UpperEngine",
|
||||
"position": {
|
||||
"x": 15.288,
|
||||
"y": 2.52
|
||||
},
|
||||
"usableDistance": 1
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"room": "Security",
|
||||
"position": {
|
||||
"x": 12.534,
|
||||
"y": -6.9492
|
||||
},
|
||||
"usableDistance": 1
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"room": "MedBay",
|
||||
"position": {
|
||||
"x": 10.608001,
|
||||
"y": -4.176
|
||||
},
|
||||
"usableDistance": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"0": {
|
||||
"name": "AdminVent",
|
||||
"name": "AdminVent (1)",
|
||||
"position": {
|
||||
"x": -2.544,
|
||||
"y": -9.955201
|
||||
@@ -10,7 +10,7 @@
|
||||
"right": 1
|
||||
},
|
||||
"1": {
|
||||
"name": "BigYVent",
|
||||
"name": "BigYVent (1)",
|
||||
"position": {
|
||||
"x": -9.384,
|
||||
"y": -6.438
|
||||
@@ -20,7 +20,7 @@
|
||||
"right": 2
|
||||
},
|
||||
"2": {
|
||||
"name": "CafeVent",
|
||||
"name": "CafeVent (1)",
|
||||
"position": {
|
||||
"x": -4.2588,
|
||||
"y": -0.27600002
|
||||
@@ -30,7 +30,7 @@
|
||||
"right": 1
|
||||
},
|
||||
"3": {
|
||||
"name": "ElecVent",
|
||||
"name": "ElecVent (1)",
|
||||
"position": {
|
||||
"x": 9.7764,
|
||||
"y": -8.034
|
||||
@@ -40,7 +40,7 @@
|
||||
"right": 6
|
||||
},
|
||||
"4": {
|
||||
"name": "LEngineVent",
|
||||
"name": "LEngineVent (1)",
|
||||
"position": {
|
||||
"x": 15.288,
|
||||
"y": 2.52
|
||||
@@ -50,7 +50,7 @@
|
||||
"right": null
|
||||
},
|
||||
"5": {
|
||||
"name": "LifeSuppVent",
|
||||
"name": "SecurityVent",
|
||||
"position": {
|
||||
"x": 12.534,
|
||||
"y": -6.9492
|
||||
@@ -60,7 +60,7 @@
|
||||
"right": 3
|
||||
},
|
||||
"6": {
|
||||
"name": "MedVent",
|
||||
"name": "MedVent (1)",
|
||||
"position": {
|
||||
"x": 10.608001,
|
||||
"y": -4.176
|
||||
@@ -70,7 +70,7 @@
|
||||
"right": 3
|
||||
},
|
||||
"7": {
|
||||
"name": "WeaponsVent",
|
||||
"name": "WeaponsVent (1)",
|
||||
"position": {
|
||||
"x": -8.820001,
|
||||
"y": 3.3240001
|
||||
@@ -80,7 +80,7 @@
|
||||
"right": 12
|
||||
},
|
||||
"8": {
|
||||
"name": "ReactorVent",
|
||||
"name": "ReactorVent (1)",
|
||||
"position": {
|
||||
"x": 20.796001,
|
||||
"y": -6.9528003
|
||||
@@ -90,7 +90,7 @@
|
||||
"right": null
|
||||
},
|
||||
"9": {
|
||||
"name": "REngineVent",
|
||||
"name": "REngineVent (1)",
|
||||
"position": {
|
||||
"x": 15.2508,
|
||||
"y": -13.656001
|
||||
@@ -100,7 +100,7 @@
|
||||
"right": null
|
||||
},
|
||||
"10": {
|
||||
"name": "ShieldsVent",
|
||||
"name": "ShieldsVent (1)",
|
||||
"position": {
|
||||
"x": -9.5232,
|
||||
"y": -14.337601
|
||||
@@ -110,7 +110,7 @@
|
||||
"right": null
|
||||
},
|
||||
"11": {
|
||||
"name": "UpperReactorVent",
|
||||
"name": "UpperReactorVent (1)",
|
||||
"position": {
|
||||
"x": 21.876,
|
||||
"y": -3.0516002
|
||||
@@ -120,7 +120,7 @@
|
||||
"right": null
|
||||
},
|
||||
"12": {
|
||||
"name": "NavVentNorth",
|
||||
"name": "NavVentNorth (1)",
|
||||
"position": {
|
||||
"x": -16.008001,
|
||||
"y": -3.1680002
|
||||
@@ -130,7 +130,7 @@
|
||||
"right": 7
|
||||
},
|
||||
"13": {
|
||||
"name": "NavVentSouth",
|
||||
"name": "NavVentSouth (1)",
|
||||
"position": {
|
||||
"x": -16.008001,
|
||||
"y": -6.3840003
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"hatIds": [
|
||||
"hat_pk04_beanie",
|
||||
"hat_pk05_Cheese",
|
||||
"hat_pk05_Cherry",
|
||||
"hat_Chocolate",
|
||||
"hat_Zipper",
|
||||
"hat_pk02_Crown",
|
||||
"hat_babybean",
|
||||
"hat_Sorry",
|
||||
"hat_mareLwyd",
|
||||
"hat_fishingHat",
|
||||
"hat_schnapp",
|
||||
"hat_pk03_Traffic",
|
||||
"hat_Pot",
|
||||
"hat_AbominalHat",
|
||||
"hat_tombstone",
|
||||
"hat_pk04_MiniCrewmate",
|
||||
"hat_pk05_Wizardhat",
|
||||
"hat_pk05_Ellryhat",
|
||||
"hat_pk05_EllieToppat"
|
||||
],
|
||||
"visorIds": [
|
||||
"visor_pk01_AngeryVisor",
|
||||
"visor_pk01_DumStickerVisor",
|
||||
"visor_vr_Vr-Black",
|
||||
"visor_pk01_PaperMaskVisor",
|
||||
"visor_wash"
|
||||
],
|
||||
"petIds": [
|
||||
"pet_YuleGoatPet",
|
||||
"pet_HamPet",
|
||||
"pet_Lava",
|
||||
"pet_Snow",
|
||||
"pet_Bush"
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Impostor.Api.Innersloth.GameFilters
|
||||
{
|
||||
[Serializable]
|
||||
public class BoolGameFilter : ISubFilter
|
||||
{
|
||||
[JsonPropertyName("FilterType")]
|
||||
public string FilterType { get; } = "bool";
|
||||
|
||||
[JsonPropertyName("AcceptedValues")]
|
||||
public required List<bool> AcceptedValues { get; set; }
|
||||
|
||||
[JsonPropertyName("OptionEnum")]
|
||||
public required BoolOptionNames OptionEnum { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Impostor.Api.Innersloth.GameFilters
|
||||
{
|
||||
[Serializable]
|
||||
public class CategorizedGameFilter : ISubFilter
|
||||
{
|
||||
[JsonPropertyName("FilterType")]
|
||||
public string FilterType { get; } = "cat";
|
||||
|
||||
[JsonPropertyName("AcceptedValues")]
|
||||
public required List<int> AcceptedValues { get; set; }
|
||||
|
||||
[JsonPropertyName("OptionEnum")]
|
||||
public required CategorizedOptionNames OptionEnum { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Impostor.Api.Innersloth.GameFilters
|
||||
{
|
||||
[Serializable]
|
||||
public class ChatModeGameFilter : ISubFilter
|
||||
{
|
||||
[JsonPropertyName("FilterType")]
|
||||
public string FilterType { get; } = "chat";
|
||||
|
||||
[JsonPropertyName("AcceptedValues")]
|
||||
public required byte AcceptedValues { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Impostor.Api.Innersloth.GameFilters
|
||||
{
|
||||
[Serializable]
|
||||
public class GameFilter
|
||||
{
|
||||
[JsonConstructor]
|
||||
public GameFilter(string optionType, string key, string subFilterString)
|
||||
{
|
||||
this.OptionType = optionType;
|
||||
this.Key = key;
|
||||
this.SubFilterString = subFilterString;
|
||||
this.SubFilter = ResolveSubFilter(OptionType, SubFilterString);
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public ISubFilter SubFilter { get; set; }
|
||||
|
||||
[JsonPropertyName("OptionType")]
|
||||
public required string OptionType { get; set; }
|
||||
|
||||
[JsonPropertyName("Key")]
|
||||
public required string Key { get; set; }
|
||||
|
||||
[JsonPropertyName("SubFilterString")]
|
||||
public required string SubFilterString { get; set; }
|
||||
|
||||
public void ModifySubFilter(ISubFilter subFilter)
|
||||
{
|
||||
this.SubFilter = subFilter;
|
||||
}
|
||||
|
||||
[OnSerializing]
|
||||
internal void OnSerializing(StreamingContext context)
|
||||
{
|
||||
this.SubFilterString = JsonSerializer.Serialize(this.SubFilter);
|
||||
}
|
||||
|
||||
[OnDeserialized]
|
||||
internal void OnDeserialized(StreamingContext context)
|
||||
{
|
||||
this.SubFilter = this.ResolveSubFilter(this.OptionType, this.SubFilterString);
|
||||
}
|
||||
|
||||
private ISubFilter ResolveSubFilter(string type, string filterString)
|
||||
{
|
||||
if (type != null)
|
||||
{
|
||||
switch (type.ToLower())
|
||||
{
|
||||
case "int":
|
||||
return JsonSerializer.Deserialize<IntGameFilter>(filterString)
|
||||
?? throw new InvalidOperationException("Deserialization returned null for IntGameFilter");
|
||||
case "platform":
|
||||
return JsonSerializer.Deserialize<PlatformGameFilter>(filterString)
|
||||
?? throw new InvalidOperationException("Deserialization returned null for PlatformGameFilter");
|
||||
case "cat":
|
||||
return JsonSerializer.Deserialize<CategorizedGameFilter>(filterString)
|
||||
?? throw new InvalidOperationException("Deserialization returned null for CategorizedGameFilter");
|
||||
case "languages":
|
||||
return JsonSerializer.Deserialize<LanguageFilter>(filterString)
|
||||
?? throw new InvalidOperationException("Deserialization returned null for LanguageFilter");
|
||||
case "chat":
|
||||
return JsonSerializer.Deserialize<ChatModeGameFilter>(filterString)
|
||||
?? throw new InvalidOperationException("Deserialization returned null for ChatModeGameFilter");
|
||||
case "map":
|
||||
return JsonSerializer.Deserialize<MapGameFilter>(filterString)
|
||||
?? throw new InvalidOperationException("Deserialization returned null for MapGameFilter");
|
||||
case "bool":
|
||||
return JsonSerializer.Deserialize<BoolGameFilter>(filterString)
|
||||
?? throw new InvalidOperationException("Deserialization returned null for BoolGameFilter");
|
||||
case "mod":
|
||||
return JsonSerializer.Deserialize<ModFilter>(filterString)
|
||||
?? throw new InvalidOperationException("Deserialization returned null for ModFilter");
|
||||
default:
|
||||
throw new InvalidOperationException("No type matches subfilter");
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("Must provide type for sub filter");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Impostor.Api.Innersloth.GameFilters
|
||||
{
|
||||
[Serializable]
|
||||
public class GameFilterSet
|
||||
{
|
||||
[JsonConstructor]
|
||||
public GameFilterSet(GameModes gameMode, List<GameFilter> filters)
|
||||
{
|
||||
GameMode = gameMode;
|
||||
Filters = filters;
|
||||
}
|
||||
|
||||
[JsonPropertyName("GameMode")]
|
||||
public required GameModes GameMode { get; set; }
|
||||
|
||||
[JsonPropertyName("Filters")]
|
||||
public required List<GameFilter> Filters { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Impostor.Api.Innersloth.GameFilters
|
||||
{
|
||||
[Serializable]
|
||||
public class GameFiltersList
|
||||
{
|
||||
[JsonPropertyName("FilterSets")]
|
||||
public required List<GameFilterSet> FilterSets { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Impostor.Api.Innersloth.GameFilters
|
||||
{
|
||||
public interface ISubFilter
|
||||
{
|
||||
string FilterType { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Impostor.Api.Innersloth.GameFilters
|
||||
{
|
||||
[Serializable]
|
||||
public class IntGameFilter : ISubFilter
|
||||
{
|
||||
[JsonPropertyName("FilterType")]
|
||||
public string FilterType { get; } = "int";
|
||||
|
||||
[JsonPropertyName("AcceptedValues")]
|
||||
public required List<int> AcceptedValues { get; set; }
|
||||
|
||||
[JsonPropertyName("OptionEnum")]
|
||||
public required Int32OptionNames OptionEnum { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Impostor.Api.Innersloth.GameFilters
|
||||
{
|
||||
[Serializable]
|
||||
public class LanguageFilter : ISubFilter
|
||||
{
|
||||
[JsonPropertyName("FilterType")]
|
||||
public string FilterType { get; } = "languages";
|
||||
|
||||
[JsonPropertyName("AcceptedValues")]
|
||||
public required uint AcceptedValues { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Impostor.Api.Innersloth.GameFilters
|
||||
{
|
||||
[Serializable]
|
||||
public class MapGameFilter : ISubFilter
|
||||
{
|
||||
[JsonPropertyName("FilterType")]
|
||||
public string FilterType { get; } = "map";
|
||||
|
||||
[JsonPropertyName("AcceptedValues")]
|
||||
public required byte AcceptedValues { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
// https://github.com/Innersloth-LLC/AmongUsModdingInformation
|
||||
namespace Impostor.Api.Innersloth.GameFilters
|
||||
{
|
||||
[Serializable]
|
||||
public class ModFilter : ISubFilter
|
||||
{
|
||||
[JsonPropertyName("FilterType")]
|
||||
public string FilterType { get; } = "mod";
|
||||
|
||||
[JsonPropertyName("AcceptedValues")]
|
||||
public required Guid AcceptedValues { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Impostor.Api.Innersloth.GameFilters
|
||||
{
|
||||
[Serializable]
|
||||
public class PlatformGameFilter : ISubFilter
|
||||
{
|
||||
[JsonPropertyName("FilterType")]
|
||||
public string FilterType { get; } = "platform";
|
||||
|
||||
[JsonPropertyName("AcceptedValues")]
|
||||
public required uint AcceptedValues { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace Impostor.Api.Innersloth.GameOptions;
|
||||
|
||||
// TODO: generate this enum based on game data
|
||||
|
||||
/// <summary>
|
||||
/// Tag that can be added to indicate the expected skill level of players in this lobby.
|
||||
/// </summary>
|
||||
public enum GameTags
|
||||
{
|
||||
None = 0,
|
||||
Beginner = 1,
|
||||
Intermediate = 2,
|
||||
Expert = 3,
|
||||
}
|
||||
@@ -2,7 +2,7 @@ namespace Impostor.Api.Innersloth.GameOptions;
|
||||
|
||||
public class HideNSeekGameOptions : IGameOptions
|
||||
{
|
||||
public const int LatestVersion = 8;
|
||||
public const int LatestVersion = 12;
|
||||
|
||||
public HideNSeekGameOptions(byte version = LatestVersion)
|
||||
{
|
||||
@@ -93,6 +93,11 @@ public class HideNSeekGameOptions : IGameOptions
|
||||
|
||||
public float MaxPingTime { get; set; } = 6f;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the experience level of people in the lobby: Beginner, Intermediate or Expert.
|
||||
/// </summary>
|
||||
public GameTags Tag { get; set; } = 0;
|
||||
|
||||
public static HideNSeekGameOptions Deserialize(IMessageReader reader, byte version)
|
||||
{
|
||||
var options = new HideNSeekGameOptions(version);
|
||||
@@ -102,6 +107,11 @@ public class HideNSeekGameOptions : IGameOptions
|
||||
|
||||
public void Deserialize(IMessageReader reader)
|
||||
{
|
||||
if (Version > LatestVersion)
|
||||
{
|
||||
IGameOptions.ThrowUnknownVersion<HideNSeekGameOptions>(Version);
|
||||
}
|
||||
|
||||
if (Version >= 8)
|
||||
{
|
||||
SpecialMode = (SpecialGameModes)reader.ReadByte();
|
||||
@@ -133,9 +143,9 @@ public class HideNSeekGameOptions : IGameOptions
|
||||
MaxPingTime = reader.ReadSingle();
|
||||
CrewmateTimeInVent = reader.ReadSingle();
|
||||
|
||||
if (Version > LatestVersion)
|
||||
if (Version >= 9)
|
||||
{
|
||||
IGameOptions.ThrowUnknownVersion<HideNSeekGameOptions>(Version);
|
||||
Tag = (GameTags)reader.ReadByte();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,6 +182,11 @@ public class HideNSeekGameOptions : IGameOptions
|
||||
writer.Write(MaxPingTime);
|
||||
writer.Write(CrewmateTimeInVent);
|
||||
|
||||
if (Version >= 9)
|
||||
{
|
||||
writer.Write((byte)Tag);
|
||||
}
|
||||
|
||||
if (Version > LatestVersion)
|
||||
{
|
||||
IGameOptions.ThrowUnknownVersion<HideNSeekGameOptions>(Version);
|
||||
|
||||
@@ -49,6 +49,8 @@ public interface IGameOptions
|
||||
/// </summary>
|
||||
public bool IsDefaults { get; set; }
|
||||
|
||||
public GameTags Tag { get; set; }
|
||||
|
||||
public void Serialize(IMessageWriter writer);
|
||||
|
||||
public static void EnsureVersionIsModular<TCaller>(byte version)
|
||||
|
||||
@@ -145,6 +145,8 @@ public class LegacyGameOptionsData : IGameOptions
|
||||
/// </summary>
|
||||
public bool IsDefaults { get; set; } = true;
|
||||
|
||||
public GameTags Tag { get; set; } = 0;
|
||||
|
||||
public static LegacyGameOptionsData Deserialize(IMessageReader reader, byte version)
|
||||
{
|
||||
var options = new LegacyGameOptionsData(version);
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace Impostor.Api.Innersloth.GameOptions;
|
||||
|
||||
public class NormalGameOptions : IGameOptions
|
||||
{
|
||||
public const int LatestVersion = 8;
|
||||
public const int LatestVersion = 12;
|
||||
|
||||
public NormalGameOptions(byte version = LatestVersion)
|
||||
{
|
||||
@@ -128,6 +128,11 @@ public class NormalGameOptions : IGameOptions
|
||||
/// </summary>
|
||||
public TaskBarUpdate TaskBarUpdate { get; set; } = TaskBarUpdate.Always;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the experience level of people in the lobby: Beginner, Intermediate or Expert.
|
||||
/// </summary>
|
||||
public GameTags Tag { get; set; } = 0;
|
||||
|
||||
public RoleOptionsCollection RoleOptions { get; set; }
|
||||
|
||||
public static NormalGameOptions Deserialize(IMessageReader reader, byte version)
|
||||
@@ -139,6 +144,11 @@ public class NormalGameOptions : IGameOptions
|
||||
|
||||
public void Deserialize(IMessageReader reader)
|
||||
{
|
||||
if (Version > LatestVersion)
|
||||
{
|
||||
IGameOptions.ThrowUnknownVersion<NormalGameOptions>(Version);
|
||||
}
|
||||
|
||||
if (Version >= 8)
|
||||
{
|
||||
SpecialMode = (SpecialGameModes)reader.ReadByte();
|
||||
@@ -173,12 +183,12 @@ public class NormalGameOptions : IGameOptions
|
||||
AnonymousVotes = reader.ReadBoolean();
|
||||
TaskBarUpdate = (TaskBarUpdate)reader.ReadByte();
|
||||
|
||||
RoleOptions.Deserialize(reader);
|
||||
|
||||
if (Version > LatestVersion)
|
||||
if (Version >= 9)
|
||||
{
|
||||
IGameOptions.ThrowUnknownVersion<NormalGameOptions>(Version);
|
||||
Tag = (GameTags)reader.ReadByte();
|
||||
}
|
||||
|
||||
RoleOptions.Deserialize(reader);
|
||||
}
|
||||
|
||||
public void Serialize(IMessageWriter writer)
|
||||
@@ -217,6 +227,11 @@ public class NormalGameOptions : IGameOptions
|
||||
writer.Write(AnonymousVotes);
|
||||
writer.Write((byte)TaskBarUpdate);
|
||||
|
||||
if (Version >= 9)
|
||||
{
|
||||
writer.Write((byte)Tag);
|
||||
}
|
||||
|
||||
RoleOptions.Serialize(writer);
|
||||
|
||||
if (Version > LatestVersion)
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
namespace Impostor.Api.Innersloth.GameOptions.RoleOptions;
|
||||
|
||||
public class DetectiveRoleOptions : IRoleOptions
|
||||
{
|
||||
public DetectiveRoleOptions(byte version)
|
||||
{
|
||||
Version = version;
|
||||
}
|
||||
|
||||
public byte Version { get; }
|
||||
|
||||
public RoleTypes Type => RoleTypes.Detective;
|
||||
|
||||
public byte DetectiveSuspectLimit { get; set; } = 3;
|
||||
|
||||
public static DetectiveRoleOptions Deserialize(IMessageReader reader, byte version)
|
||||
{
|
||||
var options = new DetectiveRoleOptions(version);
|
||||
|
||||
options.DetectiveSuspectLimit = reader.ReadByte();
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
public void Serialize(IMessageWriter writer)
|
||||
{
|
||||
writer.Write((byte)DetectiveSuspectLimit);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
namespace Impostor.Api.Innersloth.GameOptions.RoleOptions;
|
||||
|
||||
public class JudgeRoleOptions : IRoleOptions
|
||||
{
|
||||
public JudgeRoleOptions(byte version)
|
||||
{
|
||||
Version = version;
|
||||
}
|
||||
|
||||
public byte Version { get; }
|
||||
|
||||
public RoleTypes Type => RoleTypes.Judge;
|
||||
|
||||
public float JudgeTaskRequirementPercentage { get; set; } = 50f;
|
||||
|
||||
public static JudgeRoleOptions Deserialize(IMessageReader reader, byte version)
|
||||
{
|
||||
var options = new JudgeRoleOptions(version);
|
||||
|
||||
options.JudgeTaskRequirementPercentage = (int)reader.ReadByte();
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
public void Serialize(IMessageWriter writer)
|
||||
{
|
||||
writer.Write((byte)JudgeTaskRequirementPercentage);
|
||||
}
|
||||
}
|
||||
@@ -16,8 +16,6 @@ public class RoleOptionsCollection
|
||||
|
||||
public void Deserialize(IMessageReader reader)
|
||||
{
|
||||
Roles.Clear();
|
||||
|
||||
var count = reader.ReadPackedInt32();
|
||||
Roles.EnsureCapacity(count);
|
||||
for (var i = 0; i < count; i++)
|
||||
@@ -34,10 +32,14 @@ public class RoleOptionsCollection
|
||||
RoleTypes.Noisemaker => NoisemakerRoleOptions.Deserialize(roleOptionsReader, Version),
|
||||
RoleTypes.Phantom => PhantomRoleOptions.Deserialize(roleOptionsReader, Version),
|
||||
RoleTypes.Tracker => TrackerRoleOptions.Deserialize(roleOptionsReader, Version),
|
||||
RoleTypes.Detective => DetectiveRoleOptions.Deserialize(roleOptionsReader, Version),
|
||||
RoleTypes.Viper => ViperRoleOptions.Deserialize(roleOptionsReader, Version),
|
||||
RoleTypes.Judge => JudgeRoleOptions.Deserialize(roleOptionsReader, Version),
|
||||
RoleTypes.SpiritGuide => SpiritGuideRoleOptions.Deserialize(roleOptionsReader, Version),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(roleType), roleType, null),
|
||||
};
|
||||
|
||||
Roles.Add(roleType, new RoleData(roleType, roleOptions, roleRate));
|
||||
Roles[roleType] = new RoleData(roleType, roleOptions, roleRate);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
namespace Impostor.Api.Innersloth.GameOptions.RoleOptions;
|
||||
|
||||
public class SpiritGuideRoleOptions : IRoleOptions
|
||||
{
|
||||
public SpiritGuideRoleOptions(byte version)
|
||||
{
|
||||
Version = version;
|
||||
}
|
||||
|
||||
public byte Version { get; }
|
||||
|
||||
public RoleTypes Type => RoleTypes.SpiritGuide;
|
||||
|
||||
public float SpiritGuideCooldownSeconds { get; set; } = 20f;
|
||||
|
||||
public static SpiritGuideRoleOptions Deserialize(IMessageReader reader, byte version)
|
||||
{
|
||||
var options = new SpiritGuideRoleOptions(version);
|
||||
|
||||
options.SpiritGuideCooldownSeconds = reader.ReadByte();
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
public void Serialize(IMessageWriter writer)
|
||||
{
|
||||
writer.Write((byte)SpiritGuideCooldownSeconds);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
namespace Impostor.Api.Innersloth.GameOptions.RoleOptions;
|
||||
|
||||
public class ViperRoleOptions : IRoleOptions
|
||||
{
|
||||
public ViperRoleOptions(byte version)
|
||||
{
|
||||
Version = version;
|
||||
}
|
||||
|
||||
public byte Version { get; }
|
||||
|
||||
public RoleTypes Type => RoleTypes.Viper;
|
||||
|
||||
public byte ViperDissolveTime { get; set; } = 15;
|
||||
|
||||
public static ViperRoleOptions Deserialize(IMessageReader reader, byte version)
|
||||
{
|
||||
var options = new ViperRoleOptions(version);
|
||||
|
||||
options.ViperDissolveTime = reader.ReadByte();
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
public void Serialize(IMessageWriter writer)
|
||||
{
|
||||
writer.Write((byte)ViperDissolveTime);
|
||||
}
|
||||
}
|
||||
@@ -60,6 +60,22 @@ namespace Impostor.Api.Net
|
||||
/// </remarks>
|
||||
IDictionary<object, object> Items { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the friendcode of the player.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Impostor's HTTP server does not resolve friendcodes, so this value will remain null unless a plugin has written a value in this field.
|
||||
/// </remarks>
|
||||
string? FriendCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the puid of the player.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Impostor's HTTP server does not resolve puids, so this value will remain null unless a plugin has written a value in this field.
|
||||
/// </remarks>
|
||||
string? Puid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current game data of the <see cref="IClient" />.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading.Tasks;
|
||||
using Impostor.Api.Games;
|
||||
using Impostor.Api.Net.Inner;
|
||||
using Impostor.Api.Net.Inner.Objects;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Impostor.Api.Events.Player;
|
||||
|
||||
@@ -6,6 +6,24 @@ namespace Impostor.Api.Net.Inner.Objects
|
||||
{
|
||||
public interface IInnerMeetingHud : IInnerNetObject
|
||||
{
|
||||
public interface IJudgeOverrule
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the player id of the Judge that queued this overrule.
|
||||
/// </summary>
|
||||
byte JudgePlayerId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the player id of the player the Judge wants to overrule.
|
||||
/// </summary>
|
||||
byte OverruledPlayerId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the overrule nonce used to match this overrule to the voting result.
|
||||
/// </summary>
|
||||
ushort OverruleNonce { get; }
|
||||
}
|
||||
|
||||
public interface IPlayerVoteArea
|
||||
{
|
||||
/// <summary>
|
||||
@@ -55,5 +73,10 @@ namespace Impostor.Api.Net.Inner.Objects
|
||||
/// Gets the player that started the meeting.
|
||||
/// </summary>
|
||||
IInnerPlayerInfo? Reporter { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Judge overrules queued in this meeting.
|
||||
/// </summary>
|
||||
IReadOnlyCollection<IJudgeOverrule> JudgeOverrules { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Impostor.Api.Innersloth;
|
||||
using Impostor.Api.Innersloth.Customization;
|
||||
@@ -28,13 +29,14 @@ namespace Impostor.Api.Net.Inner.Objects
|
||||
/// Gets the <see cref="IInnerPlayerInfo" /> of the <see cref="IInnerPlayerControl" />.
|
||||
/// Contains metadata about the player.
|
||||
/// </summary>
|
||||
IInnerPlayerInfo PlayerInfo { get; }
|
||||
IInnerPlayerInfo? PlayerInfo { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets the name of the current <see cref="IInnerPlayerControl" />.
|
||||
/// Visible to all players.
|
||||
/// </summary>
|
||||
/// <param name="name">A name for the player.</param>
|
||||
/// <exception cref="ImpostorProtocolException">Thrown when player doesn't have a PlayerInfo.</exception>
|
||||
/// <returns>Task that must be awaited.</returns>
|
||||
ValueTask SetNameAsync(string name);
|
||||
|
||||
@@ -43,6 +45,7 @@ namespace Impostor.Api.Net.Inner.Objects
|
||||
/// Visible to all players.
|
||||
/// </summary>
|
||||
/// <param name="colorType">A color for the player.</param>
|
||||
/// <exception cref="ImpostorProtocolException">Thrown when player doesn't have a PlayerInfo.</exception>
|
||||
/// <returns>Task that must be awaited.</returns>
|
||||
ValueTask SetColorAsync(ColorType colorType);
|
||||
|
||||
@@ -51,6 +54,7 @@ namespace Impostor.Api.Net.Inner.Objects
|
||||
/// Visible to all players.
|
||||
/// </summary>
|
||||
/// <param name="hatId">An hat for the player.</param>
|
||||
/// <exception cref="ImpostorProtocolException">Thrown when player doesn't have a PlayerInfo.</exception>
|
||||
/// <returns>Task that must be awaited.</returns>
|
||||
ValueTask SetHatAsync(string hatId);
|
||||
|
||||
@@ -59,6 +63,7 @@ namespace Impostor.Api.Net.Inner.Objects
|
||||
/// Visible to all players.
|
||||
/// </summary>
|
||||
/// <param name="petId">A pet for the player.</param>
|
||||
/// <exception cref="ImpostorProtocolException">Thrown when player doesn't have a PlayerInfo.</exception>
|
||||
/// <returns>Task that must be awaited.</returns>
|
||||
ValueTask SetPetAsync(string petId);
|
||||
|
||||
@@ -67,9 +72,26 @@ namespace Impostor.Api.Net.Inner.Objects
|
||||
/// Visible to all players.
|
||||
/// </summary>
|
||||
/// <param name="skinId">A skin for the player.</param>
|
||||
/// <exception cref="ImpostorProtocolException">Thrown when player doesn't have a PlayerInfo.</exception>
|
||||
/// <returns>Task that must be awaited.</returns>
|
||||
ValueTask SetSkinAsync(string skinId);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the visor of the current <see cref="IInnerPlayerControl" />.
|
||||
/// Visible to all players.
|
||||
/// </summary>
|
||||
/// <param name="visorId">A visor for the player.</param>
|
||||
/// <returns>Task that must be awaited.</returns>
|
||||
ValueTask SetVisorAsync(string visorId);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the nameplate of the current <see cref="IInnerPlayerControl" />.
|
||||
/// Visible to all players.
|
||||
/// </summary>
|
||||
/// <param name="nameplateId">A nameplate for the player.</param>
|
||||
/// <returns>Task that must be awaited.</returns>
|
||||
ValueTask SetNamePlateAsync(string nameplateId);
|
||||
|
||||
/// <summary>
|
||||
/// Send a chat message as the current <see cref="IInnerPlayerControl" />.
|
||||
/// Visible to all players.
|
||||
@@ -98,32 +120,60 @@ namespace Impostor.Api.Net.Inner.Objects
|
||||
/// <exception cref="ImpostorProtocolException">Thrown when player is not the impostor.</exception>
|
||||
/// <exception cref="ImpostorProtocolException">Thrown when player is dead.</exception>
|
||||
/// <exception cref="ImpostorProtocolException">Thrown when target is dead.</exception>
|
||||
/// <exception cref="ImpostorProtocolException">Thrown when player or target player doesn't have a PlayerInfo.</exception>
|
||||
/// <returns>Task that must be awaited.</returns>
|
||||
ValueTask MurderPlayerAsync(IInnerPlayerControl target, MurderResultFlags result);
|
||||
|
||||
/// <summary>
|
||||
/// Murder <paramref name="target" /> player successfully.
|
||||
/// Murder <paramref name="target" /> player.
|
||||
/// </summary>
|
||||
/// <param name="target">Target player to murder.</param>
|
||||
/// <param name="result">The result of the murder operation.</param>
|
||||
/// <returns>Task that must be awaited.</returns>
|
||||
ValueTask ForceMurderPlayerAsync(IInnerPlayerControl target, MurderResultFlags result);
|
||||
|
||||
/// <summary>
|
||||
/// Murder <paramref name="target" /> player after validating if this is a valid kill.
|
||||
/// </summary>
|
||||
/// <param name="target">Target player to murder.</param>
|
||||
/// <exception cref="ImpostorProtocolException">Thrown when player is not the impostor.</exception>
|
||||
/// <exception cref="ImpostorProtocolException">Thrown when player is dead.</exception>
|
||||
/// <exception cref="ImpostorProtocolException">Thrown when target is dead.</exception>
|
||||
/// <exception cref="ImpostorProtocolException">Thrown when player or target player doesn't have a PlayerInfo.</exception>
|
||||
/// <returns>Task that must be awaited.</returns>
|
||||
[Obsolete("Please switch to version with the MurderResultFlags argument")]
|
||||
ValueTask MurderPlayerAsync(IInnerPlayerControl target);
|
||||
|
||||
/// <summary>
|
||||
/// Protect <paramref name="target" /> player.
|
||||
/// </summary>
|
||||
/// <param name="target">Target player to protect.</param>
|
||||
/// <exception cref="ImpostorProtocolException">Thrown when target is a guardian angel.</exception>
|
||||
/// <exception cref="ImpostorProtocolException">Thrown when target is dead.</exception>
|
||||
/// <exception cref="ImpostorProtocolException">Thrown when player or target player doesn't have a PlayerInfo.</exception>
|
||||
/// <returns>Task that must be awaited.</returns>
|
||||
ValueTask ProtectPlayerAsync(IInnerPlayerControl target);
|
||||
|
||||
/// <summary>
|
||||
/// Protect <paramref name="target" /> player.
|
||||
/// </summary>
|
||||
/// <param name="target">Target player to protect.</param>
|
||||
/// <returns>Task that must be awaited.</returns>
|
||||
ValueTask ForceProtectPlayerAsync(IInnerPlayerControl target);
|
||||
|
||||
/// <summary>
|
||||
/// Exile the current player. This doesn't produce a body to be reported.
|
||||
/// Visible to all players.
|
||||
/// </summary>
|
||||
/// <exception cref="ImpostorProtocolException">Thrown if player to be exiled is already dead.</exception>
|
||||
/// <exception cref="ImpostorProtocolException">Thrown when player or target player doesn't have a PlayerInfo.</exception>
|
||||
/// <returns>Task that must be awaited.</returns>
|
||||
ValueTask ExileAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Exile the current player. This doesn't produce a body to be reported.
|
||||
/// Visible to all players.
|
||||
/// </summary>
|
||||
/// <returns>Task that must be awaited.</returns>
|
||||
ValueTask ExileAsync();
|
||||
ValueTask ForceExileAsync();
|
||||
}
|
||||
}
|
||||
@@ -43,5 +43,21 @@ namespace Impostor.Api.Net.Inner.Objects
|
||||
DateTimeOffset LastMurder { get; }
|
||||
|
||||
uint PlayerLevel { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the friendcode of the player.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Impostor's HTTP server does not resolve friendcodes, so this value will remain null unless a plugin has written a value on the player's client.
|
||||
/// </remarks>
|
||||
string? FriendCode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the puid of the player.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Impostor's HTTP server does not resolve puids, so this value will remain null unless a plugin has written a value on the player's client.
|
||||
/// </remarks>
|
||||
string? Puid { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using Impostor.Api.Innersloth;
|
||||
using Impostor.Api.Innersloth;
|
||||
using Impostor.Api.Innersloth.GameOptions;
|
||||
|
||||
namespace Impostor.Api.Net.Messages.C2S
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using Impostor.Api.Innersloth;
|
||||
using Impostor.Api.Innersloth.GameOptions;
|
||||
|
||||
namespace Impostor.Api.Net.Messages.C2S
|
||||
{
|
||||
/// <summary>
|
||||
/// Special serialization rules for AU MCI HostGame messages.
|
||||
/// <see href="https://github.com/Innersloth-LLC/AmongUsModdingInformation?tab=readme-ov-file#all-client-mod-client-identification"/>
|
||||
/// for more information.
|
||||
/// </summary>
|
||||
public static class Message25HostModdedGameC2S
|
||||
{
|
||||
public static void Serialize(IMessageWriter writer, IGameOptions gameOptions, CrossplayFlags crossplayFlags, GameFilterOptions gameFilterOptions)
|
||||
{
|
||||
Message00HostGameC2S.Serialize(writer, gameOptions, crossplayFlags, gameFilterOptions);
|
||||
}
|
||||
|
||||
public static void Deserialize(IMessageReader reader, out IGameOptions gameOptions, out CrossplayFlags crossplayFlags, out GameFilterOptions gameFilterOptions, out Guid modGuid)
|
||||
{
|
||||
Message00HostGameC2S.Deserialize(reader, out gameOptions, out crossplayFlags, out gameFilterOptions);
|
||||
modGuid = new Guid(reader.ReadBytes(16).Span);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
@@ -29,6 +29,8 @@ namespace Impostor.Api.Net.Messages
|
||||
public const byte QueryPlatformIds = 22;
|
||||
public const byte QueryLobbyInfo = 23;
|
||||
public const byte EndGameHostMigration = 24;
|
||||
public const byte HostModdedGame = 25;
|
||||
public const byte PackedGameDataTo = 26;
|
||||
|
||||
private static readonly Dictionary<byte, string> FlagCache;
|
||||
|
||||
@@ -40,6 +42,11 @@ namespace Impostor.Api.Net.Messages
|
||||
.ToDictionary(x => (byte)x.GetValue(null)!, y => y.Name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a MessageFlag to the corresponding string.
|
||||
/// </summary>
|
||||
/// <param name="flag">The MessageFlag to convert.</param>
|
||||
/// <returns>A string corresponding with the value of flag.</returns>
|
||||
public static string FlagToString(byte flag)
|
||||
{
|
||||
return FlagCache.TryGetValue(flag, out var res) ? res : $"Unknown Flag {flag}";
|
||||
|
||||
@@ -2,6 +2,8 @@ namespace Impostor.Api.Net.Messages.Rpcs
|
||||
{
|
||||
public static class Rpc23VotingComplete
|
||||
{
|
||||
// Pre 18.0 version
|
||||
// TODO clean up when 17.4 and below are unsupported
|
||||
public static void Serialize(IMessageWriter writer, byte[] states, byte playerId, bool tie)
|
||||
{
|
||||
writer.WriteBytesAndSize(states);
|
||||
@@ -9,6 +11,15 @@ namespace Impostor.Api.Net.Messages.Rpcs
|
||||
writer.Write(tie);
|
||||
}
|
||||
|
||||
public static void Serialize(IMessageWriter writer, byte[] states, byte playerId, bool tie, bool wasOverruled, ushort overrideId)
|
||||
{
|
||||
writer.WriteBytesAndSize(states);
|
||||
writer.Write(playerId);
|
||||
writer.Write(tie);
|
||||
writer.Write(wasOverruled);
|
||||
writer.Write(overrideId);
|
||||
}
|
||||
|
||||
public static void Deserialize(IMessageReader reader, out IMessageReader[] states, out byte playerId, out bool tie)
|
||||
{
|
||||
var length = reader.ReadPackedInt32();
|
||||
@@ -21,5 +32,21 @@ namespace Impostor.Api.Net.Messages.Rpcs
|
||||
playerId = reader.ReadByte();
|
||||
tie = reader.ReadBoolean();
|
||||
}
|
||||
|
||||
public static void Deserialize(IMessageReader reader, out IMessageReader[] states, out byte playerId, out bool tie, out bool wasOverruled, out ushort overrideId)
|
||||
{
|
||||
var length = reader.ReadPackedInt32();
|
||||
states = new IMessageReader[length];
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
states[i] = reader.ReadMessage();
|
||||
}
|
||||
|
||||
playerId = reader.ReadByte();
|
||||
tie = reader.ReadBoolean();
|
||||
|
||||
wasOverruled = reader.ReadBoolean();
|
||||
overrideId = reader.ReadUInt16();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace Impostor.Api.Net.Messages.Rpcs
|
||||
{
|
||||
public static class Rpc66QueueOverruleVotes
|
||||
{
|
||||
public static void Serialize(IMessageWriter writer, byte judgePlayerId, byte targetPlayerId, ushort overruleNonce)
|
||||
{
|
||||
writer.Write(judgePlayerId);
|
||||
writer.Write(targetPlayerId);
|
||||
writer.Write(overruleNonce);
|
||||
}
|
||||
|
||||
public static void Deserialize(IMessageReader reader, out byte judgePlayerId, out byte targetPlayerId, out ushort overruleNonce)
|
||||
{
|
||||
judgePlayerId = reader.ReadByte();
|
||||
targetPlayerId = reader.ReadByte();
|
||||
overruleNonce = reader.ReadUInt16();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
|
||||
namespace Impostor.Api.Net.Messages.Rpcs
|
||||
{
|
||||
public static class Rpc67SpiritGuideMessage
|
||||
{
|
||||
public static void Serialize(IMessageWriter writer, byte[] images)
|
||||
{
|
||||
writer.WriteBytesAndSize(images);
|
||||
}
|
||||
|
||||
public static void Deserialize(IMessageReader reader, out ReadOnlyMemory<byte> images)
|
||||
{
|
||||
images = reader.ReadBytesAndSize();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,11 +18,9 @@ namespace Impostor.Api.Net.Messages.S2C
|
||||
writer.Write(hostId);
|
||||
writer.Write(player.Client.Name);
|
||||
player.Client.PlatformSpecificData.Serialize(writer);
|
||||
writer.WritePacked(player.Character?.PlayerInfo.PlayerLevel ?? 1);
|
||||
|
||||
// ProductUserId and FriendCode are not yet known, so set them to an empty string
|
||||
writer.Write(string.Empty);
|
||||
writer.Write(string.Empty);
|
||||
writer.WritePacked(player.Character?.PlayerInfo?.PlayerLevel ?? 1);
|
||||
writer.Write(player.Client.FriendCode ?? string.Empty);
|
||||
writer.Write(player.Client.Puid ?? string.Empty);
|
||||
writer.EndMessage();
|
||||
}
|
||||
|
||||
|
||||
@@ -22,11 +22,9 @@ namespace Impostor.Api.Net.Messages.S2C
|
||||
writer.WritePacked(ply.Client.Id);
|
||||
writer.Write(ply.Client.Name);
|
||||
ply.Client.PlatformSpecificData.Serialize(writer);
|
||||
writer.WritePacked(ply.Character?.PlayerInfo.PlayerLevel ?? 1);
|
||||
|
||||
// ProductUserId and FriendCode are not yet known, so set them to an empty string
|
||||
writer.Write(string.Empty);
|
||||
writer.Write(string.Empty);
|
||||
writer.WritePacked(ply.Character?.PlayerInfo?.PlayerLevel ?? 1);
|
||||
writer.Write(ply.Client.FriendCode ?? string.Empty);
|
||||
writer.Write(ply.Client.Puid ?? string.Empty);
|
||||
}
|
||||
|
||||
writer.EndMessage();
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
"net8.0": {
|
||||
"Impostor.Hazel.Abstractions": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.0.0, )",
|
||||
"resolved": "1.0.0",
|
||||
"contentHash": "x56AEsY5fKU2Z5O7ZrzEqKfr62kq/C05Aevcciwz5yMfOQlpwuFI1awgNGJsoe+bcx+XnFc3+l+eadtY72aZsg=="
|
||||
"requested": "[1.0.1, )",
|
||||
"resolved": "1.0.1",
|
||||
"contentHash": "de2bMj78xDXOWwyqQVk8o4vbZKGfAgOjnWnkKB1IGGovNtxLeBF8jy3wH5moTZB6juTnLf0ptkCDkRi8c2OcjA=="
|
||||
},
|
||||
"Microsoft.AspNetCore.Http.Abstractions": {
|
||||
"type": "Direct",
|
||||
@@ -40,6 +40,12 @@
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.NET.ILLink.Tasks": {
|
||||
"type": "Direct",
|
||||
"requested": "[8.0.30, )",
|
||||
"resolved": "8.0.30",
|
||||
"contentHash": "SfmwuZjUPBTEoBMAsXZ/hWkaEK9oL+Y/UtAx7N6RyWHfcMt9J0ExrNPuOgTyPbCXIyiTBFsxFNrPLEZNPmJcvA=="
|
||||
},
|
||||
"StyleCop.Analyzers": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.2.0-beta.556, )",
|
||||
@@ -117,6 +123,7 @@
|
||||
"resolved": "4.5.0",
|
||||
"contentHash": "Xg4G4Indi4dqP1iuAiMSwpiWS54ZghzR644OtsRCm/m/lBMG8dUBhLVN7hLm8NNrNTR+iGbshCPTwrvxZPlm4g=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"net8.0/win-x64": {}
|
||||
}
|
||||
}
|
||||
@@ -42,19 +42,19 @@
|
||||
},
|
||||
"Impostor.Hazel": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.0.0",
|
||||
"contentHash": "FgVKuIWK+o6Q+wOPFoZZTeoSuVh4giAhrS8Zvs6O0++Bg8RxH4Lw51uSwnexLiwosXveUCIXICzgGHmRyguVSA==",
|
||||
"resolved": "1.0.1",
|
||||
"contentHash": "DFsZcOa9ptFEj8FCfK63L/bcJ9RUVRZUhUS0qkrmdSqkgeJRdvUOTWR1gKlqbaOJcSEvexAITC0QJEPnJUcI7Q==",
|
||||
"dependencies": {
|
||||
"Impostor.Hazel.Abstractions": "1.0.0",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "7.0.0",
|
||||
"Microsoft.Extensions.ObjectPool": "7.0.1",
|
||||
"Impostor.Hazel.Abstractions": "1.0.1",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.ObjectPool": "8.0.0",
|
||||
"Serilog": "2.12.0"
|
||||
}
|
||||
},
|
||||
"Impostor.Hazel.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.0.0",
|
||||
"contentHash": "x56AEsY5fKU2Z5O7ZrzEqKfr62kq/C05Aevcciwz5yMfOQlpwuFI1awgNGJsoe+bcx+XnFc3+l+eadtY72aZsg=="
|
||||
"resolved": "1.0.1",
|
||||
"contentHash": "de2bMj78xDXOWwyqQVk8o4vbZKGfAgOjnWnkKB1IGGovNtxLeBF8jy3wH5moTZB6juTnLf0ptkCDkRi8c2OcjA=="
|
||||
},
|
||||
"Microsoft.AspNetCore.Http.Abstractions": {
|
||||
"type": "Transitive",
|
||||
@@ -588,7 +588,7 @@
|
||||
"impostor.api": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"Impostor.Hazel.Abstractions": "[1.0.0, )",
|
||||
"Impostor.Hazel.Abstractions": "[1.0.1, )",
|
||||
"Microsoft.AspNetCore.Http.Abstractions": "[2.2.0, )",
|
||||
"Microsoft.Extensions.Hosting.Abstractions": "[8.0.0, )",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "[8.0.0, )"
|
||||
@@ -597,8 +597,8 @@
|
||||
"impostor.server": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"Impostor.Api": "[1.9.0-dev, )",
|
||||
"Impostor.Hazel": "[1.0.0, )",
|
||||
"Impostor.Api": "[1.10.7-dev, )",
|
||||
"Impostor.Hazel": "[1.0.1, )",
|
||||
"Microsoft.Extensions.FileSystemGlobbing": "[8.0.0, )",
|
||||
"Microsoft.Extensions.Hosting": "[8.0.0, )",
|
||||
"Microsoft.Extensions.ObjectPool": "[8.0.1, )",
|
||||
|
||||
@@ -13,19 +13,19 @@
|
||||
},
|
||||
"Impostor.Hazel": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.0.0",
|
||||
"contentHash": "FgVKuIWK+o6Q+wOPFoZZTeoSuVh4giAhrS8Zvs6O0++Bg8RxH4Lw51uSwnexLiwosXveUCIXICzgGHmRyguVSA==",
|
||||
"resolved": "1.0.1",
|
||||
"contentHash": "DFsZcOa9ptFEj8FCfK63L/bcJ9RUVRZUhUS0qkrmdSqkgeJRdvUOTWR1gKlqbaOJcSEvexAITC0QJEPnJUcI7Q==",
|
||||
"dependencies": {
|
||||
"Impostor.Hazel.Abstractions": "1.0.0",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "7.0.0",
|
||||
"Microsoft.Extensions.ObjectPool": "7.0.1",
|
||||
"Impostor.Hazel.Abstractions": "1.0.1",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.ObjectPool": "8.0.0",
|
||||
"Serilog": "2.12.0"
|
||||
}
|
||||
},
|
||||
"Impostor.Hazel.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.0.0",
|
||||
"contentHash": "x56AEsY5fKU2Z5O7ZrzEqKfr62kq/C05Aevcciwz5yMfOQlpwuFI1awgNGJsoe+bcx+XnFc3+l+eadtY72aZsg=="
|
||||
"resolved": "1.0.1",
|
||||
"contentHash": "de2bMj78xDXOWwyqQVk8o4vbZKGfAgOjnWnkKB1IGGovNtxLeBF8jy3wH5moTZB6juTnLf0ptkCDkRi8c2OcjA=="
|
||||
},
|
||||
"Microsoft.AspNetCore.Http.Abstractions": {
|
||||
"type": "Transitive",
|
||||
@@ -97,8 +97,8 @@
|
||||
},
|
||||
"Microsoft.Extensions.ObjectPool": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.0.1",
|
||||
"contentHash": "dDFItid/TGJljAnqbUQ0PkAl6ShIG0htwRR8J0cG/5Il6lIZPfkIZMwrZTWiF23AiQcqt6suPaUy2ih6dsCB9Q=="
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "4pm+XgxSukskwjzDDfSjG4KNUIOdFF2VaqZZDtTzoyQMOVSnlV6ZM8a9aVu5dg9LVZTB//utzSc8fOi0b0Mb2Q=="
|
||||
},
|
||||
"Microsoft.Extensions.Options": {
|
||||
"type": "Transitive",
|
||||
@@ -132,7 +132,7 @@
|
||||
"impostor.api": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"Impostor.Hazel.Abstractions": "[1.0.0, )",
|
||||
"Impostor.Hazel.Abstractions": "[1.0.1, )",
|
||||
"Microsoft.AspNetCore.Http.Abstractions": "[2.2.0, )",
|
||||
"Microsoft.Extensions.Hosting.Abstractions": "[8.0.0, )",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "[8.0.0, )"
|
||||
@@ -141,8 +141,8 @@
|
||||
"impostor.client": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"Impostor.Api": "[1.9.0-dev, )",
|
||||
"Impostor.Hazel": "[1.0.0, )"
|
||||
"Impostor.Api": "[1.10.7-dev, )",
|
||||
"Impostor.Hazel": "[1.0.1, )"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Impostor.Api\Impostor.Api.csproj" />
|
||||
<PackageReference Include="Impostor.Hazel" Version="1.0.0" />
|
||||
<PackageReference Include="Impostor.Hazel" Version="1.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -4,20 +4,20 @@
|
||||
"net8.0": {
|
||||
"Impostor.Hazel": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.0.0, )",
|
||||
"resolved": "1.0.0",
|
||||
"contentHash": "FgVKuIWK+o6Q+wOPFoZZTeoSuVh4giAhrS8Zvs6O0++Bg8RxH4Lw51uSwnexLiwosXveUCIXICzgGHmRyguVSA==",
|
||||
"requested": "[1.0.1, )",
|
||||
"resolved": "1.0.1",
|
||||
"contentHash": "DFsZcOa9ptFEj8FCfK63L/bcJ9RUVRZUhUS0qkrmdSqkgeJRdvUOTWR1gKlqbaOJcSEvexAITC0QJEPnJUcI7Q==",
|
||||
"dependencies": {
|
||||
"Impostor.Hazel.Abstractions": "1.0.0",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "7.0.0",
|
||||
"Microsoft.Extensions.ObjectPool": "7.0.1",
|
||||
"Impostor.Hazel.Abstractions": "1.0.1",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
|
||||
"Microsoft.Extensions.ObjectPool": "8.0.0",
|
||||
"Serilog": "2.12.0"
|
||||
}
|
||||
},
|
||||
"Impostor.Hazel.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.0.0",
|
||||
"contentHash": "x56AEsY5fKU2Z5O7ZrzEqKfr62kq/C05Aevcciwz5yMfOQlpwuFI1awgNGJsoe+bcx+XnFc3+l+eadtY72aZsg=="
|
||||
"resolved": "1.0.1",
|
||||
"contentHash": "de2bMj78xDXOWwyqQVk8o4vbZKGfAgOjnWnkKB1IGGovNtxLeBF8jy3wH5moTZB6juTnLf0ptkCDkRi8c2OcjA=="
|
||||
},
|
||||
"Microsoft.AspNetCore.Http.Abstractions": {
|
||||
"type": "Transitive",
|
||||
@@ -89,8 +89,8 @@
|
||||
},
|
||||
"Microsoft.Extensions.ObjectPool": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.0.1",
|
||||
"contentHash": "dDFItid/TGJljAnqbUQ0PkAl6ShIG0htwRR8J0cG/5Il6lIZPfkIZMwrZTWiF23AiQcqt6suPaUy2ih6dsCB9Q=="
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "4pm+XgxSukskwjzDDfSjG4KNUIOdFF2VaqZZDtTzoyQMOVSnlV6ZM8a9aVu5dg9LVZTB//utzSc8fOi0b0Mb2Q=="
|
||||
},
|
||||
"Microsoft.Extensions.Options": {
|
||||
"type": "Transitive",
|
||||
@@ -124,7 +124,7 @@
|
||||
"impostor.api": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"Impostor.Hazel.Abstractions": "[1.0.0, )",
|
||||
"Impostor.Hazel.Abstractions": "[1.0.1, )",
|
||||
"Microsoft.AspNetCore.Http.Abstractions": "[2.2.0, )",
|
||||
"Microsoft.Extensions.Hosting.Abstractions": "[8.0.0, )",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "[8.0.0, )"
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
"net8.0": {
|
||||
"Impostor.Hazel.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.0.0",
|
||||
"contentHash": "x56AEsY5fKU2Z5O7ZrzEqKfr62kq/C05Aevcciwz5yMfOQlpwuFI1awgNGJsoe+bcx+XnFc3+l+eadtY72aZsg=="
|
||||
"resolved": "1.0.1",
|
||||
"contentHash": "de2bMj78xDXOWwyqQVk8o4vbZKGfAgOjnWnkKB1IGGovNtxLeBF8jy3wH5moTZB6juTnLf0ptkCDkRi8c2OcjA=="
|
||||
},
|
||||
"Microsoft.AspNetCore.Http.Abstractions": {
|
||||
"type": "Transitive",
|
||||
@@ -102,7 +102,7 @@
|
||||
"impostor.api": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"Impostor.Hazel.Abstractions": "[1.0.0, )",
|
||||
"Impostor.Hazel.Abstractions": "[1.0.1, )",
|
||||
"Microsoft.AspNetCore.Http.Abstractions": "[2.2.0, )",
|
||||
"Microsoft.Extensions.Hosting.Abstractions": "[8.0.0, )",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "[8.0.0, )"
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
"net8.0": {
|
||||
"Impostor.Hazel.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.0.0",
|
||||
"contentHash": "x56AEsY5fKU2Z5O7ZrzEqKfr62kq/C05Aevcciwz5yMfOQlpwuFI1awgNGJsoe+bcx+XnFc3+l+eadtY72aZsg=="
|
||||
"resolved": "1.0.1",
|
||||
"contentHash": "de2bMj78xDXOWwyqQVk8o4vbZKGfAgOjnWnkKB1IGGovNtxLeBF8jy3wH5moTZB6juTnLf0ptkCDkRi8c2OcjA=="
|
||||
},
|
||||
"Microsoft.AspNetCore.Http.Abstractions": {
|
||||
"type": "Transitive",
|
||||
@@ -102,7 +102,7 @@
|
||||
"impostor.api": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"Impostor.Hazel.Abstractions": "[1.0.0, )",
|
||||
"Impostor.Hazel.Abstractions": "[1.0.1, )",
|
||||
"Microsoft.AspNetCore.Http.Abstractions": "[2.2.0, )",
|
||||
"Microsoft.Extensions.Hosting.Abstractions": "[8.0.0, )",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "[8.0.0, )"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Impostor.Api.Events.Meeting;
|
||||
using Impostor.Api.Events.Meeting;
|
||||
using Impostor.Api.Games;
|
||||
using Impostor.Api.Net.Inner.Objects;
|
||||
|
||||
@@ -6,12 +6,14 @@ namespace Impostor.Server.Events.Meeting
|
||||
{
|
||||
public class MeetingEndedEvent : IMeetingEndedEvent
|
||||
{
|
||||
public MeetingEndedEvent(IGame game, IInnerMeetingHud meetingHud, IInnerPlayerControl? exiled, bool isTie)
|
||||
public MeetingEndedEvent(IGame game, IInnerMeetingHud meetingHud, IInnerPlayerControl? exiled, bool isTie, bool wasOverruled = false, ushort overrideId = 0)
|
||||
{
|
||||
Game = game;
|
||||
MeetingHud = meetingHud;
|
||||
Exiled = exiled;
|
||||
IsTie = isTie;
|
||||
WasOverruled = wasOverruled;
|
||||
OverrideId = overrideId;
|
||||
}
|
||||
|
||||
public IGame Game { get; }
|
||||
@@ -21,5 +23,9 @@ namespace Impostor.Server.Events.Meeting
|
||||
public IInnerPlayerControl? Exiled { get; }
|
||||
|
||||
public bool IsTie { get; }
|
||||
|
||||
public bool WasOverruled { get; }
|
||||
|
||||
public ushort OverrideId { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using Impostor.Api.Events.Player;
|
||||
using Impostor.Api.Events.Player;
|
||||
using Impostor.Api.Games;
|
||||
using Impostor.Api.Net;
|
||||
using Impostor.Api.Net.Inner.Objects;
|
||||
@@ -24,5 +24,7 @@ namespace Impostor.Server.Events.Player
|
||||
public string Message { get; }
|
||||
|
||||
public bool IsCancelled { get; set; }
|
||||
|
||||
public bool SendToAllPlayers { get; set; } = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using Impostor.Api.Innersloth.GameOptions;
|
||||
using Impostor.Hazel;
|
||||
|
||||
namespace Impostor.Server.Extensions;
|
||||
|
||||
public static class IGameOptionsExtensions
|
||||
{
|
||||
public static byte[] ToBytes(this IGameOptions gameOptions)
|
||||
{
|
||||
using var writer = MessageWriter.Get(MessageType.Unreliable);
|
||||
writer.Write(gameOptions.Version);
|
||||
writer.StartMessage(0);
|
||||
writer.Write((byte)gameOptions.GameMode);
|
||||
gameOptions.Serialize(writer);
|
||||
writer.EndMessage();
|
||||
return writer.ToByteArray(false);
|
||||
}
|
||||
|
||||
public static string ToBase64String(this IGameOptions gameOptions)
|
||||
{
|
||||
return Convert.ToBase64String(gameOptions.ToBytes());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text.Json;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Impostor.Server.Http;
|
||||
|
||||
[Route("/api/filters")]
|
||||
public sealed class FiltersController : ControllerBase
|
||||
{
|
||||
private static readonly string[] Value = new[]
|
||||
{
|
||||
"Tags",
|
||||
|
||||
// "PlayerSpeed",
|
||||
// "Roles",
|
||||
// "KillCooldown",
|
||||
// "VotingTime",
|
||||
"NumImposters",
|
||||
|
||||
// "VisualTasks",
|
||||
// "AnonymousVotes",
|
||||
// "ConfirmEjects",
|
||||
// "DiscussionTime",
|
||||
// "EmergencyCooldown",
|
||||
// "NumEmergencyMeetings",
|
||||
// "NumCommonTasks",
|
||||
// "NumShortTasks",
|
||||
// "NumLongTasks",
|
||||
// "KillDistance",
|
||||
};
|
||||
|
||||
[HttpGet]
|
||||
public IActionResult GetFilters([FromHeader] AuthenticationHeaderValue authorization)
|
||||
{
|
||||
if (authorization.Scheme != "Bearer" || authorization.Parameter == null)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
TokenController.Token? token = null;
|
||||
try
|
||||
{
|
||||
token = JsonSerializer.Deserialize<TokenController.Token>(Convert.FromBase64String(authorization.Parameter));
|
||||
}
|
||||
catch
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
return Ok(new
|
||||
{
|
||||
filters = Value,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -5,11 +5,15 @@ using System.Net;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Web;
|
||||
using Impostor.Api.Config;
|
||||
using Impostor.Api.Games;
|
||||
using Impostor.Api.Games.Managers;
|
||||
using Impostor.Api.Innersloth;
|
||||
using Impostor.Api.Innersloth.GameFilters;
|
||||
using Impostor.Server.Extensions;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Impostor.Server.Http;
|
||||
@@ -24,6 +28,7 @@ public sealed class GamesController : ControllerBase
|
||||
private readonly IGameManager _gameManager;
|
||||
private readonly ListingManager _listingManager;
|
||||
private readonly HostServer _hostServer;
|
||||
private readonly ILogger<GamesController> _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GamesController"/> class.
|
||||
@@ -31,12 +36,14 @@ public sealed class GamesController : ControllerBase
|
||||
/// <param name="gameManager">GameManager containing a list of games.</param>
|
||||
/// <param name="listingManager">ListingManager responsible for filtering.</param>
|
||||
/// <param name="serverConfig">Impostor configuration section containing the public ip address of this server.</param>
|
||||
public GamesController(IGameManager gameManager, ListingManager listingManager, IOptions<ServerConfig> serverConfig)
|
||||
/// <param name="logger">DI injected logger.</param>
|
||||
public GamesController(IGameManager gameManager, ListingManager listingManager, IOptions<ServerConfig> serverConfig, ILogger<GamesController> logger)
|
||||
{
|
||||
_gameManager = gameManager;
|
||||
_listingManager = listingManager;
|
||||
var config = serverConfig.Value;
|
||||
_hostServer = HostServer.From(IPAddress.Parse(config.ResolvePublicIp()), config.PublicPort);
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -50,6 +57,7 @@ public sealed class GamesController : ControllerBase
|
||||
[HttpGet]
|
||||
public IActionResult Index(int mapId, GameKeywords lang, int numImpostors, [FromHeader] AuthenticationHeaderValue authorization)
|
||||
{
|
||||
// NOTE: this method is no longer used by Among Us 16.0.0 and is only kept for backwards compatibility
|
||||
if (authorization.Scheme != "Bearer" || authorization.Parameter == null)
|
||||
{
|
||||
return BadRequest();
|
||||
@@ -75,6 +83,7 @@ public sealed class GamesController : ControllerBase
|
||||
[HttpPost]
|
||||
public IActionResult Post(int gameId)
|
||||
{
|
||||
// NOTE: this method is no longer used by Among Us 16.0.0 and is only kept for backwards compatibility
|
||||
var code = new GameCode(gameId);
|
||||
var game = _gameManager.Find(code);
|
||||
|
||||
@@ -97,6 +106,69 @@ public sealed class GamesController : ControllerBase
|
||||
return Ok(_hostServer);
|
||||
}
|
||||
|
||||
[HttpGet("{gameId}")]
|
||||
public IActionResult Show([FromRoute] int gameId)
|
||||
{
|
||||
var code = new GameCode(gameId);
|
||||
var game = _gameManager.Find(code);
|
||||
|
||||
// If the game was not found, print an error message.
|
||||
if (game == null)
|
||||
{
|
||||
return NotFound(new FindGameByCodeResponse(new MatchmakerError(DisconnectReason.GameNotFound)));
|
||||
}
|
||||
|
||||
return Ok(new FindGameByCodeResponse(GameListing.From(game)));
|
||||
}
|
||||
|
||||
[HttpGet("filtered")]
|
||||
public IActionResult ShowFilteredLobbies([FromQuery] string filter)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filter))
|
||||
{
|
||||
return BadRequest(new MatchmakerResponse(new MatchmakerError(DisconnectReason.ServerError, "filter query parameter not provided")));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var decodedFilter = HttpUtility.UrlDecode(filter);
|
||||
var filtersList = JsonSerializer.Deserialize<GameFiltersList>(decodedFilter);
|
||||
|
||||
// filterSets wont be null. It must at least have ChatFilter and LangFilter
|
||||
// Vanilla game only builds one filterSet and InnerSloth officials only handles first one (though you can send multiple filter sets. sloths only handle the first)
|
||||
if (filtersList == null || filtersList.FilterSets.Count != 1
|
||||
|| filtersList.FilterSets[0].Filters.Count < 2
|
||||
|| !filtersList.FilterSets[0].Filters.Any(x => x.OptionType == "languages")
|
||||
|| !filtersList.FilterSets[0].Filters.Any(x => x.OptionType == "chat"))
|
||||
{
|
||||
return BadRequest(new MatchmakerResponse(new MatchmakerError(DisconnectReason.ServerError, "Invalid filterSets")));
|
||||
}
|
||||
|
||||
var filteredGames = _listingManager.FindListingsV2(HttpContext, filtersList);
|
||||
var gameListings = filteredGames.Select(GameListing.From).ToList();
|
||||
|
||||
var response = new
|
||||
{
|
||||
games = gameListings,
|
||||
metadata = new
|
||||
{
|
||||
allGamesCount = _gameManager.Games.Count(),
|
||||
matchingGamesCount = gameListings.Count,
|
||||
},
|
||||
};
|
||||
|
||||
return Ok(response);
|
||||
}
|
||||
catch (JsonException ex)
|
||||
{
|
||||
return BadRequest(new MatchmakerResponse(new MatchmakerError(DisconnectReason.ServerError, "Unable to deserialize filter json" + ex)));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(new MatchmakerResponse(new MatchmakerError(DisconnectReason.ServerError, "Unknown exception caught in filter" + ex)));
|
||||
}
|
||||
}
|
||||
|
||||
private static uint ConvertAddressToNumber(IPAddress address)
|
||||
{
|
||||
#pragma warning disable CS0618 // Among Us only supports IPv4
|
||||
@@ -142,13 +214,33 @@ public sealed class GamesController : ControllerBase
|
||||
private class MatchmakerError
|
||||
{
|
||||
[SetsRequiredMembers]
|
||||
public MatchmakerError(DisconnectReason reason)
|
||||
public MatchmakerError(DisconnectReason reason, string message = "")
|
||||
{
|
||||
Reason = reason;
|
||||
Message = message;
|
||||
}
|
||||
|
||||
[JsonPropertyName("Reason")]
|
||||
public required DisconnectReason Reason { get; init; }
|
||||
|
||||
[JsonPropertyName("Message")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
|
||||
public required string Message { get; init; } = string.Empty;
|
||||
}
|
||||
|
||||
private class FindGameByCodeResponse
|
||||
{
|
||||
[SetsRequiredMembers]
|
||||
public FindGameByCodeResponse(MatchmakerError error) => (Errors, Game) = (new[] { error }, null);
|
||||
|
||||
[SetsRequiredMembers]
|
||||
public FindGameByCodeResponse(GameListing game) => (Errors, Game) = (null, game);
|
||||
|
||||
[JsonPropertyName("Errors")]
|
||||
public required MatchmakerError[]? Errors { get; init; }
|
||||
|
||||
[JsonPropertyName("Game")]
|
||||
public required GameListing? Game { get; init; }
|
||||
}
|
||||
|
||||
private class GameListing
|
||||
@@ -168,12 +260,18 @@ public sealed class GamesController : ControllerBase
|
||||
[JsonPropertyName("HostName")]
|
||||
public required string HostName { get; init; }
|
||||
|
||||
[JsonPropertyName("TrueHostName")]
|
||||
public required string TrueHostName { get; init; }
|
||||
|
||||
[JsonPropertyName("HostPlatformName")]
|
||||
public required string HostPlatformName { get; init; }
|
||||
|
||||
[JsonPropertyName("Platform")]
|
||||
public required Platforms Platform { get; init; }
|
||||
|
||||
[JsonPropertyName("QuickChat")]
|
||||
public required QuickChatModes QuickChat { get; init; }
|
||||
|
||||
[JsonPropertyName("Age")]
|
||||
public required int Age { get; init; }
|
||||
|
||||
@@ -189,6 +287,9 @@ public sealed class GamesController : ControllerBase
|
||||
[JsonPropertyName("Language")]
|
||||
public required GameKeywords Language { get; init; }
|
||||
|
||||
[JsonPropertyName("Options")]
|
||||
public required string Options { get; init; }
|
||||
|
||||
public static GameListing From(IGame game)
|
||||
{
|
||||
var platform = game.Host?.Client.PlatformSpecificData;
|
||||
@@ -200,13 +301,16 @@ public sealed class GamesController : ControllerBase
|
||||
GameId = game.Code,
|
||||
PlayerCount = game.PlayerCount,
|
||||
HostName = game.DisplayName ?? game.Host?.Client.Name ?? "Unknown host",
|
||||
TrueHostName = game.DisplayName ?? game.Host?.Client.Name ?? "Unknown host",
|
||||
HostPlatformName = platform?.PlatformName ?? string.Empty,
|
||||
Platform = platform?.Platform ?? Platforms.Unknown,
|
||||
QuickChat = game.Host?.Client.ChatMode ?? QuickChatModes.QuickChatOnly,
|
||||
Age = 0,
|
||||
MaxPlayers = game.Options.MaxPlayers,
|
||||
NumImpostors = game.Options.NumImpostors,
|
||||
MapId = game.Options.Map,
|
||||
Language = game.Options.Keywords,
|
||||
Options = game.Options.ToBase64String(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Impostor.Server.Http;
|
||||
|
||||
/// <summary>
|
||||
/// Generate a diagnostic page to show that the Impostor HTTP server is working.
|
||||
/// </summary>
|
||||
[Route("/")]
|
||||
public sealed class HelloController : ControllerBase
|
||||
{
|
||||
private static bool _shownHello = false;
|
||||
private readonly ILogger<HelloController> _logger;
|
||||
|
||||
public HelloController(ILogger<HelloController> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public IActionResult GetHello()
|
||||
{
|
||||
if (!_shownHello)
|
||||
{
|
||||
_shownHello = true;
|
||||
_logger.LogInformation("Impostor's Http server is reachable (this message is only printed once per start)");
|
||||
}
|
||||
|
||||
return Ok(
|
||||
"""
|
||||
Impostor is running, please configure your Among Us to connect to a game
|
||||
To generate a region file, go to https://impostor.github.io/Impostor
|
||||
"""
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ using Impostor.Api.Games;
|
||||
using Impostor.Api.Games.Managers;
|
||||
using Impostor.Api.Http;
|
||||
using Impostor.Api.Innersloth;
|
||||
using Impostor.Api.Innersloth.GameFilters;
|
||||
using Impostor.Api.Net.Manager;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Options;
|
||||
@@ -59,6 +60,11 @@ public sealed class ListingManager
|
||||
continue;
|
||||
}
|
||||
|
||||
if (game.ModGuid != null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_compatibilityConfig.AllowVersionMixing &&
|
||||
game.Host != null &&
|
||||
_compatibilityManager.CanJoinGame(game.Host.Client.GameVersion, gameVersion) != GameJoinError.None)
|
||||
@@ -103,6 +109,134 @@ public sealed class ListingManager
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<IGame> FindListingsV2(HttpContext ctx, GameFiltersList filtersList, int maxListings = 10)
|
||||
{
|
||||
if (filtersList == null || filtersList.FilterSets.Count == 0)
|
||||
{
|
||||
return Enumerable.Empty<IGame>();
|
||||
}
|
||||
|
||||
var result = new List<IGame>();
|
||||
var resultCount = 0;
|
||||
|
||||
// Apply custom filters from API
|
||||
var filters = _listingFilters.Select(f => f.GetFilter(ctx)).ToArray();
|
||||
|
||||
foreach (var game in _gameManager.Games)
|
||||
{
|
||||
if (!game.IsPublic || game.GameState != GameStates.NotStarted || game.PlayerCount >= game.Options.MaxPlayers)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!filters.All(filter => filter(game)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var matchesAnyFilterSet = false;
|
||||
foreach (var filterSet in filtersList.FilterSets)
|
||||
{
|
||||
// Not sure how to handle normal fools and seek fools
|
||||
if (game.Options.GameMode != filterSet.GameMode)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (game.ModGuid != null && !filterSet.Filters.Any(f => f.OptionType == "mod"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var matchesAllFilters = true;
|
||||
|
||||
// Hard coded to only check map, languages, impostornum, chatmode, tags
|
||||
foreach (var filter in filterSet.Filters)
|
||||
{
|
||||
switch (filter.OptionType)
|
||||
{
|
||||
case "map":
|
||||
if (filter.SubFilter is MapGameFilter mapFilter && ((1 << (int)game.Options.Map) & mapFilter.AcceptedValues) == 0)
|
||||
{
|
||||
matchesAllFilters = false;
|
||||
}
|
||||
|
||||
break;
|
||||
case "languages":
|
||||
if (filter.SubFilter is LanguageFilter langFilter && game.Options.Keywords != (GameKeywords)langFilter.AcceptedValues)
|
||||
{
|
||||
matchesAllFilters = false;
|
||||
}
|
||||
|
||||
break;
|
||||
case "int":
|
||||
if (filter.SubFilter is IntGameFilter intFilter)
|
||||
{
|
||||
switch (intFilter.OptionEnum)
|
||||
{
|
||||
case Int32OptionNames.NumImpostors:
|
||||
if (!intFilter.AcceptedValues.Contains(game.Options.NumImpostors))
|
||||
{
|
||||
matchesAllFilters = false;
|
||||
}
|
||||
|
||||
break;
|
||||
case Int32OptionNames.Tag:
|
||||
if (!intFilter.AcceptedValues.Contains((int)game.Options.Tag))
|
||||
{
|
||||
matchesAllFilters = false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case "chat":
|
||||
if (filter.SubFilter is ChatModeGameFilter chatFilter && game.Host != null)
|
||||
{
|
||||
if (((byte)game.Host.Client.ChatMode & chatFilter.AcceptedValues) == 0)
|
||||
{
|
||||
matchesAllFilters = false;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case "mod":
|
||||
if (filter.SubFilter is ModFilter modFilter && game.ModGuid != modFilter.AcceptedValues)
|
||||
{
|
||||
matchesAllFilters = false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (!matchesAllFilters)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (matchesAllFilters)
|
||||
{
|
||||
matchesAnyFilterSet = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (matchesAnyFilterSet)
|
||||
{
|
||||
result.Add(game);
|
||||
if (++resultCount >= maxListings)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static bool IsGameDesired(IGame game, int map, int impostorCount, GameKeywords language)
|
||||
{
|
||||
if ((map & (1 << (int)game.Options.Map)) == 0)
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Impostor.Api\Impostor.Api.csproj" />
|
||||
<PackageReference Include="Impostor.Hazel" Version="1.0.0" />
|
||||
<PackageReference Include="Impostor.Hazel" Version="1.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -5,6 +5,7 @@ using Impostor.Api;
|
||||
using Impostor.Api.Config;
|
||||
using Impostor.Api.Games;
|
||||
using Impostor.Api.Innersloth;
|
||||
using Impostor.Api.Innersloth.GameOptions;
|
||||
using Impostor.Api.Net;
|
||||
using Impostor.Api.Net.Custom;
|
||||
using Impostor.Api.Net.Messages;
|
||||
@@ -42,6 +43,11 @@ namespace Impostor.Server.Net
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Player != null && Player.Game.ModGuid != null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Player != null && Player.IsHost)
|
||||
{
|
||||
var isHostCheatingAllowed = _antiCheatConfig.AllowCheatingHosts switch {
|
||||
@@ -67,12 +73,22 @@ namespace Impostor.Server.Net
|
||||
{
|
||||
CheatCategory.ProtocolExtension => _antiCheatConfig.ForbidProtocolExtensions,
|
||||
CheatCategory.GameFlow => _antiCheatConfig.EnableGameFlowChecks,
|
||||
CheatCategory.InvalidObject => _antiCheatConfig.EnableInvalidObjectChecks,
|
||||
CheatCategory.MustBeHost => _antiCheatConfig.EnableMustBeHostChecks,
|
||||
CheatCategory.ColorLimits => _antiCheatConfig.EnableColorLimitChecks,
|
||||
CheatCategory.NameLimits => _antiCheatConfig.EnableNameLimitChecks,
|
||||
CheatCategory.Ownership => _antiCheatConfig.EnableOwnershipChecks,
|
||||
CheatCategory.Role => _antiCheatConfig.EnableRoleChecks,
|
||||
CheatCategory.Target => _antiCheatConfig.EnableTargetChecks,
|
||||
CheatCategory.Identifiers => _antiCheatConfig.EnableIdentifierChecks,
|
||||
CheatCategory.HostOnlyExtension => _antiCheatConfig.AllowHostOnlyExtensions switch {
|
||||
CheatingHostMode.Always => false,
|
||||
CheatingHostMode.IfRequested => !GameVersion.HasDisableServerAuthorityFlag,
|
||||
CheatingHostMode.Never => true,
|
||||
_ => true,
|
||||
},
|
||||
CheatCategory.PacketSize => _antiCheatConfig.EnablePacketSizeChecks,
|
||||
CheatCategory.ItemLimits => _antiCheatConfig.EnableItemLimitChecks,
|
||||
CheatCategory.Other => true,
|
||||
_ => LogUnknownCategory(category),
|
||||
};
|
||||
@@ -116,12 +132,25 @@ namespace Impostor.Server.Net
|
||||
switch (flag)
|
||||
{
|
||||
case MessageFlags.HostGame:
|
||||
case MessageFlags.HostModdedGame:
|
||||
{
|
||||
// Read game settings.
|
||||
Message00HostGameC2S.Deserialize(reader, out var gameOptions, out _, out var gameFilterOptions);
|
||||
IGameOptions gameOptions;
|
||||
GameFilterOptions gameFilterOptions;
|
||||
Guid? modGuid = null;
|
||||
|
||||
if (flag == MessageFlags.HostModdedGame)
|
||||
{
|
||||
Message25HostModdedGameC2S.Deserialize(reader, out gameOptions, out _, out gameFilterOptions, out var parsedModGuid);
|
||||
modGuid = parsedModGuid;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Read game settings.
|
||||
Message00HostGameC2S.Deserialize(reader, out gameOptions, out _, out gameFilterOptions);
|
||||
}
|
||||
|
||||
// Create game.
|
||||
var game = await _gameManager.CreateAsync(this, gameOptions, gameFilterOptions);
|
||||
var game = await _gameManager.CreateAsync(this, gameOptions, gameFilterOptions, modGuid);
|
||||
|
||||
if (game == null)
|
||||
{
|
||||
@@ -129,6 +158,11 @@ namespace Impostor.Server.Net
|
||||
return;
|
||||
}
|
||||
|
||||
if (modGuid != null)
|
||||
{
|
||||
_logger.LogInformation("Client {Name} ({Id}) hosted a modded game with mod GUID {ModGuid}.", Name, Id, modGuid);
|
||||
}
|
||||
|
||||
// Code in the packet below will be used in JoinGame.
|
||||
using (var writer = MessageWriter.Get(MessageType.Reliable))
|
||||
{
|
||||
@@ -193,7 +227,7 @@ namespace Impostor.Server.Net
|
||||
|
||||
case MessageFlags.StartGame:
|
||||
{
|
||||
if (!IsPacketAllowed(reader, true))
|
||||
if (!IsPacketAllowed(reader, true, flag))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -208,7 +242,7 @@ namespace Impostor.Server.Net
|
||||
|
||||
case MessageFlags.RemovePlayer:
|
||||
{
|
||||
if (!IsPacketAllowed(reader, true))
|
||||
if (!IsPacketAllowed(reader, true, flag))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -225,7 +259,7 @@ namespace Impostor.Server.Net
|
||||
case MessageFlags.GameData:
|
||||
case MessageFlags.GameDataTo:
|
||||
{
|
||||
if (!IsPacketAllowed(reader, false))
|
||||
if (!IsPacketAllowed(reader, false, flag))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -258,9 +292,80 @@ namespace Impostor.Server.Net
|
||||
break;
|
||||
}
|
||||
|
||||
case MessageFlags.PackedGameDataTo:
|
||||
{
|
||||
if (Player == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var game = Player.Game;
|
||||
|
||||
// Innersloth Special: this message uses PackedInt32 instead of a normal int32
|
||||
var code = reader.ReadPackedInt32();
|
||||
|
||||
if (code != game.Code.Value)
|
||||
{
|
||||
_logger.LogWarning("gcm2 {0} {1}", code, game.Code.Value);
|
||||
return;
|
||||
}
|
||||
|
||||
// We're limiting this to hosts right now. If you have a use case for this for
|
||||
// players to use this feature, we're open to changing this.
|
||||
if (game.HostId != Id)
|
||||
{
|
||||
await ReportCheatAsync(
|
||||
new CheatContext(MessageFlags.FlagToString(flag)),
|
||||
CheatCategory.MustBeHost,
|
||||
"Client sent a PackedGameDataTo message");
|
||||
return;
|
||||
}
|
||||
|
||||
if (await ReportCheatAsync(
|
||||
new CheatContext(MessageFlags.FlagToString(flag)),
|
||||
CheatCategory.HostOnlyExtension,
|
||||
"Client sent a PackedGameDataTo message"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
while (reader.Position < reader.Length)
|
||||
{
|
||||
using var packed = reader.ReadMessage();
|
||||
|
||||
if (packed.Tag != MessageFlags.GameDataTo)
|
||||
{
|
||||
_logger.LogWarning("PackedGameDataTo contained non-GameDataTo flag {0}.", packed.Tag);
|
||||
return;
|
||||
}
|
||||
|
||||
if (packed.ReadInt32() != game.Code.Value)
|
||||
{
|
||||
_logger.LogWarning("PackedGameDataTo contained GameDataTo for the wrong game.");
|
||||
return;
|
||||
}
|
||||
|
||||
var position = packed.Position;
|
||||
var verified = await Player.Game.HandleGameDataAsync(packed, Player, true);
|
||||
packed.Seek(position);
|
||||
|
||||
if (!verified || Player == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var writer = MessageWriter.Get(messageType);
|
||||
var target = packed.ReadPackedInt32();
|
||||
packed.CopyTo(writer);
|
||||
await Player.Game.SendToAsync(writer, target);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case MessageFlags.EndGame:
|
||||
{
|
||||
if (!IsPacketAllowed(reader, true))
|
||||
if (!IsPacketAllowed(reader, true, flag))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -275,7 +380,7 @@ namespace Impostor.Server.Net
|
||||
|
||||
case MessageFlags.AlterGame:
|
||||
{
|
||||
if (!IsPacketAllowed(reader, true))
|
||||
if (!IsPacketAllowed(reader, true, flag))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -296,7 +401,7 @@ namespace Impostor.Server.Net
|
||||
|
||||
case MessageFlags.KickPlayer:
|
||||
{
|
||||
if (!IsPacketAllowed(reader, true))
|
||||
if (!IsPacketAllowed(reader, true, flag))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -343,6 +448,7 @@ namespace Impostor.Server.Net
|
||||
#if DEBUG
|
||||
if (flag != MessageFlags.GameData &&
|
||||
flag != MessageFlags.GameDataTo &&
|
||||
flag != MessageFlags.PackedGameDataTo &&
|
||||
flag != MessageFlags.EndGame &&
|
||||
reader.Position < reader.Length)
|
||||
{
|
||||
@@ -376,7 +482,7 @@ namespace Impostor.Server.Net
|
||||
await _gameManager.OnClientDisconnectAsync(this);
|
||||
}
|
||||
|
||||
private bool IsPacketAllowed(IMessageReader message, bool hostOnly)
|
||||
private bool IsPacketAllowed(IMessageReader message, bool hostOnly, byte flag)
|
||||
{
|
||||
if (Player == null)
|
||||
{
|
||||
@@ -386,7 +492,8 @@ namespace Impostor.Server.Net
|
||||
var game = Player.Game;
|
||||
|
||||
// GameCode must match code of the current game assigned to the player.
|
||||
if (message.ReadInt32() != game.Code)
|
||||
var code = message.ReadInt32();
|
||||
if (code != game.Code.Value)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -399,7 +506,11 @@ namespace Impostor.Server.Net
|
||||
return true;
|
||||
}
|
||||
|
||||
_logger.LogWarning("[{0}] Client sent packet only allowed by the host ({1}).", Id, game.HostId);
|
||||
_logger.LogWarning(
|
||||
"[{0}] Client sent packet {1} only allowed by the host ({2}).",
|
||||
Id,
|
||||
MessageFlags.FlagToString(flag),
|
||||
game.HostId);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Impostor.Api;
|
||||
using Impostor.Api.Innersloth;
|
||||
using Impostor.Api.Innersloth.Customization;
|
||||
using Impostor.Api.Net;
|
||||
using Impostor.Server.Net.State;
|
||||
|
||||
@@ -39,6 +40,12 @@ namespace Impostor.Server.Net
|
||||
|
||||
public ClientPlayer? Player { get; set; }
|
||||
|
||||
public string? FriendCode { get; set; }
|
||||
|
||||
public string? Puid { get; set; }
|
||||
|
||||
public ColorType? PreviousColor { get; set; } = null;
|
||||
|
||||
IClientPlayer? IClient.Player => Player;
|
||||
|
||||
public virtual ValueTask<bool> ReportCheatAsync(CheatContext context, CheatCategory category, string message)
|
||||
|
||||
@@ -1,18 +1,23 @@
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Impostor.Api;
|
||||
using Impostor.Api.Config;
|
||||
using Impostor.Api.Net;
|
||||
using Impostor.Hazel;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Impostor.Server.Net.Hazel
|
||||
{
|
||||
internal class HazelConnection : IHazelConnection
|
||||
{
|
||||
private readonly ILogger<HazelConnection> _logger;
|
||||
private readonly AntiCheatConfig _antiCheatConfig;
|
||||
|
||||
public HazelConnection(Connection innerConnection, ILogger<HazelConnection> logger)
|
||||
public HazelConnection(Connection innerConnection, ILogger<HazelConnection> logger, IOptions<AntiCheatConfig> antiCheatOptions)
|
||||
{
|
||||
_logger = logger;
|
||||
_antiCheatConfig = antiCheatOptions.Value;
|
||||
InnerConnection = innerConnection;
|
||||
innerConnection.DataReceived = ConnectionOnDataReceived;
|
||||
innerConnection.Disconnected = ConnectionOnDisconnected;
|
||||
@@ -58,6 +63,19 @@ namespace Impostor.Server.Net.Hazel
|
||||
return;
|
||||
}
|
||||
|
||||
// Check raw message size against the configured limit.
|
||||
// Innersloth requires full packet ≤ 1200 bytes (1168 bytes payload after 32 bytes IP+UDP headers).
|
||||
if (e.Message.Length > _antiCheatConfig.PacketSizeLimit)
|
||||
{
|
||||
if (await Client.ReportCheatAsync(
|
||||
new CheatContext("RootMessage"),
|
||||
CheatCategory.PacketSize,
|
||||
$"Received a message that is too large, length: {e.Message.Length}"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (e.Message.Position >= e.Message.Length)
|
||||
|
||||
@@ -73,9 +73,16 @@ namespace Impostor.Server.Net.Inner
|
||||
return true;
|
||||
}
|
||||
|
||||
protected async ValueTask<bool> ValidateImpostor(CheatContext context, IClientPlayer sender, InnerPlayerInfo playerInfo, bool value = true)
|
||||
protected async ValueTask<bool> ValidateImpostor(CheatContext context, IClientPlayer sender, InnerPlayerInfo? playerInfo, bool value = true)
|
||||
{
|
||||
if (playerInfo.IsImpostor != value)
|
||||
if (playerInfo == null)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(context, CheatCategory.InvalidObject, "Couldn't check if Impostor, playerInfo not set"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (playerInfo.IsImpostor != value)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(context, CheatCategory.Role, "Failed impostor check"))
|
||||
{
|
||||
@@ -86,9 +93,16 @@ namespace Impostor.Server.Net.Inner
|
||||
return true;
|
||||
}
|
||||
|
||||
protected async ValueTask<bool> ValidateCanVent(CheatContext context, IClientPlayer sender, InnerPlayerInfo playerInfo, bool value = true)
|
||||
protected async ValueTask<bool> ValidateCanVent(CheatContext context, IClientPlayer sender, InnerPlayerInfo? playerInfo, bool value = true)
|
||||
{
|
||||
if (playerInfo.CanVent != value)
|
||||
if (playerInfo == null)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(context, CheatCategory.InvalidObject, "Couldn't check if can vent, playerInfo not set"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (playerInfo.CanVent != value)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(context, CheatCategory.Role, "Failed can vent check"))
|
||||
{
|
||||
@@ -99,9 +113,16 @@ namespace Impostor.Server.Net.Inner
|
||||
return true;
|
||||
}
|
||||
|
||||
protected async ValueTask<bool> ValidateRole(CheatContext context, IClientPlayer sender, InnerPlayerInfo playerInfo, RoleTypes role)
|
||||
protected async ValueTask<bool> ValidateRole(CheatContext context, IClientPlayer sender, InnerPlayerInfo? playerInfo, RoleTypes role)
|
||||
{
|
||||
if (playerInfo.RoleType != role)
|
||||
if (playerInfo == null)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(context, CheatCategory.InvalidObject, "Couldn't check role, playerInfo not set"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (playerInfo.RoleType != role)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(context, CheatCategory.Role, $"Failed role = {role} check"))
|
||||
{
|
||||
|
||||
@@ -37,17 +37,13 @@ namespace Impostor.Server.Net.Inner.Objects.Components
|
||||
|
||||
public override async ValueTask<bool> HandleRpcAsync(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader)
|
||||
{
|
||||
if (call != RpcCalls.BootFromVent && !await ValidateOwnership(call, sender))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (call)
|
||||
{
|
||||
case RpcCalls.EnterVent:
|
||||
case RpcCalls.ExitVent:
|
||||
{
|
||||
if (!await ValidateCanVent(call, sender, _playerControl.PlayerInfo))
|
||||
if (!await ValidateOwnership(call, sender) ||
|
||||
!await ValidateCanVent(call, sender, _playerControl.PlayerInfo))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -106,17 +102,34 @@ namespace Impostor.Server.Net.Inner.Objects.Components
|
||||
}
|
||||
|
||||
case RpcCalls.ClimbLadder:
|
||||
{
|
||||
if (!await ValidateOwnership(call, sender))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Rpc31ClimbLadder.Deserialize(reader, out var ladderId, out var lastClimbLadderSid);
|
||||
break;
|
||||
}
|
||||
|
||||
case RpcCalls.Pet:
|
||||
{
|
||||
if (!await ValidateOwnership(call, sender))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Rpc49Pet.Deserialize(reader, out var position, out var petPosition);
|
||||
break;
|
||||
}
|
||||
|
||||
case RpcCalls.CancelPet:
|
||||
{
|
||||
if (!await ValidateOwnership(call, sender))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Rpc50CancelPet.Deserialize(reader);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Impostor.Api.Net.Inner.Objects;
|
||||
|
||||
@@ -9,5 +9,7 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
IReadOnlyCollection<IInnerMeetingHud.IPlayerVoteArea> IInnerMeetingHud.PlayerStates => Array.AsReadOnly(_playerStates);
|
||||
|
||||
IInnerPlayerInfo? IInnerMeetingHud.Reporter => Reporter;
|
||||
|
||||
IReadOnlyCollection<IInnerMeetingHud.IJudgeOverrule> IInnerMeetingHud.JudgeOverrules => JudgeOverrules;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Impostor.Api.Net.Inner.Objects;
|
||||
|
||||
namespace Impostor.Server.Net.Inner.Objects
|
||||
{
|
||||
internal partial class InnerMeetingHud
|
||||
{
|
||||
public sealed class JudgeOverrule : IInnerMeetingHud.IJudgeOverrule
|
||||
{
|
||||
public JudgeOverrule(byte judgePlayerId, byte overruledPlayerId, ushort overruleNonce)
|
||||
{
|
||||
JudgePlayerId = judgePlayerId;
|
||||
OverruledPlayerId = overruledPlayerId;
|
||||
OverruleNonce = overruleNonce;
|
||||
}
|
||||
|
||||
public byte JudgePlayerId { get; set; }
|
||||
|
||||
public byte OverruledPlayerId { get; set; }
|
||||
|
||||
public ushort OverruleNonce { get; set; }
|
||||
|
||||
public static JudgeOverrule Deserialize(IMessageReader reader)
|
||||
{
|
||||
var inner = reader.ReadMessage();
|
||||
return new JudgeOverrule(inner.Tag, inner.ReadByte(), inner.ReadUInt16());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ using Impostor.Api.Innersloth.GameOptions;
|
||||
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.Events.Meeting;
|
||||
using Impostor.Server.Events.Player;
|
||||
@@ -22,10 +23,12 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
{
|
||||
internal partial class InnerMeetingHud : InnerNetObject
|
||||
{
|
||||
private static readonly GameVersion JudgeMinVersion = new GameVersion(2026, 7, 15); // 18.0
|
||||
|
||||
private readonly ILogger<InnerMeetingHud> _logger;
|
||||
private readonly IEventManager _eventManager;
|
||||
|
||||
private readonly CancellationTokenSource _timerToken;
|
||||
private readonly List<JudgeOverrule> _judgeOverrules = new();
|
||||
|
||||
[AllowNull]
|
||||
private PlayerVoteArea[] _playerStates;
|
||||
@@ -65,6 +68,8 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
public InnerPlayerInfo? Reporter { get; private set; }
|
||||
|
||||
public IReadOnlyCollection<IInnerMeetingHud.IJudgeOverrule> JudgeOverrules => _judgeOverrules;
|
||||
|
||||
public override ValueTask<bool> SerializeAsync(IMessageWriter writer, bool initialState)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
@@ -108,6 +113,19 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sender.Client.GameVersion >= JudgeMinVersion)
|
||||
{
|
||||
var overruleQueueListLength = reader.ReadPackedInt32();
|
||||
if (overruleQueueListLength > 0)
|
||||
{
|
||||
_judgeOverrules.Clear();
|
||||
for (var i = 0; i < overruleQueueListLength; i++)
|
||||
{
|
||||
_judgeOverrules.Add(JudgeOverrule.Deserialize(reader));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override async ValueTask<bool> HandleRpcAsync(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader)
|
||||
@@ -122,6 +140,7 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
}
|
||||
|
||||
Rpc22Close.Deserialize(reader);
|
||||
_judgeOverrules.Clear();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -132,10 +151,23 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
return false;
|
||||
}
|
||||
|
||||
Rpc23VotingComplete.Deserialize(reader, out var states, out var playerId, out var tie);
|
||||
foreach (var messageReader in states)
|
||||
if (sender.Client.GameVersion >= JudgeMinVersion)
|
||||
{
|
||||
messageReader.Dispose();
|
||||
Rpc23VotingComplete.Deserialize(reader, out var states, out var playerId, out var tie, out var wasOverruled, out var overrideId);
|
||||
|
||||
foreach (var messageReader in states)
|
||||
{
|
||||
messageReader.Dispose();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Rpc23VotingComplete.Deserialize(reader, out var states, out var playerId, out var tie);
|
||||
|
||||
foreach (var messageReader in states)
|
||||
{
|
||||
messageReader.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
// This would be a nice place to implement an anti cheat.
|
||||
@@ -161,6 +193,12 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
break;
|
||||
}
|
||||
|
||||
case RpcCalls.QueueOverruleVotes:
|
||||
{
|
||||
Rpc66QueueOverruleVotes.Deserialize(reader, out var judgePlayerId, out var targetPlayerId, out var overruleNonce);
|
||||
return await HandleQueueOverruleVotesAsync(sender, target, judgePlayerId, targetPlayerId, overruleNonce);
|
||||
}
|
||||
|
||||
default:
|
||||
return await base.HandleRpcAsync(sender, target, call, reader);
|
||||
}
|
||||
@@ -221,6 +259,87 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
return true;
|
||||
}
|
||||
|
||||
private async ValueTask<bool> HandleQueueOverruleVotesAsync(ClientPlayer sender, ClientPlayer? target, byte judgePlayerId, byte targetPlayerId, ushort overruleNonce)
|
||||
{
|
||||
if (!await ValidateRole(RpcCalls.QueueOverruleVotes, sender, sender.Character?.PlayerInfo, RoleTypes.Judge) ||
|
||||
!await ValidateTarget(RpcCalls.QueueOverruleVotes, sender, target) ||
|
||||
!await ValidateHost(RpcCalls.QueueOverruleVotes, target!))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// A Judge can only queue an overrule for themself.
|
||||
if (judgePlayerId != sender.Character!.PlayerId)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.QueueOverruleVotes, CheatCategory.Ownership, "Client sent a Judge overrule for another player"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// The client only generates nonces in the 1..65535 range.
|
||||
if (overruleNonce == 0)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.QueueOverruleVotes, CheatCategory.GameFlow, "Client sent a Judge overrule with an uninitialized nonce"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// A Judge can only overrule once per meeting.
|
||||
if (_judgeOverrules.Any(overrule => overrule.JudgePlayerId == judgePlayerId))
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.QueueOverruleVotes, CheatCategory.GameFlow, "Client sent more than one Judge overrule in the same meeting"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// The overruled player must exist.
|
||||
if (Game.GameNet.GameData!.GetPlayerById(targetPlayerId) == null)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.QueueOverruleVotes, CheatCategory.InvalidObject, "Client sent a Judge overrule for an unknown player"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
AddOrUpdateJudgeOverrule(judgePlayerId, targetPlayerId, overruleNonce);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void AddOrUpdateJudgeOverrule(byte judgePlayerId, byte targetPlayerId, ushort overruleNonce)
|
||||
{
|
||||
var existing = _judgeOverrules.Find(overrule => overrule.JudgePlayerId == judgePlayerId);
|
||||
if (existing != null)
|
||||
{
|
||||
existing.OverruledPlayerId = targetPlayerId;
|
||||
existing.OverruleNonce = overruleNonce;
|
||||
}
|
||||
else
|
||||
{
|
||||
_judgeOverrules.Add(new JudgeOverrule(judgePlayerId, targetPlayerId, overruleNonce));
|
||||
}
|
||||
}
|
||||
|
||||
private JudgeOverrule? GetWinningOverrule()
|
||||
{
|
||||
var gameData = Game.GameNet.GameData!;
|
||||
foreach (var overrule in _judgeOverrules)
|
||||
{
|
||||
var judge = gameData.GetPlayerById(overrule.JudgePlayerId);
|
||||
var target = gameData.GetPlayerById(overrule.OverruledPlayerId);
|
||||
|
||||
if (judge != null && target != null && !judge.Disconnected && !target.Disconnected)
|
||||
{
|
||||
return overrule;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private async ValueTask CheckForEndVotingAsync()
|
||||
{
|
||||
if (_playerStates.All(ps => ps.IsDead || ps.DidVote))
|
||||
@@ -287,13 +406,35 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
var max = MaxPair(self, out var tie);
|
||||
var exiled = tie ? null : Game.GameNet.GameData!.GetPlayerById(max.Key)?.Controller;
|
||||
|
||||
if (exiled != null)
|
||||
// exile or others?
|
||||
var wasOverruled = false;
|
||||
ushort overrideId = 0;
|
||||
|
||||
var winningOverrule = GetWinningOverrule();
|
||||
if (winningOverrule != null)
|
||||
{
|
||||
wasOverruled = true;
|
||||
overrideId = winningOverrule.OverruleNonce;
|
||||
|
||||
var overruled = Game.GameNet.GameData!.GetPlayerById(winningOverrule.OverruledPlayerId);
|
||||
if (overruled != null && overruled.IsImpostor)
|
||||
{
|
||||
exiled = overruled.Controller;
|
||||
}
|
||||
else
|
||||
{
|
||||
var judge = Game.GameNet.GameData!.GetPlayerById(winningOverrule.JudgePlayerId);
|
||||
exiled = judge?.Controller;
|
||||
}
|
||||
}
|
||||
|
||||
if (exiled != null && exiled.PlayerInfo != null)
|
||||
{
|
||||
exiled.PlayerInfo.LastDeathReason = DeathReason.Exile;
|
||||
await _eventManager.CallAsync(new PlayerExileEvent(Game, Game.GetClientPlayer(exiled!.OwnerId)!, exiled));
|
||||
}
|
||||
|
||||
await _eventManager.CallAsync(new MeetingEndedEvent(Game, this, exiled, tie));
|
||||
await _eventManager.CallAsync(new MeetingEndedEvent(Game, this, exiled, tie, wasOverruled, overrideId));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading.Tasks;
|
||||
using Impostor.Api;
|
||||
using Impostor.Api.Innersloth;
|
||||
@@ -16,10 +17,15 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
IInnerCustomNetworkTransform IInnerPlayerControl.NetworkTransform => NetworkTransform;
|
||||
|
||||
IInnerPlayerInfo IInnerPlayerControl.PlayerInfo => PlayerInfo;
|
||||
IInnerPlayerInfo? IInnerPlayerControl.PlayerInfo => PlayerInfo;
|
||||
|
||||
public async ValueTask SetNameAsync(string name)
|
||||
{
|
||||
if (PlayerInfo == null)
|
||||
{
|
||||
throw new ImpostorProtocolException("Cannot set name, PlayerInfo is null");
|
||||
}
|
||||
|
||||
PlayerInfo.CurrentOutfit.PlayerName = name;
|
||||
|
||||
using var writer = Game.StartRpc(NetId, RpcCalls.SetName);
|
||||
@@ -29,6 +35,11 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
public async ValueTask SetColorAsync(ColorType color)
|
||||
{
|
||||
if (PlayerInfo == null)
|
||||
{
|
||||
throw new ImpostorProtocolException("Cannot set color, PlayerInfo is null");
|
||||
}
|
||||
|
||||
PlayerInfo.CurrentOutfit.Color = color;
|
||||
|
||||
using var writer = Game.StartRpc(NetId, RpcCalls.SetColor);
|
||||
@@ -38,6 +49,11 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
public async ValueTask SetHatAsync(string hatId)
|
||||
{
|
||||
if (PlayerInfo == null)
|
||||
{
|
||||
throw new ImpostorProtocolException("Cannot set hat, PlayerInfo is null");
|
||||
}
|
||||
|
||||
PlayerInfo.CurrentOutfit.HatId = hatId;
|
||||
|
||||
using var writer = Game.StartRpc(NetId, RpcCalls.SetHatStr);
|
||||
@@ -47,6 +63,11 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
public async ValueTask SetPetAsync(string petId)
|
||||
{
|
||||
if (PlayerInfo == null)
|
||||
{
|
||||
throw new ImpostorProtocolException("Cannot set pet, PlayerInfo is null");
|
||||
}
|
||||
|
||||
PlayerInfo.CurrentOutfit.PetId = petId;
|
||||
|
||||
using var writer = Game.StartRpc(NetId, RpcCalls.SetPetStr);
|
||||
@@ -56,6 +77,11 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
public async ValueTask SetSkinAsync(string skinId)
|
||||
{
|
||||
if (PlayerInfo == null)
|
||||
{
|
||||
throw new ImpostorProtocolException("Cannot set skin, PlayerInfo is null");
|
||||
}
|
||||
|
||||
PlayerInfo.CurrentOutfit.SkinId = skinId;
|
||||
|
||||
using var writer = Game.StartRpc(NetId, RpcCalls.SetSkinStr);
|
||||
@@ -65,6 +91,11 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
public async ValueTask SetVisorAsync(string visorId)
|
||||
{
|
||||
if (PlayerInfo == null)
|
||||
{
|
||||
throw new ImpostorProtocolException("Cannot set visor, PlayerInfo is null");
|
||||
}
|
||||
|
||||
PlayerInfo.CurrentOutfit.VisorId = visorId;
|
||||
|
||||
using var writer = Game.StartRpc(NetId, RpcCalls.SetVisorStr);
|
||||
@@ -74,6 +105,11 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
public async ValueTask SetNamePlateAsync(string nameplateId)
|
||||
{
|
||||
if (PlayerInfo == null)
|
||||
{
|
||||
throw new ImpostorProtocolException("Cannot set nameplate, PlayerInfo is null");
|
||||
}
|
||||
|
||||
PlayerInfo.CurrentOutfit.NamePlateId = nameplateId;
|
||||
|
||||
using var writer = Game.StartRpc(NetId, RpcCalls.SetNamePlateStr);
|
||||
@@ -100,23 +136,53 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
await Game.FinishRpcAsync(writer, player.OwnerId);
|
||||
}
|
||||
|
||||
private bool ValidateMurderPlayer(IInnerPlayerControl target, MurderResultFlags result, [NotNullWhen(false)] out string? invalidReason)
|
||||
{
|
||||
if (PlayerInfo == null)
|
||||
{
|
||||
invalidReason = "Tried to murder a player, but the murderer didn't have a playerinfo";
|
||||
}
|
||||
else if (!PlayerInfo.IsImpostor)
|
||||
{
|
||||
invalidReason = "Tried to murder a player, but murderer was not an impostor.";
|
||||
}
|
||||
else if (PlayerInfo.IsDead)
|
||||
{
|
||||
invalidReason = "Tried to murder a player, but murderer was not alive.";
|
||||
}
|
||||
else if (target.PlayerInfo == null)
|
||||
{
|
||||
invalidReason = "Tried to murder a player, but the murderer didn't have a playerinfo";
|
||||
}
|
||||
else if (target.PlayerInfo.IsImpostor)
|
||||
{
|
||||
invalidReason = "Tried to murder a player, but target is an impostor";
|
||||
}
|
||||
else if (target.PlayerInfo.IsDead)
|
||||
{
|
||||
invalidReason = "Tried to murder a player, but target was not alive.";
|
||||
}
|
||||
else
|
||||
{
|
||||
invalidReason = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async ValueTask MurderPlayerAsync(IInnerPlayerControl target, MurderResultFlags result)
|
||||
{
|
||||
if (!PlayerInfo.IsImpostor)
|
||||
if (!ValidateMurderPlayer(target, result, out var reason))
|
||||
{
|
||||
throw new ImpostorProtocolException("Tried to murder a player, but murderer was not the impostor.");
|
||||
throw new ImpostorProtocolException(reason);
|
||||
}
|
||||
|
||||
if (PlayerInfo.IsDead)
|
||||
{
|
||||
throw new ImpostorProtocolException("Tried to murder a player, but murderer was not alive.");
|
||||
}
|
||||
|
||||
if (target.PlayerInfo.IsDead)
|
||||
{
|
||||
throw new ImpostorProtocolException("Tried to murder a player, but target was not alive.");
|
||||
}
|
||||
await ForceMurderPlayerAsync(target, result);
|
||||
}
|
||||
|
||||
public async ValueTask ForceMurderPlayerAsync(IInnerPlayerControl target, MurderResultFlags result)
|
||||
{
|
||||
if (!result.IsFailed())
|
||||
{
|
||||
((InnerPlayerControl)target).Die(DeathReason.Kill);
|
||||
@@ -136,25 +202,43 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
public async ValueTask ProtectPlayerAsync(IInnerPlayerControl target)
|
||||
{
|
||||
if (target.PlayerInfo.RoleType == RoleTypes.GuardianAngel)
|
||||
if (target.PlayerInfo == null)
|
||||
{
|
||||
throw new ImpostorProtocolException("Tried to protect another Guardian Angel");
|
||||
throw new ImpostorProtocolException("Tried to exile a player, but target didn't have a playerinfo");
|
||||
}
|
||||
else if (target.PlayerInfo.IsDead)
|
||||
{
|
||||
throw new ImpostorProtocolException("Tried to protect a player that is dead");
|
||||
}
|
||||
|
||||
await ForceProtectPlayerAsync(target);
|
||||
}
|
||||
|
||||
public async ValueTask ForceProtectPlayerAsync(IInnerPlayerControl target)
|
||||
{
|
||||
((InnerPlayerControl)target).Protect(this);
|
||||
|
||||
using var writer = Game.StartRpc(NetId, RpcCalls.ProtectPlayer);
|
||||
Rpc45ProtectPlayer.Serialize(writer, target, PlayerInfo.CurrentOutfit.Color);
|
||||
Rpc45ProtectPlayer.Serialize(writer, target, PlayerInfo?.CurrentOutfit.Color ?? ColorType.Red);
|
||||
await Game.FinishRpcAsync(writer);
|
||||
}
|
||||
|
||||
public async ValueTask ExileAsync()
|
||||
{
|
||||
if (PlayerInfo.IsDead)
|
||||
if (PlayerInfo == null)
|
||||
{
|
||||
throw new ImpostorProtocolException("Tried to exile a player, but target didn't have a playerinfo");
|
||||
}
|
||||
else if (PlayerInfo.IsDead)
|
||||
{
|
||||
throw new ImpostorProtocolException("Tried to exile a player, but target was not alive.");
|
||||
}
|
||||
|
||||
await ForceExileAsync();
|
||||
}
|
||||
|
||||
public async ValueTask ForceExileAsync()
|
||||
{
|
||||
// Update player.
|
||||
Die(DeathReason.Exile);
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Impostor.Api;
|
||||
@@ -57,8 +56,7 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
public InnerCustomNetworkTransform NetworkTransform { get; }
|
||||
|
||||
[AllowNull]
|
||||
public InnerPlayerInfo PlayerInfo { get; internal set; }
|
||||
public InnerPlayerInfo? PlayerInfo { get; internal set; }
|
||||
|
||||
internal Queue<string> RequestedPlayerName { get; } = new Queue<string>();
|
||||
|
||||
@@ -385,15 +383,25 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
Rpc44SetRole.Deserialize(reader, out var role, out var _);
|
||||
|
||||
if (role is RoleTypes.ImpostorGhost or RoleTypes.CrewmateGhost or RoleTypes.GuardianAngel)
|
||||
if (PlayerInfo == null)
|
||||
{
|
||||
PlayerInfo.RoleWhenAlive = PlayerInfo.RoleType;
|
||||
PlayerInfo.IsDead = true;
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.SetRole, CheatCategory.InvalidObject, "PlayerControl doesn't have PlayerInfo"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (role is RoleTypes.ImpostorGhost or RoleTypes.CrewmateGhost or RoleTypes.GuardianAngel or RoleTypes.SpiritGuide)
|
||||
{
|
||||
PlayerInfo.RoleWhenAlive = PlayerInfo.RoleType;
|
||||
PlayerInfo.IsDead = true;
|
||||
}
|
||||
|
||||
PlayerInfo.RoleType = role;
|
||||
}
|
||||
|
||||
PlayerInfo.RoleType = role;
|
||||
|
||||
if (Game.GameState == GameStates.Starting && Game.Players.All(clientPlayer => clientPlayer.Character?.PlayerInfo.RoleType != null))
|
||||
if (Game.GameState == GameStates.Starting && Game.Players.All(clientPlayer => clientPlayer.Character?.PlayerInfo?.RoleType != null))
|
||||
{
|
||||
await Game.StartedAsync();
|
||||
}
|
||||
@@ -585,8 +593,19 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
internal void Die(DeathReason reason)
|
||||
{
|
||||
PlayerInfo.IsDead = true;
|
||||
PlayerInfo.LastDeathReason = reason;
|
||||
if (PlayerInfo == null)
|
||||
{
|
||||
// "Custom Net Objects" aka hacked up PlayerControl objects as popularized by host only mods may trigger this
|
||||
if (!Game.IsHostAuthoritive)
|
||||
{
|
||||
_logger.LogWarning("Tried to kill player that didn't have a PlayerInfo set, this shouldn't happen");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
PlayerInfo.IsDead = true;
|
||||
PlayerInfo.LastDeathReason = reason;
|
||||
}
|
||||
}
|
||||
|
||||
internal void Protect(InnerPlayerControl guardianAngel)
|
||||
@@ -598,7 +617,18 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
private async ValueTask HandleCompleteTask(ClientPlayer sender, uint taskId)
|
||||
{
|
||||
var task = PlayerInfo.Tasks.ElementAtOrDefault((int)taskId);
|
||||
TaskInfo? task = null;
|
||||
if (PlayerInfo == null)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.CompleteTask, CheatCategory.InvalidObject, "PlayerControl doesn't have PlayerInfo"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
task = PlayerInfo.Tasks.ElementAtOrDefault((int)taskId);
|
||||
}
|
||||
|
||||
if (task != null)
|
||||
{
|
||||
@@ -662,7 +692,10 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
if (sender.IsOwner(this))
|
||||
{
|
||||
if (Game.Players.Any(x => x.Character != null && x.Character != this && x.Character.PlayerInfo.PlayerName == name))
|
||||
if (Game.Players.Any(x => x.Character != null &&
|
||||
x.Character != this &&
|
||||
x.Character.PlayerInfo != null &&
|
||||
x.Character.PlayerInfo.PlayerName == name))
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.SetName, CheatCategory.NameLimits, "Client sent name that is already used"))
|
||||
{
|
||||
@@ -685,14 +718,20 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
var expected = RequestedPlayerName.Dequeue();
|
||||
var requested = expected;
|
||||
|
||||
if (Game.Players.Any(x => x.Character != null && x.Character != this && x.Character.PlayerInfo.PlayerName == expected))
|
||||
if (Game.Players.Any(x => x.Character != null &&
|
||||
x.Character != this &&
|
||||
x.Character.PlayerInfo != null &&
|
||||
x.Character.PlayerInfo.PlayerName == expected))
|
||||
{
|
||||
var i = 1;
|
||||
while (true)
|
||||
{
|
||||
var text = expected + " " + i;
|
||||
|
||||
if (Game.Players.All(x => x.Character == null || x.Character == this || x.Character.PlayerInfo.PlayerName != text))
|
||||
if (Game.Players.All(x => x.Character == null ||
|
||||
x.Character == this ||
|
||||
x.Character.PlayerInfo == null ||
|
||||
x.Character.PlayerInfo.PlayerName != text))
|
||||
{
|
||||
expected = text;
|
||||
break;
|
||||
@@ -723,7 +762,17 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
}
|
||||
}
|
||||
|
||||
PlayerInfo.CurrentOutfit.PlayerName = name;
|
||||
if (PlayerInfo == null)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.SetName, CheatCategory.InvalidObject, "PlayerControl doesn't have PlayerInfo"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
PlayerInfo.CurrentOutfit.PlayerName = name;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -843,7 +892,27 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
}
|
||||
}
|
||||
|
||||
PlayerInfo.CurrentOutfit.Color = color;
|
||||
if (PlayerInfo == null)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.SetColor, CheatCategory.InvalidObject, "PlayerControl doesn't have PlayerInfo"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
PlayerInfo.CurrentOutfit.Color = color;
|
||||
}
|
||||
|
||||
// Record the color so it can be restored on the next game
|
||||
if (Game.TryGetPlayer(OwnerId, out var clientPlayer))
|
||||
{
|
||||
clientPlayer.Client.PreviousColor = color;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("Tried to record color, but couldn't get player with id {PlayerId}", OwnerId);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -856,8 +925,18 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
return false;
|
||||
}
|
||||
|
||||
PlayerInfo.CurrentOutfit.HatId = hat;
|
||||
PlayerInfo.CurrentOutfit.HatSequenceId = nextRpcSequenceId;
|
||||
if (PlayerInfo == null)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.SetHatStr, CheatCategory.InvalidObject, "PlayerControl doesn't have PlayerInfo"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
PlayerInfo.CurrentOutfit.HatId = hat;
|
||||
PlayerInfo.CurrentOutfit.HatSequenceId = nextRpcSequenceId;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -870,8 +949,18 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
return false;
|
||||
}
|
||||
|
||||
PlayerInfo.CurrentOutfit.SkinId = skin;
|
||||
PlayerInfo.CurrentOutfit.SkinSequenceId = nextRpcSequenceId;
|
||||
if (PlayerInfo == null)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.SetSkinStr, CheatCategory.InvalidObject, "PlayerControl doesn't have PlayerInfo"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
PlayerInfo.CurrentOutfit.SkinId = skin;
|
||||
PlayerInfo.CurrentOutfit.SkinSequenceId = nextRpcSequenceId;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -884,8 +973,18 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
return false;
|
||||
}
|
||||
|
||||
PlayerInfo.CurrentOutfit.VisorId = visor;
|
||||
PlayerInfo.CurrentOutfit.VisorSequenceId = nextRpcSequenceId;
|
||||
if (PlayerInfo == null)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.SetVisorStr, CheatCategory.InvalidObject, "PlayerControl doesn't have PlayerInfo"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
PlayerInfo.CurrentOutfit.VisorId = visor;
|
||||
PlayerInfo.CurrentOutfit.VisorSequenceId = nextRpcSequenceId;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -898,8 +997,18 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
return false;
|
||||
}
|
||||
|
||||
PlayerInfo.CurrentOutfit.NamePlateId = namePlate;
|
||||
PlayerInfo.CurrentOutfit.NamePlateSequenceId = nextRpcSequenceId;
|
||||
if (PlayerInfo == null)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.SetNamePlateStr, CheatCategory.InvalidObject, "PlayerControl doesn't have PlayerInfo"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
PlayerInfo.CurrentOutfit.NamePlateId = namePlate;
|
||||
PlayerInfo.CurrentOutfit.NamePlateSequenceId = nextRpcSequenceId;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -912,14 +1021,31 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
return false;
|
||||
}
|
||||
|
||||
PlayerInfo.PlayerLevel = level;
|
||||
if (PlayerInfo == null)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.SetLevel, CheatCategory.InvalidObject, "PlayerControl doesn't have PlayerInfo"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
PlayerInfo.PlayerLevel = level;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async ValueTask<bool> HandleCheckMurder(ClientPlayer sender, InnerPlayerControl? target)
|
||||
{
|
||||
if (!PlayerInfo.CanMurder(Game, _dateTimeProvider))
|
||||
if (PlayerInfo == null)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.CheckMurder, CheatCategory.InvalidObject, "PlayerControl doesn't have PlayerInfo"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (!PlayerInfo.CanMurder(Game, _dateTimeProvider))
|
||||
{
|
||||
if (IsMurdering == target)
|
||||
{
|
||||
@@ -934,21 +1060,11 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Host-only mods intentionally desync players, so it may appear that one killing role (like a genuine impostor) is killing another
|
||||
// killing role (like an sheriff). So this needs to be allowed if the host requested authority.
|
||||
if (target == null || (target.PlayerInfo.IsImpostor && !_game.IsHostAuthoritive))
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.CheckMurder, CheatCategory.GameFlow, "Client tried to murder invalid target"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
PlayerInfo.LastMurder = _dateTimeProvider.UtcNow - TimeSpan.FromMilliseconds(sender.Client.Connection.AveragePing);
|
||||
IsMurdering = target;
|
||||
}
|
||||
|
||||
PlayerInfo.LastMurder = _dateTimeProvider.UtcNow - TimeSpan.FromMilliseconds(sender.Client.Connection.AveragePing);
|
||||
IsMurdering = target;
|
||||
|
||||
// Check if host authority mode is on
|
||||
if (_game.IsHostAuthoritive)
|
||||
{
|
||||
@@ -956,17 +1072,34 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
return true;
|
||||
}
|
||||
|
||||
if (target != null)
|
||||
if (target == null)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.CheckMurder, CheatCategory.GameFlow, "Client tried to murder a nonexisting target"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = target.IsProtected ? MurderResultFlags.FailedProtected : MurderResultFlags.Succeeded;
|
||||
|
||||
if (!ValidateMurderPlayer(target, result, out var invalidReason))
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.CheckMurder, CheatCategory.GameFlow, invalidReason))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var evt = new PlayerCheckMurderEvent(Game, sender, this, target, result);
|
||||
await _eventManager.CallAsync(evt);
|
||||
|
||||
if (!evt.IsCancelled)
|
||||
{
|
||||
target.ProtectedOn = null; // Clear GA protection in all cases
|
||||
await MurderPlayerAsync(target, evt.Result);
|
||||
|
||||
// Don't repeat checks as they were already done in ValidateMP
|
||||
await ForceMurderPlayerAsync(target, evt.Result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1000,7 +1133,7 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
}
|
||||
}
|
||||
|
||||
if (target != null && !target.PlayerInfo.IsDead)
|
||||
if (target != null && target.PlayerInfo != null && !target.PlayerInfo.IsDead)
|
||||
{
|
||||
// In host authoritive mode every client has to figure out if the kill was prevented by guardian protection on it's own
|
||||
if ((result & MurderResultFlags.Succeeded) != 0 && target.IsProtected)
|
||||
@@ -1050,9 +1183,35 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
private async ValueTask<bool> HandleSendChat(ClientPlayer sender, string message)
|
||||
{
|
||||
var @event = new PlayerChatEvent(Game, sender, this, message);
|
||||
|
||||
// See https://github.com/Innersloth-LLC/AmongUsModdingInformation?tab=readme-ov-file#chat-commands
|
||||
// Details not mentioned:
|
||||
// - If the host sends a message that starts with /cmd, it is sent to all players
|
||||
// - If the message starts with " /cmd" (note the space) it is sent to all players
|
||||
if (Game.IsHostAuthoritive && !sender.IsHost && message.StartsWith("/cmd"))
|
||||
{
|
||||
@event.SendToAllPlayers = false;
|
||||
}
|
||||
|
||||
await _eventManager.CallAsync(@event);
|
||||
|
||||
return !@event.IsCancelled;
|
||||
if (@event.IsCancelled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (@event.SendToAllPlayers == false)
|
||||
{
|
||||
if (Game.Host != null)
|
||||
{
|
||||
await SendChatToPlayerAsync(message, Game.Host.Character);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask HandleStartMeeting(byte targetId)
|
||||
@@ -1069,8 +1228,18 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
return false;
|
||||
}
|
||||
|
||||
PlayerInfo.CurrentOutfit.PetId = pet;
|
||||
PlayerInfo.CurrentOutfit.PetSequenceId = nextRpcSequenceId;
|
||||
if (PlayerInfo == null)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.SetPetStr, CheatCategory.InvalidObject, "PlayerControl doesn't have PlayerInfo"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
PlayerInfo.CurrentOutfit.PetId = pet;
|
||||
PlayerInfo.CurrentOutfit.PetSequenceId = nextRpcSequenceId;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Impostor.Api;
|
||||
using Impostor.Api.Events.Managers;
|
||||
using Impostor.Api.Games;
|
||||
using Impostor.Api.Innersloth;
|
||||
@@ -51,14 +52,18 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
|
||||
public bool Disconnected { get; internal set; }
|
||||
|
||||
public bool IsImpostor => RoleType is RoleTypes.Impostor or RoleTypes.Shapeshifter or RoleTypes.ImpostorGhost or RoleTypes.Phantom;
|
||||
public bool IsImpostor => RoleType is RoleTypes.Impostor or RoleTypes.Shapeshifter or RoleTypes.ImpostorGhost or RoleTypes.Phantom or RoleTypes.Viper;
|
||||
|
||||
public bool CanVent => RoleType is RoleTypes.Impostor or RoleTypes.Shapeshifter or RoleTypes.Phantom or RoleTypes.Engineer;
|
||||
public bool CanVent => RoleType is RoleTypes.Impostor or RoleTypes.Shapeshifter or RoleTypes.Phantom or RoleTypes.Viper or RoleTypes.Engineer;
|
||||
|
||||
public bool IsDead { get; internal set; }
|
||||
|
||||
public DeathReason LastDeathReason { get; internal set; }
|
||||
|
||||
public string? FriendCode { get; internal set; }
|
||||
|
||||
public string? Puid { get; internal set; }
|
||||
|
||||
public List<TaskInfo> Tasks { get; internal set; } = new List<TaskInfo>(0);
|
||||
|
||||
public DateTimeOffset LastMurder { get; set; }
|
||||
@@ -128,12 +133,13 @@ namespace Impostor.Server.Net.Inner.Objects
|
||||
Tasks[i].Serialize(writer);
|
||||
}
|
||||
|
||||
writer.Write(string.Empty); // FriendCode
|
||||
writer.Write(string.Empty); // PUID
|
||||
// Resolved by the matchmaker and pushed onto the player by a plugin
|
||||
writer.Write(FriendCode ?? string.Empty);
|
||||
writer.Write(Puid ?? string.Empty);
|
||||
return new ValueTask<bool>(true);
|
||||
}
|
||||
|
||||
public override ValueTask DeserializeAsync(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState)
|
||||
public override async ValueTask DeserializeAsync(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState)
|
||||
{
|
||||
PlayerId = reader.ReadByte();
|
||||
ClientId = reader.ReadPackedInt32();
|
||||
@@ -175,11 +181,29 @@ 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
|
||||
var friendCode = reader.ReadString();
|
||||
var puid = reader.ReadString();
|
||||
|
||||
return ValueTask.CompletedTask;
|
||||
if (Game.GetClientPlayer(ClientId) is { } owner)
|
||||
{
|
||||
FriendCode ??= owner.Client.FriendCode;
|
||||
Puid ??= owner.Client.Puid;
|
||||
}
|
||||
|
||||
await ValidateIdentifiersAsync(sender, friendCode, puid);
|
||||
}
|
||||
|
||||
private async ValueTask ValidateIdentifiersAsync(IClientPlayer sender, string? friendCode, string? puid)
|
||||
{
|
||||
var mismatch = (!string.IsNullOrEmpty(friendCode) && FriendCode != null && FriendCode != friendCode)
|
||||
|| (!string.IsNullOrEmpty(puid) && Puid != null && Puid != puid);
|
||||
|
||||
if (!mismatch)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await sender.Client.ReportCheatAsync(CheatContext.Deserialize, CheatCategory.Identifiers, $"Claimed identifiers for player {ClientId} do not match the values known for that player");
|
||||
}
|
||||
|
||||
public override async ValueTask<bool> HandleRpcAsync(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader)
|
||||
|
||||
@@ -74,16 +74,12 @@ namespace Impostor.Server.Net.Inner.Objects.ShipStatus
|
||||
|
||||
public override async ValueTask<bool> HandleRpcAsync(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader)
|
||||
{
|
||||
if (!await ValidateCmd(call, sender, target))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (call)
|
||||
{
|
||||
case RpcCalls.CloseDoorsOfType:
|
||||
{
|
||||
if (!await ValidateImpostor(call, sender, sender.Character!.PlayerInfo))
|
||||
if (!await ValidateCmd(call, sender, target) ||
|
||||
!await ValidateImpostor(call, sender, sender.Character?.PlayerInfo))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -94,11 +90,40 @@ namespace Impostor.Server.Net.Inner.Objects.ShipStatus
|
||||
|
||||
case RpcCalls.UpdateSystem:
|
||||
{
|
||||
if (!await ValidateCmd(call, sender, target))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: properly deserialize this RPC
|
||||
// Rpc35UpdateSystem.Deserialize(reader, Game, out var systemType, out var playerControl, out var sequenceId, out var state, out var ventId);
|
||||
break;
|
||||
}
|
||||
|
||||
case RpcCalls.SpiritGuideMessage:
|
||||
{
|
||||
if (!await ValidateTarget(call, sender, target) ||
|
||||
!await ValidateRole(call, sender, sender.Character?.PlayerInfo, RoleTypes.SpiritGuide))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Rpc67SpiritGuideMessage.Deserialize(reader, out var images);
|
||||
|
||||
if (images.Length == 0 || images.Length > 3)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(
|
||||
RpcCalls.SpiritGuideMessage,
|
||||
CheatCategory.ItemLimits,
|
||||
$"Client sent {images.Length} images (allowed: 1, 2 or 3)"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
return await base.HandleRpcAsync(sender, target, call, reader);
|
||||
}
|
||||
|
||||
@@ -101,8 +101,8 @@ 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
|
||||
// Warn when players connect using the +25 flag that disables server authority.
|
||||
// This changes game behaviour, so we'd like to know if it's in use.
|
||||
if (clientVersion.HasDisableServerAuthorityFlag)
|
||||
{
|
||||
if (!_compatibilityConfig.AllowHostAuthority)
|
||||
@@ -112,7 +112,7 @@ namespace Impostor.Server.Net.Manager
|
||||
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);
|
||||
_logger.LogInformation("Player {Name} connected with server authority disabled, please mention that this mode is in use when asking for support.", name);
|
||||
}
|
||||
|
||||
if (name.Length > 10)
|
||||
|
||||
@@ -22,6 +22,39 @@ internal class CompatibilityManager : ICompatibilityManager
|
||||
new GameVersion(2024, 4, 2), // 2024.9.4
|
||||
new GameVersion(2024, 8, 10), // 2024.10.29
|
||||
},
|
||||
new[]
|
||||
{
|
||||
new GameVersion(2024, 8, 11), // 16.0.0 (2025-03-25)
|
||||
},
|
||||
new[]
|
||||
{
|
||||
new GameVersion(2025, 4, 15), // 16.0.5 (2025-05-20) & 16.1.0 (2025-06-10)
|
||||
},
|
||||
new[]
|
||||
{
|
||||
new GameVersion(2025, 7, 15), // 17.0.0 (2025-09-09)
|
||||
new GameVersion(2025, 9, 12), // 17.0.1 (2025-10-14)
|
||||
new GameVersion(2025, 10, 9), // 17.1 (2025-11-18)
|
||||
new GameVersion(2025, 11, 6), // 17.1.1 (2025-12-03, mobile only)
|
||||
new GameVersion(2025, 12, 8), // 17.1.2 (2025-12-11, mobile only)
|
||||
new GameVersion(2025, 11, 5), // 17.2 (2026-02-17, build 6630, not a typo, Innersloth went backwards)
|
||||
new GameVersion(2026, 1, 22), // 17.2 (2026-02-19, build 6686, hotfix)
|
||||
new GameVersion(2026, 2, 2), // 17.2.2 (2026-03-17, build 6768)
|
||||
new GameVersion(2026, 1, 12), // 17.3 (2026-03-31, build 6803)
|
||||
new GameVersion(2026, 3, 17), // 17.3.1 (2026-04-08, build 6841, mobile only)
|
||||
new GameVersion(2026, 3, 18), // 17.4 (2026-06-05, build 7044, pc only)
|
||||
new GameVersion(2026, 4, 23), // 17.4 (2026-06-05, build 7045, mobile only)
|
||||
},
|
||||
new[]
|
||||
{
|
||||
new GameVersion(2026, 7, 15), // 18.0 (2026-08-18, build 7238, pc only)
|
||||
new GameVersion(2026, 7, 16), // 18.0 (2026-08-18, build 7238, mobile only)
|
||||
},
|
||||
new[]
|
||||
{
|
||||
new GameVersion(2026, 7, 20), // 19.0 (2026-09-29, build 7489, pc only)
|
||||
new GameVersion(2026, 7, 21), // 19.0 (2026-09-29, build 7492, mobile only)
|
||||
},
|
||||
};
|
||||
|
||||
private readonly List<CompatibilityGroup> _compatibilityGroups = new();
|
||||
|
||||
Loaded 100 of 114 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user