Improve JoinLobbyModal (#3482)

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

<img width="780" height="758" alt="Screenshot 2026-03-21 011805"
src="https://github.com/user-attachments/assets/9d3fcaa9-3a50-42b2-a351-ac737ef18230"
/>

A private game with lots of custom settings:

<img width="776" height="530" alt="Screenshot 2026-03-21 011940"
src="https://github.com/user-attachments/assets/8f9a3809-844d-4f24-8f92-46c4ce480f8c"
/>

A private game with disabled units:

<img width="786" height="562" alt="Screenshot 2026-03-21 012134"
src="https://github.com/user-attachments/assets/61058329-1d86-4667-a945-7819b89cbf41"
/>

Regular public FFA (No modifiers):

<img width="780" height="372" alt="Screenshot 2026-03-21 012228"
src="https://github.com/user-attachments/assets/abdc42f0-8f2c-40c1-8719-76c648a12bae"
/>

This PR also includes a fix for UsernameInput:

<img width="910" height="647" alt="Screenshot 2026-03-20 222021"
src="https://github.com/user-attachments/assets/e1922395-9dfc-4b32-b987-e2dbff9af917"
/>

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
This commit is contained in:
FloPinguin
2026-03-21 04:43:35 +01:00
committed by GitHub
parent 13df5cf324
commit bf09b9c9be
4 changed files with 228 additions and 44 deletions
+199 -41
View File
@@ -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`<lobby-config-item
.label=${translateText("host_modal.crowded")}
.value=${translateText("common.enabled")}
></lobby-config-item>`,
);
if (
pm?.isHardNations ||
(c.gameType === GameType.Private && c.difficulty !== Difficulty.Easy)
)
cards.push(
html`<lobby-config-item
.label=${translateText("difficulty.difficulty")}
.value=${translateText(`difficulty.${c.difficulty.toLowerCase()}`)}
></lobby-config-item>`,
);
if (c.infiniteTroops)
cards.push(
html`<lobby-config-item
.label=${translateText("host_modal.infinite_troops")}
.value=${translateText("common.enabled")}
></lobby-config-item>`,
);
if (c.infiniteGold)
cards.push(
html`<lobby-config-item
.label=${translateText("host_modal.infinite_gold")}
.value=${translateText("common.enabled")}
></lobby-config-item>`,
);
if (c.instantBuild)
cards.push(
html`<lobby-config-item
.label=${translateText("host_modal.instant_build")}
.value=${translateText("common.enabled")}
></lobby-config-item>`,
);
if (c.randomSpawn)
cards.push(
html`<lobby-config-item
.label=${translateText("host_modal.random_spawn")}
.value=${translateText("common.enabled")}
></lobby-config-item>`,
);
if (c.maxTimerValue)
cards.push(
html`<lobby-config-item
.label=${translateText("private_lobby.game_length")}
.value=${`${c.maxTimerValue} min`}
></lobby-config-item>`,
);
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`<lobby-config-item
.label=${translateText("private_lobby.pvp_immunity")}
.value=${immunityValue}
></lobby-config-item>`,
);
}
if (c.startingGold)
cards.push(
html`<lobby-config-item
.label=${translateText("private_lobby.starting_gold")}
.value=${`${parseFloat((c.startingGold / 1_000_000).toPrecision(12))}M`}
></lobby-config-item>`,
);
if (c.goldMultiplier)
cards.push(
html`<lobby-config-item
.label=${translateText("host_modal.gold_multiplier")}
.value=${`x${c.goldMultiplier}`}
></lobby-config-item>`,
);
if (c.disableAlliances)
cards.push(
html`<lobby-config-item
.label=${translateText(
"public_game_modifier.disable_alliances_label",
)}
.value=${translateText("common.disabled")}
></lobby-config-item>`,
);
if ((isTeam && !c.donateGold) || (!isTeam && c.donateGold))
cards.push(
html`<lobby-config-item
.label=${translateText("host_modal.donate_gold")}
.value=${translateText(
c.donateGold ? "common.enabled" : "common.disabled",
)}
></lobby-config-item>`,
);
if ((isTeam && !c.donateTroops) || (!isTeam && c.donateTroops))
cards.push(
html`<lobby-config-item
.label=${translateText("host_modal.donate_troops")}
.value=${translateText(
c.donateTroops ? "common.enabled" : "common.disabled",
)}
></lobby-config-item>`,
);
const isCompact =
c.gameMapSize === GameMapSize.Compact || c.publicGameModifiers?.isCompact;
if (isCompact)
cards.push(
html`<lobby-config-item
.label=${translateText("host_modal.compact_map")}
.value=${translateText("common.enabled")}
></lobby-config-item>`,
);
{
const defaultBots = isCompact ? 100 : 400;
if (c.bots !== defaultBots)
cards.push(
html`<lobby-config-item
.label=${translateText("host_modal.bots")}
.value=${String(c.bots)}
></lobby-config-item>`,
);
}
{
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`<lobby-config-item
.label=${translateText("host_modal.nations")}
.value=${String(c.nations)}
></lobby-config-item>`,
);
}
if (c.nations === "disabled" && !(c.gameType === GameType.Public && isTeam))
cards.push(
html`<lobby-config-item
.label=${translateText("host_modal.nations")}
.value=${translateText("common.disabled")}
></lobby-config-item>`,
);
return html`
<div class="grid grid-cols-2 sm:grid-cols-3 gap-2">
<lobby-config-item
.label=${translateText("map.map")}
.value=${mapName}
></lobby-config-item>
<lobby-config-item
.label=${translateText("host_modal.mode")}
.value=${modeName}
></lobby-config-item>
${modifiers.map(
(m) => html`
<lobby-config-item
.label=${translateText(m.labelKey)}
.value=${m.formattedValue ??
(m.value !== undefined
? renderNumber(m.value)
: translateText("common.enabled"))}
></lobby-config-item>
`,
)}
${c.gameMode !== GameMode.FFA &&
c.playerTeams &&
c.playerTeams !== HumansVsNations
? html`
<lobby-config-item
.label=${typeof c.playerTeams === "string"
? translateText("host_modal.team_type")
: translateText("host_modal.team_count")}
.value=${typeof c.playerTeams === "string"
? translateText("host_modal.teams_" + c.playerTeams)
: c.playerTeams.toString()}
></lobby-config-item>
`
: html``}
<div class="flex items-center gap-3 mb-6">
<img
src=${thumbnailUrl}
alt=${mapName ?? c.gameMap}
class="w-20 h-20 rounded-lg object-cover border border-white/10 shrink-0"
@error=${(e: Event) => {
(e.target as HTMLImageElement).style.display = "none";
}}
/>
<div class="flex flex-col gap-1">
<span class="text-lg font-bold text-white">${mapName}</span>
<span class="text-sm text-white/60">${modeSubtitle}</span>
</div>
</div>
${cards.length > 0
? html`<div class="grid grid-cols-2 sm:grid-cols-3 gap-2 mb-6">
${cards}
</div>`
: html``}
${this.renderDisabledUnits()}
`;
}
@@ -495,7 +651,9 @@ export class JoinLobbyModal extends BaseModal {
};
return html`
<div class="mt-4 p-3 bg-red-500/10 border border-red-500/20 rounded-lg">
<div
class="mt-4 mb-6 p-3 bg-red-500/10 border border-red-500/20 rounded-lg"
>
<div
class="text-xs font-bold text-red-400 uppercase tracking-widest mb-2"
>