${modalHeader({
title: translateText("private_lobby.title"),
onBack: () => this.closeAndLeave(),
@@ -280,15 +276,12 @@ export class JoinLobbyModal extends BaseModal {
`;
}
- public open(lobbyId: string = "", isPublic: boolean = false) {
+ public open(lobbyId: string = "", lobbyInfo?: GameInfo) {
super.open();
if (lobbyId) {
- this.startTrackingLobby(lobbyId);
- // If opened with lobbyInfo (public lobby case), auto-join the lobby
- if (isPublic) {
- this.joinPublicLobby(lobbyId);
- } else {
- // If opened with lobbyId but no lobbyInfo (URL join case), check if active and join
+ this.startTrackingLobby(lobbyId, lobbyInfo);
+ // If opened with lobbyId but no lobbyInfo (URL join case), auto-join the lobby
+ if (!lobbyInfo) {
this.handleUrlJoin(lobbyId);
}
}
@@ -326,20 +319,6 @@ export class JoinLobbyModal extends BaseModal {
}
}
- private joinPublicLobby(lobbyId: string) {
- // Dispatch join-lobby event to actually connect to the lobby
- this.dispatchEvent(
- new CustomEvent("join-lobby", {
- detail: {
- gameID: lobbyId,
- source: "public",
- } as JoinLobbyEvent,
- bubbles: true,
- composed: true,
- }),
- );
- }
-
private startTrackingLobby(lobbyId: string, lobbyInfo?: GameInfo) {
this.currentLobbyId = lobbyId;
// clientID will be assigned by server via lobby_info message
diff --git a/src/client/LangSelector.ts b/src/client/LangSelector.ts
index 98519f9b55..b1af603a12 100644
--- a/src/client/LangSelector.ts
+++ b/src/client/LangSelector.ts
@@ -218,7 +218,6 @@ export class LangSelector extends LitElement {
"help-modal",
"settings-modal",
"username-input",
- "public-lobby",
"user-setting",
"o-modal",
"o-button",
@@ -233,6 +232,7 @@ export class LangSelector extends LitElement {
"flag-input",
"matchmaking-button",
"token-login",
+ "game-mode-selector",
];
document.title = this.translateText("main.title") ?? document.title;
diff --git a/src/client/LanguageModal.ts b/src/client/LanguageModal.ts
index 6d90c1d3e2..7e60236482 100644
--- a/src/client/LanguageModal.ts
+++ b/src/client/LanguageModal.ts
@@ -31,12 +31,12 @@ export class LanguageModal extends BaseModal {
render() {
const content = html`
${modalHeader({
title: translateText("select_lang.title"),
- onBack: this.close,
+ onBack: () => this.close(),
ariaLabel: translateText("common.back"),
})}
@@ -70,7 +70,7 @@ export class LanguageModal extends BaseModal {
>
diff --git a/src/client/LeaderboardModal.ts b/src/client/LeaderboardModal.ts
index 9c9f40a537..d0de01e11e 100644
--- a/src/client/LeaderboardModal.ts
+++ b/src/client/LeaderboardModal.ts
@@ -82,11 +82,7 @@ export class LeaderboardModal extends BaseModal {
>`;
const content = html`
-
+
${modalHeader({
titleContent: html`
@@ -99,7 +95,7 @@ export class LeaderboardModal extends BaseModal {
${this.activeTab === "players" ? refreshTime : ""}
`,
- onBack: this.close,
+ onBack: () => this.close(),
ariaLabel: translateText("common.close"),
})}
diff --git a/src/client/Main.ts b/src/client/Main.ts
index 564fd4fd5e..0826f3dcb7 100644
--- a/src/client/Main.ts
+++ b/src/client/Main.ts
@@ -1,7 +1,12 @@
import version from "resources/version.txt?raw";
import { UserMeResponse } from "../core/ApiSchemas";
import { EventBus } from "../core/EventBus";
-import { GAME_ID_REGEX, GameRecord, GameStartInfo } from "../core/Schemas";
+import {
+ GAME_ID_REGEX,
+ GameInfo,
+ GameRecord,
+ GameStartInfo,
+} from "../core/Schemas";
import { GameEnv } from "../core/configuration/Config";
import { getServerConfigFromClient } from "../core/configuration/ConfigLoader";
import { GameType } from "../core/game/Game";
@@ -32,9 +37,7 @@ import { MatchmakingModal } from "./Matchmaking";
import { initNavigation } from "./Navigation";
import "./NewsModal";
import "./PatternInput";
-import "./PublicLobby";
-import { PublicLobby, ShowPublicLobbyModalEvent } from "./PublicLobby";
-import { SinglePlayerModal } from "./SinglePlayerModal";
+import "./SinglePlayerModal";
import { TerritoryPatternsModal } from "./TerritoryPatternsModal";
import { TokenLoginModal } from "./TokenLoginModal";
import {
@@ -52,9 +55,11 @@ import {
} from "./Utils";
import "./components/DesktopNavBar";
import "./components/Footer";
+import { GameModeSelector } from "./components/GameModeSelector";
import "./components/MainLayout";
import "./components/MobileNavBar";
import "./components/PlayPage";
+import "./components/RankedModal";
import "./components/baseComponents/Button";
import "./components/baseComponents/Modal";
import "./styles.css";
@@ -203,9 +208,9 @@ declare global {
// Extend the global interfaces to include your custom events
interface DocumentEventMap {
"join-lobby": CustomEvent
;
- "show-public-lobby-modal": CustomEvent;
"kick-player": CustomEvent;
"join-changed": CustomEvent;
+ "open-matchmaking": CustomEvent;
}
}
@@ -217,6 +222,7 @@ export interface JoinLobbyEvent {
// GameRecord exists when replaying an archived game.
gameRecord?: GameRecord;
source?: "public" | "private" | "host" | "matchmaking" | "singleplayer";
+ publicLobbyInfo?: GameInfo;
}
class Client {
@@ -230,21 +236,18 @@ class Client {
private hostModal: HostPrivateLobbyModal;
private joinModal: JoinLobbyModal;
- private publicLobby: PublicLobby;
+ private gameModeSelector: GameModeSelector | null;
private userSettings: UserSettings = new UserSettings();
private patternsModal: TerritoryPatternsModal;
private tokenLoginModal: TokenLoginModal;
private matchmakingModal: MatchmakingModal;
private gutterAds: GutterAds;
-
private turnstileTokenPromise: Promise<{
token: string;
createdAt: number;
}> | null = null;
- constructor() {}
-
async initialize(): Promise {
crazyGamesSDK.maybeInit();
// Prefetch turnstile token so it is available when
@@ -287,8 +290,13 @@ class Client {
console.warn("Username input element not found");
}
- this.publicLobby = document.querySelector("public-lobby") as PublicLobby;
-
+ const gameModeSelector = document.querySelector("game-mode-selector");
+ if (gameModeSelector instanceof GameModeSelector) {
+ this.gameModeSelector = gameModeSelector;
+ } else {
+ this.gameModeSelector = null;
+ console.warn("Game mode selector element not found");
+ }
window.addEventListener("beforeunload", async () => {
console.log("Browser is closing");
if (this.gameStop !== null) {
@@ -303,41 +311,16 @@ class Client {
this.gutterAds = gutterAds;
document.addEventListener("join-lobby", this.handleJoinLobby.bind(this));
- document.addEventListener(
- "show-public-lobby-modal",
- this.handleShowPublicLobbyModal.bind(this),
- );
document.addEventListener("leave-lobby", this.handleLeaveLobby.bind(this));
document.addEventListener("kick-player", this.handleKickPlayer.bind(this));
document.addEventListener(
"update-game-config",
this.handleUpdateGameConfig.bind(this),
);
-
- const spModal = document.querySelector(
- "single-player-modal",
- ) as SinglePlayerModal;
- if (!spModal || !(spModal instanceof SinglePlayerModal)) {
- console.warn("Singleplayer modal element not found");
- }
-
- const singlePlayer = document.getElementById("single-player");
- if (singlePlayer === null) throw new Error("Missing single-player");
- singlePlayer.addEventListener("click", () => {
- if (this.usernameInput?.isValid()) {
- window.showPage?.("page-single-player");
- } else {
- window.dispatchEvent(
- new CustomEvent("show-message", {
- detail: {
- message: this.usernameInput?.validationError,
- color: "red",
- duration: 3000,
- },
- }),
- );
- }
- });
+ document.addEventListener(
+ "open-matchmaking",
+ this.handleOpenMatchmaking.bind(this),
+ );
const hlpModal = document.querySelector("help-modal") as HelpModal;
if (!hlpModal || !(hlpModal instanceof HelpModal)) {
@@ -506,23 +489,6 @@ class Client {
} else {
this.hostModal.eventBus = this.eventBus;
}
- const hostLobbyButton = document.getElementById("host-lobby-button");
- if (hostLobbyButton === null) throw new Error("Missing host-lobby-button");
- hostLobbyButton.addEventListener("click", () => {
- if (this.usernameInput?.isValid()) {
- window.showPage?.("page-host-lobby");
- } else {
- window.dispatchEvent(
- new CustomEvent("show-message", {
- detail: {
- message: this.usernameInput?.validationError,
- color: "red",
- duration: 3000,
- },
- }),
- );
- }
- });
this.joinModal = document.querySelector(
"join-lobby-modal",
@@ -532,26 +498,6 @@ class Client {
} else {
this.joinModal.eventBus = this.eventBus;
}
- const joinPrivateLobbyButton = document.getElementById(
- "join-private-lobby-button",
- );
- if (joinPrivateLobbyButton === null)
- throw new Error("Missing join-private-lobby-button");
- joinPrivateLobbyButton.addEventListener("click", () => {
- if (this.usernameInput?.isValid()) {
- window.showPage?.("page-join-lobby");
- } else {
- window.dispatchEvent(
- new CustomEvent("show-message", {
- detail: {
- message: this.usernameInput?.validationError,
- color: "red",
- duration: 3000,
- },
- }),
- );
- }
- });
if (this.userSettings.darkMode()) {
document.documentElement.classList.add("dark");
@@ -792,15 +738,18 @@ class Client {
this.gameStop(true);
document.body.classList.remove("in-game");
}
- const config = await getServerConfigFromClient();
+ if (lobby.source === "public") {
+ this.joinModal?.open(lobby.gameID, lobby.publicLobbyInfo);
+ }
+ const [config, cosmetics] = await Promise.all([
+ getServerConfigFromClient(),
+ fetchCosmetics(),
+ ]);
// Only update URL immediately for private lobbies, not public ones
if (lobby.source !== "public") {
this.updateJoinUrlForShare(lobby.gameID, config);
}
-
- const pattern = this.userSettings.getSelectedPatternName(
- await fetchCosmetics(),
- );
+ const pattern = this.userSettings.getSelectedPatternName(cosmetics);
this.gameStop = joinLobby(
this.eventBus,
@@ -862,7 +811,7 @@ class Client {
modal.isModalOpen = false;
}
});
- this.publicLobby.stop();
+ this.gameModeSelector?.stop();
document.querySelectorAll(".ad").forEach((ad) => {
(ad as HTMLElement).style.display = "none";
});
@@ -878,8 +827,8 @@ class Client {
}
},
() => {
- this.joinModal.close();
- this.publicLobby.stop();
+ this.joinModal?.closeWithoutLeaving();
+ this.gameModeSelector?.stop();
incrementGamesPlayed();
document.querySelectorAll(".ad").forEach((ad) => {
@@ -921,17 +870,6 @@ class Client {
}
}
- private handleShowPublicLobbyModal(
- event: CustomEvent,
- ) {
- const { lobby } = event.detail;
- console.log(`Opening JoinLobbyModal for public lobby ${lobby.gameID}`);
-
- // Open the join lobby modal page and pass the lobby info
- window.showPage?.("page-join-lobby");
- this.joinModal?.open(lobby.gameID, true);
- }
-
private async handleLeaveLobby(/* event: CustomEvent */) {
if (this.gameStop === null) {
return;
@@ -952,6 +890,10 @@ class Client {
crazyGamesSDK.gameplayStop();
}
+ private handleOpenMatchmaking(_event: CustomEvent) {
+ this.matchmakingModal?.open();
+ }
+
private handleKickPlayer(event: CustomEvent) {
const { target } = event.detail;
diff --git a/src/client/Matchmaking.ts b/src/client/Matchmaking.ts
index 7387dbe3fb..92569b5f48 100644
--- a/src/client/Matchmaking.ts
+++ b/src/client/Matchmaking.ts
@@ -1,5 +1,5 @@
import { html, LitElement } from "lit";
-import { customElement, query, state } from "lit/decorators.js";
+import { customElement, state } from "lit/decorators.js";
import { UserMeResponse } from "../core/ApiSchemas";
import { getServerConfigFromClient } from "../core/configuration/ConfigLoader";
import { getUserMe, hasLinkedAccount } from "./Api";
@@ -18,7 +18,7 @@ export class MatchmakingModal extends BaseModal {
@state() private connected = false;
@state() private socket: WebSocket | null = null;
@state() private gameID: string | null = null;
- private elo: number | "unknown" = "unknown";
+ private elo: number | string = "...";
constructor() {
super();
@@ -37,14 +37,10 @@ export class MatchmakingModal extends BaseModal {
`;
const content = html`
-
+
${modalHeader({
title: translateText("matchmaking_modal.title"),
- onBack: this.close,
+ onBack: () => this.close(),
ariaLabel: translateText("common.back"),
})}
@@ -71,39 +67,21 @@ export class MatchmakingModal extends BaseModal {
private renderInner() {
if (!this.connected) {
- return html`
-
-
-
- ${translateText("matchmaking_modal.connecting")}
-
-
- `;
+ return this.renderLoadingSpinner(
+ translateText("matchmaking_modal.connecting"),
+ "blue",
+ );
}
if (this.gameID === null) {
- return html`
-
-
-
- ${translateText("matchmaking_modal.searching")}
-
-
- `;
+ return this.renderLoadingSpinner(
+ translateText("matchmaking_modal.searching"),
+ "green",
+ );
} else {
- return html`
-
-
-
- ${translateText("matchmaking_modal.waiting_for_game")}
-
-
- `;
+ return this.renderLoadingSpinner(
+ translateText("matchmaking_modal.waiting_for_game"),
+ "yellow",
+ );
}
}
@@ -177,7 +155,9 @@ export class MatchmakingModal extends BaseModal {
return;
}
- this.elo = userMe.player.leaderboard?.oneVone?.elo ?? "unknown";
+ this.elo =
+ userMe.player.leaderboard?.oneVone?.elo ??
+ translateText("matchmaking_modal.no_elo");
this.connected = false;
this.gameID = null;
@@ -241,7 +221,6 @@ export class MatchmakingModal extends BaseModal {
@customElement("matchmaking-button")
export class MatchmakingButton extends LitElement {
- @query("matchmaking-modal") private matchmakingModal?: MatchmakingModal;
@state() private isLoggedIn = false;
constructor() {
@@ -281,7 +260,6 @@ export class MatchmakingButton extends LitElement {
${translateText("matchmaking_button.description")}
-
`
: html`