From bf09b9c9be4768164fd46d5864e56c0d593392f1 Mon Sep 17 00:00:00 2001 From: FloPinguin <25036848+FloPinguin@users.noreply.github.com> Date: Sat, 21 Mar 2026 04:43:35 +0100 Subject: [PATCH] =?UTF-8?q?Improve=20JoinLobbyModal=20=E2=9C=A8=20(#3482)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description: Perviously, JoinLobbyModal did not show settings like "Infinite gold" or "Instant Build" or changed tribe count. Now it does. Only if the setting differs from the default. I tested a lot of scenarios, I also thought of the public game modifiers. And we show a small map image now. Public game with lots of modifiers: Screenshot 2026-03-21 011805 A private game with lots of custom settings: Screenshot 2026-03-21 011940 A private game with disabled units: Screenshot 2026-03-21 012134 Regular public FFA (No modifiers): Screenshot 2026-03-21 012228 This PR also includes a fix for UsernameInput: Screenshot 2026-03-20 222021 This PR also fixes the default private lobby difficulty in GameManager ## 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 --- resources/lang/en.json | 6 +- src/client/JoinLobbyModal.ts | 240 +++++++++++++++++++++++++++++------ src/client/UsernameInput.ts | 24 ++++ src/server/GameManager.ts | 2 +- 4 files changed, 228 insertions(+), 44 deletions(-) diff --git a/resources/lang/en.json b/resources/lang/en.json index cf3dfe2ce..fabdc614b 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -368,7 +368,10 @@ "error": "An error occurred. Please try again or contact support.", "joined_waiting": "Lobby joined! Waiting for host to start...", "version_mismatch": "This game was created with a different version. Cannot join.", - "disabled_units": "Disabled Units" + "disabled_units": "Disabled Units", + "game_length": "Game length", + "pvp_immunity": "PVP immunity duration", + "starting_gold": "Starting Gold" }, "public_lobby": { "title": "Waiting for Game Start...", @@ -406,7 +409,6 @@ "title": "Create Private Lobby", "mode": "Mode", "team_count": "Number of Teams", - "team_type": "Team Type", "options_title": "Options", "bots": "Tribes: ", "bots_disabled": "Disabled", diff --git a/src/client/JoinLobbyModal.ts b/src/client/JoinLobbyModal.ts index 4bea53d43..3d6ff86c9 100644 --- a/src/client/JoinLobbyModal.ts +++ b/src/client/JoinLobbyModal.ts @@ -2,13 +2,10 @@ import { html, TemplateResult } from "lit"; import { customElement, property, query, state } from "lit/decorators.js"; import { calculateServerTimeOffset, - getActiveModifiers, - getGameModeLabel, getMapName, getSecondsUntilServerTimestamp, getServerNow, renderDuration, - renderNumber, translateText, } from "../client/Utils"; import { EventBus } from "../core/EventBus"; @@ -22,11 +19,18 @@ import { PublicGameInfo, } from "../core/Schemas"; import { getServerConfigFromClient } from "../core/configuration/ConfigLoader"; -import { GameMode, GameType, HumansVsNations } from "../core/game/Game"; +import { + Difficulty, + GameMapSize, + GameMode, + GameType, + HumansVsNations, +} from "../core/game/Game"; import { getApiBase } from "./Api"; import { crazyGamesSDK } from "./CrazyGamesSDK"; import { JoinLobbyEvent } from "./Main"; import { terrainMapFileLoader } from "./TerrainMapFileLoader"; +import { normaliseMapKey } from "./Utils"; import { BaseModal } from "./components/BaseModal"; import "./components/CopyButton"; import "./components/LobbyConfigItem"; @@ -426,45 +430,197 @@ export class JoinLobbyModal extends BaseModal { const c = this.gameConfig; const mapName = getMapName(c.gameMap); - const modeName = getGameModeLabel(c); - const modifiers = getActiveModifiers(c.publicGameModifiers); + const normalizedMap = normaliseMapKey(c.gameMap); + const thumbnailUrl = `/maps/${encodeURIComponent(normalizedMap)}/thumbnail.webp`; + const isTeam = c.gameMode === GameMode.Team; + + let modeSubtitle: string; + if (!isTeam) { + modeSubtitle = translateText("game_mode.ffa"); + } else if (c.playerTeams === HumansVsNations) { + modeSubtitle = translateText("host_modal.teams_Humans Vs Nations"); + } else if (typeof c.playerTeams === "string") { + modeSubtitle = translateText("host_modal.teams_" + c.playerTeams); + } else if (typeof c.playerTeams === "number") { + modeSubtitle = translateText("public_lobby.teams", { + num: c.playerTeams, + }); + } else { + modeSubtitle = translateText("game_mode.ffa"); + } + + const pm = c.publicGameModifiers; + const cards: TemplateResult[] = []; + if (pm?.isCrowded) + cards.push( + html``, + ); + if ( + pm?.isHardNations || + (c.gameType === GameType.Private && c.difficulty !== Difficulty.Easy) + ) + cards.push( + html``, + ); + if (c.infiniteTroops) + cards.push( + html``, + ); + if (c.infiniteGold) + cards.push( + html``, + ); + if (c.instantBuild) + cards.push( + html``, + ); + if (c.randomSpawn) + cards.push( + html``, + ); + if (c.maxTimerValue) + cards.push( + html``, + ); + if ( + c.spawnImmunityDuration && + Math.round(c.spawnImmunityDuration / 10) !== 5 + ) { + const totalSeconds = Math.round(c.spawnImmunityDuration / 10); + const immunityValue = + totalSeconds < 60 + ? `${totalSeconds}s` + : totalSeconds % 60 > 0 + ? `${Math.floor(totalSeconds / 60)}m ${totalSeconds % 60}s` + : `${Math.floor(totalSeconds / 60)} min`; + cards.push( + html``, + ); + } + if (c.startingGold) + cards.push( + html``, + ); + if (c.goldMultiplier) + cards.push( + html``, + ); + if (c.disableAlliances) + cards.push( + html``, + ); + if ((isTeam && !c.donateGold) || (!isTeam && c.donateGold)) + cards.push( + html``, + ); + if ((isTeam && !c.donateTroops) || (!isTeam && c.donateTroops)) + cards.push( + html``, + ); + const isCompact = + c.gameMapSize === GameMapSize.Compact || c.publicGameModifiers?.isCompact; + if (isCompact) + cards.push( + html``, + ); + { + const defaultBots = isCompact ? 100 : 400; + if (c.bots !== defaultBots) + cards.push( + html``, + ); + } + { + const defaultNations = isCompact + ? Math.max(0, Math.floor(this.nationCount * 0.25)) + : this.nationCount; + if (typeof c.nations === "number" && c.nations !== defaultNations) + cards.push( + html``, + ); + } + if (c.nations === "disabled" && !(c.gameType === GameType.Public && isTeam)) + cards.push( + html``, + ); return html` -
- - - ${modifiers.map( - (m) => html` - - `, - )} - ${c.gameMode !== GameMode.FFA && - c.playerTeams && - c.playerTeams !== HumansVsNations - ? html` - - ` - : html``} +
+ ${mapName { + (e.target as HTMLImageElement).style.display = "none"; + }} + /> +
+ ${mapName} + ${modeSubtitle} +
+ ${cards.length > 0 + ? html`
+ ${cards} +
` + : html``} ${this.renderDisabledUnits()} `; } @@ -495,7 +651,9 @@ export class JoinLobbyModal extends BaseModal { }; return html` -
+
diff --git a/src/client/UsernameInput.ts b/src/client/UsernameInput.ts index cb1030705..69082ebea 100644 --- a/src/client/UsernameInput.ts +++ b/src/client/UsernameInput.ts @@ -13,6 +13,12 @@ import { } from "../core/validations/username"; import { crazyGamesSDK } from "./CrazyGamesSDK"; +interface LangSelectorLike { + currentLang?: string; + translations?: Record; + defaultTranslations?: Record; +} + const usernameKey: string = "username"; const clanTagKey: string = "clanTag"; @@ -23,6 +29,7 @@ export class UsernameInput extends LitElement { @property({ type: String }) validationError: string = ""; private _isValid: boolean = true; + private _lastValidatedLang: string | null = null; // Remove static styles since we're using Tailwind @@ -60,6 +67,23 @@ export class UsernameInput extends LitElement { }); } + protected updated(): void { + // Re-validate when translations become available or language changes, + // since initial validation may run before translations are loaded. + if (this.validationError) { + const langSelector = document.querySelector( + "lang-selector", + ); + const lang = langSelector?.currentLang; + const hasTranslations = + langSelector?.translations ?? langSelector?.defaultTranslations; + if (hasTranslations && lang && lang !== this._lastValidatedLang) { + this._lastValidatedLang = lang; + this.validateAndStore(); + } + } + } + private loadStoredUsername() { const storedUsername = localStorage.getItem(usernameKey); if (storedUsername) { diff --git a/src/server/GameManager.ts b/src/server/GameManager.ts index 11e2a87b9..ceecc8526 100644 --- a/src/server/GameManager.ts +++ b/src/server/GameManager.ts @@ -71,7 +71,7 @@ export class GameManager { gameMap: GameMapType.World, gameType: GameType.Private, gameMapSize: GameMapSize.Normal, - difficulty: Difficulty.Medium, + difficulty: Difficulty.Easy, nations: "default", infiniteGold: false, infiniteTroops: false,