mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 08:20:50 +00:00
crazy games integrations (#2675)
## Description: Integrate with crazy games SDK ## Please complete the following: - [ ] I have added screenshots for all UI updates - [ ] I process any text displayed to the user through translateText() and I've added it to the en.json file - [ ] I have added relevant tests to the test directory - [ ] 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: evan
This commit is contained in:
@@ -0,0 +1,205 @@
|
||||
declare global {
|
||||
interface Window {
|
||||
CrazyGames?: {
|
||||
SDK: {
|
||||
init: () => Promise<void>;
|
||||
game: {
|
||||
gameplayStart: () => Promise<void>;
|
||||
gameplayStop: () => Promise<void>;
|
||||
happytime: () => Promise<void>;
|
||||
loadingStart: () => void;
|
||||
loadingStop: () => void;
|
||||
showInviteButton: (options: {
|
||||
gameId: string | number;
|
||||
[key: string]: string | number;
|
||||
}) => string;
|
||||
hideInviteButton: () => void;
|
||||
getInviteParam: (paramName: string) => string | null;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class CrazyGamesSDK {
|
||||
private initialized = false;
|
||||
private isGameplayActive = false;
|
||||
|
||||
isOnCrazyGames(): boolean {
|
||||
try {
|
||||
// Check if we're in an iframe
|
||||
if (window.self !== window.top) {
|
||||
// Try to access parent URL
|
||||
return window?.top?.location?.hostname.includes("crazygames") ?? false;
|
||||
}
|
||||
return false;
|
||||
} catch (e) {
|
||||
// If we get a cross-origin error, we're definitely iframed
|
||||
// Check our own referrer as fallback
|
||||
return document.referrer.includes("crazygames");
|
||||
}
|
||||
}
|
||||
|
||||
isReady(): boolean {
|
||||
return this.isOnCrazyGames() && this.initialized;
|
||||
}
|
||||
|
||||
async maybeInit(): Promise<void> {
|
||||
if (this.initialized) {
|
||||
console.warn("CrazyGames SDK already initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.isOnCrazyGames()) {
|
||||
console.log("Not running on CrazyGames platform, not initializing SDK");
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait for SDK to load
|
||||
let attempts = 0;
|
||||
while (typeof window.CrazyGames === "undefined" && attempts < 100) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
attempts++;
|
||||
}
|
||||
|
||||
if (typeof window.CrazyGames === "undefined") {
|
||||
console.warn("CrazyGames SDK not available");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await window.CrazyGames.SDK.init();
|
||||
this.initialized = true;
|
||||
console.log("CrazyGames SDK initialized");
|
||||
} catch (error) {
|
||||
console.error("Failed to initialize CrazyGames SDK:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async gameplayStart(): Promise<void> {
|
||||
if (!this.isReady()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isGameplayActive) {
|
||||
console.warn("Gameplay already started");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await window.CrazyGames!.SDK.game.gameplayStart();
|
||||
this.isGameplayActive = true;
|
||||
console.log("CrazyGames: gameplay started");
|
||||
} catch (error) {
|
||||
console.error("Failed to report gameplay start:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async gameplayStop(): Promise<void> {
|
||||
if (!this.isReady()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.isGameplayActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await window.CrazyGames!.SDK.game.gameplayStop();
|
||||
this.isGameplayActive = false;
|
||||
console.log("CrazyGames: gameplay stopped");
|
||||
} catch (error) {
|
||||
console.error("Failed to report gameplay stop:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async happytime(): Promise<void> {
|
||||
if (!this.isReady()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await window.CrazyGames!.SDK.game.happytime();
|
||||
console.log("CrazyGames: happy time triggered");
|
||||
} catch (error) {
|
||||
console.error("Failed to trigger happy time:", error);
|
||||
}
|
||||
}
|
||||
|
||||
loadingStart(): void {
|
||||
if (!this.isReady()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
window.CrazyGames!.SDK.game.loadingStart();
|
||||
console.log("CrazyGames: loading started");
|
||||
} catch (error) {
|
||||
console.error("Failed to report loading start:", error);
|
||||
}
|
||||
}
|
||||
|
||||
loadingStop(): void {
|
||||
if (!this.isReady()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
window.CrazyGames!.SDK.game.loadingStop();
|
||||
console.log("CrazyGames: loading stopped");
|
||||
} catch (error) {
|
||||
console.error("Failed to report loading stop:", error);
|
||||
}
|
||||
}
|
||||
|
||||
showInviteButton(gameId: string): string | null {
|
||||
if (!this.isReady()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const options: {
|
||||
gameId: string | number;
|
||||
[key: string]: string | number;
|
||||
} = {
|
||||
gameId,
|
||||
};
|
||||
const link = window.CrazyGames!.SDK.game.showInviteButton(options);
|
||||
console.log("CrazyGames: invite button shown, link:", link);
|
||||
return link;
|
||||
} catch (error) {
|
||||
console.error("Failed to show invite button:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
hideInviteButton(): void {
|
||||
if (!this.isReady()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
window.CrazyGames!.SDK.game.hideInviteButton();
|
||||
console.log("CrazyGames: invite button hidden");
|
||||
} catch (error) {
|
||||
console.error("Failed to hide invite button:", error);
|
||||
}
|
||||
}
|
||||
|
||||
getInviteGameId(): string | null {
|
||||
if (!this.isReady()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const value = window.CrazyGames!.SDK.game.getInviteParam("gameId");
|
||||
console.log(`CrazyGames: got invite gameId:`, value);
|
||||
return value;
|
||||
} catch (error) {
|
||||
console.error(`Failed to get invite gameId:`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const crazyGamesSDK = new CrazyGamesSDK();
|
||||
@@ -27,6 +27,7 @@ import "./components/baseComponents/Modal";
|
||||
import "./components/Difficulties";
|
||||
import "./components/LobbyTeamView";
|
||||
import "./components/Maps";
|
||||
import { crazyGamesSDK } from "./CrazyGamesSDK";
|
||||
import { JoinLobbyEvent } from "./Main";
|
||||
import { terrainMapFileLoader } from "./TerrainMapFileLoader";
|
||||
import { renderUnitTypeOptions } from "./utilities/RenderUnitTypeOptions";
|
||||
@@ -35,6 +36,7 @@ export class HostLobbyModal extends LitElement {
|
||||
@query("o-modal") private modalEl!: HTMLElement & {
|
||||
open: () => void;
|
||||
close: () => void;
|
||||
onClose?: () => void;
|
||||
};
|
||||
@state() private selectedMap: GameMapType = GameMapType.World;
|
||||
@state() private selectedDifficulty: Difficulty = Difficulty.Medium;
|
||||
@@ -607,7 +609,7 @@ export class HostLobbyModal extends LitElement {
|
||||
createLobby(this.lobbyCreatorClientID)
|
||||
.then((lobby) => {
|
||||
this.lobbyId = lobby.gameID;
|
||||
// join lobby
|
||||
crazyGamesSDK.showInviteButton(this.lobbyId);
|
||||
})
|
||||
.then(() => {
|
||||
this.dispatchEvent(
|
||||
@@ -622,11 +624,16 @@ export class HostLobbyModal extends LitElement {
|
||||
);
|
||||
});
|
||||
this.modalEl?.open();
|
||||
this.modalEl.onClose = () => {
|
||||
this.close();
|
||||
};
|
||||
this.playersInterval = setInterval(() => this.pollPlayers(), 1000);
|
||||
this.loadNationCount();
|
||||
}
|
||||
|
||||
public close() {
|
||||
console.log("Closing host lobby modal");
|
||||
crazyGamesSDK.hideInviteButton();
|
||||
this.modalEl?.close();
|
||||
this.copySuccess = false;
|
||||
if (this.playersInterval) {
|
||||
@@ -828,7 +835,6 @@ export class HostLobbyModal extends LitElement {
|
||||
|
||||
private async copyToClipboard() {
|
||||
try {
|
||||
//TODO: Convert id to url and copy
|
||||
await navigator.clipboard.writeText(
|
||||
`${location.origin}/#join=${this.lobbyId}`,
|
||||
);
|
||||
@@ -851,8 +857,6 @@ export class HostLobbyModal extends LitElement {
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((data: GameInfo) => {
|
||||
console.log(`got game info response: ${JSON.stringify(data)}`);
|
||||
|
||||
this.clients = data.clients ?? [];
|
||||
});
|
||||
}
|
||||
|
||||
+26
-4
@@ -12,6 +12,7 @@ import { getUserMe } from "./Api";
|
||||
import { userAuth } from "./Auth";
|
||||
import { joinLobby } from "./ClientGameRunner";
|
||||
import { fetchCosmetics } from "./Cosmetics";
|
||||
import { crazyGamesSDK } from "./CrazyGamesSDK";
|
||||
import "./DarkModeButton";
|
||||
import { DarkModeButton } from "./DarkModeButton";
|
||||
import "./FlagInput";
|
||||
@@ -115,6 +116,7 @@ class Client {
|
||||
constructor() {}
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
crazyGamesSDK.maybeInit();
|
||||
// Prefetch turnstile token so it is available when
|
||||
// the user joins a lobby.
|
||||
this.turnstileTokenPromise = getTurnstileToken();
|
||||
@@ -161,10 +163,11 @@ class Client {
|
||||
|
||||
this.publicLobby = document.querySelector("public-lobby") as PublicLobby;
|
||||
|
||||
window.addEventListener("beforeunload", () => {
|
||||
window.addEventListener("beforeunload", async () => {
|
||||
console.log("Browser is closing");
|
||||
if (this.gameStop !== null) {
|
||||
this.gameStop();
|
||||
await crazyGamesSDK.gameplayStop();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -343,7 +346,7 @@ class Client {
|
||||
}
|
||||
|
||||
// Attempt to join lobby
|
||||
this.handleHash();
|
||||
this.handleUrl();
|
||||
|
||||
const onHashUpdate = () => {
|
||||
// Reset the UI to its initial state
|
||||
@@ -353,7 +356,7 @@ class Client {
|
||||
}
|
||||
|
||||
// Attempt to join lobby
|
||||
this.handleHash();
|
||||
this.handleUrl();
|
||||
};
|
||||
|
||||
// Handle browser navigation & manual hash edits
|
||||
@@ -380,7 +383,17 @@ class Client {
|
||||
this.initializeFuseTag();
|
||||
}
|
||||
|
||||
private handleHash() {
|
||||
private handleUrl() {
|
||||
// Check if CrazyGames SDK is enabled first (no hash needed in CrazyGames)
|
||||
if (crazyGamesSDK.isOnCrazyGames()) {
|
||||
const lobbyId = crazyGamesSDK.getInviteGameId();
|
||||
if (lobbyId && ID.safeParse(lobbyId).success) {
|
||||
this.joinModal.open(lobbyId);
|
||||
console.log(`CrazyGames: joining lobby ${lobbyId} from invite param`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const strip = () =>
|
||||
history.replaceState(
|
||||
null,
|
||||
@@ -449,6 +462,7 @@ class Client {
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback to hash-based join for non-CrazyGames environments
|
||||
if (decodedHash.startsWith("#join=")) {
|
||||
const lobbyId = decodedHash.substring(6); // Remove "#join="
|
||||
if (lobbyId && ID.safeParse(lobbyId).success) {
|
||||
@@ -546,6 +560,8 @@ class Client {
|
||||
document.documentElement.classList.add("in-game");
|
||||
removeSnowflakes(); // Stop snowflakes when joining a game
|
||||
|
||||
crazyGamesSDK.loadingStart();
|
||||
|
||||
// show when the game loads
|
||||
const startingModal = document.querySelector(
|
||||
"game-starting-modal",
|
||||
@@ -564,6 +580,9 @@ class Client {
|
||||
(ad as HTMLElement).style.display = "none";
|
||||
});
|
||||
|
||||
crazyGamesSDK.loadingStop();
|
||||
crazyGamesSDK.gameplayStart();
|
||||
|
||||
// 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");
|
||||
@@ -580,6 +599,9 @@ class Client {
|
||||
console.log("leaving lobby, cancelling game");
|
||||
this.gameStop();
|
||||
this.gameStop = null;
|
||||
|
||||
crazyGamesSDK.gameplayStop();
|
||||
|
||||
this.gutterAds.hide();
|
||||
this.publicLobby.leaveLobby();
|
||||
// Show snowflakes when leaving lobby (back to homepage)
|
||||
|
||||
@@ -8,6 +8,7 @@ export class OModal extends LitElement {
|
||||
@property({ type: String }) title = "";
|
||||
@property({ type: String }) translationKey = "";
|
||||
@property({ type: Boolean }) alwaysMaximized = false;
|
||||
@property({ type: Function }) onClose?: () => void;
|
||||
|
||||
static styles = css`
|
||||
.c-modal {
|
||||
@@ -75,10 +76,10 @@ export class OModal extends LitElement {
|
||||
this.isModalOpen = true;
|
||||
}
|
||||
public close() {
|
||||
this.isModalOpen = false;
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("modal-close", { bubbles: true, composed: true }),
|
||||
);
|
||||
if (this.isModalOpen) {
|
||||
this.isModalOpen = false;
|
||||
this.onClose?.();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
@@ -9,6 +9,7 @@ import { EventBus } from "../../../core/EventBus";
|
||||
import { GameType } from "../../../core/game/Game";
|
||||
import { GameUpdateType } from "../../../core/game/GameUpdates";
|
||||
import { GameView } from "../../../core/game/GameView";
|
||||
import { crazyGamesSDK } from "../../CrazyGamesSDK";
|
||||
import { PauseGameEvent } from "../../Transport";
|
||||
import { translateText } from "../../Utils";
|
||||
import { Layer } from "./Layer";
|
||||
@@ -106,8 +107,10 @@ export class GameRightSidebar extends LitElement implements Layer {
|
||||
);
|
||||
if (!isConfirmed) return;
|
||||
}
|
||||
// redirect to the home page
|
||||
window.location.href = "/";
|
||||
crazyGamesSDK.gameplayStop().then(() => {
|
||||
// redirect to the home page
|
||||
window.location.href = "/";
|
||||
});
|
||||
}
|
||||
|
||||
private onSettingsButtonClick() {
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
handlePurchase,
|
||||
patternRelationship,
|
||||
} from "../../Cosmetics";
|
||||
import { crazyGamesSDK } from "../../CrazyGamesSDK";
|
||||
import { SendWinnerEvent } from "../../Transport";
|
||||
import { Layer } from "./Layer";
|
||||
|
||||
@@ -293,6 +294,7 @@ export class WinModal extends LitElement implements Layer {
|
||||
if (wu.winner[1] === this.game.myPlayer()?.team()) {
|
||||
this._title = translateText("win_modal.your_team");
|
||||
this.isWin = true;
|
||||
crazyGamesSDK.happytime();
|
||||
} else {
|
||||
this._title = translateText("win_modal.other_team", {
|
||||
team: wu.winner[1],
|
||||
@@ -315,6 +317,7 @@ export class WinModal extends LitElement implements Layer {
|
||||
) {
|
||||
this._title = translateText("win_modal.you_won");
|
||||
this.isWin = true;
|
||||
crazyGamesSDK.happytime();
|
||||
} else {
|
||||
this._title = translateText("win_modal.other_won", {
|
||||
player: winner.name(),
|
||||
|
||||
@@ -130,6 +130,12 @@
|
||||
document.documentElement.className = "preload";
|
||||
</script>
|
||||
|
||||
<!-- CrazyGames SDK -->
|
||||
<script
|
||||
src="https://sdk.crazygames.com/crazygames-sdk-v3.js"
|
||||
async
|
||||
></script>
|
||||
|
||||
<!-- Cloudflare Turnstile -->
|
||||
<script
|
||||
src="https://challenges.cloudflare.com/turnstile/v0/api.js"
|
||||
@@ -480,7 +486,9 @@
|
||||
></div>
|
||||
<button
|
||||
class="mt-4 w-full bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded"
|
||||
onclick="document.getElementById('language-modal').classList.add('hidden')"
|
||||
onclick="
|
||||
document.getElementById('language-modal').classList.add('hidden')
|
||||
"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user