mirror of
https://github.com/Empostor/Empostor.git
synced 2026-10-20 21:35:38 +00:00
Compare commits
50
Commits
40p-support
...
2.0.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6bf3af1c9 | ||
|
|
2c6c51724a | ||
|
|
8883529f0c | ||
|
|
cb3d78ace8 | ||
|
|
d4f898b92f | ||
|
|
ae8cf7fc27 | ||
|
|
2f50097250 | ||
|
|
24c6097cbe | ||
|
|
6a98d63db2 | ||
|
|
6c83b127d3 | ||
|
|
b9e40852f4 | ||
|
|
f3043cfc5e | ||
|
|
b675d07d2e | ||
|
|
bcd18b7d85 | ||
|
|
2651a93230 | ||
|
|
211c79f9d0 | ||
|
|
dc0f115948 | ||
|
|
2d3a5ca800 | ||
|
|
1ced63af8d | ||
|
|
ca54e6f2ed | ||
|
|
a2daa01993 | ||
|
|
edfff9ae19 | ||
|
|
8767bcbd4d | ||
|
|
1d7252aa36 | ||
|
|
90e995261d | ||
|
|
cc8085f138 | ||
|
|
34e6751d33 | ||
|
|
f72a8aa59f | ||
|
|
a5dd48a5ed | ||
|
|
490a59be62 | ||
|
|
e1285972d7 | ||
|
|
d761f31fa3 | ||
|
|
4ca2efc6cd | ||
|
|
e747f8d71a | ||
|
|
6a21cde5ac | ||
|
|
9fcc718f63 | ||
|
|
f3a7463949 | ||
|
|
5c650e8970 | ||
|
|
43f8281486 | ||
|
|
07f81c5d75 | ||
|
|
f0cb36f118 | ||
|
|
2ba175d16c | ||
|
|
068cbcabc4 | ||
|
|
6498752430 | ||
|
|
031b6c1388 | ||
|
|
3efbb19785 | ||
|
|
989c1b5ff2 | ||
|
|
f8eeeaae8b | ||
|
|
40bbc2027b | ||
|
|
7c8c629ea5 |
No files matched your search
@@ -0,0 +1,70 @@
|
||||
name: nuget
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "src/Empostor.Api/**"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup .NET 8
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
|
||||
# Package version = <VersionPrefix>-ci.<CI run_number + 500>, mirroring
|
||||
# the nightly build numbering. The floor (max already-published ci.N + 1)
|
||||
# keeps versions monotonic; push is skipped by --skip-duplicate if the
|
||||
# exact version already exists.
|
||||
- name: Resolve version
|
||||
id: ver
|
||||
run: |
|
||||
set -euo pipefail
|
||||
PROJ=src/Empostor.Api/Empostor.Api.csproj
|
||||
BASE=$(sed -n 's/.*<VersionPrefix>\([^<]*\)<\/VersionPrefix>.*/\1/p' "$PROJ" | head -1)
|
||||
CI_RUN=$(curl -sf -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \
|
||||
"https://api.github.com/repos/${{ github.repository }}/actions/workflows/ci.yml/runs?per_page=1" \
|
||||
| jq -r '.workflow_runs[0].run_number')
|
||||
N=$(( CI_RUN + 500 ))
|
||||
MAXN=$(curl -sf "https://api.nuget.org/v3-flatcontainer/empostor.api/index.json" \
|
||||
| jq -r '.versions[]' | grep -oE 'ci\.[0-9]+$' | grep -oE '[0-9]+$' | sort -n | tail -1 || true)
|
||||
MAXN=${MAXN:-0}
|
||||
if [ "$N" -le "$MAXN" ]; then N=$((MAXN + 1)); fi
|
||||
echo "suffix=ci.${N}" >> "$GITHUB_OUTPUT"
|
||||
echo "version=${BASE}-ci.${N}" >> "$GITHUB_OUTPUT"
|
||||
echo "Publishing version: ${BASE}-ci.${N}"
|
||||
|
||||
- name: Pack
|
||||
run: dotnet pack src/Empostor.Api/Empostor.Api.csproj -c Release -p:VersionSuffix=${{ steps.ver.outputs.suffix }} -p:GeneratePackageOnBuild=false -o artifacts --nologo
|
||||
|
||||
# Trusted publishing: exchanges the GitHub OIDC token for a short-lived
|
||||
# nuget.org API key tied to the policy (no secret needed).
|
||||
- name: NuGet login (trusted publishing)
|
||||
uses: NuGet/login@v1
|
||||
id: login
|
||||
with:
|
||||
user: ELinmei
|
||||
|
||||
- name: Push to nuget.org
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ls -la artifacts
|
||||
dotnet nuget push "artifacts/Empostor.Api.*.nupkg" \
|
||||
--api-key "${{ steps.login.outputs.NUGET_API_KEY }}" \
|
||||
--source https://api.nuget.org/v3/index.json \
|
||||
--skip-duplicate
|
||||
dotnet nuget push "artifacts/Empostor.Api.*.snupkg" \
|
||||
--api-key "${{ steps.login.outputs.NUGET_API_KEY }}" \
|
||||
--source https://api.nuget.org/v3/index.json \
|
||||
--skip-duplicate || true
|
||||
+6
-4
@@ -25,8 +25,7 @@ WORKDIR /source
|
||||
COPY src/Empostor.Server/Empostor.Server.csproj ./src/Empostor.Server/
|
||||
COPY src/Empostor.Api/Empostor.Api.csproj ./src/Empostor.Api/
|
||||
COPY src/Empostor.Api.Innersloth.Generator/Empostor.Api.Innersloth.Generator.csproj ./src/Empostor.Api.Innersloth.Generator/
|
||||
COPY src/NextFast.Hazel-New/Next.Hazel/Next.Hazel.csproj ./src/NextFast.Hazel-New/Next.Hazel/
|
||||
COPY src/NextFast.Hazel-New/Next.Hazel.Abstractions/Next.Hazel.Abstractions.csproj ./src/NextFast.Hazel-New/Next.Hazel.Abstractions/
|
||||
COPY src/Empostor.Plugins.PlayerLog/Empostor.Plugins.PlayerLog.csproj ./src/Empostor.Plugins.PlayerLog/
|
||||
COPY src/Directory.Build.props ./src/
|
||||
|
||||
RUN case "$TARGETARCH" in \
|
||||
@@ -76,11 +75,14 @@ RUN mkdir -p /app/plugins /app/libraries /app/Languages /app/Log /app/Data /app/
|
||||
ENV ASPNETCORE_URLS=http://0.0.0.0:80
|
||||
ENV EMPOSTOR_Server__PublicIp=0.0.0.0
|
||||
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
|
||||
ENV DOTNET_RUNNING_IN_CONTAINER=1
|
||||
|
||||
EXPOSE 80/tcp
|
||||
EXPOSE 22023/tcp
|
||||
# Delta UDP port pool (no static UDP port when delta mode is enabled).
|
||||
EXPOSE 22024-22099/udp
|
||||
# Game / matchmaker UDP (Server.ListenPort) — always bound, even in delta mode.
|
||||
EXPOSE 22023/udp
|
||||
# Delta UDP port pool (Server.DeltaPortStart/End). Keep in sync with config.json.
|
||||
EXPOSE 22024-42024/udp
|
||||
|
||||
VOLUME ["/app/config", "/app/plugins", "/app/libraries", "/app/Languages", "/app/Log", "/app/Data", "/app/marketplace"]
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
# Empostor
|
||||
Empostor is an open-source private server implementation for Among Us.
|
||||
|
||||
[](https://dsc.gg/empostor)
|
||||
[](https://qm.qq.com/q/GeX3Q0Ft0k)
|
||||
[](https://github.com/Empostor/Empostor/blob/main/LICENSE)
|
||||
[](https://github.com/Empostor/Empostor/commit/)
|
||||
[](https://github.com/Empostor/Empostor/releases/)
|
||||
[](https://github.com/Empostor/Empostor/graphs/contributors/)
|
||||
[](https://github.com/Empostor/Empostor/pull/)
|
||||
|
||||
## Features
|
||||
- FriendCode support (authentication)
|
||||
- Dynamic ports
|
||||
- Admin Panel (dashboard)
|
||||
- Plugin support
|
||||
- Server-side anticheat
|
||||
|
||||
## Getting Started
|
||||
|
||||
To run the server, you need:
|
||||
|
||||
- [.NET 8.0 Runtime](https://dotnet.microsoft.com/en-us/download/dotnet/8.0)
|
||||
|
||||
Download the build you'd like from the [releases](https://github.com/Empostor/Empostor/releases). You most likely want the x64 build. Use the arm64 build if you are running Empostor on a Raspberry Pi, another ARM-based SBC, or an ARM VPS.
|
||||
|
||||
Unzip the file and go to config.json. To make your server playable for other devices, replace the "PublicIp" field with your actual public IP address.
|
||||
|
||||
It is also recommended to [set up a Reverse Proxy](https://empostor.github.io/get-started/reverse-proxy#use-a-reverse-proxy) (for HTTPS connections).
|
||||
|
||||
## Client Setup
|
||||
|
||||
### Windows
|
||||
1. Go [here](https://empostor.github.io/empostor) and enter your server's IP/domain, port and name. Press "Download server file"
|
||||
2. Press Win + R and enter this (leave the quotation marks):
|
||||
```cmd
|
||||
"%userprofile%\AppData\LocalLow\Innersloth\Among Us"
|
||||
```
|
||||
3. Put the new regionInfo.json file you downloaded in the folder. Overwrite the existing file in the folder.
|
||||
4. Launch Among Us. If everything worked, your server should appear in the regions list!
|
||||
|
||||
### Android/iOS
|
||||
1. Launch Among Us. When you reach the main menu, close the app.
|
||||
2. Go [here](https://empostor.github.io/empostor) and enter your server's IP/domain, port and name.
|
||||
3. Scroll until you see Instructions and press the Android or Apple logo.
|
||||
4. Press "Open in Among Us". Among Us should open.
|
||||
5. Your server should appear in the regions list!
|
||||
|
||||
## Contributing
|
||||
Please read the [contributing guidelines](https://github.com/Empostor/Empostor/blob/main/CONTRIBUTING.md).
|
||||
|
||||
You're welcome to open a pull request/issue!
|
||||
|
||||
## Documentation
|
||||
The documentation is available [here](https://empostor.github.io)!
|
||||
|
||||
## License
|
||||
This project is licensed under the [GPL-v3.0 License](https://github.com/Empostor/Empostor/blob/main/LICENSE).
|
||||
|
||||
## Credits
|
||||
|
||||
- [Impostor](https://github.com/Impostor/Impostor)
|
||||
- [Next-Impostor](https://github.com/BunchHanpiDev/Next-Impostor)
|
||||
- [NextFast.Hazel](https://github.com/Next-Fast/NextFast.Hazel)
|
||||
- [Reactor.Impostor](https://github.com/NuclearPowered/Reactor.Impostor)
|
||||
+16
-6
@@ -7,18 +7,24 @@ services:
|
||||
restart: unless-stopped
|
||||
|
||||
ports:
|
||||
# HTTP API / admin panel (TCP)
|
||||
# HTTP API / Admin panel (TCP)
|
||||
- "22023:22023/tcp"
|
||||
# Delta UDP port pool. With DeltaPortStart/End enabled, the server does
|
||||
# NOT bind a static UDP port at startup — each player gets a unique port
|
||||
# from this range, and when only one port is left it is shared by all
|
||||
# new players (IP-based matching). Adjust to match config.json.
|
||||
# Game / Matchmaker UDP — REQUIRED. The matchmaker always binds this UDP
|
||||
# port (Server.ListenPort) so players can connect; without it the server
|
||||
# is unreachable even in delta mode.
|
||||
- "22023:22023/udp"
|
||||
# Delta UDP port pool. With DeltaPortStart/End enabled, each player is
|
||||
# assigned a unique UDP port from this range (Server.DeltaPortStart/End).
|
||||
# Must stay in sync with config.json.
|
||||
- "22024-42024:22024-42024/udp"
|
||||
# Uncomment to expose admin HTTP panel:
|
||||
# - "80:80/tcp"
|
||||
|
||||
volumes:
|
||||
# Server config (can be read-only if you only edit via file)
|
||||
# Server config. NOTE: if ./config.json does NOT exist on the host, Docker
|
||||
# creates an EMPTY DIRECTORY at that path and mounts it over the baked-in
|
||||
# /app/config.json, which breaks startup. Create the file first (or remove
|
||||
# this line to use the image's default config) before running.
|
||||
- ./config.json:/app/config.json:ro
|
||||
# Writable runtime data
|
||||
- empostor_plugins:/app/plugins
|
||||
@@ -31,8 +37,12 @@ services:
|
||||
- ./discord_webhook.json:/app/discord_webhook.json
|
||||
|
||||
environment:
|
||||
- DOTNET_RUNNING_IN_CONTAINER=1
|
||||
- ASPNETCORE_URLS=http://0.0.0.0:80
|
||||
# PublicIp MUST be your real public IP / domain, or remote players can't
|
||||
# connect. 0.0.0.0 / 127.0.0.1 will NOT work for anyone but localhost.
|
||||
- EMPOSTOR_Server__PublicIp=0.0.0.0
|
||||
- EMPOSTOR_Server__ListenIp=0.0.0.0
|
||||
|
||||
volumes:
|
||||
empostor_plugins:
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 70 B After Width: | Height: | Size: 3.8 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+78
-20
@@ -9,11 +9,6 @@
|
||||
"version": "1.0.0",
|
||||
"empostor_version": "2.0.0",
|
||||
"download_url": "https://raw.githubusercontent.com/Empostor/Empostor/master/marketplace/Empostor.Plugin.Chat.dll"
|
||||
},
|
||||
{
|
||||
"version": "1.1.0",
|
||||
"empostor_version": "2.0.0",
|
||||
"download_url": "https://raw.githubusercontent.com/Empostor/Empostor/master/marketplace/Empostor.Plugin.Chat1.1.0.dll"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -40,16 +35,6 @@
|
||||
"version": "1.0.0",
|
||||
"empostor_version": "2.0.0",
|
||||
"download_url": "https://raw.githubusercontent.com/Empostor/Empostor/master/marketplace/Empostor.Plugins.Welcome.dll"
|
||||
},
|
||||
{
|
||||
"version": "1.1.0",
|
||||
"empostor_version": "2.0.0",
|
||||
"download_url": "https://raw.githubusercontent.com/Empostor/Empostor/master/marketplace/Empostor.Plugins.Welcome1.1.0.dll"
|
||||
},
|
||||
{
|
||||
"version": "1.2.0",
|
||||
"empostor_version": "2.0.0",
|
||||
"download_url": "https://raw.githubusercontent.com/Empostor/Empostor/master/marketplace/Empostor.Plugins.Welcome1.2.0.dll"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -89,11 +74,6 @@
|
||||
"version": "1.0.0",
|
||||
"empostor_version": "2.0.0",
|
||||
"download_url": "https://raw.githubusercontent.com/Empostor/Empostor/master/marketplace/Empostor.Plugins.Titles.dll"
|
||||
},
|
||||
{
|
||||
"version": "1.1.0",
|
||||
"empostor_version": "2.0.0",
|
||||
"download_url": "https://raw.githubusercontent.com/Empostor/Empostor/master/marketplace/Empostor.Plugins.Titles1.1.0.dll"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -174,5 +154,83 @@
|
||||
"download_url": "https://raw.githubusercontent.com/Empostor/Empostor/main/marketplace/Reactor.Empostor.dll"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "cn.hayashiume.chatfilter",
|
||||
"name": "Chat Filter",
|
||||
"description": "Filter the message depend on the banwords",
|
||||
"author": "HayashiUme",
|
||||
"versions": [
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"empostor_version": "2.0.0",
|
||||
"download_url": "https://raw.githubusercontent.com/Empostor/Empostor/main/marketplace/Empostor.Plugins.ChatFilter.dll"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "cn.hayashiume.discordwebhook",
|
||||
"name": "Discord Webhook",
|
||||
"description": "Send lobby and admin message to dicord by webhook",
|
||||
"author": "HayashiUme",
|
||||
"versions": [
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"empostor_version": "2.0.0",
|
||||
"download_url": "https://raw.githubusercontent.com/Empostor/Empostor/main/marketplace/Empostor.Plugins.DiscordWebhook.dll"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "cn.hayashiume.playerstats",
|
||||
"name": "Player Stats",
|
||||
"description": "Stat player logs with #stats command",
|
||||
"author": "HayashiUme",
|
||||
"versions": [
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"empostor_version": "2.0.0",
|
||||
"download_url": "https://raw.githubusercontent.com/Empostor/Empostor/main/marketplace/Empostor.Plugins.PlayerStats.dll"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "gg.empostor.playerlog",
|
||||
"name": "Player Log",
|
||||
"description": "Logs all player activity (chat, kills, meetings, joins, leaves) and provides an admin panel to view, search, and export logs.",
|
||||
"author": "Empostor",
|
||||
"versions": [
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"empostor_version": "2.0.0",
|
||||
"download_url": "https://raw.githubusercontent.com/Empostor/Empostor/main/marketplace/Empostor.Plugins.PlayerLog.dll"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "gg.empostor.monitor",
|
||||
"name": "Monitor",
|
||||
"description": "Provides /api/monitor/status and /api/monitor/health endpoints for server health checks and uptime monitoring.",
|
||||
"author": "Empostor",
|
||||
"versions": [
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"empostor_version": "2.0.0",
|
||||
"download_url": "https://raw.githubusercontent.com/Empostor/Empostor/main/marketplace/Empostor.Plugins.Monitor.dll"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "gg.empostor.privacy",
|
||||
"name": "Privacy Policy",
|
||||
"description": "Serves a customizable privacy policy page at /privacy with an admin panel editor.",
|
||||
"author": "Empostor",
|
||||
"versions": [
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"empostor_version": "2.0.0",
|
||||
"download_url": "https://raw.githubusercontent.com/Empostor/Empostor/main/marketplace/Empostor.Plugins.Privacy.dll"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,65 @@
|
||||
[
|
||||
{
|
||||
"id": "a/",
|
||||
"name": "Anthropic",
|
||||
"description": "The theme like anthropic design",
|
||||
"author": "Empostor",
|
||||
"downloadUrl": "https://raw.githubusercontent.com/Empostor/Empostor/main/marketplace/themes/anthropic.json"
|
||||
},
|
||||
{
|
||||
"id": "nord",
|
||||
"name": "Nord",
|
||||
"description": "Cool polar blues with muted arctic greys. Calm, low-contrast and easy on the eyes.",
|
||||
"author": "Empostor",
|
||||
"downloadUrl": "https://raw.githubusercontent.com/Empostor/Empostor/main/marketplace/themes/nord.json"
|
||||
},
|
||||
{
|
||||
"id": "dracula",
|
||||
"name": "Dracula",
|
||||
"description": "Purple-accented dark palette with rounded cards and soft glow highlights.",
|
||||
"author": "Empostor",
|
||||
"downloadUrl": "https://raw.githubusercontent.com/Empostor/Empostor/main/marketplace/themes/dracula.json"
|
||||
},
|
||||
{
|
||||
"id": "solarized",
|
||||
"name": "Solarized",
|
||||
"description": "Precision-tuned warm beige and deep teal pair with a monospace typeface.",
|
||||
"author": "Empostor",
|
||||
"downloadUrl": "https://raw.githubusercontent.com/Empostor/Empostor/main/marketplace/themes/solarized.json"
|
||||
},
|
||||
{
|
||||
"id": "gruvbox",
|
||||
"name": "Gruvbox",
|
||||
"description": "Retro warm earth tones, chunky two-pixel borders and bold labels.",
|
||||
"author": "Empostor",
|
||||
"downloadUrl": "https://raw.githubusercontent.com/Empostor/Empostor/main/marketplace/themes/gruvbox.json"
|
||||
},
|
||||
{
|
||||
"id": "tokyo-night",
|
||||
"name": "Tokyo Night",
|
||||
"description": "Neon-lit midnight blues with gradient surfaces and a soft accent glow.",
|
||||
"author": "Empostor",
|
||||
"downloadUrl": "https://raw.githubusercontent.com/Empostor/Empostor/main/marketplace/themes/tokyo-night.json"
|
||||
},
|
||||
{
|
||||
"id": "catppuccin",
|
||||
"name": "Catppuccin",
|
||||
"description": "Pastel palette with heavily rounded pill buttons and gentle shadows.",
|
||||
"author": "Empostor",
|
||||
"downloadUrl": "https://raw.githubusercontent.com/Empostor/Empostor/main/marketplace/themes/catppuccin.json"
|
||||
},
|
||||
{
|
||||
"id": "rose-pine",
|
||||
"name": "Rosé Pine",
|
||||
"description": "Dusty rose and iris accents on a warm serif-heavy layout.",
|
||||
"author": "Empostor",
|
||||
"downloadUrl": "https://raw.githubusercontent.com/Empostor/Empostor/main/marketplace/themes/rose-pine.json"
|
||||
},
|
||||
{
|
||||
"id": "midnight",
|
||||
"name": "Midnight OLED",
|
||||
"description": "True-black AMOLED panels with a high-contrast accent for late-night shifts.",
|
||||
"author": "Empostor",
|
||||
"downloadUrl": "https://raw.githubusercontent.com/Empostor/Empostor/main/marketplace/themes/midnight.json"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"Id": "anthropic",
|
||||
"Name": "Anthropic",
|
||||
"Extends": null,
|
||||
"Tokens": {
|
||||
"--bg": "#FAF9F5",
|
||||
"--s": "#F1EFE8",
|
||||
"--b": "#E4E0D4",
|
||||
"--t": "#1F1E1B",
|
||||
"--m": "#6E6A61",
|
||||
"--a": "#CC785C",
|
||||
"--g": "#3D7A4E",
|
||||
"--y": "#B7791F",
|
||||
"--r": "#C2412D",
|
||||
"--p": "#7C5CBF",
|
||||
"--o": "#C2603F",
|
||||
"--field": "#FFFFFF",
|
||||
"--hover": "rgba(31,30,27,.05)",
|
||||
"--on-a": "#FFFFFF"
|
||||
},
|
||||
"DarkTokens": {
|
||||
"--bg": "#1A1917",
|
||||
"--s": "#242321",
|
||||
"--b": "#34312D",
|
||||
"--t": "#ECEAE4",
|
||||
"--m": "#A8A49B",
|
||||
"--a": "#D97757",
|
||||
"--g": "#6FB37E",
|
||||
"--y": "#D4A017",
|
||||
"--r": "#E0654F",
|
||||
"--p": "#A78BFA",
|
||||
"--o": "#E08A5F",
|
||||
"--field": "#242321",
|
||||
"--hover": "rgba(255,255,255,.05)",
|
||||
"--on-a": "#FFFFFF"
|
||||
},
|
||||
"CustomCss": "body{font-family:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;letter-spacing:.1px}\nh1,h2,h3,.nlbl{font-family:ui-serif,Georgia,'Iowan Old Style','Times New Roman',serif;font-weight:600;letter-spacing:.2px}\nh1,h2{color:var(--t)}\nh2{padding-bottom:10px;border-bottom:1px solid var(--b);margin-bottom:16px}\n.nav,.form,.sc,table,.field{border-radius:10px}\n.nav{background:var(--s)}\n.ni{border-radius:8px;transition:background .15s ease,color .15s ease,transform .12s ease}\n.ni:hover{transform:translateX(2px)}\n.ni.active{background:var(--hover);color:var(--a);font-weight:600}\n.ni.active svg{color:var(--a)}\n.form,.sc{border:1px solid var(--b);box-shadow:0 1px 2px rgba(31,30,27,.03);transition:border-color .15s ease,box-shadow .15s ease}\n.form:hover,.sc:hover{border-color:var(--a);box-shadow:0 2px 10px rgba(204,120,92,.08)}\n.pnl.active{animation:antIn .28s ease}\n@keyframes antIn{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}\nbutton.bp,button.bd,button.bw,button.bsm{border-radius:8px;font-weight:600;letter-spacing:.2px;transition:transform .1s ease,box-shadow .12s ease,opacity .12s ease}\nbutton.bp:hover,button.bd:hover,button.bw:hover,button.bsm:hover{transform:translateY(-1px)}\nbutton.bp{background:var(--a);box-shadow:0 1px 3px rgba(204,120,92,.35)}\nbutton.bp:hover{box-shadow:0 3px 10px rgba(204,120,92,.4)}\ninput,select,textarea{border-radius:7px}\ninput:focus,select:focus,textarea:focus{border-color:var(--a);box-shadow:0 0 0 3px rgba(204,120,92,.18)}\ntable{border:1px solid var(--b);overflow:hidden}\nth{background:var(--s);font-weight:600}\ntd,th{border-bottom:1px solid var(--b)}\n.sc .v{color:var(--a);font-family:ui-serif,Georgia,serif}\n.theme-toggle,.theme-sel{border-radius:8px}\n.msg.ok{border-left:3px solid var(--g)}\n.msg.err{border-left:3px solid var(--r)}"
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"Id": "catppuccin",
|
||||
"Name": "Catppuccin",
|
||||
"Extends": null,
|
||||
"Tokens": {
|
||||
"--bg": "#EFF1F5",
|
||||
"--s": "#E6E9EF",
|
||||
"--b": "#DCE0E8",
|
||||
"--t": "#4C4F69",
|
||||
"--m": "#6C6F85",
|
||||
"--a": "#1E66F5",
|
||||
"--g": "#40A02B",
|
||||
"--y": "#DF8E1D",
|
||||
"--r": "#D20F39",
|
||||
"--p": "#8839EF",
|
||||
"--o": "#FE640B",
|
||||
"--field": "#FFFFFF",
|
||||
"--hover": "rgba(76,79,105,.05)",
|
||||
"--on-a": "#FFFFFF"
|
||||
},
|
||||
"DarkTokens": {
|
||||
"--bg": "#1E1E2E",
|
||||
"--s": "#313244",
|
||||
"--b": "#45475A",
|
||||
"--t": "#CDD6F4",
|
||||
"--m": "#A6ADC8",
|
||||
"--a": "#89B4FA",
|
||||
"--g": "#A6E3A1",
|
||||
"--y": "#F9E2AF",
|
||||
"--r": "#F38BA8",
|
||||
"--p": "#CBA6F7",
|
||||
"--o": "#FAB387",
|
||||
"--field": "#181825",
|
||||
"--hover": "rgba(205,214,244,.06)",
|
||||
"--on-a": "#1E1E2E"
|
||||
},
|
||||
"CustomCss": "body{letter-spacing:.15px}\nh2{padding-bottom:8px;border-bottom:1px solid var(--b)}\n.form,.sc,.ig,table{border-radius:14px}\n.form,.sc{border:1px solid var(--b);box-shadow:0 1px 3px rgba(76,79,105,.05)}\n.form:hover,.sc:hover{border-color:var(--a);box-shadow:0 4px 16px rgba(137,180,250,.14)}\n.badge{border-radius:999px;padding:3px 10px;font-weight:600}\n.chip{border-radius:999px}\n.ni{border-radius:0 12px 12px 0;transition:background .15s ease,color .15s ease}\n.ni:hover{background:var(--hover)}\n.ni.active{background:rgba(137,180,250,.14);font-weight:600}\nbutton.bp,button.bd,button.bw,button.bsm{border-radius:999px;font-weight:600;padding-left:18px;padding-right:18px;transition:transform .12s ease,box-shadow .12s ease}\nbutton.bp:hover,button.bd:hover,button.bw:hover{transform:translateY(-1px);box-shadow:0 4px 12px rgba(76,79,105,.18)}\ninput,select,textarea{border-radius:10px}\ninput:focus,select:focus,textarea:focus{border-color:var(--a);box-shadow:0 0 0 3px rgba(137,180,250,.18)}\nth{background:var(--s);font-weight:600}"
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"Id": "dracula",
|
||||
"Name": "Dracula",
|
||||
"Extends": null,
|
||||
"Tokens": {
|
||||
"--bg": "#F7F7FB",
|
||||
"--s": "#EFEFF6",
|
||||
"--b": "#DFDFEC",
|
||||
"--t": "#282A36",
|
||||
"--m": "#6272A4",
|
||||
"--a": "#7C4DFF",
|
||||
"--g": "#1E9E52",
|
||||
"--y": "#B08400",
|
||||
"--r": "#D63031",
|
||||
"--p": "#C2185B",
|
||||
"--o": "#C4601A",
|
||||
"--field": "#FFFFFF",
|
||||
"--hover": "rgba(40,42,54,.05)",
|
||||
"--on-a": "#FFFFFF"
|
||||
},
|
||||
"DarkTokens": {
|
||||
"--bg": "#282A36",
|
||||
"--s": "#323444",
|
||||
"--b": "#44475A",
|
||||
"--t": "#F8F8F2",
|
||||
"--m": "#A3ACCC",
|
||||
"--a": "#BD93F9",
|
||||
"--g": "#50FA7B",
|
||||
"--y": "#F1FA8C",
|
||||
"--r": "#FF5555",
|
||||
"--p": "#FF79C6",
|
||||
"--o": "#FFB86C",
|
||||
"--field": "#21222C",
|
||||
"--hover": "rgba(248,248,242,.06)",
|
||||
"--on-a": "#282A36"
|
||||
},
|
||||
"CustomCss": "body{letter-spacing:.15px}\nh2{padding-bottom:8px;border-bottom:1px solid var(--b)}\n.form,.sc,.ig,table{border-radius:12px}\n.form,.sc{box-shadow:0 1px 2px rgba(40,42,54,.06)}\n.form:hover{border-color:var(--a);box-shadow:0 3px 14px rgba(189,147,249,.14)}\n.ni{border-radius:10px;transition:background .15s ease,color .15s ease}\n.ni:hover{background:var(--hover)}\n.ni.active{background:rgba(189,147,249,.14);color:var(--a);font-weight:600}\nbutton.bp,button.bd,button.bw,button.bsm{border-radius:9px;font-weight:600;transition:transform .1s ease}\nbutton.bp:hover,button.bd:hover,button.bw:hover{transform:translateY(-1px)}\ninput,select,textarea{border-radius:9px}\ninput:focus,select:focus,textarea:focus{box-shadow:0 0 0 3px rgba(189,147,249,.22)}\n.pnl.active{animation:fadeIn .25s ease}\n@keyframes fadeIn{from{opacity:0;transform:translateY(5px)}to{opacity:1;transform:none}}"
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"Id": "gruvbox",
|
||||
"Name": "Gruvbox",
|
||||
"Extends": null,
|
||||
"Tokens": {
|
||||
"--bg": "#FBF1C7",
|
||||
"--s": "#F2E5BC",
|
||||
"--b": "#E0CFA0",
|
||||
"--t": "#3C3836",
|
||||
"--m": "#7C6F64",
|
||||
"--a": "#AF3A03",
|
||||
"--g": "#79740E",
|
||||
"--y": "#B57614",
|
||||
"--r": "#9D0006",
|
||||
"--p": "#8F3F71",
|
||||
"--o": "#D65D0E",
|
||||
"--field": "#FFFBF0",
|
||||
"--hover": "rgba(60,56,54,.06)",
|
||||
"--on-a": "#FBF1C7"
|
||||
},
|
||||
"DarkTokens": {
|
||||
"--bg": "#282828",
|
||||
"--s": "#3C3836",
|
||||
"--b": "#504945",
|
||||
"--t": "#EBDBB2",
|
||||
"--m": "#A89984",
|
||||
"--a": "#FE8019",
|
||||
"--g": "#B8BB26",
|
||||
"--y": "#FABD2F",
|
||||
"--r": "#FB4934",
|
||||
"--p": "#D3869B",
|
||||
"--o": "#D65D0E",
|
||||
"--field": "#1D2021",
|
||||
"--hover": "rgba(235,219,178,.06)",
|
||||
"--on-a": "#282828"
|
||||
},
|
||||
"CustomCss": "body{letter-spacing:.1px}\nh2{padding-bottom:8px;border-bottom:2px solid var(--b)}\n.form,.sc,.ig,table{border-radius:6px;border-width:2px}\n.ni{border-radius:0 8px 8px 0}\n.ni.active{font-weight:700}\nbutton.bp,button.bd,button.bw,button.bsm{border-radius:6px;font-weight:700;text-transform:none}\ninput,select,textarea{border-radius:6px;border-width:2px}\nth{font-weight:700;letter-spacing:.6px}\n.badge{border-radius:6px}\n.code{font-weight:700}"
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"Id": "midnight",
|
||||
"Name": "Midnight OLED",
|
||||
"Extends": null,
|
||||
"Tokens": {
|
||||
"--bg": "#F5F7FA",
|
||||
"--s": "#EBEFF5",
|
||||
"--b": "#D9E0EA",
|
||||
"--t": "#0B1220",
|
||||
"--m": "#5A6B85",
|
||||
"--a": "#0A84FF",
|
||||
"--g": "#1FA84F",
|
||||
"--y": "#B57A00",
|
||||
"--r": "#E5484D",
|
||||
"--p": "#8E4EC6",
|
||||
"--o": "#C4601A",
|
||||
"--field": "#FFFFFF",
|
||||
"--hover": "rgba(11,18,32,.05)",
|
||||
"--on-a": "#FFFFFF"
|
||||
},
|
||||
"DarkTokens": {
|
||||
"--bg": "#000000",
|
||||
"--s": "#0B0F14",
|
||||
"--b": "#1C232C",
|
||||
"--t": "#FFFFFF",
|
||||
"--m": "#8B98A9",
|
||||
"--a": "#4C9AFF",
|
||||
"--g": "#3DDC84",
|
||||
"--y": "#FFD60A",
|
||||
"--r": "#FF453A",
|
||||
"--p": "#BF5AF2",
|
||||
"--o": "#FF9F0A",
|
||||
"--field": "#080B10",
|
||||
"--hover": "rgba(255,255,255,.05)",
|
||||
"--on-a": "#000000"
|
||||
},
|
||||
"CustomCss": "[data-theme=\"dark\"] body,body{background:var(--bg)}\n[data-theme=\"dark\"] header,[data-theme=\"dark\"] nav{background:#050709}\n[data-theme=\"dark\"] .sc,[data-theme=\"dark\"] .form,[data-theme=\"dark\"] table{border-color:#1C232C}\n[data-theme=\"dark\"] .ni.active{background:rgba(76,154,255,.16)}\n[data-theme=\"dark\"] .code{color:#4C9AFF;text-shadow:0 0 10px rgba(76,154,255,.5)}\n[data-theme=\"dark\"] .dot{box-shadow:0 0 8px var(--g)}\nh2{padding-bottom:8px;border-bottom:1px solid var(--b)}\n.form,.sc,.ig,table{border-radius:8px}\n.ni{border-left-width:3px}\nbutton.bp,button.bd,button.bw,button.bsm{border-radius:6px;font-weight:600}\ninput,select,textarea{border-radius:6px}\ninput:focus,select:focus,textarea:focus{box-shadow:0 0 0 3px rgba(76,154,255,.22)}"
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"Id": "nord",
|
||||
"Name": "Nord",
|
||||
"Extends": null,
|
||||
"Tokens": {
|
||||
"--bg": "#ECEFF4",
|
||||
"--s": "#E5E9F0",
|
||||
"--b": "#D8DEE9",
|
||||
"--t": "#2E3440",
|
||||
"--m": "#4C566A",
|
||||
"--a": "#5E81AC",
|
||||
"--g": "#4C8C4A",
|
||||
"--y": "#B58A28",
|
||||
"--r": "#BF616A",
|
||||
"--p": "#8F6FA8",
|
||||
"--o": "#D08770",
|
||||
"--field": "#FFFFFF",
|
||||
"--hover": "rgba(46,52,64,.05)",
|
||||
"--on-a": "#FFFFFF"
|
||||
},
|
||||
"DarkTokens": {
|
||||
"--bg": "#2E3440",
|
||||
"--s": "#3B4252",
|
||||
"--b": "#4C566A",
|
||||
"--t": "#ECEFF4",
|
||||
"--m": "#97A1B5",
|
||||
"--a": "#88C0D0",
|
||||
"--g": "#A3BE8C",
|
||||
"--y": "#EBCB8B",
|
||||
"--r": "#BF616A",
|
||||
"--p": "#B48EAD",
|
||||
"--o": "#D08770",
|
||||
"--field": "#2B3140",
|
||||
"--hover": "rgba(236,239,244,.06)",
|
||||
"--on-a": "#2E3440"
|
||||
},
|
||||
"CustomCss": "body{letter-spacing:.1px}\nh2{border-bottom:1px solid var(--b);padding-bottom:8px}\n.form,.sc,.ig,table{border-radius:8px}\n.ni{border-radius:0 8px 8px 0}\nbutton.bp,button.bd,button.bw{border-radius:7px;letter-spacing:.2px}\ninput:focus,select:focus,textarea:focus{box-shadow:0 0 0 3px rgba(94,129,172,.2)}\n.hdr{background:var(--s)}"
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"Id": "rose-pine",
|
||||
"Name": "Rosé Pine",
|
||||
"Extends": null,
|
||||
"Tokens": {
|
||||
"--bg": "#FAF4ED",
|
||||
"--s": "#F2E9E1",
|
||||
"--b": "#E4DAD2",
|
||||
"--t": "#575279",
|
||||
"--m": "#797593",
|
||||
"--a": "#286983",
|
||||
"--g": "#56949F",
|
||||
"--y": "#EA9D34",
|
||||
"--r": "#B4637A",
|
||||
"--p": "#907AA9",
|
||||
"--o": "#D7827E",
|
||||
"--field": "#FFFCF9",
|
||||
"--hover": "rgba(87,82,121,.05)",
|
||||
"--on-a": "#FFFFFF"
|
||||
},
|
||||
"DarkTokens": {
|
||||
"--bg": "#191724",
|
||||
"--s": "#1F1D2E",
|
||||
"--b": "#26233A",
|
||||
"--t": "#E0DEF4",
|
||||
"--m": "#908CAA",
|
||||
"--a": "#C4A7E7",
|
||||
"--g": "#31748F",
|
||||
"--y": "#F6C177",
|
||||
"--r": "#EB6F92",
|
||||
"--p": "#C4A7E7",
|
||||
"--o": "#EA9D34",
|
||||
"--field": "#12111A",
|
||||
"--hover": "rgba(224,222,244,.06)",
|
||||
"--on-a": "#191724"
|
||||
},
|
||||
"CustomCss": "body{font-family:'Georgia','Iowan Old Style',ui-serif,serif;letter-spacing:.15px}\nh1,h2,h3,.nlbl{font-family:ui-serif,Georgia,serif}\nh2{padding-bottom:8px;border-bottom:1px solid var(--b)}\n.form,.sc,.ig,table{border-radius:16px}\n.form,.sc{box-shadow:0 1px 4px rgba(87,82,121,.05)}\n.ni{border-radius:0 12px 12px 0}\n.ni.active{background:rgba(196,167,231,.14);font-weight:600}\nbutton.bp,button.bd,button.bw,button.bsm{border-radius:12px;font-weight:600}\ninput,select,textarea{border-radius:12px}\ninput:focus,select:focus,textarea:focus{border-color:var(--a);box-shadow:0 0 0 3px rgba(196,167,231,.2)}\nth{font-style:italic;font-weight:600}\n.pnl.active{animation:rpIn .3s ease}\n@keyframes rpIn{from{opacity:0}to{opacity:1}}"
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"Id": "solarized",
|
||||
"Name": "Solarized",
|
||||
"Extends": null,
|
||||
"Tokens": {
|
||||
"--bg": "#FDF6E3",
|
||||
"--s": "#EEE8D5",
|
||||
"--b": "#DDD6C1",
|
||||
"--t": "#073642",
|
||||
"--m": "#657B83",
|
||||
"--a": "#268BD2",
|
||||
"--g": "#7A8C00",
|
||||
"--y": "#B58900",
|
||||
"--r": "#DC322F",
|
||||
"--p": "#6C71C4",
|
||||
"--o": "#CB4B16",
|
||||
"--field": "#FFFBF0",
|
||||
"--hover": "rgba(7,54,66,.05)",
|
||||
"--on-a": "#FFFFFF"
|
||||
},
|
||||
"DarkTokens": {
|
||||
"--bg": "#002B36",
|
||||
"--s": "#073642",
|
||||
"--b": "#14505F",
|
||||
"--t": "#EEE8D5",
|
||||
"--m": "#93A1A1",
|
||||
"--a": "#3FA3E8",
|
||||
"--g": "#B4C43A",
|
||||
"--y": "#D4A72C",
|
||||
"--r": "#E85550",
|
||||
"--p": "#8B90E0",
|
||||
"--o": "#E5763F",
|
||||
"--field": "#00252E",
|
||||
"--hover": "rgba(238,232,213,.06)",
|
||||
"--on-a": "#002B36"
|
||||
},
|
||||
"CustomCss": "body{font-family:'Menlo','Consolas','DejaVu Sans Mono',ui-monospace,monospace;font-size:13px}\nh1,h2{font-weight:600}\nh2{padding-bottom:8px;border-bottom:1px solid var(--b)}\n.form,.sc,.ig,table{border-radius:4px}\n.ni{border-left-width:3px}\nth{background:var(--s);font-weight:700}\nbutton.bp,button.bd,button.bw{border-radius:4px;font-weight:600}\ninput,select,textarea{border-radius:4px}\n.code,.ip,td.mono{font-weight:600}"
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"Id": "tokyo-night",
|
||||
"Name": "Tokyo Night",
|
||||
"Extends": null,
|
||||
"Tokens": {
|
||||
"--bg": "#F6F7FB",
|
||||
"--s": "#EDEFF7",
|
||||
"--b": "#DCE0EC",
|
||||
"--t": "#1A1B26",
|
||||
"--m": "#565A6E",
|
||||
"--a": "#2E5FA3",
|
||||
"--g": "#3D7A33",
|
||||
"--y": "#8F5E15",
|
||||
"--r": "#8C4351",
|
||||
"--p": "#5A4A78",
|
||||
"--o": "#965027",
|
||||
"--field": "#FFFFFF",
|
||||
"--hover": "rgba(26,27,38,.05)",
|
||||
"--on-a": "#FFFFFF"
|
||||
},
|
||||
"DarkTokens": {
|
||||
"--bg": "#1A1B26",
|
||||
"--s": "#24283B",
|
||||
"--b": "#343B58",
|
||||
"--t": "#C0CAF5",
|
||||
"--m": "#7C86B0",
|
||||
"--a": "#7AA2F7",
|
||||
"--g": "#9ECE6A",
|
||||
"--y": "#E0AF68",
|
||||
"--r": "#F7768E",
|
||||
"--p": "#BB9AF7",
|
||||
"--o": "#FF9E64",
|
||||
"--field": "#16161E",
|
||||
"--hover": "rgba(192,202,245,.06)",
|
||||
"--on-a": "#1A1B26"
|
||||
},
|
||||
"CustomCss": "body{letter-spacing:.1px}\nh2{padding-bottom:8px;border-bottom:1px solid var(--b);letter-spacing:.6px}\n.form,.sc,.ig,table{border-radius:10px}\n.sc{background:linear-gradient(160deg,var(--s),transparent)}\n.ni{border-radius:8px;transition:background .18s ease,color .18s ease}\n.ni:hover{background:var(--hover);transform:translateX(2px)}\n.ni.active{background:rgba(122,162,247,.12)}\n.form:hover{border-color:var(--a)}\nbutton.bp,button.bd,button.bw,button.bsm{border-radius:8px;font-weight:600;transition:transform .12s ease,box-shadow .12s ease}\nbutton.bp:hover,button.bd:hover,button.bw:hover{transform:translateY(-1px)}\nbutton.bp{box-shadow:0 1px 3px rgba(122,162,247,.3)}\ninput,select,textarea{border-radius:8px}\ninput:focus,select:focus,textarea:focus{box-shadow:0 0 0 3px rgba(122,162,247,.2)}\n.code{text-shadow:0 0 8px rgba(122,162,247,.35)}\n.pnl.active{animation:tnIn .25s ease}\n@keyframes tnIn{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}"
|
||||
}
|
||||
@@ -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", "Empostor.Api.Innersloth.GameFilters");
|
||||
enumGenerator.Generate("ByteOptionNames", "Empostor.Api.Innersloth.GameFilters");
|
||||
enumGenerator.Generate("CategorizedOptionNames", "Empostor.Api.Innersloth.GameFilters");
|
||||
enumGenerator.Generate("FloatArrayOptionNames", "Empostor.Api.Innersloth.GameFilters");
|
||||
enumGenerator.Generate("FloatOptionNames", "Empostor.Api.Innersloth.GameFilters");
|
||||
enumGenerator.Generate("Int32ArrayOptionNames", "Empostor.Api.Innersloth.GameFilters");
|
||||
enumGenerator.Generate("Int32OptionNames", "Empostor.Api.Innersloth.GameFilters");
|
||||
enumGenerator.Generate("UInt32OptionNames", "Empostor.Api.Innersloth.GameFilters");
|
||||
|
||||
enumGenerator.Generate("RpcCalls", "Empostor.Api.Net.Inner", underlyingType: CSharpEnumUnderlyingType.Byte);
|
||||
|
||||
|
||||
@@ -150,6 +150,7 @@
|
||||
"System.Runtime.CompilerServices.Unsafe": "4.5.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
".NETStandard,Version=v2.0/win-x64": {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Empostor.Api.Admin;
|
||||
|
||||
public delegate ValueTask<AdminActionResult> AdminAction(AdminActionContext context);
|
||||
|
||||
/// <summary>
|
||||
/// The result of an admin action. Tells the renderer what to do next.
|
||||
/// </summary>
|
||||
/// <param name="Success">Whether the action completed successfully.</param>
|
||||
/// <param name="Message">An optional toast message shown to the operator.</param>
|
||||
/// <param name="Refresh">When true, the client re-fetches the panel to reflect new state.</param>
|
||||
public sealed record AdminActionResult(bool Success, string? Message = null, bool Refresh = true)
|
||||
{
|
||||
public static AdminActionResult Ok(string? message = null, bool refresh = true) => new(true, message, refresh);
|
||||
|
||||
public static AdminActionResult Fail(string message) => new(false, message, true);
|
||||
}
|
||||
|
||||
public sealed class AdminActionContext
|
||||
{
|
||||
public AdminActionContext(
|
||||
string extensionId,
|
||||
string actionId,
|
||||
string? value,
|
||||
JsonElement? payload,
|
||||
IServiceProvider services)
|
||||
{
|
||||
ExtensionId = extensionId;
|
||||
ActionId = actionId;
|
||||
Value = value;
|
||||
Payload = payload;
|
||||
Services = services;
|
||||
}
|
||||
|
||||
/// <summary>The extension that owns the invoked action.</summary>
|
||||
public string ExtensionId { get; }
|
||||
|
||||
/// <summary>The action id that was invoked.</summary>
|
||||
public string ActionId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The new value submitted by the widget (textbox text, toggle on/off, select value,
|
||||
/// number value, chip value). <c>null</c> for plain button clicks.
|
||||
/// </summary>
|
||||
public string? Value { get; }
|
||||
|
||||
/// <summary>The full JSON body of the request, if the widget sent structured data.</summary>
|
||||
public JsonElement? Payload { get; }
|
||||
|
||||
/// <summary>The DI container, for resolving services the handler may need.</summary>
|
||||
public IServiceProvider Services { get; }
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Empostor.Api.Admin;
|
||||
|
||||
/// <summary>
|
||||
/// Fluent builder used by an <see cref="IAdminExtension"/> to declare its admin-panel content.
|
||||
/// Register* methods add declarative widgets; widgets that carry a callback get a stable,
|
||||
/// auto-assigned action id that the client sends back on interaction.
|
||||
/// </summary>
|
||||
public sealed class AdminPanelBuilder
|
||||
{
|
||||
private readonly Dictionary<string, AdminAction> _actions = new(StringComparer.Ordinal);
|
||||
private int _actionCounter;
|
||||
|
||||
public AdminPanelBuilder(string extensionId, string title, string icon, string? section)
|
||||
{
|
||||
ExtensionId = extensionId;
|
||||
Title = title;
|
||||
Icon = icon;
|
||||
Section = string.IsNullOrWhiteSpace(section) ? "Plugins" : section;
|
||||
}
|
||||
|
||||
public string ExtensionId { get; }
|
||||
|
||||
public string Title { get; }
|
||||
|
||||
public string Icon { get; }
|
||||
|
||||
public string Section { get; }
|
||||
|
||||
public List<AdminWidget> Widgets { get; } = new();
|
||||
|
||||
public IReadOnlyDictionary<string, AdminAction> Actions => _actions;
|
||||
|
||||
public AdminPanelBuilder RegisterButton(Action<AdminButton> configure) => Register(configure);
|
||||
|
||||
public AdminPanelBuilder RegisterText(string content, string tone = "default", bool monospace = false)
|
||||
{
|
||||
Widgets.Add(new AdminText { Content = content, Tone = tone, Monospace = monospace });
|
||||
return this;
|
||||
}
|
||||
|
||||
public AdminPanelBuilder RegisterText(Action<AdminText> configure) => Register(configure);
|
||||
|
||||
public AdminPanelBuilder RegisterBlock(Action<AdminBlock> configure) => Register(configure);
|
||||
|
||||
public AdminPanelBuilder RegisterTextbox(Action<AdminTextbox> configure) => Register(configure);
|
||||
|
||||
public AdminPanelBuilder RegisterToggle(Action<AdminToggle> configure) => Register(configure);
|
||||
|
||||
public AdminPanelBuilder RegisterSelect(Action<AdminSelect> configure) => Register(configure);
|
||||
|
||||
public AdminPanelBuilder RegisterNumber(Action<AdminNumber> configure) => Register(configure);
|
||||
|
||||
public AdminPanelBuilder RegisterTable(Action<AdminTable> configure) => Register(configure);
|
||||
|
||||
public AdminPanelBuilder RegisterChips(Action<AdminChips> configure) => Register(configure);
|
||||
|
||||
public AdminPanelBuilder RegisterEntries(Action<AdminEntries> configure) => Register(configure);
|
||||
|
||||
public AdminPanelBuilder RegisterDivider()
|
||||
{
|
||||
Widgets.Add(new AdminDivider());
|
||||
return this;
|
||||
}
|
||||
|
||||
private AdminPanelBuilder Register<T>(Action<T> configure)
|
||||
where T : AdminWidget, new()
|
||||
{
|
||||
var widget = new T();
|
||||
configure(widget);
|
||||
Bind(widget);
|
||||
Widgets.Add(widget);
|
||||
return this;
|
||||
}
|
||||
|
||||
private void Bind(AdminWidget widget)
|
||||
{
|
||||
if (widget.Handler != null)
|
||||
{
|
||||
widget.Action = NextAction();
|
||||
_actions[widget.Action] = widget.Handler;
|
||||
}
|
||||
|
||||
if (widget is AdminChips chips)
|
||||
{
|
||||
if (chips.AddHandler != null)
|
||||
{
|
||||
chips.AddAction = NextAction();
|
||||
_actions[chips.AddAction] = chips.AddHandler;
|
||||
}
|
||||
|
||||
if (chips.RemoveHandler != null)
|
||||
{
|
||||
chips.RemoveAction = NextAction();
|
||||
_actions[chips.RemoveAction] = chips.RemoveHandler;
|
||||
}
|
||||
}
|
||||
|
||||
if (widget is AdminEntries entries)
|
||||
{
|
||||
if (entries.AddHandler != null)
|
||||
{
|
||||
entries.AddAction = NextAction();
|
||||
_actions[entries.AddAction] = entries.AddHandler;
|
||||
}
|
||||
|
||||
if (entries.EditHandler != null)
|
||||
{
|
||||
entries.EditAction = NextAction();
|
||||
_actions[entries.EditAction] = entries.EditHandler;
|
||||
}
|
||||
|
||||
if (entries.RemoveHandler != null)
|
||||
{
|
||||
entries.RemoveAction = NextAction();
|
||||
_actions[entries.RemoveAction] = entries.RemoveHandler;
|
||||
}
|
||||
}
|
||||
|
||||
if (widget is AdminBlock block)
|
||||
{
|
||||
foreach (var child in block.Children)
|
||||
{
|
||||
Bind(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string NextAction() => $"{ExtensionId}.a{_actionCounter++}";
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Empostor.Api.Admin;
|
||||
|
||||
/// <summary>
|
||||
/// A theme that reskins the admin panel, in the spirit of VuePress's "theme as plugin".
|
||||
/// A theme can also be shipped as a folder under <c>Pages/themes/{Id}/theme.json</c>.
|
||||
/// </summary>
|
||||
public interface IAdminTheme
|
||||
{
|
||||
/// <summary>Unique id. Also the folder name for disk-based themes.</summary>
|
||||
string Id { get; }
|
||||
|
||||
/// <summary>Human-readable name.</summary>
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>Id of a parent theme whose tokens/css/layout this theme inherits.</summary>
|
||||
string? Extends => null;
|
||||
|
||||
/// <summary>Build the theme definition (tokens, palette, css, layout).</summary>
|
||||
AdminThemeDefinition Define();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Declarative description of a theme's styling layers.
|
||||
/// </summary>
|
||||
public sealed class AdminThemeDefinition
|
||||
{
|
||||
/// <summary>
|
||||
/// Design tokens for the default (light) appearance, keyed by CSS custom-property name
|
||||
/// (e.g. <c>--bg</c>, <c>--a</c>). Use the names in <see cref="AdminThemeTokens"/>.
|
||||
/// </summary>
|
||||
public Dictionary<string, string> Tokens { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Dark-appearance overrides applied under <c>[data-theme="dark"]</c>. When omitted,
|
||||
/// the framework keeps the light tokens for both modes.
|
||||
/// </summary>
|
||||
public Dictionary<string, string>? DarkTokens { get; set; }
|
||||
|
||||
/// <summary>Raw CSS appended after the tokens (palette/component styles, animations).</summary>
|
||||
public string? CustomCss { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional layout template. Slots are <c>{{slot:header}}</c>, <c>{{slot:nav}}</c>,
|
||||
/// <c>{{slot:content}}</c> and <c>{{slot:footer}}</c>. When omitted the built-in layout is used.
|
||||
/// </summary>
|
||||
public string? Layout { get; set; }
|
||||
|
||||
/// <summary>Named component aliases (component name → HTML fragment) usable in the layout.</summary>
|
||||
public Dictionary<string, string> Components { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The standard set of design-token names the admin panel consumes. Themes should fill
|
||||
/// these keys so their palette applies to every built-in control.
|
||||
/// </summary>
|
||||
public static class AdminThemeTokens
|
||||
{
|
||||
public const string Background = "--bg";
|
||||
public const string Surface = "--s";
|
||||
public const string Border = "--b";
|
||||
public const string Text = "--t";
|
||||
public const string Muted = "--m";
|
||||
public const string Accent = "--a";
|
||||
public const string Success = "--g";
|
||||
public const string Warning = "--y";
|
||||
public const string Danger = "--r";
|
||||
public const string Purple = "--p";
|
||||
public const string Orange = "--o";
|
||||
public const string Field = "--field";
|
||||
public const string Hover = "--hover";
|
||||
public const string OnAccent = "--on-a";
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Empostor.Api.Admin;
|
||||
|
||||
[JsonPolymorphic(TypeDiscriminatorPropertyName = "type")]
|
||||
[JsonDerivedType(typeof(AdminButton), "button")]
|
||||
[JsonDerivedType(typeof(AdminText), "text")]
|
||||
[JsonDerivedType(typeof(AdminBlock), "block")]
|
||||
[JsonDerivedType(typeof(AdminTextbox), "textbox")]
|
||||
[JsonDerivedType(typeof(AdminToggle), "toggle")]
|
||||
[JsonDerivedType(typeof(AdminSelect), "select")]
|
||||
[JsonDerivedType(typeof(AdminNumber), "number")]
|
||||
[JsonDerivedType(typeof(AdminTable), "table")]
|
||||
[JsonDerivedType(typeof(AdminChips), "chips")]
|
||||
[JsonDerivedType(typeof(AdminDivider), "divider")]
|
||||
[JsonDerivedType(typeof(AdminEntries), "entries")]
|
||||
public abstract class AdminWidget
|
||||
{
|
||||
public string? Action { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public AdminAction? Handler { get; set; }
|
||||
}
|
||||
|
||||
public sealed class AdminButton : AdminWidget
|
||||
{
|
||||
public string Label { get; set; } = string.Empty;
|
||||
|
||||
public string? Icon { get; set; }
|
||||
|
||||
public string Style { get; set; } = "primary";
|
||||
|
||||
public AdminButton OnClick(AdminAction handler)
|
||||
{
|
||||
Handler = handler;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class AdminText : AdminWidget
|
||||
{
|
||||
public string Content { get; set; } = string.Empty;
|
||||
|
||||
public string Tone { get; set; } = "default";
|
||||
|
||||
public bool Monospace { get; set; }
|
||||
}
|
||||
|
||||
public sealed class AdminBlock : AdminWidget
|
||||
{
|
||||
public string? Title { get; set; }
|
||||
|
||||
public string? Description { get; set; }
|
||||
|
||||
public List<AdminWidget> Children { get; set; } = new();
|
||||
}
|
||||
|
||||
public sealed class AdminTextbox : AdminWidget
|
||||
{
|
||||
public string Label { get; set; } = string.Empty;
|
||||
|
||||
public string? Value { get; set; }
|
||||
|
||||
public string? Placeholder { get; set; }
|
||||
|
||||
public bool Secret { get; set; }
|
||||
|
||||
public bool Multiline { get; set; }
|
||||
|
||||
public bool SubmitOnChange { get; set; }
|
||||
|
||||
public AdminTextbox OnSubmit(AdminAction handler)
|
||||
{
|
||||
Handler = handler;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class AdminToggle : AdminWidget
|
||||
{
|
||||
public string Label { get; set; } = string.Empty;
|
||||
|
||||
public bool Value { get; set; }
|
||||
|
||||
public AdminToggle OnChange(AdminAction handler)
|
||||
{
|
||||
Handler = handler;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class AdminSelect : AdminWidget
|
||||
{
|
||||
public string Label { get; set; } = string.Empty;
|
||||
|
||||
public List<AdminOption> Options { get; set; } = new();
|
||||
|
||||
public string? Value { get; set; }
|
||||
|
||||
public AdminSelect OnChange(AdminAction handler)
|
||||
{
|
||||
Handler = handler;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class AdminNumber : AdminWidget
|
||||
{
|
||||
public string Label { get; set; } = string.Empty;
|
||||
|
||||
public int Value { get; set; }
|
||||
|
||||
public int? Min { get; set; }
|
||||
|
||||
public int? Max { get; set; }
|
||||
|
||||
public AdminNumber OnChange(AdminAction handler)
|
||||
{
|
||||
Handler = handler;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class AdminTable : AdminWidget
|
||||
{
|
||||
public List<string> Columns { get; set; } = new();
|
||||
|
||||
public List<List<AdminTableCell>> Rows { get; set; } = new();
|
||||
}
|
||||
|
||||
public sealed class AdminChips : AdminWidget
|
||||
{
|
||||
public List<AdminChip> Items { get; set; } = new();
|
||||
|
||||
public string? Placeholder { get; set; }
|
||||
|
||||
public string? AddLabel { get; set; }
|
||||
|
||||
public string? AddAction { get; set; }
|
||||
|
||||
public string? RemoveAction { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public AdminAction? AddHandler { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public AdminAction? RemoveHandler { get; set; }
|
||||
|
||||
public AdminChips OnAdd(AdminAction handler)
|
||||
{
|
||||
AddHandler = handler;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AdminChips OnRemove(AdminAction handler)
|
||||
{
|
||||
RemoveHandler = handler;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class AdminDivider : AdminWidget
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>One editable column of an <see cref="AdminEntries"/> widget.</summary>
|
||||
public sealed class AdminEntryField
|
||||
{
|
||||
/// <summary>Row value key. The <b>first</b> field is the row identity used by edit/remove actions.</summary>
|
||||
public string Key { get; set; } = string.Empty;
|
||||
|
||||
public string Label { get; set; } = string.Empty;
|
||||
|
||||
public bool Monospace { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An editable list of structured entries (search + add + per-row edit/delete), backed by
|
||||
/// three actions: add (payload carries the new row), edit (value = old identity, payload carries
|
||||
/// the replacement row) and remove (value = row identity).
|
||||
/// </summary>
|
||||
public sealed class AdminEntries : AdminWidget
|
||||
{
|
||||
public List<AdminEntryField> Fields { get; set; } = new();
|
||||
|
||||
/// <summary>Rows keyed by field key; the first field's value identifies the row.</summary>
|
||||
public List<Dictionary<string, string>> Rows { get; set; } = new();
|
||||
|
||||
public string? SearchPlaceholder { get; set; }
|
||||
|
||||
public string? AddLabel { get; set; }
|
||||
|
||||
public string AddAction { get; set; } = string.Empty;
|
||||
|
||||
public string EditAction { get; set; } = string.Empty;
|
||||
|
||||
public string RemoveAction { get; set; } = string.Empty;
|
||||
|
||||
[JsonIgnore]
|
||||
public AdminAction? AddHandler { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public AdminAction? EditHandler { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public AdminAction? RemoveHandler { get; set; }
|
||||
|
||||
public AdminEntries OnAdd(AdminAction handler)
|
||||
{
|
||||
AddHandler = handler;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AdminEntries OnEdit(AdminAction handler)
|
||||
{
|
||||
EditHandler = handler;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AdminEntries OnRemove(AdminAction handler)
|
||||
{
|
||||
RemoveHandler = handler;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed record AdminOption(string Label, string Value);
|
||||
|
||||
public sealed class AdminTableCell
|
||||
{
|
||||
public string Text { get; set; } = string.Empty;
|
||||
|
||||
public string? Tone { get; set; }
|
||||
|
||||
public bool Monospace { get; set; }
|
||||
}
|
||||
|
||||
public sealed record AdminChip(string Value, string Label);
|
||||
@@ -0,0 +1,29 @@
|
||||
namespace Empostor.Api.Admin;
|
||||
|
||||
/// <summary>
|
||||
/// Implemented by a plugin to contribute a panel to the admin interface
|
||||
/// </summary>
|
||||
public interface IAdminExtension
|
||||
{
|
||||
/// <summary>Unique, stable id. Also the last path segment of <c>/api/admin/ext/{Id}</c>.</summary>
|
||||
string Id { get; }
|
||||
|
||||
/// <summary>Display title shown in the sidebar and panel header.</summary>
|
||||
string Title { get; }
|
||||
|
||||
/// <summary>Icon name (from the built-in icon set), rendered as an inline glyph.</summary>
|
||||
string Icon => "puzzle";
|
||||
|
||||
/// <summary>Sidebar grouping label. Defaults to "Plugins".</summary>
|
||||
string Section => "Plugins";
|
||||
|
||||
/// <summary>
|
||||
/// Whether the panel should be re-fetched once per second while it is visible. Only panels
|
||||
/// that mirror live server state (e.g. an in-game chat feed) need this; static panels keep
|
||||
/// their DOM stable, which matters for widgets with in-progress typing or row editing.
|
||||
/// </summary>
|
||||
bool AutoRefresh => false;
|
||||
|
||||
/// <summary>Declare the panel's widgets and callbacks.</summary>
|
||||
void Build(AdminPanelBuilder builder);
|
||||
}
|
||||
@@ -32,6 +32,9 @@ public enum CheatCategory
|
||||
/// <summary>A packet was sent that exceeded the maximum allowed RPC size.</summary>
|
||||
PacketSize,
|
||||
|
||||
/// <summary>A packet was sent with more items than possible in the game.</summary>
|
||||
ItemLimits,
|
||||
|
||||
/// <summary>Legacy category for unsorted anticheat checks.</summary>
|
||||
Other,
|
||||
}
|
||||
@@ -8,5 +8,16 @@ namespace Empostor.Api.Config
|
||||
|
||||
public string MarketplaceUrl { get; set; } =
|
||||
"https://raw.githubusercontent.com/Empostor/Empostor/main/marketplace/plugins.json";
|
||||
|
||||
public string ThemeMarketplaceUrl { get; set; } =
|
||||
"https://raw.githubusercontent.com/Empostor/Empostor/main/marketplace/themes.json";
|
||||
|
||||
public string GitHubToken { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Active admin theme id. Themes live under <c>Pages/themes/{Id}</c> or as plugins.</summary>
|
||||
public string Theme { get; set; } = "default";
|
||||
|
||||
/// <summary>Initial color mode ("light" or "dark"). Operators can still toggle it in the panel.</summary>
|
||||
public string ThemeMode { get; set; } = "dark";
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,8 @@ namespace Empostor.Api.Config
|
||||
|
||||
public bool EnableNameLimitChecks { get; set; } = true;
|
||||
|
||||
public bool EnableItemLimitChecks { get; set; } = true;
|
||||
|
||||
public bool EnableOwnershipChecks { get; set; } = true;
|
||||
|
||||
public bool EnableRoleChecks { get; set; } = true;
|
||||
|
||||
@@ -3,9 +3,7 @@ namespace Empostor.Api.Config;
|
||||
public enum AuthApiMode
|
||||
{
|
||||
Innersloth,
|
||||
Niko,
|
||||
Both,
|
||||
Relay,
|
||||
Ume,
|
||||
}
|
||||
|
||||
@@ -15,14 +13,6 @@ public class AuthApiConfig
|
||||
|
||||
public AuthApiMode Mode { get; set; } = AuthApiMode.Innersloth;
|
||||
|
||||
public string NikoApiKey { get; set; } = string.Empty;
|
||||
|
||||
public string NikoApiBaseUrl { get; set; } = "https://au-verify.niko233.top";
|
||||
|
||||
public string RelayApiBaseUrl { get; set; } = "http://localhost:5100";
|
||||
|
||||
public string RelayApiKey { get; set; } = string.Empty;
|
||||
|
||||
public string UmeApiBaseUrl { get; set; } = "https://auverify.hayashiume.top";
|
||||
|
||||
public string UmeApiKey { get; set; } = "sk-empostor-globalapikey";
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Empostor.Api.Config
|
||||
{
|
||||
public class ChatFilterConfig
|
||||
{
|
||||
public const string Section = "ChatFilter";
|
||||
|
||||
public bool Enabled { get; set; } = false;
|
||||
|
||||
public List<string> BlockedWords { get; set; } = new();
|
||||
|
||||
public bool BlockMessage { get; set; } = true;
|
||||
|
||||
public int SpamThreshold { get; set; } = 5;
|
||||
|
||||
public int SpamWindowSeconds { get; set; } = 10;
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
namespace Empostor.Api.Config
|
||||
{
|
||||
public class DiscordWebhookConfig
|
||||
{
|
||||
public const string Section = "DiscordWebhook";
|
||||
|
||||
public string MatchmakerUrl { get; set; } = string.Empty;
|
||||
|
||||
public string AdminUrl { get; set; } = string.Empty;
|
||||
public bool? Enabled { get; set; }
|
||||
public string? WebhookUrl { get; set; }
|
||||
public bool? NotifyOnGameCreated { get; set; }
|
||||
public bool? NotifyOnBan { get; set; }
|
||||
public bool? NotifyOnReport { get; set; }
|
||||
public bool? NotifyOnPlayerJoin { get; set; }
|
||||
public bool? NotifyOnGameEnded { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,10 @@
|
||||
namespace Empostor.Api.Config
|
||||
{
|
||||
/// <summary>
|
||||
/// Configuration for the HPLP (HTTP Public Lobby List Protocol) endpoint,
|
||||
/// which allows Starlight clients to discover games hosted on this server.
|
||||
/// HPLP settings, which let Starlight clients discover games hosted on this server.
|
||||
/// </summary>
|
||||
public class HplpConfig
|
||||
{
|
||||
public const string Section = "HPLP";
|
||||
|
||||
/// <summary>
|
||||
/// Whether the HPLP endpoint (<c>GET /x-api/games</c>) is enabled.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
namespace Empostor.Api.Config
|
||||
{
|
||||
public class PlayerStatsConfig
|
||||
{
|
||||
public const string Section = "PlayerStats";
|
||||
|
||||
public bool Enabled { get; set; } = false;
|
||||
|
||||
public bool PersistToFile { get; set; } = true;
|
||||
}
|
||||
}
|
||||
@@ -6,17 +6,20 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
<VersionPrefix>2.0.1</VersionPrefix>
|
||||
<AssemblyName>Empostor.Api</AssemblyName>
|
||||
<AssemblyTitle>Empostor.Api</AssemblyTitle>
|
||||
<Authors>AeonLucid</Authors>
|
||||
<Description>An api library for Empostor, an Among Us private server. You need this package to write plugins for Empostor.</Description>
|
||||
<PackageId>Empostor.Api</PackageId>
|
||||
<PackageTags>Among Us;Empostor;Empostor Plugin</PackageTags>
|
||||
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
||||
<PackageIconUrl>https://raw.githubusercontent.com/Empostor/Empostor/dev/docs/images/logo_100.png</PackageIconUrl>
|
||||
<PackageIcon>logo_100.png</PackageIcon>
|
||||
<PackageProjectUrl>https://github.com/Empostor/Empostor</PackageProjectUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<RepositoryUrl>https://github.com/Empostor/Empostor</RepositoryUrl>
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -25,16 +28,13 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Empostor.Hazel" Version="1.0.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
|
||||
<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" />
|
||||
|
||||
<ProjectReference Include="..\Empostor.Api.Innersloth.Generator\Empostor.Api.Innersloth.Generator.csproj" ReferenceOutputAssembly="false" OutputItemType="Analyzer" />
|
||||
|
||||
<ProjectReference Include="..\NextFast.Hazel-New\Next.Hazel.Abstractions\Next.Hazel.Abstractions.csproj" />
|
||||
|
||||
<ProjectReference Include="..\NextFast.Hazel-New\Next.Hazel\Next.Hazel.csproj" />
|
||||
<ProjectReference Include="..\Empostor.Api.Innersloth.Generator\Empostor.Api.Innersloth.Generator.csproj" ReferenceOutputAssembly="false" OutputItemType="Analyzer" PrivateAssets="all" />
|
||||
<AdditionalFiles Include="Innersloth/Data/**/*.json" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Empostor.Api.Net.Inner.Objects;
|
||||
using Empostor.Api.Net.Inner.Objects;
|
||||
|
||||
namespace Empostor.Api.Events.Meeting
|
||||
{
|
||||
@@ -13,5 +13,15 @@ namespace Empostor.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,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
@@ -49,6 +50,11 @@ namespace Empostor.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)
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -11,5 +11,7 @@
|
||||
"Phantom": 9,
|
||||
"Tracker": 10,
|
||||
"Detective": 12,
|
||||
"Viper": 18
|
||||
"Viper": 18,
|
||||
"Judge": 19,
|
||||
"SpiritGuide": 21
|
||||
}
|
||||
@@ -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,
|
||||
@@ -1576,6 +1584,14 @@
|
||||
"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,
|
||||
@@ -1623,5 +1639,50 @@
|
||||
"DetectiveNotesPrepositionMaybe": 4079,
|
||||
"DetectiveNotesTitlePostIt": 4080,
|
||||
"DetectiveNotesPostIt": 4081,
|
||||
"DetectiveNotesSuspectNumber": 4082
|
||||
"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-dev",
|
||||
"gameVersion": "2025.10.14",
|
||||
"dumpostorVersion": "1.0.2-dev",
|
||||
"gameVersion": "2026.9.29",
|
||||
"platformType": "StandaloneItch"
|
||||
}
|
||||
@@ -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 Empostor.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 Empostor.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 Empostor.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 Empostor.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 Empostor.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 Empostor.Api.Innersloth.GameFilters
|
||||
{
|
||||
[Serializable]
|
||||
public class GameFiltersList
|
||||
{
|
||||
[JsonPropertyName("FilterSets")]
|
||||
public required List<GameFilterSet> FilterSets { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Empostor.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 Empostor.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 Empostor.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 Empostor.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 Empostor.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 Empostor.Api.Innersloth.GameFilters
|
||||
{
|
||||
[Serializable]
|
||||
public class PlatformGameFilter : ISubFilter
|
||||
{
|
||||
[JsonPropertyName("FilterType")]
|
||||
public string FilterType { get; } = "platform";
|
||||
|
||||
[JsonPropertyName("AcceptedValues")]
|
||||
public required uint AcceptedValues { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ namespace Empostor.Api.Innersloth.GameOptions;
|
||||
|
||||
public class HideNSeekGameOptions : IGameOptions
|
||||
{
|
||||
public const int LatestVersion = 10;
|
||||
public const int LatestVersion = 12;
|
||||
|
||||
public HideNSeekGameOptions(byte version = LatestVersion)
|
||||
{
|
||||
@@ -48,7 +48,7 @@ public class HideNSeekGameOptions : IGameOptions
|
||||
public float CrewLightMod { get; set; } = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Light modifier for the players that are Empostors as a multiplier value.
|
||||
/// Gets or sets the Light modifier for the players that are Impostors as a multiplier value.
|
||||
/// </summary>
|
||||
public float ImpostorLightMod { get; set; } = 1f;
|
||||
|
||||
@@ -75,7 +75,7 @@ public class HideNSeekGameOptions : IGameOptions
|
||||
|
||||
public float CrewmateFlashlightSize { get; set; } = 0.35f;
|
||||
|
||||
public float EmpostorFlashlightSize { get; set; } = 0.25f;
|
||||
public float ImpostorFlashlightSize { get; set; } = 0.25f;
|
||||
|
||||
public bool UseFlashlight { get; set; } = true;
|
||||
|
||||
@@ -107,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();
|
||||
@@ -127,7 +132,7 @@ public class HideNSeekGameOptions : IGameOptions
|
||||
CrewmateVentUses = reader.ReadInt32();
|
||||
HidingTime = reader.ReadSingle();
|
||||
CrewmateFlashlightSize = reader.ReadSingle();
|
||||
EmpostorFlashlightSize = reader.ReadSingle();
|
||||
ImpostorFlashlightSize = reader.ReadSingle();
|
||||
UseFlashlight = reader.ReadBoolean();
|
||||
FinalHideSeekMap = reader.ReadBoolean();
|
||||
FinalHideTime = reader.ReadSingle();
|
||||
@@ -142,11 +147,6 @@ public class HideNSeekGameOptions : IGameOptions
|
||||
{
|
||||
Tag = (GameTags)reader.ReadByte();
|
||||
}
|
||||
|
||||
if (Version > LatestVersion)
|
||||
{
|
||||
IGameOptions.ThrowUnknownVersion<HideNSeekGameOptions>(Version);
|
||||
}
|
||||
}
|
||||
|
||||
public void Serialize(IMessageWriter writer)
|
||||
@@ -171,7 +171,7 @@ public class HideNSeekGameOptions : IGameOptions
|
||||
writer.Write(CrewmateVentUses);
|
||||
writer.Write(HidingTime);
|
||||
writer.Write(CrewmateFlashlightSize);
|
||||
writer.Write(EmpostorFlashlightSize);
|
||||
writer.Write(ImpostorFlashlightSize);
|
||||
writer.Write(UseFlashlight);
|
||||
writer.Write(FinalHideSeekMap);
|
||||
writer.Write(FinalHideTime);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -53,12 +53,12 @@ public class LegacyGameOptionsData : IGameOptions
|
||||
public float CrewLightMod { get; set; } = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Light modifier for the players that are Empostors as a multiplier value.
|
||||
/// Gets or sets the Light modifier for the players that are Impostors as a multiplier value.
|
||||
/// </summary>
|
||||
public float ImpostorLightMod { get; set; } = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Empostor cooldown to kill in seconds.
|
||||
/// Gets or sets the Impostor cooldown to kill in seconds.
|
||||
/// </summary>
|
||||
public float KillCooldown { get; set; } = 15f;
|
||||
|
||||
@@ -115,7 +115,7 @@ public class LegacyGameOptionsData : IGameOptions
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether an ejected player is an impostor or not.
|
||||
/// </summary>
|
||||
public bool ConfirmEmpostor { get; set; } = true;
|
||||
public bool ConfirmImpostor { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether players are able to see tasks being performed by other players.
|
||||
@@ -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);
|
||||
@@ -184,7 +186,7 @@ public class LegacyGameOptionsData : IGameOptions
|
||||
|
||||
if (Version >= 3)
|
||||
{
|
||||
ConfirmEmpostor = reader.ReadBoolean();
|
||||
ConfirmImpostor = reader.ReadBoolean();
|
||||
VisualTasks = reader.ReadBoolean();
|
||||
}
|
||||
|
||||
@@ -241,7 +243,7 @@ public class LegacyGameOptionsData : IGameOptions
|
||||
|
||||
if (Version >= 3)
|
||||
{
|
||||
writer.Write((bool)ConfirmEmpostor);
|
||||
writer.Write((bool)ConfirmImpostor);
|
||||
writer.Write((bool)VisualTasks);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace Empostor.Api.Innersloth.GameOptions;
|
||||
|
||||
public class NormalGameOptions : IGameOptions
|
||||
{
|
||||
public const int LatestVersion = 10;
|
||||
public const int LatestVersion = 12;
|
||||
|
||||
public NormalGameOptions(byte version = LatestVersion)
|
||||
{
|
||||
@@ -51,12 +51,12 @@ public class NormalGameOptions : IGameOptions
|
||||
public float CrewLightMod { get; set; } = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Light modifier for the players that are Empostors as a multiplier value.
|
||||
/// Gets or sets the Light modifier for the players that are Impostors as a multiplier value.
|
||||
/// </summary>
|
||||
public float ImpostorLightMod { get; set; } = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Empostor cooldown to kill in seconds.
|
||||
/// Gets or sets the Impostor cooldown to kill in seconds.
|
||||
/// </summary>
|
||||
public float KillCooldown { get; set; } = 15f;
|
||||
|
||||
@@ -108,7 +108,7 @@ public class NormalGameOptions : IGameOptions
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether an ejected player is an impostor or not.
|
||||
/// </summary>
|
||||
public bool ConfirmEmpostor { get; set; } = true;
|
||||
public bool ConfirmImpostor { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether players are able to see tasks being performed by other players.
|
||||
@@ -144,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,7 +178,7 @@ public class NormalGameOptions : IGameOptions
|
||||
IsDefaults = reader.ReadBoolean();
|
||||
|
||||
EmergencyCooldown = reader.ReadByte();
|
||||
ConfirmEmpostor = reader.ReadBoolean();
|
||||
ConfirmImpostor = reader.ReadBoolean();
|
||||
VisualTasks = reader.ReadBoolean();
|
||||
AnonymousVotes = reader.ReadBoolean();
|
||||
TaskBarUpdate = (TaskBarUpdate)reader.ReadByte();
|
||||
@@ -184,11 +189,6 @@ public class NormalGameOptions : IGameOptions
|
||||
}
|
||||
|
||||
RoleOptions.Deserialize(reader);
|
||||
|
||||
if (Version > LatestVersion)
|
||||
{
|
||||
IGameOptions.ThrowUnknownVersion<NormalGameOptions>(Version);
|
||||
}
|
||||
}
|
||||
|
||||
public void Serialize(IMessageWriter writer)
|
||||
@@ -222,7 +222,7 @@ public class NormalGameOptions : IGameOptions
|
||||
writer.Write(IsDefaults);
|
||||
|
||||
writer.Write((byte)EmergencyCooldown);
|
||||
writer.Write(ConfirmEmpostor);
|
||||
writer.Write(ConfirmImpostor);
|
||||
writer.Write(VisualTasks);
|
||||
writer.Write(AnonymousVotes);
|
||||
writer.Write((byte)TaskBarUpdate);
|
||||
|
||||
@@ -15,7 +15,7 @@ public class GuardianAngelRoleOptions : IRoleOptions
|
||||
|
||||
public byte ProtectionDurationSeconds { get; set; } = 10;
|
||||
|
||||
public bool EmpostorsCanSeeProtect { get; set; }
|
||||
public bool ImpostorsCanSeeProtect { get; set; }
|
||||
|
||||
public static GuardianAngelRoleOptions Deserialize(IMessageReader reader, byte version)
|
||||
{
|
||||
@@ -23,7 +23,7 @@ public class GuardianAngelRoleOptions : IRoleOptions
|
||||
|
||||
options.Cooldown = reader.ReadByte();
|
||||
options.ProtectionDurationSeconds = reader.ReadByte();
|
||||
options.EmpostorsCanSeeProtect = reader.ReadBoolean();
|
||||
options.ImpostorsCanSeeProtect = reader.ReadBoolean();
|
||||
|
||||
return options;
|
||||
}
|
||||
@@ -32,6 +32,6 @@ public class GuardianAngelRoleOptions : IRoleOptions
|
||||
{
|
||||
writer.Write(Cooldown);
|
||||
writer.Write(ProtectionDurationSeconds);
|
||||
writer.Write(EmpostorsCanSeeProtect);
|
||||
writer.Write(ImpostorsCanSeeProtect);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
namespace Empostor.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,7 +16,7 @@ public class LegacyRoleOptionsData
|
||||
|
||||
public byte GuardianAngelCooldown { get; set; } = 60;
|
||||
|
||||
public bool EmpostorsCanSeeProtect { get; set; }
|
||||
public bool ImpostorsCanSeeProtect { get; set; }
|
||||
|
||||
public byte ProtectionDurationSeconds { get; set; } = 10;
|
||||
|
||||
@@ -46,7 +46,7 @@ public class LegacyRoleOptionsData
|
||||
roleOptionsData.EngineerInVentMaxTime = reader.ReadByte();
|
||||
roleOptionsData.ScientistBatteryCharge = reader.ReadByte();
|
||||
roleOptionsData.ProtectionDurationSeconds = reader.ReadByte();
|
||||
roleOptionsData.EmpostorsCanSeeProtect = reader.ReadBoolean();
|
||||
roleOptionsData.ImpostorsCanSeeProtect = reader.ReadBoolean();
|
||||
return roleOptionsData;
|
||||
}
|
||||
|
||||
@@ -68,6 +68,6 @@ public class LegacyRoleOptionsData
|
||||
writer.Write(EngineerInVentMaxTime);
|
||||
writer.Write(ScientistBatteryCharge);
|
||||
writer.Write(ProtectionDurationSeconds);
|
||||
writer.Write(EmpostorsCanSeeProtect);
|
||||
writer.Write(ImpostorsCanSeeProtect);
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ public class NoisemakerRoleOptions : IRoleOptions
|
||||
|
||||
public RoleTypes Type => RoleTypes.Noisemaker;
|
||||
|
||||
public bool EmpostorAlert { get; set; } = true;
|
||||
public bool ImpostorAlert { get; set; } = true;
|
||||
|
||||
public byte AlertDuration { get; set; } = 10;
|
||||
|
||||
@@ -20,7 +20,7 @@ public class NoisemakerRoleOptions : IRoleOptions
|
||||
var options = new NoisemakerRoleOptions(version);
|
||||
|
||||
options.AlertDuration = reader.ReadByte();
|
||||
options.EmpostorAlert = reader.ReadBoolean();
|
||||
options.ImpostorAlert = reader.ReadBoolean();
|
||||
|
||||
return options;
|
||||
}
|
||||
@@ -28,6 +28,6 @@ public class NoisemakerRoleOptions : IRoleOptions
|
||||
public void Serialize(IMessageWriter writer)
|
||||
{
|
||||
writer.Write(AlertDuration);
|
||||
writer.Write(EmpostorAlert);
|
||||
writer.Write(ImpostorAlert);
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,8 @@ public class RoleOptionsCollection
|
||||
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),
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
namespace Empostor.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);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Empostor.Api.Innersloth
|
||||
namespace Empostor.Api.Innersloth
|
||||
{
|
||||
public static class TextBox
|
||||
{
|
||||
|
||||
@@ -166,7 +166,6 @@ public sealed class LanguageService
|
||||
"command.note.set": "Note set: {0}",
|
||||
"command.color.invalid": "Invalid color ID. Valid range: 0–17.",
|
||||
"command.color.set": "Color changed to {0} ({1}).",
|
||||
|
||||
"command.title.too_long": "Title too long. Max 12 characters.",
|
||||
"command.title.cleared": "Title removed.",
|
||||
"command.title.set": "Title set: {0}",
|
||||
@@ -205,7 +204,12 @@ public sealed class LanguageService
|
||||
"command.note.description": "Set or clear a lobby note (host only).",
|
||||
"command.note.usage": "note <text> | note clear",
|
||||
"command.max.description": "Set max players for the lobby (host only).",
|
||||
"command.max.usage": "max <1-15>"
|
||||
"command.max.usage": "max <1-15>",
|
||||
"ban.notice.header": "You are banned from this server.",
|
||||
"ban.notice.reason": "Reason: {0}",
|
||||
"ban.notice.unban": "Unban time: {0}",
|
||||
"ban.notice.unban_permanent": "Unban time: Permanent",
|
||||
"ban.notice.contact": "If you have any questions, please contact the administrator."
|
||||
}
|
||||
""";
|
||||
|
||||
@@ -260,7 +264,12 @@ public sealed class LanguageService
|
||||
"command.note.description": "设置或清除房间备注(仅房主)。",
|
||||
"command.note.usage": "note <文字> | note clear",
|
||||
"command.max.description": "设置房间最大人数(仅房主)。",
|
||||
"command.max.usage": "max <1-15>"
|
||||
"command.max.usage": "max <1-15>",
|
||||
"ban.notice.header": "您已被服务器封禁",
|
||||
"ban.notice.reason": "理由:{0}",
|
||||
"ban.notice.unban": "解封时间:{0}",
|
||||
"ban.notice.unban_permanent": "解封时间:永久",
|
||||
"ban.notice.contact": "如有问题请联系管理员"
|
||||
}
|
||||
""";
|
||||
|
||||
@@ -315,7 +324,12 @@ public sealed class LanguageService
|
||||
"command.note.description": "設置或清除房間備註(僅房主)。",
|
||||
"command.note.usage": "note <文字> | note clear",
|
||||
"command.max.description": "設置房間最大人數(僅房主)。",
|
||||
"command.max.usage": "max <1-15>"
|
||||
"command.max.usage": "max <1-15>",
|
||||
"ban.notice.header": "您已被伺服器封禁",
|
||||
"ban.notice.reason": "理由:{0}",
|
||||
"ban.notice.unban": "解封時間:{0}",
|
||||
"ban.notice.unban_permanent": "解封時間:永久",
|
||||
"ban.notice.contact": "如有問題請聯絡管理員"
|
||||
}
|
||||
""";
|
||||
|
||||
@@ -356,7 +370,12 @@ public sealed class LanguageService
|
||||
"command.players.not_in_lobby": "이 명령어는 로비에서만 사용할 수 있습니다.",
|
||||
"command.players.header": "=== 플레이어 ({0}) ===",
|
||||
"command.players.entry": "{0} | {1} | {2}ms",
|
||||
"command.ping.result": "내 핑: {0}ms"
|
||||
"command.ping.result": "내 핑: {0}ms",
|
||||
"ban.notice.header": "이 서버에서 차단되었습니다.",
|
||||
"ban.notice.reason": "사유: {0}",
|
||||
"ban.notice.unban": "차단 해제 시간: {0}",
|
||||
"ban.notice.unban_permanent": "차단 해제 시간: 영구",
|
||||
"ban.notice.contact": "문의 사항이 있으면 관리자에게 연락해 주세요."
|
||||
}
|
||||
""";
|
||||
|
||||
@@ -397,7 +416,12 @@ public sealed class LanguageService
|
||||
"command.players.not_in_lobby": "Эта команда может использоваться только в лобби.",
|
||||
"command.players.header": "=== Игроки ({0}) ===",
|
||||
"command.players.entry": "{0} | {1} | {2}мс",
|
||||
"command.ping.result": "Ваш пинг: {0}мс"
|
||||
"command.ping.result": "Ваш пинг: {0}мс",
|
||||
"ban.notice.header": "Вы забанены на этом сервере.",
|
||||
"ban.notice.reason": "Причина: {0}",
|
||||
"ban.notice.unban": "Время разбана: {0}",
|
||||
"ban.notice.unban_permanent": "Время разбана: навсегда",
|
||||
"ban.notice.contact": "Если у вас есть вопросы, обратитесь к администратору."
|
||||
}
|
||||
""";
|
||||
|
||||
@@ -438,7 +462,12 @@ public sealed class LanguageService
|
||||
"command.players.not_in_lobby": "Dieser Befehl kann nur in der Lobby verwendet werden.",
|
||||
"command.players.header": "=== Spieler ({0}) ===",
|
||||
"command.players.entry": "{0} | {1} | {2}ms",
|
||||
"command.ping.result": "Dein Ping: {0}ms"
|
||||
"command.ping.result": "Dein Ping: {0}ms",
|
||||
"ban.notice.header": "Du wurdest von diesem Server gebannt.",
|
||||
"ban.notice.reason": "Grund: {0}",
|
||||
"ban.notice.unban": "Entbannungszeit: {0}",
|
||||
"ban.notice.unban_permanent": "Entbannungszeit: Dauerhaft",
|
||||
"ban.notice.contact": "Bei Fragen wende dich an den Administrator."
|
||||
}
|
||||
""";
|
||||
|
||||
@@ -479,7 +508,12 @@ public sealed class LanguageService
|
||||
"command.players.not_in_lobby": "Cette commande ne peut être utilisée que dans le lobby.",
|
||||
"command.players.header": "=== Joueurs ({0}) ===",
|
||||
"command.players.entry": "{0} | {1} | {2}ms",
|
||||
"command.ping.result": "Votre ping : {0}ms"
|
||||
"command.ping.result": "Votre ping : {0}ms",
|
||||
"ban.notice.header": "Vous êtes banni de ce serveur.",
|
||||
"ban.notice.reason": "Raison : {0}",
|
||||
"ban.notice.unban": "Débannissement : {0}",
|
||||
"ban.notice.unban_permanent": "Débannissement : Permanent",
|
||||
"ban.notice.contact": "En cas de question, contactez un administrateur."
|
||||
}
|
||||
""";
|
||||
|
||||
@@ -520,7 +554,12 @@ public sealed class LanguageService
|
||||
"command.players.not_in_lobby": "このコマンドはロビーでのみ使用できます。",
|
||||
"command.players.header": "=== プレイヤー ({0}) ===",
|
||||
"command.players.entry": "{0} | {1} | {2}ms",
|
||||
"command.ping.result": "あなたのPing: {0}ms"
|
||||
"command.ping.result": "あなたのPing: {0}ms",
|
||||
"ban.notice.header": "このサーバーからBANされています。",
|
||||
"ban.notice.reason": "理由:{0}",
|
||||
"ban.notice.unban": "BAN解除日時:{0}",
|
||||
"ban.notice.unban_permanent": "BAN解除日時:永久",
|
||||
"ban.notice.contact": "ご不明な点があれば管理者にお問い合わせください。"
|
||||
}
|
||||
""";
|
||||
|
||||
@@ -561,7 +600,12 @@ public sealed class LanguageService
|
||||
"command.players.not_in_lobby": "Este comando só pode ser usado no lobby.",
|
||||
"command.players.header": "=== Jogadores ({0}) ===",
|
||||
"command.players.entry": "{0} | {1} | {2}ms",
|
||||
"command.ping.result": "Seu ping: {0}ms"
|
||||
"command.ping.result": "Seu ping: {0}ms",
|
||||
"ban.notice.header": "Você foi banido deste servidor.",
|
||||
"ban.notice.reason": "Motivo: {0}",
|
||||
"ban.notice.unban": "Desbanimento: {0}",
|
||||
"ban.notice.unban_permanent": "Desbanimento: Permanente",
|
||||
"ban.notice.contact": "Em caso de dúvidas, contacte o administrador."
|
||||
}
|
||||
""";
|
||||
|
||||
@@ -600,7 +644,12 @@ public sealed class LanguageService
|
||||
"command.players.not_in_lobby": "Este comando só pode ser usado no lobby.",
|
||||
"command.players.header": "=== Jogadores ({0}) ===",
|
||||
"command.players.entry": "{0} | {1} | {2}ms",
|
||||
"command.ping.result": "Seu ping: {0}ms"
|
||||
"command.ping.result": "Seu ping: {0}ms",
|
||||
"ban.notice.header": "Você foi banido deste servidor.",
|
||||
"ban.notice.reason": "Motivo: {0}",
|
||||
"ban.notice.unban": "Desbanimento: {0}",
|
||||
"ban.notice.unban_permanent": "Desbanimento: Permanente",
|
||||
"ban.notice.contact": "Em caso de dúvidas, contate o administrador."
|
||||
}
|
||||
""";
|
||||
|
||||
@@ -639,7 +688,12 @@ public sealed class LanguageService
|
||||
"command.players.not_in_lobby": "Este comando solo puede usarse en el lobby.",
|
||||
"command.players.header": "=== Jugadores ({0}) ===",
|
||||
"command.players.entry": "{0} | {1} | {2}ms",
|
||||
"command.ping.result": "Tu ping: {0}ms"
|
||||
"command.ping.result": "Tu ping: {0}ms",
|
||||
"ban.notice.header": "Has sido baneado de este servidor.",
|
||||
"ban.notice.reason": "Motivo: {0}",
|
||||
"ban.notice.unban": "Fin del baneo: {0}",
|
||||
"ban.notice.unban_permanent": "Fin del baneo: Permanente",
|
||||
"ban.notice.contact": "Si tienes dudas, contacta con un administrador."
|
||||
}
|
||||
""";
|
||||
|
||||
@@ -678,7 +732,12 @@ public sealed class LanguageService
|
||||
"command.players.not_in_lobby": "Questo comando può essere usato solo nella lobby.",
|
||||
"command.players.header": "=== Giocatori ({0}) ===",
|
||||
"command.players.entry": "{0} | {1} | {2}ms",
|
||||
"command.ping.result": "Il tuo ping: {0}ms"
|
||||
"command.ping.result": "Il tuo ping: {0}ms",
|
||||
"ban.notice.header": "Sei stato bannato da questo server.",
|
||||
"ban.notice.reason": "Motivo: {0}",
|
||||
"ban.notice.unban": "Sbanno: {0}",
|
||||
"ban.notice.unban_permanent": "Sbanno: Permanente",
|
||||
"ban.notice.contact": "Per qualsiasi domanda, contatta l'amministratore."
|
||||
}
|
||||
""";
|
||||
|
||||
@@ -717,7 +776,12 @@ public sealed class LanguageService
|
||||
"command.players.not_in_lobby": "Dit commando kan alleen in de lobby worden gebruikt.",
|
||||
"command.players.header": "=== Spelers ({0}) ===",
|
||||
"command.players.entry": "{0} | {1} | {2}ms",
|
||||
"command.ping.result": "Jouw ping: {0}ms"
|
||||
"command.ping.result": "Jouw ping: {0}ms",
|
||||
"ban.notice.header": "Je bent geband van deze server.",
|
||||
"ban.notice.reason": "Reden: {0}",
|
||||
"ban.notice.unban": "Ontbantijd: {0}",
|
||||
"ban.notice.unban_permanent": "Ontbantijd: Permanent",
|
||||
"ban.notice.contact": "Neem bij vragen contact op met de beheerder."
|
||||
}
|
||||
""";
|
||||
|
||||
@@ -756,7 +820,12 @@ public sealed class LanguageService
|
||||
"command.players.not_in_lobby": "Ang command na ito ay maaari lamang gamitin sa lobby.",
|
||||
"command.players.header": "=== Mga Manlalaro ({0}) ===",
|
||||
"command.players.entry": "{0} | {1} | {2}ms",
|
||||
"command.ping.result": "Ang iyong ping: {0}ms"
|
||||
"command.ping.result": "Ang iyong ping: {0}ms",
|
||||
"ban.notice.header": "Banned ka sa server na ito.",
|
||||
"ban.notice.reason": "Dahilan: {0}",
|
||||
"ban.notice.unban": "Oras ng pag-unban: {0}",
|
||||
"ban.notice.unban_permanent": "Oras ng pag-unban: Permanente",
|
||||
"ban.notice.contact": "Kung may tanong, makipag-ugnayan sa administrator."
|
||||
}
|
||||
""";
|
||||
|
||||
@@ -795,7 +864,12 @@ public sealed class LanguageService
|
||||
"command.players.not_in_lobby": "Ní féidir an t-ordú seo a úsáid ach sa stocaireacht.",
|
||||
"command.players.header": "=== Imreoirí ({0}) ===",
|
||||
"command.players.entry": "{0} | {1} | {2}ms",
|
||||
"command.ping.result": "Do phing: {0}ms"
|
||||
"command.ping.result": "Do phing: {0}ms",
|
||||
"ban.notice.header": "Tá cosc ort ar an bhfreastalaí seo.",
|
||||
"ban.notice.reason": "Cúis: {0}",
|
||||
"ban.notice.unban": "Am dí-chosc: {0}",
|
||||
"ban.notice.unban_permanent": "Am dí-chosc: Buan",
|
||||
"ban.notice.contact": "Má tá ceist agat, déan teagmháil le riarthóir."
|
||||
}
|
||||
""";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Empostor.Api.Events.Player;
|
||||
|
||||
@@ -6,6 +6,24 @@ namespace Empostor.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 Empostor.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; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using Empostor.Api.Innersloth;
|
||||
using Empostor.Api.Innersloth.GameOptions;
|
||||
|
||||
namespace Empostor.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,7 @@ namespace Empostor.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;
|
||||
|
||||
@@ -2,6 +2,8 @@ namespace Empostor.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 Empostor.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 Empostor.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 Empostor.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 Empostor.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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
## Empostor.Api
|
||||
An api library for Empostor, an Among Us private server. You need this package to write plugins for Empostor
|
||||
@@ -93,12 +93,16 @@ public abstract class JsonDataStore<TData> : IDisposable
|
||||
/// Load data from the file on disk. If the file does not exist or fails to parse,
|
||||
/// the store remains in its default (constructor-initialized) state.
|
||||
/// </summary>
|
||||
protected void Load()
|
||||
/// <returns>
|
||||
/// <c>true</c> when a persisted snapshot was applied, <c>false</c> when the store kept its
|
||||
/// constructor defaults (no file yet or unreadable). Callers use this to seed from config.
|
||||
/// </returns>
|
||||
protected bool Load()
|
||||
{
|
||||
if (!File.Exists(FilePath))
|
||||
{
|
||||
Logger.LogDebug("{Name} no data file at {Path}, using defaults", GetType().Name, FilePath);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
@@ -109,12 +113,15 @@ public abstract class JsonDataStore<TData> : IDisposable
|
||||
{
|
||||
ApplySnapshot(data);
|
||||
Logger.LogInformation("{Name} loaded from {Path}", GetType().Name, FilePath);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogWarning(ex, "{Name} failed to load {Path}, using defaults", GetType().Name, FilePath);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Loaded 100 of 349 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user