mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 13:20:43 +00:00
9821e8e041
## Description: - Adds a "Host Cheats" toggle in the private lobby options section that reveals a dedicated section with four host-only cheats: infinite gold, infinite troops, gold multiplier, and starting gold - Only the lobby creator receives the cheat effects in-game (checked via `isLobbyCreator` in DefaultConfig) - Joining players see active host cheats displayed as yellow badges in the lobby UI - Adds `hostCheats` optional object to `GameConfigSchema` and wires it through the server config update whitelist - Raises the intent size limit for `update_game_config` messages (lobby-only, not stored in turn history) to prevent rate-limiter kicks (I always got too-much-data-kicked after selecting "host cheats" lol) <img width="861" height="525" alt="image" src="https://github.com/user-attachments/assets/51e51ec4-c2e8-46ca-b258-11a93487964f" /> <img width="933" height="825" alt="image" src="https://github.com/user-attachments/assets/5acbd38d-2097-42e1-ba78-0fb17d6afe82" /> ## Please complete the following: - [X] I have added screenshots for all UI updates - [X] I process any text displayed to the user through translateText() and I've added it to the en.json file - [X] I have added relevant tests to the test directory - [X] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: FloPinguin
77 lines
2.1 KiB
TypeScript
77 lines
2.1 KiB
TypeScript
import { RateLimiter } from "limiter";
|
|
import { ClientID } from "../core/Schemas";
|
|
|
|
const INTENTS_PER_SECOND = 10;
|
|
const INTENTS_PER_MINUTE = 150;
|
|
const MAX_INTENT_SIZE = 500;
|
|
const MAX_CONFIG_INTENT_SIZE = 2000;
|
|
const TOTAL_BYTES = 2 * 1024 * 1024; // 2MB per client
|
|
export type RateLimitResult = "ok" | "limit" | "kick";
|
|
|
|
interface ClientBucket {
|
|
perSecond: RateLimiter;
|
|
perMinute: RateLimiter;
|
|
totalBytes: number;
|
|
}
|
|
|
|
export class ClientMsgRateLimiter {
|
|
private buckets = new Map<ClientID, ClientBucket>();
|
|
|
|
check(
|
|
clientID: ClientID,
|
|
type: string,
|
|
bytes: number,
|
|
intentType?: string,
|
|
): RateLimitResult {
|
|
const bucket = this.getOrCreate(clientID);
|
|
bucket.totalBytes += bytes;
|
|
|
|
if (bucket.totalBytes >= TOTAL_BYTES) return "kick";
|
|
|
|
if (type === "intent") {
|
|
// Config updates are lobby-only and not stored in turn history,
|
|
// so they can be larger than regular intents.
|
|
const maxSize =
|
|
intentType === "update_game_config"
|
|
? MAX_CONFIG_INTENT_SIZE
|
|
: MAX_INTENT_SIZE;
|
|
// Intents are stored in turn history for the duration of the game, so
|
|
// oversized intents would accumulate and fill up server RAM.
|
|
// Intents are also sent to all players, so it increase outgoing
|
|
// data.
|
|
// Intents should never be larger than MAX_INTENT_SIZE, so we assume the client is malicious.
|
|
if (bytes > maxSize) {
|
|
return "kick";
|
|
}
|
|
if (
|
|
!bucket.perSecond.tryRemoveTokens(1) ||
|
|
!bucket.perMinute.tryRemoveTokens(1)
|
|
) {
|
|
return "limit";
|
|
}
|
|
}
|
|
|
|
return "ok";
|
|
}
|
|
|
|
private getOrCreate(clientID: ClientID): ClientBucket {
|
|
const existing = this.buckets.get(clientID);
|
|
if (existing) {
|
|
return existing;
|
|
}
|
|
const bucket = {
|
|
perSecond: new RateLimiter({
|
|
tokensPerInterval: INTENTS_PER_SECOND,
|
|
interval: "second",
|
|
}),
|
|
perMinute: new RateLimiter({
|
|
tokensPerInterval: INTENTS_PER_MINUTE,
|
|
interval: "minute",
|
|
}),
|
|
totalBytes: 0,
|
|
};
|
|
this.buckets.set(clientID, bucket);
|
|
return bucket;
|
|
}
|
|
}
|