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``}
+
+

{
+ (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,