Anonymized/hidden names on lobby preview (#2965)

Resolves #2962

## Description:

Anonymize lobby preview player names when “Hidden names” is enabled,
using the same deterministic mapping as in-game.

<img width="864" height="618" alt="スクリーンショット 2026-01-20 21 13 19"
src="https://github.com/user-attachments/assets/30ebe155-c66e-49a5-8957-f8ec0a1ccd76"
/>

<img width="668" height="341" alt="スクリーンショット 2026-01-20 21 13 27"
src="https://github.com/user-attachments/assets/6ef74d98-ea2f-4156-a321-306acf012672"
/>


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

aotumuri

Co-authored-by: Ryan <7389646+ryanbarlow97@users.noreply.github.com>
This commit is contained in:
Aotumuri
2026-01-28 08:49:06 +09:00
committed by GitHub
parent da4b8aa5e1
commit 6cca96b545
3 changed files with 46 additions and 16 deletions
+1
View File
@@ -653,6 +653,7 @@ export class HostLobbyModal extends BaseModal {
.gameMode=${this.gameMode}
.clients=${this.clients}
.lobbyCreatorClientID=${this.lobbyCreatorClientID}
.currentClientID=${this.lobbyCreatorClientID}
.teamCount=${this.teamCount}
.nationCount=${this.nationCount}
.disableNations=${this.disableNations}
+7 -2
View File
@@ -28,6 +28,7 @@ export class JoinPrivateLobbyModal extends BaseModal {
@state() private gameConfig: GameConfig | null = null;
@state() private lobbyCreatorClientID: string | null = null;
@state() private currentLobbyId: string = "";
@state() private currentClientID: string = "";
@state() private nationCount: number = 0;
private playersInterval: NodeJS.Timeout | null = null;
@@ -101,6 +102,7 @@ export class JoinPrivateLobbyModal extends BaseModal {
.gameMode=${this.gameConfig?.gameMode ?? GameMode.FFA}
.clients=${this.players}
.lobbyCreatorClientID=${this.lobbyCreatorClientID}
.currentClientID=${this.currentClientID}
.teamCount=${this.gameConfig?.playerTeams ?? 2}
.nationCount=${this.nationCount}
.disableNations=${this.gameConfig?.disableNations ?? false}
@@ -290,6 +292,7 @@ export class JoinPrivateLobbyModal extends BaseModal {
this.hasJoined = false;
this.message = "";
this.currentLobbyId = "";
this.currentClientID = "";
this.nationCount = 0;
this.leaveLobbyOnClose = true;
@@ -418,6 +421,7 @@ export class JoinPrivateLobbyModal extends BaseModal {
this.showMessage(translateText("private_lobby.joined_waiting"));
this.message = "";
this.hasJoined = true;
this.currentClientID = generateID();
// If the modal closes as part of joining the game, do not leave the lobby
this.leaveLobbyOnClose = false;
@@ -426,7 +430,7 @@ export class JoinPrivateLobbyModal extends BaseModal {
new CustomEvent("join-lobby", {
detail: {
gameID: lobbyId,
clientID: generateID(),
clientID: this.currentClientID,
} as JoinLobbyEvent,
bubbles: true,
composed: true,
@@ -477,12 +481,13 @@ export class JoinPrivateLobbyModal extends BaseModal {
return "version_mismatch";
}
this.currentClientID = generateID();
this.dispatchEvent(
new CustomEvent("join-lobby", {
detail: {
gameID: lobbyId,
gameRecord: parsed.data,
clientID: generateID(),
clientID: this.currentClientID,
} as JoinLobbyEvent,
bubbles: true,
composed: true,
+38 -14
View File
@@ -15,7 +15,9 @@ import {
} from "../../core/game/Game";
import { getCompactMapNationCount } from "../../core/game/NationCreation";
import { assignTeamsLobbyPreview } from "../../core/game/TeamAssignment";
import { UserSettings } from "../../core/game/UserSettings";
import { ClientInfo, TeamCountConfig } from "../../core/Schemas";
import { createRandomName } from "../../core/Util";
import { translateText } from "../Utils";
export interface TeamPreviewData {
@@ -30,6 +32,7 @@ export class LobbyTeamView extends LitElement {
@state() private teamPreview: TeamPreviewData[] = [];
@state() private teamMaxSize: number = 0;
@property({ type: String }) lobbyCreatorClientID: string = "";
@property({ type: String }) currentClientID: string = "";
@property({ attribute: "team-count" }) teamCount: TeamCountConfig = 2;
@property({ type: Function }) onKickPlayer?: (clientID: string) => void;
@property({ type: Number }) nationCount: number = 0;
@@ -38,6 +41,7 @@ export class LobbyTeamView extends LitElement {
private theme: PastelTheme = new PastelTheme();
@state() private showTeamColors: boolean = false;
private userSettings: UserSettings = new UserSettings();
willUpdate(changedProperties: Map<string, any>) {
// Recompute team preview when relevant properties change
@@ -108,12 +112,14 @@ export class LobbyTeamView extends LitElement {
${repeat(
this.clients,
(c) => c.clientID ?? c.username,
(client) =>
html`<div
(client) => {
const displayName = this.displayUsername(client);
return html`<div
class="px-2 py-1 rounded-sm bg-gray-700/70 mb-1 text-xs text-white"
>
${client.username}
</div>`,
${displayName}
</div>`;
},
)}
</div>
<div class="flex-1 flex flex-col gap-3 md:gap-4 md:pr-1">
@@ -151,9 +157,10 @@ export class LobbyTeamView extends LitElement {
return html`${repeat(
this.clients,
(c) => c.clientID ?? c.username,
(client) =>
html`<span class="player-tag">
<span class="text-white">${client.username}</span>
(client) => {
const displayName = this.displayUsername(client);
return html`<span class="player-tag">
<span class="text-white">${displayName}</span>
${client.clientID === this.lobbyCreatorClientID
? html`<span class="host-badge"
>(${translateText("host_modal.host_badge")})</span
@@ -163,13 +170,14 @@ export class LobbyTeamView extends LitElement {
class="remove-player-btn"
@click=${() => this.onKickPlayer?.(client.clientID)}
aria-label=${translateText("host_modal.remove_player", {
username: client.username,
username: displayName,
})}
>
×
</button>`
: html``}
</span>`,
</span>`;
},
)} `;
}
@@ -207,11 +215,12 @@ export class LobbyTeamView extends LitElement {
: repeat(
preview.players,
(p) => p.clientID ?? p.username,
(p) =>
html` <div
(p) => {
const displayName = this.displayUsername(p);
return html` <div
class="bg-gray-700/70 px-2 py-1 rounded-sm text-xs flex items-center justify-between"
>
<span class="truncate text-white">${p.username}</span>
<span class="truncate text-white">${displayName}</span>
${p.clientID === this.lobbyCreatorClientID
? html`<span class="ml-2 text-[11px] text-green-300"
>(${translateText("host_modal.host_badge")})</span
@@ -223,14 +232,15 @@ export class LobbyTeamView extends LitElement {
aria-label=${translateText(
"host_modal.remove_player",
{
username: p.username,
username: displayName,
},
)}
>
×
</button>`
: html``}
</div>`,
</div>`;
},
)}
</div>
</div>
@@ -353,4 +363,18 @@ export class LobbyTeamView extends LitElement {
}
return getCompactMapNationCount(this.nationCount, this.isCompactMap);
}
private displayUsername(client: ClientInfo): string {
if (!this.userSettings.anonymousNames()) {
return client.username;
}
if (this.currentClientID && client.clientID === this.currentClientID) {
return client.username;
}
return (
createRandomName(client.username, PlayerType.Human) ?? client.username
);
}
}