Clan System Part 1 (#3276)

## Description:

Properly split out clantags and usernames, a clantag should not be part
of a username.

<img width="285" height="286" alt="image"
src="https://github.com/user-attachments/assets/8ac56e82-b12c-4fc0-9774-e445252a6e61"
/>

https://api.openfront.dev/game/ojkqZFb2


<img width="296" height="596" alt="image"
src="https://github.com/user-attachments/assets/85152f80-c111-4f87-b85b-8516c9c6137b"
/>


https://api.openfront.dev/game/MF32BkVc


requires;
https://github.com/openfrontio/infra/pull/264

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

w.o.n
This commit is contained in:
Ryan
2026-03-17 22:55:47 +00:00
committed by GitHub
parent 9785666b98
commit 1049b7e7dc
47 changed files with 507 additions and 531 deletions
+3
View File
@@ -56,6 +56,7 @@ export interface LobbyConfig {
serverConfig: ServerConfig;
cosmetics: PlayerCosmeticRefs;
playerName: string;
playerClanTag: string | null;
gameID: GameID;
turnstileToken: string | null;
// GameStartInfo only exists when playing a singleplayer game.
@@ -228,6 +229,7 @@ async function createClientGame(
gameMap,
clientID,
lobbyConfig.playerName,
lobbyConfig.playerClanTag,
lobbyConfig.gameStartInfo.gameID,
lobbyConfig.gameStartInfo.players,
);
@@ -301,6 +303,7 @@ export class ClientGameRunner {
{
persistentID: getPersistentID(),
username: this.lobby.playerName,
clanTag: this.lobby.playerClanTag ?? null,
clientID: this.clientID,
stats: update.allPlayersStats[this.clientID],
},
+4 -15
View File
@@ -4,7 +4,6 @@ import { GameEndInfo } from "../core/Schemas";
import { GameMapType } from "../core/game/Game";
import { fetchGameById } from "./Api";
import { terrainMapFileLoader } from "./TerrainMapFileLoader";
import { UsernameInput } from "./UsernameInput";
import { renderDuration, translateText } from "./Utils";
import {
PlayerInfo,
@@ -28,7 +27,7 @@ export class GameInfoModal extends LitElement {
@property({ type: String }) gameId: string | null = null;
@property({ type: String }) rankType = RankType.Lifetime;
@state() private username: string | null = null;
@state() private currentClientID: string | null = null;
@state() private isLoadingGame: boolean = true;
private ranking: Ranking | null = null;
@@ -152,7 +151,7 @@ export class GameInfoModal extends LitElement {
.score=${this.ranking?.score(player, this.rankType) ?? 0}
.rankType=${this.rankType}
.bestScore=${bestScore}
.currentPlayer=${this.username === player.rawUsername}
.currentPlayer=${this.currentClientID === player.id}
></player-row>
`,
)}
@@ -183,26 +182,16 @@ export class GameInfoModal extends LitElement {
}
}
public loadUserName() {
const usernameInput = document.querySelector(
"username-input",
) as UsernameInput;
if (usernameInput) {
this.username = usernameInput.getCurrentUsername();
}
}
public async loadGame(gameId: string) {
public async loadGame(gameId: string, currentClientID: string | null = null) {
try {
this.isLoadingGame = true;
this.loadUserName();
this.currentClientID = currentClientID;
const session = await fetchGameById(gameId);
if (!session) return;
this.gameInfo = session.info;
this.ranking = new Ranking(session);
this.updateRanking();
this.isLoadingGame = false;
await this.loadMapImage(session.info.config.gameMap);
} catch (err) {
console.error("Failed to load game:", err);
+5 -14
View File
@@ -17,6 +17,7 @@ import { PublicLobbySocket } from "./LobbySocket";
import { JoinLobbyEvent } from "./Main";
import { SinglePlayerModal } from "./SinglePlayerModal";
import { terrainMapFileLoader } from "./TerrainMapFileLoader";
import { UsernameInput } from "./UsernameInput";
import {
calculateServerTimeOffset,
getMapName,
@@ -48,20 +49,10 @@ export class GameModeSelector extends LitElement {
* Returns true if valid, false otherwise.
*/
private validateUsername(): boolean {
const usernameInput = document.querySelector("username-input") as any;
if (usernameInput?.isValid?.() === false) {
window.dispatchEvent(
new CustomEvent("show-message", {
detail: {
message: usernameInput.validationError,
color: "red",
duration: 3000,
},
}),
);
return false;
}
return true;
const usernameInput = document.querySelector(
"username-input",
) as UsernameInput | null;
return usernameInput ? usernameInput.validateOrShowError() : true;
}
connectedCallback() {
+1 -2
View File
@@ -15,7 +15,6 @@ import {
import {
createPartialGameRecord,
decompressGameRecord,
getClanTag,
replacer,
} from "../core/Util";
import { getPersistentID } from "./Auth";
@@ -273,10 +272,10 @@ export class LocalServer {
{
persistentID: getPersistentID(),
username: this.lobbyConfig.playerName,
clanTag: this.lobbyConfig.playerClanTag ?? null,
clientID: this.clientID!,
stats: this.allPlayersStats[this.clientID!],
cosmetics: this.lobbyConfig.gameStartInfo?.players[0].cosmetics,
clanTag: getClanTag(this.lobbyConfig.playerName) ?? undefined,
},
];
if (this.lobbyConfig.gameStartInfo === undefined) {
+6 -2
View File
@@ -732,6 +732,10 @@ class Client {
private async handleJoinLobby(event: CustomEvent<JoinLobbyEvent>) {
const lobby = event.detail;
if (this.usernameInput && !this.usernameInput.validateOrShowError()) {
return;
}
console.log(`joining lobby ${lobby.gameID}`);
if (this.gameStop !== null) {
console.log("joining lobby, stopping existing game");
@@ -753,8 +757,8 @@ class Client {
serverConfig: config,
cosmetics: await getPlayerCosmeticsRefs(),
turnstileToken: await this.getTurnstileToken(lobby),
playerName:
this.usernameInput?.getCurrentUsername() ?? genAnonUsername(),
playerName: this.usernameInput?.getUsername() ?? genAnonUsername(),
playerClanTag: this.usernameInput?.getClanTag() ?? null,
gameStartInfo: lobby.gameStartInfo ?? lobby.gameRecord?.info,
gameRecord: lobby.gameRecord,
},
+2 -4
View File
@@ -654,9 +654,6 @@ export class SinglePlayerModal extends BaseModal {
const usernameInput = document.querySelector(
"username-input",
) as UsernameInput;
if (!usernameInput) {
console.warn("Username input element not found");
}
await crazyGamesSDK.requestMidgameAd();
@@ -669,7 +666,8 @@ export class SinglePlayerModal extends BaseModal {
players: [
{
clientID,
username: usernameInput.getCurrentUsername(),
username: usernameInput.getUsername(),
clanTag: usernameInput.getClanTag() ?? null,
cosmetics: await getPlayerCosmetics(),
},
],
+1
View File
@@ -399,6 +399,7 @@ export class Transport {
gameID: this.lobbyConfig.gameID,
// Note: clientID is not sent - server assigns it based on persistentID
username: this.lobbyConfig.playerName,
clanTag: this.lobbyConfig.playerClanTag ?? null,
cosmetics: this.lobbyConfig.cosmetics,
turnstileToken: this.lobbyConfig.turnstileToken,
token: await getPlayToken(),
+59 -62
View File
@@ -2,15 +2,19 @@ import { LitElement, html } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { v4 as uuidv4 } from "uuid";
import { translateText } from "../client/Utils";
import { getClanTagOriginalCase, sanitizeClanTag } from "../core/Util";
import { sanitizeClanTag } from "../core/Util";
import {
MAX_CLAN_TAG_LENGTH,
MAX_USERNAME_LENGTH,
MIN_CLAN_TAG_LENGTH,
MIN_USERNAME_LENGTH,
validateClanTag,
validateUsername,
} from "../core/validations/username";
import { crazyGamesSDK } from "./CrazyGamesSDK";
const usernameKey: string = "username";
const clanTagKey: string = "clanTag";
@customElement("username-input")
export class UsernameInput extends LitElement {
@@ -27,46 +31,45 @@ export class UsernameInput extends LitElement {
return this;
}
public getCurrentUsername(): string {
return this.constructFullUsername();
public getUsername(): string {
return this.baseUsername.trim();
}
private constructFullUsername(): string {
if (this.clanTag.length >= 2) {
return `[${this.clanTag}] ${this.baseUsername}`;
}
return this.baseUsername;
public getClanTag(): string | null {
return this.clanTag.length >= MIN_CLAN_TAG_LENGTH &&
this.clanTag.length <= MAX_CLAN_TAG_LENGTH &&
validateClanTag(this.clanTag).isValid
? this.clanTag
: null;
}
connectedCallback() {
super.connectedCallback();
const stored = this.getUsername();
this.parseAndSetUsername(stored);
this.loadStoredUsername();
crazyGamesSDK.getUsername().then((username) => {
if (username) {
this.parseAndSetUsername(username ?? genAnonUsername());
this.requestUpdate();
this.baseUsername = username;
this.validateAndStore();
}
});
crazyGamesSDK.addAuthListener((user) => {
if (user) {
this.parseAndSetUsername(user?.username);
this.baseUsername = user.username;
this.validateAndStore();
}
this.requestUpdate();
});
}
private parseAndSetUsername(fullUsername: string) {
const tag = getClanTagOriginalCase(fullUsername);
if (tag) {
this.clanTag = tag.toUpperCase();
this.baseUsername = fullUsername.replace(`[${tag}]`, "").trim();
private loadStoredUsername() {
const storedUsername = localStorage.getItem(usernameKey);
if (storedUsername) {
this.clanTag = localStorage.getItem(clanTagKey) ?? "";
this.baseUsername = storedUsername;
this.validateAndStore();
} else {
this.clanTag = "";
this.baseUsername = fullUsername;
this.baseUsername = genAnonUsername();
this.validateAndStore();
}
this.validateAndStore();
}
render() {
@@ -77,7 +80,8 @@ export class UsernameInput extends LitElement {
.value=${this.clanTag}
@input=${this.handleClanTagChange}
placeholder="${translateText("username.tag")}"
maxlength="5"
minlength="${MIN_CLAN_TAG_LENGTH}"
maxlength="${MAX_CLAN_TAG_LENGTH}"
class="w-[6rem] text-xl font-medium tracking-wider text-center uppercase shrink-0 bg-transparent text-white placeholder-white/70 focus:placeholder-transparent border-0 border-b border-white/40 focus:outline-none focus:border-white/60"
/>
<input
@@ -85,6 +89,7 @@ export class UsernameInput extends LitElement {
.value=${this.baseUsername}
@input=${this.handleUsernameChange}
placeholder="${translateText("username.enter_username")}"
minlength="${MIN_USERNAME_LENGTH}"
maxlength="${MAX_USERNAME_LENGTH}"
class="flex-1 min-w-0 border-0 text-2xl font-medium tracking-wider text-left text-white placeholder-white/70 focus:outline-none focus:ring-0 overflow-x-auto whitespace-nowrap text-ellipsis pr-2 bg-transparent"
/>
@@ -147,59 +152,51 @@ export class UsernameInput extends LitElement {
}
private validateAndStore() {
// Prevent empty username even if clan tag is present
const trimmedBase = this.baseUsername.trim();
if (!trimmedBase || trimmedBase.length < MIN_USERNAME_LENGTH) {
const trimmedBase = this.getUsername();
const clanTagResult = validateClanTag(this.clanTag);
if (!clanTagResult.isValid) {
this._isValid = false;
this.validationError = translateText("username.too_short", {
min: MIN_USERNAME_LENGTH,
});
this.validationError = clanTagResult.error ?? "";
return;
}
// Validate clan tag if present
if (this.clanTag.length > 0 && this.clanTag.length < 2) {
this._isValid = false;
this.validationError = translateText("username.tag_too_short");
return;
}
const full = this.constructFullUsername();
const trimmedFull = full.trim();
const result = validateUsername(trimmedFull);
const result = validateUsername(trimmedBase);
this._isValid = result.isValid;
if (result.isValid) {
this.storeUsername(trimmedFull);
localStorage.setItem(usernameKey, trimmedBase);
localStorage.setItem(clanTagKey, this.getClanTag() ?? "");
this.validationError = "";
} else {
this.validationError = result.error ?? "";
}
}
private getUsername(): string {
const storedUsername = localStorage.getItem(usernameKey);
if (storedUsername) {
return storedUsername;
}
return this.generateNewUsername();
}
private storeUsername(username: string) {
if (username) {
localStorage.setItem(usernameKey, username);
}
}
private generateNewUsername(): string {
const newUsername = genAnonUsername();
this.storeUsername(newUsername);
return newUsername;
}
public isValid(): boolean {
return this._isValid;
}
public showValidationFeedback(): void {
const message =
this.validationError || translateText("username.invalid_chars");
window.dispatchEvent(
new CustomEvent("show-message", {
detail: {
message,
color: "red",
duration: 2500,
},
}),
);
}
public validateOrShowError(): boolean {
if (this.isValid()) {
return true;
}
this.showValidationFeedback();
return false;
}
}
export function genAnonUsername(): string {
+20 -13
View File
@@ -16,7 +16,7 @@ import {
import { assignTeamsLobbyPreview } from "../../core/game/TeamAssignment";
import { UserSettings } from "../../core/game/UserSettings";
import { ClientInfo, TeamCountConfig } from "../../core/Schemas";
import { createRandomName } from "../../core/Util";
import { createRandomName, formatPlayerDisplayName } from "../../core/Util";
import { getTranslatedPlayerTeamLabel, translateText } from "../Utils";
export interface TeamPreviewData {
@@ -122,7 +122,7 @@ export class LobbyTeamView extends LitElement {
this.clients,
(c) => c.clientID ?? c.username,
(client) => {
const displayName = this.displayUsername(client);
const displayName = this.getClientDisplayName(client);
return html`<div
class="px-2 py-1 rounded-sm bg-gray-700/70 mb-1 text-xs text-white"
>
@@ -167,7 +167,7 @@ export class LobbyTeamView extends LitElement {
this.clients,
(c) => c.clientID ?? c.username,
(client) => {
const displayName = this.displayUsername(client);
const displayName = this.getClientDisplayName(client);
return html`<span class="player-tag">
<span class="text-white">${displayName}</span>
${client.clientID === this.lobbyCreatorClientID
@@ -226,7 +226,7 @@ export class LobbyTeamView extends LitElement {
preview.players,
(p) => p.clientID ?? p.username,
(p) => {
const displayName = this.displayUsername(p);
const displayName = this.getClientDisplayName(p);
return html` <div
class="bg-gray-700/70 px-2 py-1 rounded-sm text-xs flex items-center justify-between"
>
@@ -318,7 +318,14 @@ export class LobbyTeamView extends LitElement {
const players = this.clients.map(
(c) =>
new PlayerInfo(c.username, PlayerType.Human, c.clientID, c.clientID),
new PlayerInfo(
c.username,
PlayerType.Human,
c.clientID,
c.clientID,
false,
c.clanTag,
),
);
const assignment = assignTeamsLobbyPreview(
players,
@@ -358,17 +365,17 @@ export class LobbyTeamView extends LitElement {
}));
}
private displayUsername(client: ClientInfo): string {
private getClientDisplayName(client: ClientInfo): string {
const full = formatPlayerDisplayName(client.username, client.clanTag);
if (!this.userSettings.anonymousNames()) {
return client.username;
return full;
}
if (this.currentClientID && client.clientID === this.currentClientID) {
return client.username;
return full;
}
return (
createRandomName(client.username, PlayerType.Human) ?? client.username
);
// Keep clan tag visible while anonymizing only the username.
const anonymizedUsername =
createRandomName(client.username, PlayerType.Human) ?? client.username;
return formatPlayerDisplayName(anonymizedUsername, client.clanTag);
}
}
@@ -26,9 +26,8 @@ export enum RankType {
export interface PlayerInfo {
id: string;
rawUsername: string;
username: string;
tag?: string;
clanTag: string | null;
killedAt?: number;
gold: bigint[];
conquests: bigint[];
@@ -77,18 +76,12 @@ export class Ranking {
for (const player of session.info.players) {
if (player === undefined || !hasPlayed(player)) continue;
const stats = player.stats!;
const match = player.username.match(/^\[(.*?)\]\s*(.*)$/);
let username = player.username;
if (player.clanTag && match) {
username = match[2];
}
const gold = (stats.gold ?? []).map((v) => BigInt(v ?? 0));
const conquests = (stats.conquests ?? []).map((v) => BigInt(v ?? 0));
players[player.clientID] = {
id: player.clientID,
rawUsername: player.username,
username,
tag: player.clanTag,
username: player.username,
clanTag: player.clanTag,
conquests,
flag: player.cosmetics?.flag ?? undefined,
killedAt: stats.killedAt !== null ? Number(stats.killedAt) : undefined,
@@ -220,7 +220,7 @@ export class PlayerRow extends LitElement {
private renderPlayerName() {
return html`
<div class="flex gap-1 items-center w-50 shrink-0">
${this.player.tag ? this.renderTag(this.player.tag) : ""}
${this.player.clanTag ? this.renderTag(this.player.clanTag) : ""}
<div
class="text-xs sm:text-sm font-bold tracking-wide text-white/80 text-ellipsis w-37.5 shrink-0 overflow-hidden whitespace-nowrap"
>
@@ -249,9 +249,7 @@ export class LeaderboardPlayerList extends LitElement {
</div>`
: ""}
<span class="font-bold text-blue-300 truncate text-base"
>${player.clanTag
? player.username.replace(/^\[.*?\]\s*/, "")
: player.username}</span
>${player.username}</span
>
</div>
</td>
@@ -434,14 +432,18 @@ export class LeaderboardPlayerList extends LitElement {
"leaderboard_modal.your_ranking",
)}</span
>
<span class="font-bold text-white text-base"
>${this.currentUserEntry.clanTag
? this.currentUserEntry.username.replace(
/^\[.*?\]\s*/,
"",
)
: this.currentUserEntry.username}</span
>
<div class="flex items-center gap-2">
${this.currentUserEntry.clanTag
? html`<div
class="px-2 py-0.5 rounded bg-blue-500/10 border border-blue-300/40 text-[10px] font-bold text-blue-100 shrink-0"
>
${this.currentUserEntry.clanTag}
</div>`
: ""}
<span class="font-bold text-white text-base"
>${this.currentUserEntry.username}</span
>
</div>
</div>
<div class="flex flex-col items-end w-20">
<div class="font-mono text-white font-bold text-lg">
+1 -1
View File
@@ -52,7 +52,7 @@ export function placeName(game: Game, player: Player): NameViewData {
),
);
const fontSize = calculateFontSize(largestRectangle, player.name());
const fontSize = calculateFontSize(largestRectangle, player.displayName());
center = new Cell(center.x, center.y - fontSize / 3);
return {
+4 -4
View File
@@ -235,7 +235,7 @@ export class AttacksDisplay extends LitElement implements Layer {
<span class="truncate ml-1"
>${(
this.game.playerBySmallID(attack.attackerID) as PlayerView
)?.name()}</span
)?.displayName()}</span
>
${attack.retreating
? `(${translateText("events_display.retreating")}...)`
@@ -282,7 +282,7 @@ export class AttacksDisplay extends LitElement implements Layer {
<span class="truncate ml-1"
>${(
this.game.playerBySmallID(attack.targetID) as PlayerView
)?.name()}</span
)?.displayName()}</span
> `,
onClick: async () => this.attackWarningOnClick(attack),
className:
@@ -346,7 +346,7 @@ export class AttacksDisplay extends LitElement implements Layer {
const ownerID = this.game.ownerID(target);
if (ownerID === 0) return "";
const player = this.game.playerBySmallID(ownerID) as PlayerView;
return player?.name() ?? "";
return player?.displayName() ?? "";
}
private renderBoatIcon(boat: UnitView) {
@@ -409,7 +409,7 @@ export class AttacksDisplay extends LitElement implements Layer {
>${renderTroops(boat.troops())}</span
>
<span class="truncate text-xs ml-1"
>${boat.owner()?.name()}</span
>${boat.owner()?.displayName()}</span
>`,
onClick: () => this.eventBus.emit(new GoToUnitEvent(boat)),
className:
+6 -5
View File
@@ -147,7 +147,7 @@ export class ChatModal extends LitElement {
.toHex()};"
@click=${() => this.selectPlayer(player)}
>
${player.name()}
${player.displayName()}
</button>
`,
)}
@@ -216,7 +216,8 @@ export class ChatModal extends LitElement {
private selectPlayer(player: PlayerView) {
if (this.previewText) {
this.previewText =
this.selectedPhraseTemplate?.replace("[P1]", player.name()) ?? null;
this.selectedPhraseTemplate?.replace("[P1]", player.displayName()) ??
null;
this.selectedPlayer = player;
this.requiresPlayerSelection = false;
this.requestUpdate();
@@ -255,13 +256,13 @@ export class ChatModal extends LitElement {
private getSortedFilteredPlayers(): PlayerView[] {
const sorted = [...this.players].sort((a, b) =>
a.name().localeCompare(b.name()),
a.displayName().localeCompare(b.displayName()),
);
const filtered = sorted.filter((p) =>
p.name().toLowerCase().includes(this.playerSearchQuery),
p.displayName().toLowerCase().includes(this.playerSearchQuery),
);
const others = sorted.filter(
(p) => !p.name().toLowerCase().includes(this.playerSearchQuery),
(p) => !p.displayName().toLowerCase().includes(this.playerSearchQuery),
);
return [...filtered, ...others];
}
+9 -9
View File
@@ -283,7 +283,7 @@ export class EventsDisplay extends LitElement implements Layer {
this.addEvent({
description: translateText("events_display.about_to_expire", {
name: other.name(),
name: other.displayName(),
}),
type: MessageType.RENEW_ALLIANCE,
duration: this.game.config().allianceExtensionPromptOffset() - 3 * 10, // 3 second buffer
@@ -296,7 +296,7 @@ export class EventsDisplay extends LitElement implements Layer {
},
{
text: translateText("events_display.renew_alliance", {
name: other.name(),
name: other.displayName(),
}),
className: "btn",
action: () =>
@@ -460,7 +460,7 @@ export class EventsDisplay extends LitElement implements Layer {
this.addEvent({
description: translateText("events_display.request_alliance", {
name: requestor.name(),
name: requestor.displayName(),
}),
buttons: [
{
@@ -525,7 +525,7 @@ export class EventsDisplay extends LitElement implements Layer {
) as PlayerView;
this.addEvent({
description: translateText("events_display.alliance_request_status", {
name: recipient.name(),
name: recipient.displayName(),
status: update.accepted
? translateText("events_display.alliance_accepted")
: translateText("events_display.alliance_rejected"),
@@ -569,7 +569,7 @@ export class EventsDisplay extends LitElement implements Layer {
this.addEvent({
description: translateText("events_display.betrayal_description", {
name: betrayed.name(),
name: betrayed.displayName(),
malusPercent: malusPercent,
durationText: durationText,
}),
@@ -589,7 +589,7 @@ export class EventsDisplay extends LitElement implements Layer {
];
this.addEvent({
description: translateText("events_display.betrayed_you", {
name: traitor.name(),
name: traitor.displayName(),
}),
type: MessageType.ALLIANCE_BROKEN,
highlight: true,
@@ -616,7 +616,7 @@ export class EventsDisplay extends LitElement implements Layer {
this.addEvent({
description: translateText("events_display.alliance_expired", {
name: other.name(),
name: other.displayName(),
}),
type: MessageType.ALLIANCE_EXPIRED,
highlight: true,
@@ -641,8 +641,8 @@ export class EventsDisplay extends LitElement implements Layer {
this.addEvent({
description: translateText("events_display.attack_request", {
name: other.name(),
target: target.name(),
name: other.displayName(),
target: target.displayName(),
}),
type: MessageType.ATTACK_REQUEST,
highlight: true,
+2 -2
View File
@@ -239,7 +239,7 @@ export class NameLayer implements Layer {
const nameSpan = document.createElement("span");
nameSpan.className = "player-name-span";
nameSpan.innerHTML = player.name();
nameSpan.textContent = player.displayName();
nameDiv.appendChild(nameSpan);
element.appendChild(nameDiv);
@@ -338,7 +338,7 @@ export class NameLayer implements Layer {
nameDiv.style.color = render.fontColor;
const span = nameDiv.querySelector(".player-name-span");
if (span) {
span.innerHTML = render.player.name();
span.textContent = render.player.displayName();
}
if (flagDiv) {
flagDiv.style.height = `${render.fontSize}px`;
@@ -380,7 +380,7 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
src=${"/flags/" + player.cosmetics.flag! + ".svg"}
/>`
: html``}
<span>${player.name()}</span>
<span>${player.displayName()}</span>
${playerTeam !== "" && player.type() !== PlayerType.Bot
? html`<div class="flex flex-col leading-tight">
<span class="text-gray-400 text-xs font-normal"
@@ -488,7 +488,7 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
return html`
<div class="p-2">
<div class="font-bold mb-1 ${isAlly ? "text-green-500" : "text-white"}">
${unit.owner().name()}
${unit.owner().displayName()}
</div>
<div class="mt-1">
<div class="text-sm opacity-80">${unit.type()}</div>
@@ -65,7 +65,7 @@ export class PlayerModerationModal extends LitElement {
if (!targetClientID || targetClientID.length === 0) return;
const confirmed = confirm(
translateText("player_panel.kick_confirm", { name: other.name() }),
translateText("player_panel.kick_confirm", { name: other.displayName() }),
);
if (!confirmed) return;
@@ -142,9 +142,9 @@ export class PlayerModerationModal extends LitElement {
>
<div
class="text-sm font-semibold text-zinc-100 truncate"
title=${other.name()}
title=${other.displayName()}
>
${other.name()}
${other.displayName()}
</div>
</div>
+5 -5
View File
@@ -505,9 +505,9 @@ export class PlayerPanel extends LitElement implements Layer {
<div class="flex-1 min-w-0">
<h2
class="text-xl font-bold tracking-[-0.01em] text-zinc-50 truncate"
title=${other.name()}
title=${other.displayName()}
>
${other.name()}
${other.displayName()}
</h2>
</div>
${chip
@@ -626,7 +626,7 @@ export class PlayerPanel extends LitElement implements Layer {
const nameCollator = new Intl.Collator(undefined, { sensitivity: "base" });
const alliesSorted = [...allies].sort((a, b) =>
nameCollator.compare(a.name(), b.name()),
nameCollator.compare(a.displayName(), b.displayName()),
);
return html`
@@ -669,9 +669,9 @@ export class PlayerPanel extends LitElement implements Layer {
rounded-md border border-white/10 bg-white/5
px-2.5 py-1 text-[14px] text-zinc-100
hover:bg-white/8 active:scale-[0.99] transition"
title=${p.name()}
title=${p.displayName()}
>
<span class="truncate">${p.name()}</span>
<span class="truncate">${p.displayName()}</span>
</li>`,
)}
</ul>
+1 -1
View File
@@ -334,7 +334,7 @@ export class WinModal extends LitElement implements Layer {
crazyGamesSDK.happytime();
} else {
this._title = translateText("win_modal.other_won", {
player: winner.name(),
player: winner.displayName(),
});
this.isWin = false;
}