Fix 2 HvN UI bugs 🔧 (#3378)

## Description:

I noticed two HvN bugs.

1. Private lobbies don't set `maxPlayers` in `GameConfig`, causing
`getGameModeLabel()` to render "0 Humans vs 0 Nations". Fall back to the
simple "Humans vs Nations" label when `maxPlayers` is unavailable.

<img width="239" height="84" alt="Screenshot 2026-03-07 034150"
src="https://github.com/user-attachments/assets/b2f01b96-674f-47dc-ae03-06bec71e3134"
/>

2. In public HumansVsNations games, the server matches the nation count
to the human player count at game start. The lobby team size preview
wasn't reflecting this - it displayed the raw config value instead.
Added `isPublicGame` prop to `LobbyPlayerView` and an
`effectiveNationCount` getter that overrides the displayed nation count
to match `clients.length` only for public HvN games. Private lobby hosts
retain full slider control. (This bug got introduced with my
"Configurable nation count" PR)

## 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-07 21:57:16 +01:00
committed by GitHub
parent 3fca25f421
commit 526cb723d6
3 changed files with 32 additions and 11 deletions
+2
View File
@@ -130,6 +130,8 @@ export class JoinLobbyModal extends BaseModal {
.lobbyCreatorClientID=${hostClientID}
.currentClientID=${this.currentClientID}
.teamCount=${this.gameConfig?.playerTeams ?? 2}
.isPublicGame=${this.gameConfig?.gameType ===
GameType.Public}
.nationCount=${nationsConfigToSlider(
this.gameConfig?.nations ?? "default",
this.nationCount,
+6 -3
View File
@@ -36,9 +36,12 @@ export function getGameModeLabel(gameConfig: GameConfig): string {
// Humans vs Nations
if (playerTeams === HumansVsNations) {
return translateText("public_lobby.teams_hvn_detailed", {
num: maxPlayers ?? 0,
});
if (maxPlayers) {
return translateText("public_lobby.teams_hvn_detailed", {
num: maxPlayers,
});
}
return translateText("public_lobby.teams_hvn");
}
// Named team types (Duos, Trios, Quads)
+24 -8
View File
@@ -35,11 +35,24 @@ export class LobbyTeamView extends LitElement {
@property({ attribute: "team-count" }) teamCount: TeamCountConfig = 2;
@property({ type: Function }) onKickPlayer?: (clientID: string) => void;
@property({ type: Number }) nationCount: number = 0;
@property({ type: Boolean }) isPublicGame: boolean = false;
private theme: PastelTheme = new PastelTheme();
@state() private showTeamColors: boolean = false;
private userSettings: UserSettings = new UserSettings();
/**
* For public HumansVsNations games, nation count always matches human count
* (server enforces this in NationCreation). For private games, the host
* controls the nation count via the slider.
*/
private get effectiveNationCount(): number {
if (this.isPublicGame && this.teamCount === HumansVsNations) {
return this.clients.length;
}
return this.nationCount;
}
willUpdate(changedProperties: Map<string, any>) {
// Recompute team preview when relevant properties change
// clients is updated from WebSocket lobby_info events
@@ -47,7 +60,8 @@ export class LobbyTeamView extends LitElement {
changedProperties.has("gameMode") ||
changedProperties.has("clients") ||
changedProperties.has("teamCount") ||
changedProperties.has("nationCount")
changedProperties.has("nationCount") ||
changedProperties.has("isPublicGame")
) {
const teamsList = this.getTeamList();
this.computeTeamPreview(teamsList);
@@ -67,8 +81,8 @@ export class LobbyTeamView extends LitElement {
? translateText("host_modal.player")
: translateText("host_modal.players")}
<span style="margin: 0 8px;">•</span>
${this.nationCount}
${this.nationCount === 1
${this.effectiveNationCount}
${this.effectiveNationCount === 1
? translateText("host_modal.nation_player")
: translateText("host_modal.nation_players")}
</div>
@@ -179,12 +193,12 @@ export class LobbyTeamView extends LitElement {
private renderTeamCard(preview: TeamPreviewData, isEmpty: boolean = false) {
const displayCount =
preview.team === ColoredTeams.Nations
? this.nationCount
? this.effectiveNationCount
: preview.players.length;
const maxTeamSize =
preview.team === ColoredTeams.Nations
? this.nationCount
? this.effectiveNationCount
: this.teamMaxSize;
const teamLabel = getTranslatedPlayerTeamLabel(preview.team);
@@ -245,7 +259,7 @@ export class LobbyTeamView extends LitElement {
private getTeamList(): Team[] {
if (this.gameMode !== GameMode.Team) return [];
const playerCount = this.clients.length + this.nationCount;
const playerCount = this.clients.length + this.effectiveNationCount;
const config = this.teamCount;
if (config === HumansVsNations) {
@@ -309,7 +323,7 @@ export class LobbyTeamView extends LitElement {
const assignment = assignTeamsLobbyPreview(
players,
teams,
this.nationCount,
this.effectiveNationCount,
);
const buckets = new Map<Team, ClientInfo[]>();
for (const t of teams) buckets.set(t, []);
@@ -333,7 +347,9 @@ export class LobbyTeamView extends LitElement {
// Fallback: divide players across teams; guard against 0 and empty lobbies
this.teamMaxSize = Math.max(
1,
Math.ceil((this.clients.length + this.nationCount) / teams.length),
Math.ceil(
(this.clients.length + this.effectiveNationCount) / teams.length,
),
);
}
this.teamPreview = teams.map((t) => ({