Crowded modifier 😄 (#3023)

## 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)

| <img width="321" height="269" alt="Screenshot 2026-01-25 200427"
src="https://github.com/user-attachments/assets/7d2e90c1-e6bc-40a8-a19e-a0849612f472"
/> | <img width="317" height="264" alt="Screenshot 2026-01-25 200554"
src="https://github.com/user-attachments/assets/8b4bd5da-bed1-4743-a107-9ce07fce3040"
/> | <img width="317" height="244" alt="Screenshot 2026-01-25 200521"
src="https://github.com/user-attachments/assets/16293de3-0fc4-431f-8151-31b4e11040fe"
/> |
|---|---|---|






## 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
This commit is contained in:
FloPinguin
2026-01-27 01:29:52 +01:00
committed by GitHub
parent bc479af5c9
commit 7942990037
6 changed files with 56 additions and 9 deletions
+2
View File
@@ -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": {
+3
View File
@@ -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"));
}
+1
View File
@@ -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(),
+1
View File
@@ -211,6 +211,7 @@ export enum GameMapSize {
export interface PublicGameModifiers {
isCompact: boolean;
isRandomSpawn: boolean;
isCrowded: boolean;
startingGold?: number;
}
+48 -8
View File
@@ -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<boolean> {
// 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<number | undefined> {
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;
+1 -1
View File
@@ -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<boolean> {
throw new Error("Method not implemented.");