diff --git a/src/client/ClientGameRunner.ts b/src/client/ClientGameRunner.ts index 09271afda..0e2eb960c 100644 --- a/src/client/ClientGameRunner.ts +++ b/src/client/ClientGameRunner.ts @@ -64,15 +64,24 @@ export interface LobbyConfig { gameRecord?: GameRecord; } +export interface JoinLobbyResult { + stop: (force?: boolean) => boolean; + prestart: Promise; + join: Promise; +} + export function joinLobby( eventBus: EventBus, lobbyConfig: LobbyConfig, - onPrestart: () => void, - onJoin: () => void, -): (force?: boolean) => boolean { +): JoinLobbyResult { // Mutable clientID state — assigned by server (multiplayer) or derived from gameStartInfo (singleplayer) let clientID: ClientID | undefined; + let resolvePrestart: () => void; + let resolveJoin: () => void; + const prestartPromise = new Promise((r) => (resolvePrestart = r)); + const joinPromise = new Promise((r) => (resolveJoin = r)); + console.log(`joining lobby: gameID: ${lobbyConfig.gameID}`); const userSettings: UserSettings = new UserSettings(); @@ -105,17 +114,17 @@ export function joinLobby( message.gameMapSize, terrainMapFileLoader, ); - onPrestart(); + resolvePrestart(); } if (message.type === "start") { // Trigger prestart for singleplayer games - onPrestart(); + resolvePrestart(); console.log( `lobby: game started: ${JSON.stringify(message, replacer, 2)}`, ); // Server tells us our assigned clientID (also sent on start for late joins) clientID = message.myClientID; - onJoin(); + resolveJoin(); // For multiplayer games, GameStartInfo is not known until game starts. lobbyConfig.gameStartInfo = message.gameStartInfo; createClientGame( @@ -176,19 +185,19 @@ export function joinLobby( } }; transport.connect(onconnect, onmessage); - return (force: boolean = false) => { - if (!force && currentGameRunner?.shouldPreventWindowClose()) { - console.log("Player is active, prevent leaving game"); - - return false; - } - - console.log("leaving game"); - - currentGameRunner = null; - transport.leaveGame(); - - return true; + return { + stop: (force: boolean = false) => { + if (!force && currentGameRunner?.shouldPreventWindowClose()) { + console.log("Player is active, prevent leaving game"); + return false; + } + console.log("leaving game"); + currentGameRunner = null; + transport.leaveGame(); + return true; + }, + prestart: prestartPromise, + join: joinPromise, }; } diff --git a/src/client/Main.ts b/src/client/Main.ts index 8499d84dd..55406ed30 100644 --- a/src/client/Main.ts +++ b/src/client/Main.ts @@ -15,7 +15,7 @@ import { UserSettings } from "../core/game/UserSettings"; import "./AccountModal"; import { getUserMe } from "./Api"; import { userAuth } from "./Auth"; -import { joinLobby } from "./ClientGameRunner"; +import { joinLobby, type JoinLobbyResult } from "./ClientGameRunner"; import { getPlayerCosmeticsRefs } from "./Cosmetics"; import { crazyGamesSDK } from "./CrazyGamesSDK"; import "./FlagInput"; @@ -230,7 +230,7 @@ export interface JoinLobbyEvent { } class Client { - private gameStop: ((force?: boolean) => boolean) | null = null; + private lobbyHandle: JoinLobbyResult | null = null; private eventBus: EventBus = new EventBus(); private currentUrl: string | null = null; @@ -300,8 +300,8 @@ class Client { window.addEventListener("beforeunload", async () => { console.log("Browser is closing"); - if (this.gameStop !== null) { - this.gameStop(true); + if (this.lobbyHandle !== null) { + this.lobbyHandle.stop(true); await crazyGamesSDK.gameplayStop(); } }); @@ -521,10 +521,10 @@ class Client { }; const onPopState = () => { - if (this.currentUrl !== null && this.gameStop !== null) { + if (this.currentUrl !== null && this.lobbyHandle !== null) { console.info("Game is active"); - if (!this.gameStop()) { + if (!this.lobbyHandle.stop()) { console.info("Player is active, ask before leaving game"); const isConfirmed = confirm( @@ -552,7 +552,7 @@ class Client { }; const onJoinChanged = () => { - if (this.gameStop !== null) { + if (this.lobbyHandle !== null) { this.handleLeaveLobby(); } @@ -733,9 +733,9 @@ class Client { private async handleJoinLobby(event: CustomEvent) { const lobby = event.detail; console.log(`joining lobby ${lobby.gameID}`); - if (this.gameStop !== null) { + if (this.lobbyHandle !== null) { console.log("joining lobby, stopping existing game"); - this.gameStop(true); + this.lobbyHandle.stop(true); document.body.classList.remove("in-game"); } if (lobby.source === "public") { @@ -746,106 +746,104 @@ class Client { if (lobby.source !== "public") { this.updateJoinUrlForShare(lobby.gameID, config); } - this.gameStop = joinLobby( - this.eventBus, - { - gameID: lobby.gameID, - serverConfig: config, - cosmetics: await getPlayerCosmeticsRefs(), - turnstileToken: await this.getTurnstileToken(lobby), - playerName: - this.usernameInput?.getCurrentUsername() ?? genAnonUsername(), - gameStartInfo: lobby.gameStartInfo ?? lobby.gameRecord?.info, - gameRecord: lobby.gameRecord, - }, - () => { - console.log("Closing modals"); - document.getElementById("settings-button")?.classList.add("hidden"); - if (this.usernameInput) { - // fix edge case where username-validation-error is re-rendered and hidden tag removed - this.usernameInput.validationError = ""; + this.lobbyHandle = joinLobby(this.eventBus, { + gameID: lobby.gameID, + serverConfig: config, + cosmetics: await getPlayerCosmeticsRefs(), + turnstileToken: await this.getTurnstileToken(lobby), + playerName: this.usernameInput?.getCurrentUsername() ?? genAnonUsername(), + gameStartInfo: lobby.gameStartInfo ?? lobby.gameRecord?.info, + gameRecord: lobby.gameRecord, + }); + + this.lobbyHandle.prestart.then(() => { + console.log("Closing modals"); + document.getElementById("settings-button")?.classList.add("hidden"); + if (this.usernameInput) { + // fix edge case where username-validation-error is re-rendered and hidden tag removed + this.usernameInput.validationError = ""; + } + document + .getElementById("username-validation-error") + ?.classList.add("hidden"); + this.joinModal?.closeWithoutLeaving(); + [ + "single-player-modal", + "host-lobby-modal", + "game-starting-modal", + "game-top-bar", + "help-modal", + "user-setting", + "troubleshooting-modal", + "territory-patterns-modal", + "language-modal", + "news-modal", + "flag-input-modal", + "account-button", + "leaderboard-button", + "token-login", + "matchmaking-modal", + "lang-selector", + "gutter-ads", + ].forEach((tag) => { + const modal = document.querySelector(tag) as HTMLElement & { + close?: () => void; + isModalOpen?: boolean; + }; + if (modal?.close) { + modal.close(); + } else if (modal && "isModalOpen" in modal) { + modal.isModalOpen = false; } - document - .getElementById("username-validation-error") - ?.classList.add("hidden"); - this.joinModal?.closeWithoutLeaving(); - [ - "single-player-modal", - "host-lobby-modal", - "game-starting-modal", - "game-top-bar", - "help-modal", - "user-setting", - "troubleshooting-modal", - "territory-patterns-modal", - "language-modal", - "news-modal", - "flag-input-modal", - "account-button", - "leaderboard-button", - "token-login", - "matchmaking-modal", - "lang-selector", - "gutter-ads", - ].forEach((tag) => { - const modal = document.querySelector(tag) as HTMLElement & { - close?: () => void; - isModalOpen?: boolean; - }; - if (modal?.close) { - modal.close(); - } else if (modal && "isModalOpen" in modal) { - modal.isModalOpen = false; - } - }); - this.gameModeSelector.stop(); - document.querySelectorAll(".ad").forEach((ad) => { - (ad as HTMLElement).style.display = "none"; - }); + }); + this.gameModeSelector.stop(); + document.querySelectorAll(".ad").forEach((ad) => { + (ad as HTMLElement).style.display = "none"; + }); - crazyGamesSDK.loadingStart(); + crazyGamesSDK.loadingStart(); - // show when the game loads - const startingModal = document.querySelector( - "game-starting-modal", - ) as GameStartingModal; - if (startingModal && startingModal instanceof GameStartingModal) { - startingModal.show(); - } - }, - () => { - this.joinModal?.closeWithoutLeaving(); - this.gameModeSelector.stop(); - incrementGamesPlayed(); + // show when the game loads + const startingModal = document.querySelector( + "game-starting-modal", + ) as GameStartingModal; + if (startingModal && startingModal instanceof GameStartingModal) { + startingModal.show(); + } + }); - document.querySelectorAll(".ad").forEach((ad) => { - (ad as HTMLElement).style.display = "none"; - }); + this.lobbyHandle.join.then(() => { + this.joinModal?.closeWithoutLeaving(); + this.gameModeSelector.stop(); + incrementGamesPlayed(); - if (window.PageOS?.session?.newPageView) { - window.PageOS.session.newPageView(); - } - crazyGamesSDK.loadingStop(); - crazyGamesSDK.gameplayStart(); - document.body.classList.add("in-game"); + document.querySelectorAll(".ad").forEach((ad) => { + (ad as HTMLElement).style.display = "none"; + }); - // Ensure there's a homepage entry in history before adding the lobby entry - if (window.location.hash === "" || window.location.hash === "#") { - history.replaceState(null, "", window.location.origin + "#refresh"); - } - const lobbyIdHidden = !this.userSettings.lobbyIdVisibility(); - history.pushState( - null, - "", - lobbyIdHidden - ? "/streamer-mode" - : `/${config.workerPath(lobby.gameID)}/game/${lobby.gameID}?live`, - ); + if (window.PageOS?.session?.newPageView) { + window.PageOS.session.newPageView(); + } + crazyGamesSDK.loadingStop(); + crazyGamesSDK.gameplayStart(); + document.body.classList.add("in-game"); - // Store current URL for popstate confirmation - this.currentUrl = window.location.href; - }, - ); + // Ensure there's a homepage entry in history before adding the lobby entry + if (window.location.hash === "" || window.location.hash === "#") { + history.replaceState(null, "", window.location.origin + "#refresh"); + } + const lobbyIdHidden = !this.userSettings.lobbyIdVisibility(); + history.pushState( + null, + "", + lobbyIdHidden + ? "/streamer-mode" + : `/${config.workerPath(lobby.gameID)}/game/${lobby.gameID}?live`, + ); + + // Store current URL for popstate confirmation + this.currentUrl = window.location.href; + }); } private updateJoinUrlForShare( @@ -864,12 +862,12 @@ class Client { } private async handleLeaveLobby(/* event: CustomEvent */) { - if (this.gameStop === null) { + if (this.lobbyHandle === null) { return; } console.log("leaving lobby, cancelling game"); - this.gameStop(true); - this.gameStop = null; + this.lobbyHandle.stop(true); + this.lobbyHandle = null; this.currentUrl = null; try {