From 6154b779f58bb4282984ab3aac719e1109c55cc7 Mon Sep 17 00:00:00 2001 From: FloPinguin <25036848+FloPinguin@users.noreply.github.com> Date: Fri, 6 Mar 2026 00:21:35 +0100 Subject: [PATCH] =?UTF-8?q?Fix=20compact=20map=20playercount=20?= =?UTF-8?q?=F0=9F=94=A7=20(#3361)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description: The recently improved `supportsCompactMapForTeams` only checked that each team had ≥2 players but not that there were ≥2 teams. On very small maps with compact + Duos (rare), this allowed lobbies with 1 team of 2, which makes no sense (Wonder noticed that randomly while looking at the homepage). Added a `numberOfTeams ≥ 2` check so the compact map modifier isn't active when the player count can't sustain multiple teams. The 125-player performance cap was also applied inside `calculateMapPlayerCounts` before the compact 75% reduction, so a map sized for 200 players would get capped to 125 then reduced to 31 instead of the expected 50. Moved the cap (`MAX_PLAYER_COUNT`) to after the compact reduction. ## 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 --- src/server/MapPlaylist.ts | 51 +++++++++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 15 deletions(-) diff --git a/src/server/MapPlaylist.ts b/src/server/MapPlaylist.ts index 0984367f8..0d901a82e 100644 --- a/src/server/MapPlaylist.ts +++ b/src/server/MapPlaylist.ts @@ -21,6 +21,9 @@ import { getMapLandTiles } from "./MapLandTiles"; const log = logger.child({}); const ARCADE_MAPS = new Set(mapCategories.arcade); +// Hard cap on player count for performance. Applied after compact-map reduction. +const MAX_PLAYER_COUNT = 125; + // How many times each map should appear in the playlist. // Note: The Partial should eventually be removed for better type safety. const frequency: Partial> = { @@ -164,7 +167,7 @@ export class MapPlaylist { } // 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) + // set player count to MAX_PLAYER_COUNT (or 60 if compact map is also enabled) let crowdedMaxPlayers: number | undefined; if (isCrowded) { crowdedMaxPlayers = await this.getCrowdedMaxPlayers(map, isCompact); @@ -537,12 +540,15 @@ export class MapPlaylist { const [l, , s] = this.calculateMapPlayerCounts(landTiles); // Worst case: smallest tier with team mode 1.5x multiplier, capped at l let p = Math.min(Math.ceil(s * 1.5), l); - // Apply compact 75% player reduction - p = Math.max(3, Math.floor(p * 0.25)); + // Apply compact 75% player reduction, then cap for performance + p = Math.min(Math.max(3, Math.floor(p * 0.25)), MAX_PLAYER_COUNT); // Apply team adjustment p = this.adjustForTeams(p, playerTeams); - // Check at least 2 players per team - return this.playersPerTeam(p, playerTeams) >= 2; + // Check at least 2 players per team AND at least 2 teams + return ( + this.playersPerTeam(p, playerTeams) >= 2 && + this.numberOfTeams(p, playerTeams) >= 2 + ); } private playersPerTeam( @@ -563,6 +569,24 @@ export class MapPlaylist { } } + private numberOfTeams( + adjustedPlayerCount: number, + playerTeams: TeamCountConfig, + ): number { + switch (playerTeams) { + case Duos: + return Math.floor(adjustedPlayerCount / 2); + case Trios: + return Math.floor(adjustedPlayerCount / 3); + case Quads: + return Math.floor(adjustedPlayerCount / 4); + case HumansVsNations: + return 2; // always 2 teams + default: + return playerTeams; // numeric value IS the team count + } + } + /** * Centralised spawn-immunity duration logic. * - HumansVsNations: always 5s (nations can't benefit from longer PVP immunity) @@ -586,9 +610,10 @@ export class MapPlaylist { isCompact: boolean, ): Promise { const landTiles = await getMapLandTiles(map); - const [firstPlayerCount] = this.calculateMapPlayerCounts(landTiles); + const [rawFirstPlayerCount] = this.calculateMapPlayerCounts(landTiles); + const firstPlayerCount = Math.min(rawFirstPlayerCount, MAX_PLAYER_COUNT); if (firstPlayerCount <= 60) { - return isCompact ? 60 : 125; + return isCompact ? 60 : MAX_PLAYER_COUNT; } return undefined; } @@ -608,6 +633,8 @@ export class MapPlaylist { if (isCompactMap) { p = Math.max(3, Math.floor(p * 0.25)); } + // Cap for performance + p = Math.min(p, MAX_PLAYER_COUNT); return this.adjustForTeams(p, numPlayerTeams); } @@ -641,7 +668,6 @@ export class MapPlaylist { /** * Calculate player counts from land tiles * For every 1,000,000 land tiles, take 50 players - * Limit to max 125 players for performance * Second value is 75% of calculated value, third is 50% * All values are rounded to the nearest 5 */ @@ -650,12 +676,7 @@ export class MapPlaylist { ): [number, number, number] { const roundToNearest5 = (n: number) => Math.round(n / 5) * 5; - const base = roundToNearest5((landTiles / 1_000_000) * 50); - const limitedBase = Math.min(Math.max(base, 5), 125); - return [ - limitedBase, - roundToNearest5(limitedBase * 0.75), - roundToNearest5(limitedBase * 0.5), - ]; + const base = Math.max(roundToNearest5((landTiles / 1_000_000) * 50), 5); + return [base, roundToNearest5(base * 0.75), roundToNearest5(base * 0.5)]; } }