@@ -1137,6 +1184,20 @@ export class HelpModal extends BaseModal {
`;
}
+ openTroubleshooting() {
+ const troubleshootingModal = document.querySelector(
+ "troubleshooting-modal",
+ ) as TroubleshootingModal;
+ if (
+ !troubleshootingModal ||
+ !(troubleshootingModal instanceof TroubleshootingModal)
+ ) {
+ console.warn("Troubleshooting modal element not found");
+ return;
+ }
+ troubleshootingModal.open();
+ }
+
protected onOpen(): void {
this.keybinds = this.getKeybinds();
}
diff --git a/src/client/HostLobbyModal.ts b/src/client/HostLobbyModal.ts
index e5bb92f11..eff76894b 100644
--- a/src/client/HostLobbyModal.ts
+++ b/src/client/HostLobbyModal.ts
@@ -12,7 +12,6 @@ import {
Quads,
Trios,
UnitType,
- mapCategories,
} from "../core/game/Game";
import {
ClientInfo,
@@ -28,7 +27,7 @@ import "./components/CopyButton";
import "./components/Difficulties";
import "./components/FluentSlider";
import "./components/LobbyPlayerView";
-import "./components/Maps";
+import "./components/map/MapPicker";
import { modalHeader } from "./components/ui/ModalHeader";
import { crazyGamesSDK } from "./CrazyGamesSDK";
import { JoinLobbyEvent } from "./Main";
@@ -38,7 +37,6 @@ import {
renderToggleInputCardInput,
} from "./utilities/RenderToggleInputCard";
import { renderUnitTypeOptions } from "./utilities/RenderUnitTypeOptions";
-import randomMap from "/images/RandomMap.webp?url";
@customElement("host-lobby-modal")
export class HostLobbyModal extends BaseModal {
@state() private selectedMap: GameMapType = GameMapType.World;
@@ -209,80 +207,14 @@ export class HostLobbyModal extends BaseModal {
${translateText("map.map")}
-
-
- ${Object.entries(mapCategories).map(
- ([categoryKey, maps]) => html`
-
-
- ${translateText(`map_categories.${categoryKey}`)}
-
-
- ${maps.map((mapValue) => {
- const mapKey = Object.entries(GameMapType).find(
- ([, v]) => v === mapValue,
- )?.[0];
- return html`
-
this.handleMapSelection(mapValue)}
- class="cursor-pointer transition-transform duration-200 active:scale-95"
- >
-
-
- `;
- })}
-
-
- `,
- )}
-
-
-
- ${translateText("map_categories.special")}
-
-
-
-
-
-
+
+ this.handleMapSelection(mapValue)}
+ .onSelectRandom=${() => this.handleSelectRandomMap()}
+ >
@@ -653,6 +585,7 @@ export class HostLobbyModal extends BaseModal {
.gameMode=${this.gameMode}
.clients=${this.clients}
.lobbyCreatorClientID=${this.lobbyCreatorClientID}
+ .currentClientID=${this.lobbyCreatorClientID}
.teamCount=${this.teamCount}
.nationCount=${this.nationCount}
.disableNations=${this.disableNations}
diff --git a/src/client/JoinPrivateLobbyModal.ts b/src/client/JoinPrivateLobbyModal.ts
index c04b8fa93..9287b1730 100644
--- a/src/client/JoinPrivateLobbyModal.ts
+++ b/src/client/JoinPrivateLobbyModal.ts
@@ -28,6 +28,7 @@ export class JoinPrivateLobbyModal extends BaseModal {
@state() private gameConfig: GameConfig | null = null;
@state() private lobbyCreatorClientID: string | null = null;
@state() private currentLobbyId: string = "";
+ @state() private currentClientID: string = "";
@state() private nationCount: number = 0;
private playersInterval: NodeJS.Timeout | null = null;
@@ -101,6 +102,7 @@ export class JoinPrivateLobbyModal extends BaseModal {
.gameMode=${this.gameConfig?.gameMode ?? GameMode.FFA}
.clients=${this.players}
.lobbyCreatorClientID=${this.lobbyCreatorClientID}
+ .currentClientID=${this.currentClientID}
.teamCount=${this.gameConfig?.playerTeams ?? 2}
.nationCount=${this.nationCount}
.disableNations=${this.gameConfig?.disableNations ?? false}
@@ -290,6 +292,7 @@ export class JoinPrivateLobbyModal extends BaseModal {
this.hasJoined = false;
this.message = "";
this.currentLobbyId = "";
+ this.currentClientID = "";
this.nationCount = 0;
this.leaveLobbyOnClose = true;
@@ -418,6 +421,7 @@ export class JoinPrivateLobbyModal extends BaseModal {
this.showMessage(translateText("private_lobby.joined_waiting"));
this.message = "";
this.hasJoined = true;
+ this.currentClientID = generateID();
// If the modal closes as part of joining the game, do not leave the lobby
this.leaveLobbyOnClose = false;
@@ -426,7 +430,7 @@ export class JoinPrivateLobbyModal extends BaseModal {
new CustomEvent("join-lobby", {
detail: {
gameID: lobbyId,
- clientID: generateID(),
+ clientID: this.currentClientID,
} as JoinLobbyEvent,
bubbles: true,
composed: true,
@@ -477,12 +481,13 @@ export class JoinPrivateLobbyModal extends BaseModal {
return "version_mismatch";
}
+ this.currentClientID = generateID();
this.dispatchEvent(
new CustomEvent("join-lobby", {
detail: {
gameID: lobbyId,
gameRecord: parsed.data,
- clientID: generateID(),
+ clientID: this.currentClientID,
} as JoinLobbyEvent,
bubbles: true,
composed: true,
diff --git a/src/client/LocalServer.ts b/src/client/LocalServer.ts
index 2514dc695..75121b38a 100644
--- a/src/client/LocalServer.ts
+++ b/src/client/LocalServer.ts
@@ -20,7 +20,13 @@ import {
import { getPersistentID } from "./Auth";
import { LobbyConfig } from "./ClientGameRunner";
import { ReplaySpeedChangeEvent } from "./InputHandler";
-import { defaultReplaySpeedMultiplier } from "./utilities/ReplaySpeedMultiplier";
+import {
+ defaultReplaySpeedMultiplier,
+ ReplaySpeedMultiplier,
+} from "./utilities/ReplaySpeedMultiplier";
+
+// build a small backlog so MAX can catch up.
+const MAX_REPLAY_BACKLOG_TURNS = 60;
export class LocalServer {
// All turns from the game record on replay.
@@ -64,9 +70,16 @@ export class LocalServer {
const turnIntervalMs =
this.lobbyConfig.serverConfig.turnIntervalMs() *
this.replaySpeedMultiplier;
+ const backlog = Math.max(0, this.turns.length - this.turnsExecuted);
+ const allowReplayBacklog =
+ this.replaySpeedMultiplier === ReplaySpeedMultiplier.fastest &&
+ this.lobbyConfig.gameRecord !== undefined;
+ const maxBacklog = allowReplayBacklog ? MAX_REPLAY_BACKLOG_TURNS : 0;
+ const canQueueNextTurn =
+ backlog === 0 || (maxBacklog > 0 && backlog < maxBacklog);
if (
- this.turnsExecuted === this.turns.length &&
+ canQueueNextTurn &&
Date.now() > this.turnStartTime + turnIntervalMs
) {
this.turnStartTime = Date.now();
diff --git a/src/client/Main.ts b/src/client/Main.ts
index 8858b2f43..b89c7a3cd 100644
--- a/src/client/Main.ts
+++ b/src/client/Main.ts
@@ -813,6 +813,7 @@ class Client {
"game-top-bar",
"help-modal",
"user-setting",
+ "troubleshooting-modal",
"territory-patterns-modal",
"language-modal",
"news-modal",
diff --git a/src/client/NewsModal.ts b/src/client/NewsModal.ts
index 4c67f10c8..fe0d622f3 100644
--- a/src/client/NewsModal.ts
+++ b/src/client/NewsModal.ts
@@ -65,7 +65,7 @@ export class NewsModal extends BaseModal {
protected onOpen(): void {
if (!this.initialized) {
this.initialized = true;
- fetch(changelog)
+ fetch(`${changelog}?v=${encodeURIComponent(version.trim())}`)
.then((response) => (response.ok ? response.text() : "Failed to load"))
.then((markdown) =>
markdown
diff --git a/src/client/PublicLobby.ts b/src/client/PublicLobby.ts
index 4c895ab8f..e7610672e 100644
--- a/src/client/PublicLobby.ts
+++ b/src/client/PublicLobby.ts
@@ -374,6 +374,9 @@ export class PublicLobby extends LitElement {
if (publicGameModifiers.isCompact) {
labels.push(translateText("public_game_modifier.compact_map"));
}
+ if (publicGameModifiers.isCrowded) {
+ labels.push(translateText("public_game_modifier.crowded"));
+ }
if (publicGameModifiers.startingGold) {
labels.push(translateText("public_game_modifier.starting_gold"));
}
diff --git a/src/client/SinglePlayerModal.ts b/src/client/SinglePlayerModal.ts
index dece359e4..e423d7596 100644
--- a/src/client/SinglePlayerModal.ts
+++ b/src/client/SinglePlayerModal.ts
@@ -13,7 +13,6 @@ import {
Quads,
Trios,
UnitType,
- mapCategories,
} from "../core/game/Game";
import { UserSettings } from "../core/game/UserSettings";
import { TeamCountConfig } from "../core/Schemas";
@@ -24,7 +23,7 @@ import "./components/baseComponents/Modal";
import { BaseModal } from "./components/BaseModal";
import "./components/Difficulties";
import "./components/FluentSlider";
-import "./components/Maps";
+import "./components/map/MapPicker";
import { modalHeader } from "./components/ui/ModalHeader";
import { fetchCosmetics } from "./Cosmetics";
import { FlagInput } from "./FlagInput";
@@ -35,7 +34,6 @@ import {
renderToggleInputCardInput,
} from "./utilities/RenderToggleInputCard";
import { renderUnitTypeOptions } from "./utilities/RenderUnitTypeOptions";
-import randomMap from "/images/RandomMap.webp?url";
@customElement("single-player-modal")
export class SinglePlayerModal extends BaseModal {
@@ -197,84 +195,15 @@ export class SinglePlayerModal extends BaseModal {
-
+ ${modalHeader({
+ titleContent: html`
`,
+ onBack: this.close,
+ ariaLabel: translateText("common.back"),
+ })}
+ ${this.loading
+ ? ""
+ : html`
+
+ ${this.section(
+ "",
+ html`${this.infoTip(
+ translateText("troubleshooting.hardware_acceleration_tip"),
+ true,
+ )}`,
+ )}
+ ${this.section(
+ translateText("troubleshooting.environment"),
+ html`
+ ${this.row(
+ translateText("troubleshooting.browser"),
+ this.diagnostics!.browser.engine,
+ )}
+ ${this.row(
+ translateText("troubleshooting.platform"),
+ this.diagnostics!.browser.platform,
+ )}
+ ${this.row(
+ translateText("troubleshooting.os"),
+ this.diagnostics!.browser.os,
+ )}
+ ${this.row(
+ translateText("troubleshooting.device_pixel_ratio"),
+ this.diagnostics!.browser.dpr,
+ )}
+ ${this.infoTip(
+ translateText("troubleshooting.chromium_tip"),
+ )}
+ `,
+ )}
+ ${this.section(
+ translateText("troubleshooting.rendering"),
+ html`
+ ${this.row(
+ translateText("troubleshooting.renderer"),
+ this.describeRenderer(this.diagnostics!.rendering),
+ )}
+ ${this.row(
+ translateText("troubleshooting.max_texture_size"),
+ this.diagnostics!.rendering.maxTextureSize ??
+ translateText("troubleshooting.unknown"),
+ )}
+ ${this.row(
+ translateText("troubleshooting.high_precision_shaders"),
+ this.diagnostics!.rendering.shaderHighp === true
+ ? translateText("troubleshooting.yes")
+ : translateText("troubleshooting.no"),
+ )}${this.row(
+ translateText("troubleshooting.gpu"),
+ !this.diagnostics!.rendering.gpu ||
+ this.diagnostics!.rendering.gpu.unavailable
+ ? translateText("troubleshooting.unavailable")
+ : `${this.diagnostics!.rendering.gpu.vendor} — ${this.diagnostics!.rendering.gpu.renderer}`,
+ )}
+ ${this.infoTip(translateText("troubleshooting.gpu_tip"))}
+ `,
+ )}
+ ${this.section(
+ translateText("troubleshooting.power"),
+ html`
+ ${this.diagnostics!.power.unavailable
+ ? this.row(
+ translateText("troubleshooting.battery"),
+ translateText("troubleshooting.unavailable"),
+ )
+ : html`
+ ${this.row(
+ translateText("troubleshooting.charging"),
+ this.diagnostics!.power.charging
+ ? translateText("troubleshooting.yes")
+ : translateText("troubleshooting.no"),
+ )}
+ ${this.row(
+ translateText("troubleshooting.battery_level"),
+ this.diagnostics!.power.level,
+ )}
+ `}
+ ${this.infoTip(
+ translateText("troubleshooting.power_saving_tip"),
+ )}
+ `,
+ )}
+
+ `}
+
+ `;
+
+ if (this.inline) {
+ return content;
+ }
+
+ return html`
+