Fix compact map playercount 🔧 (#3361)

## 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
This commit is contained in:
FloPinguin
2026-03-06 00:21:35 +01:00
committed by GitHub
parent 0733c680b9
commit 6154b779f5
+36 -15
View File
@@ -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<Record<GameMapName, number>> = {
@@ -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<number | undefined> {
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)];
}
}