diff --git a/index.html b/index.html index 9a6bbd5738..875dcefbf7 100644 --- a/index.html +++ b/index.html @@ -125,7 +125,7 @@
diff --git a/resources/lang/en.json b/resources/lang/en.json index 516d9645ab..e80e2f6daa 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -362,13 +362,10 @@ }, "public_lobby": { "title": "Waiting for Game Start...", - "teams_Duos": "{team_count} teams of 2 (Duos)", - "teams_Trios": "{team_count} teams of 3 (Trios)", - "teams_Quads": "{team_count} teams of 4 (Quads)", "waiting_for_players": "Waiting for players", "connecting": "Connecting to lobby...", "starting_in": "Starting in {time}", - "starting_game": "Starting game…", + "starting_game": "Starting…", "teams_hvn": "Humans vs Nations", "teams_hvn_detailed": "{num} Humans vs {num} Nations", "teams": "{num} teams", @@ -464,7 +461,6 @@ "teams": "Teams" }, "mode_selector": { - "special_title": "Special Mix", "teams_title": "Teams", "teams_count": "{teamCount} teams", "teams_of": "{teamCount} teams of {playersPerTeam}", diff --git a/src/client/GameInfoModal.ts b/src/client/GameInfoModal.ts index 787f5820d4..9dc8bf395e 100644 --- a/src/client/GameInfoModal.ts +++ b/src/client/GameInfoModal.ts @@ -1,7 +1,7 @@ import { html, LitElement } from "lit"; import { customElement, property, query, state } from "lit/decorators.js"; import { GameEndInfo } from "../core/Schemas"; -import { GameMapType, hasUnusualThumbnailSize } from "../core/game/Game"; +import { GameMapType } from "../core/game/Game"; import { fetchGameById } from "./Api"; import { terrainMapFileLoader } from "./TerrainMapFileLoader"; import { UsernameInput } from "./UsernameInput"; @@ -107,7 +107,6 @@ export class GameInfoModal extends LitElement { if (!info) { return html``; } - const isUnusualThumbnailSize = hasUnusualThumbnailSize(info.config.gameMap); return html`
` : html`
= new Map(); private serverTimeOffset: number = 0; + private defaultLobbyTime: number = 0; private lobbySocket = new PublicLobbySocket((lobbies) => this.handleLobbiesUpdate(lobbies), @@ -61,6 +64,9 @@ export class GameModeSelector extends LitElement { connectedCallback() { super.connectedCallback(); this.lobbySocket.start(); + getServerConfigFromClient().then((config) => { + this.defaultLobbyTime = config.gameCreationRate() / 1000; + }); } disconnectedCallback() { @@ -81,6 +87,30 @@ export class GameModeSelector extends LitElement { }), ); this.requestUpdate(); + + const allGames = Object.values(lobbies.games ?? {}).flat(); + for (const game of allGames) { + const mapType = game.gameConfig?.gameMap as GameMapType; + if (mapType && !this.mapAspectRatios.has(mapType)) { + // New Map reference triggers Lit reactivity; placeholder ratio 1 lets + // has() guard against duplicate in-flight fetches. + this.mapAspectRatios = new Map(this.mapAspectRatios).set(mapType, 1); + terrainMapFileLoader + .getMapData(mapType) + .manifest() + .then((m: any) => { + if (m?.map?.width && m?.map?.height) { + this.mapAspectRatios = new Map(this.mapAspectRatios).set( + mapType, + m.map.width / m.map.height, + ); + } + }) + .catch((e) => + console.error(`Failed to load manifest for ${mapType}`, e), + ); + } + } } render() { @@ -89,76 +119,112 @@ export class GameModeSelector extends LitElement { const special = this.lobbies?.games?.["special"]?.[0]; return html` -
-
- ${this.renderSoloButton()} +
+ +
+ ${this.renderSmallActionCard( + translateText("main.solo"), + this.openSinglePlayerModal, + "bg-sky-600", + )} +
+ +
+ ${this.renderSmallActionCard( + translateText("main.create"), + this.openHostLobby, + "bg-[color-mix(in_oklab,var(--frenchBlue)_75%,black)]", + )} + ${this.renderSmallActionCard( + translateText("mode_selector.ranked_title"), + this.openRankedMenu, + "bg-[color-mix(in_oklab,var(--frenchBlue)_75%,black)]", + )} + ${this.renderSmallActionCard( + translateText("main.join"), + this.openJoinLobby, + "bg-[color-mix(in_oklab,var(--frenchBlue)_75%,black)]", + )}
+
- ${ffa - ? html`
- ${this.renderLobbyCard(ffa, this.getLobbyTitle(ffa))} + + ${special + ? html`` - : nothing} - ${teams - ? this.renderLobbyCard(teams, this.getLobbyTitle(teams)) - : nothing} - ${special ? this.renderSpecialLobbyCard(special) : nothing} -
- ${this.renderQuickActionsSection()} -
- `; - } + : ffa + ? html`` + : nothing} - private renderSpecialLobbyCard(lobby: PublicGameInfo) { - const subtitle = this.getLobbyTitle(lobby); - const mainTitle = translateText("mode_selector.special_title"); - const titleContent = subtitle - ? html` - ${mainTitle} - - ${subtitle} - - ` - : mainTitle; - return this.renderLobbyCard(lobby, titleContent); - } + + - private renderSoloButton() { - const title = translateText("main.solo"); - return html` - - `; - } + +
+ ${special ? this.renderSpecialLobbyCard(special) : nothing} +
+
+ ${ffa + ? this.renderLobbyCard(ffa, this.getLobbyTitle(ffa)) + : nothing} +
+
+ ${teams + ? this.renderLobbyCard(teams, this.getLobbyTitle(teams)) + : nothing} +
+
- private renderQuickActionsSection() { - return html` -
- -
+ + + +
`; } + private renderSpecialLobbyCard(lobby: PublicGameInfo) { + return this.renderLobbyCard(lobby, this.getLobbyTitle(lobby)); + } + private openRankedMenu = () => { if (!this.validateUsername()) return; window.showPage?.("page-ranked"); @@ -181,11 +247,15 @@ export class GameModeSelector extends LitElement { (document.querySelector("join-lobby-modal") as JoinLobbyModal)?.open(); }; - private renderSmallActionCard(title: string, onClick: () => void) { + private renderSmallActionCard( + title: string, + onClick: () => void, + bgClass: string = CARD_BG, + ) { return html` @@ -198,6 +268,11 @@ export class GameModeSelector extends LitElement { ) { const mapType = lobby.gameConfig!.gameMap as GameMapType; const mapImageSrc = terrainMapFileLoader.getMapData(mapType).webpPath; + const aspectRatio = this.mapAspectRatios.get(mapType); + // Use object-contain for extreme aspect ratios (e.g. Amazon River ~20:1) so + // the full map is visible instead of being cropped by object-cover. + const useContain = + aspectRatio !== undefined && (aspectRatio > 4 || aspectRatio < 0.25); const timeRemaining = lobby.startsAt ? Math.max( 0, @@ -208,12 +283,14 @@ export class GameModeSelector extends LitElement { : undefined; let timeDisplay: string = ""; + let timeDisplayUppercase = false; if (timeRemaining === undefined) { - timeDisplay = "-s"; + timeDisplay = renderDuration(this.defaultLobbyTime); } else if (timeRemaining > 0) { timeDisplay = renderDuration(timeRemaining); } else { timeDisplay = translateText("public_lobby.starting_game"); + timeDisplayUppercase = true; } const mapName = getMapName(lobby.gameConfig?.gameMap); @@ -229,59 +306,78 @@ export class GameModeSelector extends LitElement { return html` `; @@ -334,31 +430,19 @@ export class GameModeSelector extends LitElement { const teamCount = totalPlayers ? Math.floor(totalPlayers / 2) : undefined; - return teamCount - ? translateText("public_lobby.teams_Duos", { - team_count: String(teamCount), - }) - : formatTeamsOf(undefined, 2); + return formatTeamsOf(teamCount, 2); } case Trios: { const teamCount = totalPlayers ? Math.floor(totalPlayers / 3) : undefined; - return teamCount - ? translateText("public_lobby.teams_Trios", { - team_count: String(teamCount), - }) - : formatTeamsOf(undefined, 3); + return formatTeamsOf(teamCount, 3); } case Quads: { const teamCount = totalPlayers ? Math.floor(totalPlayers / 4) : undefined; - return teamCount - ? translateText("public_lobby.teams_Quads", { - team_count: String(teamCount), - }) - : formatTeamsOf(undefined, 4); + return formatTeamsOf(teamCount, 4); } case HumansVsNations: { const humanSlots = config.maxPlayers ?? lobby.numClients; diff --git a/src/client/GutterAds.ts b/src/client/GutterAds.ts index 4495a802a3..fcb14e2ba6 100644 --- a/src/client/GutterAds.ts +++ b/src/client/GutterAds.ts @@ -120,8 +120,8 @@ export class GutterAds extends LitElement { return html`