From 7942990037309f310eee160d2f45b5cf0d22c40b Mon Sep 17 00:00:00 2001
From: FloPinguin <25036848+FloPinguin@users.noreply.github.com>
Date: Tue, 27 Jan 2026 01:29:52 +0100
Subject: [PATCH] =?UTF-8?q?Crowded=20modifier=20=F0=9F=98=84=20(#3023)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## Description:
To increase variety a bit more I present: The "crowded" public game
modifier :)
It basically simulates a crazy youtuber lobby. Cramp a lot of players on
a small map 😄
I think its fun, exciting and you actually need skill to manage the
chaos.
5% of public games get this modifier, but because we remove the modifier
for big maps its more like 2.5% (should be something special)
|
|
|
|
|---|---|---|
## 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
---
resources/lang/en.json | 2 ++
src/client/PublicLobby.ts | 3 ++
src/core/Schemas.ts | 1 +
src/core/game/Game.ts | 1 +
src/server/MapPlaylist.ts | 56 +++++++++++++++++++++++++++++-----
tests/util/TestServerConfig.ts | 2 +-
6 files changed, 56 insertions(+), 9 deletions(-)
diff --git a/resources/lang/en.json b/resources/lang/en.json
index a9663b2b0..5f76bd3b2 100644
--- a/resources/lang/en.json
+++ b/resources/lang/en.json
@@ -166,6 +166,7 @@
"infinite_gold": "Infinite gold",
"infinite_troops": "Infinite troops",
"compact_map": "Compact Map",
+ "crowded": "Crowded",
"max_timer": "Game length (minutes)",
"max_timer_placeholder": "Mins",
"max_timer_invalid": "Please enter a valid max timer value (1-120 minutes)",
@@ -421,6 +422,7 @@
"public_game_modifier": {
"random_spawn": "Random Spawn",
"compact_map": "Compact Map",
+ "crowded": "Crowded",
"starting_gold": "5M Starting Gold"
},
"select_lang": {
diff --git a/src/client/PublicLobby.ts b/src/client/PublicLobby.ts
index 4c895ab8f..e7610672e 100644
--- a/src/client/PublicLobby.ts
+++ b/src/client/PublicLobby.ts
@@ -374,6 +374,9 @@ export class PublicLobby extends LitElement {
if (publicGameModifiers.isCompact) {
labels.push(translateText("public_game_modifier.compact_map"));
}
+ if (publicGameModifiers.isCrowded) {
+ labels.push(translateText("public_game_modifier.crowded"));
+ }
if (publicGameModifiers.startingGold) {
labels.push(translateText("public_game_modifier.starting_gold"));
}
diff --git a/src/core/Schemas.ts b/src/core/Schemas.ts
index d225857c5..9255156c1 100644
--- a/src/core/Schemas.ts
+++ b/src/core/Schemas.ts
@@ -190,6 +190,7 @@ export const GameConfigSchema = z.object({
.object({
isCompact: z.boolean(),
isRandomSpawn: z.boolean(),
+ isCrowded: z.boolean(),
startingGold: z.number().int().min(0).optional(),
})
.optional(),
diff --git a/src/core/game/Game.ts b/src/core/game/Game.ts
index 1c56d5d46..7e613e0c5 100644
--- a/src/core/game/Game.ts
+++ b/src/core/game/Game.ts
@@ -211,6 +211,7 @@ export enum GameMapSize {
export interface PublicGameModifiers {
isCompact: boolean;
isRandomSpawn: boolean;
+ isCrowded: boolean;
startingGold?: number;
}
diff --git a/src/server/MapPlaylist.ts b/src/server/MapPlaylist.ts
index 5bbc11b00..b54aa54ff 100644
--- a/src/server/MapPlaylist.ts
+++ b/src/server/MapPlaylist.ts
@@ -97,7 +97,7 @@ export class MapPlaylist {
const modifiers = this.getRandomPublicGameModifiers();
const { startingGold } = modifiers;
- let { isCompact, isRandomSpawn } = modifiers;
+ let { isCompact, isRandomSpawn, isCrowded } = modifiers;
// Duos, Trios, and Quads should not get random spawn (as it defeats the purpose)
if (
@@ -108,8 +108,8 @@ export class MapPlaylist {
isRandomSpawn = false;
}
- // Maps with smallest player count < 50 don't support compact map in team games
- // The smallest player count is the 3rd number in the player counts array
+ // Maps with smallest player count (third number of calculateMapPlayerCounts) < 50 don't support compact map in team games
+ // (not enough players after 75% player reduction for compact maps)
if (
mode === GameMode.Team &&
!(await this.supportsCompactMapForTeams(map))
@@ -117,15 +117,34 @@ export class MapPlaylist {
isCompact = false;
}
+ // Crowded modifier: if the map's biggest player count (first number of calculateMapPlayerCounts) is 60 or lower (small maps),
+ // set player count to 125 (or 60 if compact map is also enabled)
+ let crowdedMaxPlayers: number | undefined;
+ if (isCrowded) {
+ crowdedMaxPlayers = await this.getCrowdedMaxPlayers(map, isCompact);
+ if (crowdedMaxPlayers === undefined) {
+ isCrowded = false;
+ } else {
+ crowdedMaxPlayers = this.adjustForTeams(crowdedMaxPlayers, playerTeams);
+ }
+ }
+
// Create the default public game config (from your GameManager)
return {
donateGold: mode === GameMode.Team,
donateTroops: mode === GameMode.Team,
gameMap: map,
- maxPlayers: await this.lobbyMaxPlayers(map, mode, playerTeams, isCompact),
+ maxPlayers:
+ crowdedMaxPlayers ??
+ (await this.lobbyMaxPlayers(map, mode, playerTeams, isCompact)),
gameType: GameType.Public,
gameMapSize: isCompact ? GameMapSize.Compact : GameMapSize.Normal,
- publicGameModifiers: { isCompact, isRandomSpawn, startingGold },
+ publicGameModifiers: {
+ isCompact,
+ isRandomSpawn,
+ isCrowded,
+ startingGold,
+ },
startingGold,
difficulty:
playerTeams === HumansVsNations ? Difficulty.Medium : Difficulty.Easy,
@@ -209,18 +228,31 @@ export class MapPlaylist {
return {
isRandomSpawn: Math.random() < 0.1, // 10% chance
isCompact: Math.random() < 0.05, // 5% chance
+ isCrowded: Math.random() < 0.05, // 5% chance
startingGold: Math.random() < 0.05 ? 5_000_000 : undefined, // 5% chance
};
}
+ // Maps with smallest player count (third number of calculateMapPlayerCounts) < 50 don't support compact map in team games
+ // (not enough players after 75% player reduction for compact maps)
private async supportsCompactMapForTeams(map: GameMapType): Promise {
- // Maps with smallest player count < 50 don't support compact map in team games
- // The smallest player count is the 3rd number in the player counts array
const landTiles = await getMapLandTiles(map);
const [, , smallest] = this.calculateMapPlayerCounts(landTiles);
return smallest >= 50;
}
+ private async getCrowdedMaxPlayers(
+ map: GameMapType,
+ isCompact: boolean,
+ ): Promise {
+ const landTiles = await getMapLandTiles(map);
+ const [firstPlayerCount] = this.calculateMapPlayerCounts(landTiles);
+ if (firstPlayerCount <= 60) {
+ return isCompact ? 60 : 125;
+ }
+ return undefined;
+ }
+
private async lobbyMaxPlayers(
map: GameMapType,
mode: GameMode,
@@ -236,7 +268,15 @@ export class MapPlaylist {
if (isCompactMap) {
p = Math.max(3, Math.floor(p * 0.25));
}
- if (numPlayerTeams === undefined) return p;
+ return this.adjustForTeams(p, numPlayerTeams);
+ }
+
+ private adjustForTeams(
+ playerCount: number,
+ numPlayerTeams: TeamCountConfig | undefined,
+ ): number {
+ if (numPlayerTeams === undefined) return playerCount;
+ let p = playerCount;
switch (numPlayerTeams) {
case Duos:
p -= p % 2;
diff --git a/tests/util/TestServerConfig.ts b/tests/util/TestServerConfig.ts
index 6a879ccd5..94b625943 100644
--- a/tests/util/TestServerConfig.ts
+++ b/tests/util/TestServerConfig.ts
@@ -80,7 +80,7 @@ export class TestServerConfig implements ServerConfig {
throw new Error("Method not implemented.");
}
getRandomPublicGameModifiers(): PublicGameModifiers {
- return { isCompact: false, isRandomSpawn: false };
+ return { isCompact: false, isRandomSpawn: false, isCrowded: false };
}
async supportsCompactMapForTeams(): Promise {
throw new Error("Method not implemented.");