50 Commits
Author SHA1 Message Date
linmeideli e6bf3af1c9 fix
CI / build (push) Canceled after 0s
nuget / publish (push) Canceled after 0s
CI / publish-nightly (push) Canceled after 0s
CI / docker (map[docker:linux/amd64 rid:amd64]) (push) Canceled after 0s
CI / docker (map[docker:linux/arm/v7 rid:arm]) (push) Canceled after 0s
CI / docker (map[docker:linux/arm64 rid:arm64]) (push) Canceled after 0s
2026-10-01 18:12:51 +08:00
linmeideli 2c6c51724a fix autofresh 2026-10-01 18:03:41 +08:00
linmeideli 8883529f0c Update 2026-10-01 17:37:23 +08:00
linmeideli cb3d78ace8 .
CI / build (push) Canceled after 0s
nuget / publish (push) Canceled after 0s
CI / publish-nightly (push) Canceled after 0s
CI / docker (map[docker:linux/amd64 rid:amd64]) (push) Canceled after 0s
CI / docker (map[docker:linux/arm/v7 rid:arm]) (push) Canceled after 0s
CI / docker (map[docker:linux/arm64 rid:arm64]) (push) Canceled after 0s
2026-10-01 10:29:15 +08:00
linmeideli d4f898b92f fix 2026-10-01 10:26:52 +08:00
linmeideli ae8cf7fc27 Merge 2026-10-01 10:08:28 +08:00
linmeideli 2f50097250 Update Chat Plugin 2026-10-01 10:07:18 +08:00
Delusional Syndrome 24c6097cbe Merge pull request #10 from Empostor/feature/v19
CI / build (push) Canceled after 0s
nuget / publish (push) Canceled after 0s
CI / publish-nightly (push) Canceled after 0s
CI / docker (map[docker:linux/amd64 rid:amd64]) (push) Canceled after 0s
CI / docker (map[docker:linux/arm/v7 rid:arm]) (push) Canceled after 0s
CI / docker (map[docker:linux/arm64 rid:arm64]) (push) Canceled after 0s
feature/v19
2026-09-30 20:58:35 +08:00
FangkuaiYa 6a98d63db2 feature/v19 2026-09-30 16:38:07 +08:00
linmeideli 6c83b127d3 restore Dockerfile
CI / build (push) Canceled after 0s
CI / publish-nightly (push) Canceled after 0s
CI / docker (map[docker:linux/amd64 rid:amd64]) (push) Canceled after 0s
CI / docker (map[docker:linux/arm/v7 rid:arm]) (push) Canceled after 0s
CI / docker (map[docker:linux/arm64 rid:arm64]) (push) Canceled after 0s
2026-09-25 19:39:10 +08:00
linmeideli b9e40852f4 clean up and bump 2026-09-25 19:33:50 +08:00
linmeideli f3043cfc5e ci: restore ci.N prerelease suffix and drop workflow self-trigger 2026-09-25 19:06:44 +08:00
linmeideli b675d07d2e ci: package version comes from project file only 2026-09-25 18:46:26 +08:00
linmeideli bcd18b7d85 ci: use nuget.org trusted publishing (OIDC) instead of static key 2026-09-25 18:17:43 +08:00
Delusional Syndrome 2651a93230 Update README.md
CI / build (push) Canceled after 0s
nuget / publish (push) Canceled after 0s
CI / publish-nightly (push) Canceled after 0s
CI / docker (map[docker:linux/amd64 rid:amd64]) (push) Canceled after 0s
CI / docker (map[docker:linux/arm/v7 rid:arm]) (push) Canceled after 0s
CI / docker (map[docker:linux/arm64 rid:arm64]) (push) Canceled after 0s
2026-09-25 13:01:51 +08:00
linmeideli 211c79f9d0 fix nuget version numbering to follow CI run number 2026-09-25 12:49:38 +08:00
Delusional Syndrome dc0f115948 Fix project name and add description to README
Updated the README to correct the project name and provide a brief description.
2026-09-25 12:38:49 +08:00
linmeideli 2d3a5ca800 fix docker remain issue 2026-09-25 12:32:20 +08:00
linmeideli 1ced63af8d fix 2026-09-25 12:26:02 +08:00
linmeideli ca54e6f2ed Update Nuget 2026-09-25 11:52:26 +08:00
linmeideli a2daa01993 both revert + docker fix 2026-09-25 10:26:21 +08:00
linmeideli edfff9ae19 Deleted Niko and Relay auth 2026-09-25 10:20:17 +08:00
linmeideli 8767bcbd4d Change IpGeolocationService to english 2026-09-25 10:04:44 +08:00
linmeideli 1d7252aa36 Merge branch 'main' of https://github.com/Empostor/Empostor 2026-09-25 09:59:49 +08:00
linmeideli 90e995261d enhance docker 2026-09-25 09:59:38 +08:00
FangkuaiYa cc8085f138 Fix build 2026-09-12 02:42:37 +08:00
FangkuaiYa 34e6751d33 Some support and improvements 2026-09-12 02:33:43 +08:00
linmeideli f72a8aa59f Update 2026-09-06 14:00:49 +08:00
linmeideli a5dd48a5ed matching Impostor 2026-09-06 11:57:30 +08:00
linmeideli 490a59be62 Improve file formal and clear program self 2026-08-29 16:28:01 +08:00
linmeideli e1285972d7 update theme 2026-08-29 15:59:30 +08:00
linmeideli d761f31fa3 Reapply "Add fault tolerance to GameData messages"
This reverts commit 4ca2efc6cd.
2026-08-29 15:45:32 +08:00
FangkuaiYa 4ca2efc6cd Revert "Add fault tolerance to GameData messages"
This reverts commit 7c8c629ea5.
2026-08-22 14:33:47 +08:00
Christina e747f8d71a Merge pull request #7 from Nb1X/docs/add-readme
docs: add project README
2026-08-20 23:47:58 +08:00
Christina 6a21cde5ac Update README with credits and QQ group
Added credits section and QQ group link to README.
2026-08-20 22:16:49 +08:00
Christina 9fcc718f63 Enhance README with new features and links
Updated README to include Admin Panel feature and new Discord link.
2026-08-20 22:01:58 +08:00
Christina f3a7463949 Merge pull request #2 from Empostor/gamedata-enhance
Add fault tolerance to GameData messages
2026-08-20 21:58:44 +08:00
Nb1X 5c650e8970 Added a README.md
Added README.md with project details and setup instructions.
2026-08-20 14:09:51 +02:00
FangkuaiYa 43f8281486 Fix playerlog 2026-08-20 15:17:46 +08:00
linmeideli 07f81c5d75 Merge branch 'main' of https://github.com/Empostor/Empostor 2026-08-20 12:14:28 +08:00
linmeideli f0cb36f118 merge 2026-08-20 12:14:26 +08:00
Christina 2ba175d16c Merge pull request #6 from Empostor/adminpanel-refect
Adminpanel refect
2026-08-20 10:19:21 +08:00
Christina 068cbcabc4 Merge pull request #5 from Empostor/v18.0-support
filter + upstream support
2026-08-20 09:15:47 +08:00
linmeideli 6498752430 filter + upstream support 2026-08-20 09:14:30 +08:00
linmeideli 031b6c1388 Update 2026-08-19 10:52:47 +08:00
Fang-Kuai 3efbb19785 Merge pull request #3 from XtraCube/main
Support new Starlight API Spec
2026-08-18 11:12:43 +08:00
Sameer Umar 989c1b5ff2 Support new Starlight API Spec 2026-08-17 23:03:50 -04:00
linmeideli f8eeeaae8b Enhanced Ban 2026-08-18 00:11:41 +08:00
linmeideli 40bbc2027b Bump 2026-08-17 23:42:49 +08:00
linmeideli 7c8c629ea5 Add fault tolerance to GameData messages 2026-08-17 12:12:40 +08:00
349 changed files with 71368 additions and 12194 deletions

No files matched your search

+70
View File
@@ -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
View File
@@ -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"]
+65
View File
@@ -0,0 +1,65 @@
# Empostor
Empostor is an open-source private server implementation for Among Us.
[![Discord](https://img.shields.io/badge/Discord-%235865F2.svg?style=flat&logo=discord&logoColor=white)](https://dsc.gg/empostor)
[![QQ](https://img.shields.io/badge/QQ-Group-black?style=flat-square)](https://qm.qq.com/q/GeX3Q0Ft0k)
[![GitHub license](https://badgen.net/github/license/Empostor/Empostor)](https://github.com/Empostor/Empostor/blob/main/LICENSE)
[![GitHub latest commit](https://badgen.net/github/last-commit/Empostor/Empostor)](https://github.com/Empostor/Empostor/commit/)
[![GitHub all releases](https://img.shields.io/github/downloads/Empostor/Empostor/total.svg)](https://github.com/Empostor/Empostor/releases/)
[![GitHub contributors](https://badgen.net/github/contributors/Empostor/Empostor)](https://github.com/Empostor/Empostor/graphs/contributors/)
[![GitHub total-pull-requests](https://badgen.net/github/prs/Empostor/Empostor)](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
View File
@@ -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
View File
@@ -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"
}
]
}
]
+65
View File
@@ -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"
}
]
+38
View File
@@ -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)}"
}
+38
View File
@@ -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}"
}
+38
View File
@@ -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}}"
}
+38
View File
@@ -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}"
}
+38
View File
@@ -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)}"
}
+38
View File
@@ -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)}"
}
+38
View File
@@ -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}}"
}
+38
View File
@@ -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}"
}
+38
View File
@@ -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": {}
}
}
+55
View File
@@ -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; }
}
+132
View File
@@ -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++}";
}
+74
View File
@@ -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";
}
+239
View File
@@ -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);
+29
View File
@@ -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);
}
+3
View File
@@ -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,
}
+11
View File
@@ -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;
-10
View File
@@ -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 -4
View File
@@ -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;
}
}
+5 -5
View File
@@ -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; }
}
}
+6
View File
@@ -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
}
+2 -2
View File
@@ -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 -1
View File
@@ -1,4 +1,4 @@
namespace Empostor.Api.Innersloth
namespace Empostor.Api.Innersloth
{
public static class TextBox
{
+90 -16
View File
@@ -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();
}
}
}
+2
View File
@@ -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
+9 -2
View File
@@ -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