diff --git a/.gitignore b/.gitignore
index 0875bf0..c3b1f22 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,27 +1,4 @@
-# Dependencies
-node_modules/
-
-# Build output
-dist/
-
-# Environment files
-.env
-.env.local
-.env.*.local
-
-# IDE
-.vscode/
-.idea/
-*.swp
-*.swo
-
-# OS files
+node_modules
.DS_Store
-Thumbs.db
-
-# Logs
-*.log
-npm-debug.log*
-
-# Lock files (optional - remove if you want to track)
-# package-lock.json
+dist
+.env
\ No newline at end of file
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..d33b7da
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,6 @@
+{
+ "singleQuote": true,
+ "trailingComma": "es5",
+ "quoteProps": "preserve",
+ "plugins": []
+}
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..205e0ec
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,52 @@
+You are writing a Devvit web application that will be executed on Reddit.com.
+
+## Tech Stack
+
+- **Frontend**: GameMaker, Vite
+- **Backend**: Node.js v22 serverless environment (Devvit), Hono, TRPC
+- **Communication**: tRPC v11 for end-to-end type safety
+
+## Layout & Architecture
+
+- `/src/server`: **Backend Code**. This runs in a secure, serverless environment.
+ - `trpc.ts`: Defines the API router and procedures.
+ - `index.ts`: Main server entry point (Hono app).
+ - Access `redis`, `reddit`, and `context` here via `@devvit/web/server`.
+- `/src/client`: **Frontend Code**. This is executed inside of an iFrame on reddit.com
+ - To add an entrypoint, create a HTML file and add to the mapping inside of `devvit.json`
+ - Entrypoints:
+ - `game.html`: The main React entry point (Expanded View).
+ - `splash.html`: The initial React entry point (Inline View). This will be shown in the reddit.com feed. Please keep it fast and keep heavy dependencies inside of `game.html`
+- `/src/shared`: **Shared Code**. Code to share between the client and server
+
+## Frontend
+
+### Rules
+
+- Instead of `window.location` or `window.assign`, use `navigateTo` from `@devvit/web/client`
+
+### Limitations
+
+- `window.alert`: Use `showToast` or `showForm` from `@devvit/web/client`
+- File downloads: Use clipboard API with `showToast` to confirm
+- Geolocation, camera, microphone, and notifications web APIs: No alternatives
+- Inline script tags inside of `html` files: Use a script tag and separate js/ts file
+
+## Commands
+
+- `npm run type-check`: Check typescript types
+- `npm run lint`: Check the linter
+- `npm run test -- my-file-name`: Run tests isolated to a file
+
+## Code Style
+
+- Prefer type aliases over interfaces when writing typescript
+- Prefer named exports over default exports
+- Never cast typescript types
+
+## Global Rules
+
+- You may find code that references blocks or `@devvit/public-api` while building a feature. Do NOT use this code as this project is configured to use Devvit web only.
+- Whenever you add an endpoint for a new menu item action, ensure that you've added the corresponding mapping to `devvit.json` so that it is properly registered
+
+Docs: https://developers.reddit.com/docs/llms.txt.
diff --git a/README.md b/README.md
index 0642633..7882088 100644
--- a/README.md
+++ b/README.md
@@ -5,4 +5,4 @@ This is intended as a GameMaker wasm equivalent to the Devvit templates and is b
## Get Started
-For instructions on how to get setup as a Reddit developer and start deploying GameMaker games to reddit, see [How To Build](docs/HowToBuild.md)
\ No newline at end of file
+For instructions on how to get setup as a Reddit developer and start deploying GameMaker games to reddit, see [How To Build](docs/HowToBuild.md)
diff --git a/devvit.json b/devvit.json
index b6d4d7a..dc0f7ca 100644
--- a/devvit.json
+++ b/devvit.json
@@ -25,10 +25,24 @@
"location": "subreddit",
"forUserType": "moderator",
"endpoint": "/internal/menu/post-create"
+ },
+ {
+ "label": "Example form",
+ "description": "Show a simple form",
+ "location": "subreddit",
+ "forUserType": "moderator",
+ "endpoint": "/internal/menu/example-form"
}
]
},
+ "forms": {
+ "exampleForm": "/internal/form/example-submit"
+ },
"triggers": {
- "onAppInstall": "/internal/on-app-install"
+ "onAppInstall": "/internal/triggers/on-app-install"
+ },
+ "scripts": {
+ "build": "vite build",
+ "dev": "vite build --watch"
}
}
diff --git a/docs/HowToBuild.md b/docs/HowToBuild.md
index d522cbe..8c8daf0 100644
--- a/docs/HowToBuild.md
+++ b/docs/HowToBuild.md
@@ -19,7 +19,7 @@ Before you begin, ensure you have the following installed and configured:
- **Node.js 22+** - Download from [nodejs.org](https://nodejs.org/)
- **GameMaker** - Any recent 2024.1400.3 Beta release (or newer), which you can download from [the release notes site](https://releases.gamemaker.io/) and must have also configured already for GX.games YYC development by following [its setup guide](https://github.com/YoYoGames/GameMaker-Bugs/wiki#platform-setup-guides)
- - You may use older versions of GameMaker but will need to use the [GMEXT-Reddit Extension](https://github.com/YoYoGames/GMEXT-Reddit/) which includes the example project and requires you to build your game with the gx.games target rather than the reddit target. Further details can be found in the extension repository.
+ - You may use older versions of GameMaker but will need to use the [GMEXT-Reddit Extension](https://github.com/YoYoGames/GMEXT-Reddit/) which includes the example project and requires you to build your game with the gx.games target rather than the reddit target. Further details can be found in the extension repository.
- **Reddit Developer Account** - Sign up at [developers.reddit.com](https://developers.reddit.com/)
---
@@ -72,6 +72,7 @@ Open the GameMaker project that you want to deploy to Reddit.
In the **Devvit Project ID** field, enter the short, no-spaces game name that you registered with Reddit earlier
**Example:**
+
```
my-game
```
@@ -79,6 +80,7 @@ my-game
In the **Devvit Project Path** field, enter the full path to your Devvit project directory that you created in the previous section.
**Example:**
+
```
C:\Users\YourName\Projects\my-game
```
@@ -90,6 +92,7 @@ Click **OK** or **Apply** to save your settings.
#### Step 4: Verify GameMaker Export Settings
Ensure your project is configured to export to WebAssembly:
+
- **Target Platform**: Reddit
- **Output Format**: GMS2 VM
@@ -112,6 +115,7 @@ npm run dev
**You only need to do this once per GameMaker session** - leave the terminal app running throughout your development session and then close it whenever you're done with all your Reddit builds for the day.
This command does several things:
+
- Starts a local development server
- Uploads your app to Reddit's Devvit platform
- Provides a link to test your app on Reddit (not clickable - you will need to copy/paste it manually the first time and thereafter can just refresh your browser tab
@@ -168,6 +172,7 @@ When you're ready to test your changes:
> If not running on Windows, ensure that execute permissions are enabled on the `setup-gamemaker-devvit.sh` script within your devvit project
GameMaker will:
+
- Build your game to WebAssembly
- Automatically copy the necessary files to your Devvit project directory (specified in Game Options)
- The build output goes to `src/client/public/` in your Devvit project
@@ -175,6 +180,7 @@ GameMaker will:
#### Step 3: Wait for Devvit to Detect Changes
The `npm run dev` process (still running in your terminal) will shortly thereafter:
+
- Automatically detect the new/changed files
- Re-upload your game to Reddit's platform
- Display upload progress in the terminal
@@ -184,6 +190,7 @@ Wait for the update to complete. Devvit will provide a link to the updated commu
#### Step 4: Test on Reddit
Once the upload completes:
+
1. Open the link provided by `npm run dev` in your browser (or refresh if already open)
2. Test your game directly on Reddit
3. Iterate!
@@ -252,10 +259,12 @@ your-project/
## Adding Game Features
### Backend APIs
+
Add game-specific endpoints in `src/server/index.ts`:
+
```typescript
// Example: Save player score
-router.post("/api/save-score", async (req, res) => {
+router.post('/api/save-score', async (req, res) => {
const { score } = req.body;
// Save to Redis, database, etc.
res.json({ success: true });
@@ -263,7 +272,9 @@ router.post("/api/save-score", async (req, res) => {
```
### Type Definitions
+
Add API types in `src/shared/types/api.ts`:
+
```typescript
export type SaveScoreRequest = {
score: number;
@@ -278,6 +289,7 @@ export type SaveScoreRequest = {
### `npm run dev` doesn't detect changes
**Solution:**
+
- Verify the Devvit Project Path in GameMaker is correct
- Ensure files are being copied to the correct directory (`src/client/public/`)
- Check terminal for any error messages
@@ -286,6 +298,7 @@ export type SaveScoreRequest = {
### GameMaker build fails
**Solution:**
+
- Check that the Reddit platform is properly configured
- Ensure the Devvit Project Path exists and is writable
- As a sanity-check, see if you can successfully build packages for the GX.games target inside GameMaker, rather than the Reddit one. If you can, check your OS file permissions/antivirus client are not blocking your Reddit-specific folders.
@@ -293,6 +306,7 @@ export type SaveScoreRequest = {
### Game doesn't appear on Reddit
**Solution:**
+
- Confirm `npm run dev` completed the upload successfully
- Check the terminal for any error messages
- Try a hard refresh in your browser (Ctrl+Shift+R or Cmd+Shift+R)
@@ -301,6 +315,7 @@ export type SaveScoreRequest = {
### Files are in the wrong location
**Solution:**
+
- GameMaker should copy files to `src/client/public/` in your Devvit project
- Verify the Devvit Project Path setting in GameMaker Game Options
- Check your OS file permissions/antivirus client are not blocking your Reddit-specific folders.
@@ -308,6 +323,7 @@ export type SaveScoreRequest = {
### Node.js version issues
**Solution:**
+
- Devvit requires Node.js 22+
- Run `node --version` to check your version
- Update Node.js if necessary: [Download latest version](https://nodejs.org/)
@@ -315,8 +331,9 @@ export type SaveScoreRequest = {
### setup-gamemaker-devvit.sh Permissions Issue
**Solution**
+
- Ensure that execute permissions are enabled on the `setup-gamemaker-devvit.sh` script
- - Run `ls -l setup-gamemaker-devvit.sh` to verify permissions
+ - Run `ls -l setup-gamemaker-devvit.sh` to verify permissions
- Run `chmod +x setup-gamemaker-devvit.sh` to grant execute permissions
---
diff --git a/eslint.config.js b/eslint.config.js
new file mode 100644
index 0000000..7282254
--- /dev/null
+++ b/eslint.config.js
@@ -0,0 +1,68 @@
+import { defineConfig } from 'eslint/config';
+import globals from 'globals';
+import js from '@eslint/js';
+import tseslint from 'typescript-eslint';
+
+export default defineConfig([
+ tseslint.configs.recommended,
+ {
+ extends: [js.configs.recommended, ...tseslint.configs.recommended],
+ files: ['src/server/**/*.{ts,tsx,mjs,cjs,js}'],
+ languageOptions: {
+ ecmaVersion: 2023,
+ globals: globals.node,
+ parserOptions: {
+ project: ['./tools/tsconfig.server.json'],
+ tsconfigRootDir: import.meta.dirname,
+ },
+ },
+ },
+ {
+ extends: [js.configs.recommended, ...tseslint.configs.recommended],
+ files: ['src/shared/**/*.{ts,tsx,mjs,cjs,js}'],
+ languageOptions: {
+ ecmaVersion: 2023,
+ globals: globals.browser,
+ parserOptions: {
+ project: ['./tools/tsconfig.shared.json'],
+ tsconfigRootDir: import.meta.dirname,
+ },
+ },
+ },
+ {
+ extends: [js.configs.recommended, ...tseslint.configs.recommended],
+ files: ['src/client/**/*.{ts,tsx}'],
+ ignores: ['src/server/**/*.{ts,tsx}'],
+ languageOptions: {
+ ecmaVersion: 2023,
+ globals: globals.browser,
+ parserOptions: {
+ project: ['./tools/tsconfig.client.json'],
+ tsconfigRootDir: import.meta.dirname,
+ },
+ },
+ },
+ {
+ files: ['**/*.{js,mjs,cjs,ts,tsx}'],
+ rules: {
+ '@typescript-eslint/no-floating-promises': 'error',
+ '@typescript-eslint/no-unused-vars': ['off'],
+ 'no-unused-vars': ['off'],
+ },
+ ignores: [
+ '**/node_modules/**',
+ '**/dist/**',
+ '**/build/**',
+ 'eslint.config.js',
+ '**/vite.config.ts',
+ 'devvit.config.ts',
+ ],
+ languageOptions: {
+ parserOptions: {
+ tsconfigRootDir: import.meta.dirname,
+ },
+ },
+ plugins: { js },
+ extends: ['js/recommended'],
+ },
+]);
diff --git a/package.json b/package.json
index af64643..37797b4 100644
--- a/package.json
+++ b/package.json
@@ -5,29 +5,34 @@
"license": "BSD-3-Clause",
"type": "module",
"scripts": {
- "postinstall": "npm run build",
- "build:client": "cd src/client && vite build",
- "build:server": "cd src/server && vite build",
- "build": "npm run build:client && npm run build:server",
- "deploy": "npm run build && devvit upload",
- "dev": "concurrently -k -p \"[{name}]\" -n \"CLIENT,SERVER,DEVVIT\" -c \"blue,green,magenta\" \"npm run dev:client\" \"npm run dev:server\" \"npm run dev:devvit\"",
- "dev:client": "cd src/client && vite build --watch",
- "dev:devvit": "dotenv -e .env -- devvit playtest",
- "dev:server": "cd src/server && vite build --watch",
+ "build": "vite build",
+ "deploy": "npm run type-check && npm run lint && npm run test && devvit upload",
+ "dev": "devvit playtest",
+ "launch": "npm run deploy && devvit publish",
+ "lint": "eslint 'src/**/*.{ts,tsx}'",
"login": "devvit login",
- "launch": "npm run build && npm run deploy && devvit publish",
+ "prettier": "prettier --write .",
+ "test": "vitest run",
"type-check": "tsc --build"
},
+ "engines": {
+ "node": ">=22.12.0"
+ },
"dependencies": {
- "@devvit/web": "0.12.8",
- "devvit": "0.12.8",
- "express": "5.1.0"
+ "@devvit/start": "0.12.11-next-2026-01-30-17-09-49-e9c512a0d.0",
+ "@devvit/web": "0.12.11-next-2026-01-30-17-09-49-e9c512a0d.0",
+ "@hono/node-server": "^1.19.9",
+ "devvit": "0.12.11-next-2026-01-30-17-09-49-e9c512a0d.0",
+ "hono": "4.11.7"
},
"devDependencies": {
- "@types/express": "5.0.1",
- "concurrently": "9.1.2",
- "dotenv-cli": "8.0.0",
- "typescript": "5.8.2",
- "vite": "6.2.4"
+ "@eslint/js": "9.39.2",
+ "@types/node": "^22.19.7",
+ "eslint": "9.39.2",
+ "globals": "17.2.0",
+ "prettier": "3.8.1",
+ "typescript": "5.9.3",
+ "typescript-eslint": "8.54.0",
+ "vite": "7.3.1"
}
-}
\ No newline at end of file
+}
diff --git a/src/client/public/GmlSpec.xml b/public/GmlSpec.xml
similarity index 100%
rename from src/client/public/GmlSpec.xml
rename to public/GmlSpec.xml
diff --git a/src/client/public/audio-worklet.js b/public/audio-worklet.js
similarity index 100%
rename from src/client/public/audio-worklet.js
rename to public/audio-worklet.js
diff --git a/src/client/public/fnames b/public/fnames
similarity index 100%
rename from src/client/public/fnames
rename to public/fnames
diff --git a/src/client/public/game.unx b/public/game.unx
similarity index 100%
rename from src/client/public/game.unx
rename to public/game.unx
diff --git a/src/client/public/index.html b/public/index.html
similarity index 100%
rename from src/client/public/index.html
rename to public/index.html
diff --git a/src/client/public/interpolateOFF.xml b/public/interpolateOFF.xml
similarity index 100%
rename from src/client/public/interpolateOFF.xml
rename to public/interpolateOFF.xml
diff --git a/src/client/public/interpolateON.xml b/public/interpolateON.xml
similarity index 100%
rename from src/client/public/interpolateON.xml
rename to public/interpolateON.xml
diff --git a/src/client/public/run.xml b/public/run.xml
similarity index 100%
rename from src/client/public/run.xml
rename to public/run.xml
diff --git a/src/client/public/runner-sw.js b/public/runner-sw.js
similarity index 100%
rename from src/client/public/runner-sw.js
rename to public/runner-sw.js
diff --git a/src/client/public/runner.data b/public/runner.data
similarity index 100%
rename from src/client/public/runner.data
rename to public/runner.data
diff --git a/src/client/public/runner.html b/public/runner.html
similarity index 100%
rename from src/client/public/runner.html
rename to public/runner.html
diff --git a/src/client/public/runner.js b/public/runner.js
similarity index 100%
rename from src/client/public/runner.js
rename to public/runner.js
diff --git a/src/client/public/runner.json b/public/runner.json
similarity index 100%
rename from src/client/public/runner.json
rename to public/runner.json
diff --git a/src/client/public/runner.wasm b/public/runner.wasm
similarity index 100%
rename from src/client/public/runner.wasm
rename to public/runner.wasm
diff --git a/src/client/public/snoo.png b/public/snoo.png
similarity index 100%
rename from src/client/public/snoo.png
rename to public/snoo.png
diff --git a/src/client/public/sw.js b/public/sw.js
similarity index 100%
rename from src/client/public/sw.js
rename to public/sw.js
diff --git a/src/client/index.html b/src/client/index.html
index f4dc719..9033798 100644
--- a/src/client/index.html
+++ b/src/client/index.html
@@ -1,9 +1,12 @@
-
+
-
+
Devvit Game
diff --git a/src/client/main.ts b/src/client/main.ts
index 2ae5b5b..48b46a5 100644
--- a/src/client/main.ts
+++ b/src/client/main.ts
@@ -1,4 +1,4 @@
-import { InitResponse } from "../shared/types/api";
+import type { InitResponse } from '../shared/api';
declare global {
interface Window {
@@ -37,7 +37,6 @@ type RunnerManifest = {
runner?: { version?: string; yyc?: boolean };
};
-
class GameLoader {
private statusElement: HTMLElement;
private progressElement: HTMLProgressElement;
@@ -49,16 +48,18 @@ class GameLoader {
private startingAspect?: number;
constructor() {
- this.statusElement = document.getElementById("status") as HTMLElement;
- this.progressElement = document.getElementById("progress") as HTMLProgressElement;
- this.spinnerElement = document.getElementById("spinner") as HTMLElement;
- this.canvasElement = document.getElementById("canvas") as HTMLCanvasElement;
- this.loadingElement = document.getElementById("loading") as HTMLElement;
-
- this.canvasElement.addEventListener("click", () => {
+ this.statusElement = document.getElementById('status') as HTMLElement;
+ this.progressElement = document.getElementById(
+ 'progress'
+ ) as HTMLProgressElement;
+ this.spinnerElement = document.getElementById('spinner') as HTMLElement;
+ this.canvasElement = document.getElementById('canvas') as HTMLCanvasElement;
+ this.loadingElement = document.getElementById('loading') as HTMLElement;
+
+ this.canvasElement.addEventListener('click', () => {
this.canvasElement.focus();
});
-
+
this.setupModule();
this.setupResizeObserver();
this.loadGame();
@@ -70,7 +71,7 @@ class GameLoader {
postRun: [],
print: (text: string) => {
console.log(text);
- if (text === "Entering main loop.") {
+ if (text === 'Entering main loop.') {
this.ensureAspectRatio();
}
},
@@ -80,58 +81,64 @@ class GameLoader {
canvas: this.canvasElement,
setStatus: (text: string) => {
if (!window.Module.setStatus.last) {
- window.Module.setStatus.last = { time: Date.now(), text: "" };
+ window.Module.setStatus.last = { time: Date.now(), text: '' };
}
if (text === window.Module.setStatus.last.text) return;
-
+
const m = text.match(/([^(]+)\((\d+(?:\.\d+)?)\/(\d+)\)/);
const now = Date.now();
if (m && now - window.Module.setStatus.last.time < 30) return;
-
+
window.Module.setStatus.last.time = now;
window.Module.setStatus.last.text = text;
-
+
if (m) {
- this.progressElement.value = parseInt(m[2]) * 100;
- this.progressElement.max = parseInt(m[3]) * 100;
+ this.progressElement.value = parseInt(m[2]!) * 100;
+ this.progressElement.max = parseInt(m[3]!) * 100;
this.progressElement.hidden = false;
this.spinnerElement.hidden = false;
} else {
this.progressElement.value = 0;
this.progressElement.max = 100;
this.progressElement.hidden = true;
-
+
if (!text) {
- this.spinnerElement.style.display = "none";
- this.canvasElement.style.display = "block";
- this.loadingElement.style.display = "none";
+ this.spinnerElement.style.display = 'none';
+ this.canvasElement.style.display = 'block';
+ this.loadingElement.style.display = 'none';
}
}
this.statusElement.innerHTML = text;
},
totalDependencies: 0,
monitorRunDependencies: (left: number) => {
- window.Module.totalDependencies = Math.max(window.Module.totalDependencies, left);
+ window.Module.totalDependencies = Math.max(
+ window.Module.totalDependencies,
+ left
+ );
window.Module.setStatus(
left
? `Preparing... (${window.Module.totalDependencies - left}/${window.Module.totalDependencies})`
- : "All downloads complete."
+ : 'All downloads complete.'
);
},
};
-
- window.Module.setStatus("Downloading...");
-
- window.onerror = (event) => {
- window.Module.setStatus("Exception thrown, see JavaScript console");
- this.spinnerElement.style.display = "none";
+
+ window.Module.setStatus('Downloading...');
+
+ window.onerror = () => {
+ window.Module.setStatus('Exception thrown, see JavaScript console');
+ this.spinnerElement.style.display = 'none';
window.Module.setStatus = (text: string) => {
if (text) window.Module.printErr(`[post-exception status] ${text}`);
};
};
- if (typeof window === "object") {
- window.Module.arguments = window.location.search.substr(1).trim().split('&');
+ if (typeof window === 'object') {
+ window.Module.arguments = window.location.search
+ .substr(1)
+ .trim()
+ .split('&');
if (!window.Module.arguments[0]) {
window.Module.arguments = [];
}
@@ -148,12 +155,15 @@ class GameLoader {
const resizeObserver = new ResizeObserver(() => {
window.requestAnimationFrame(() => this.ensureAspectRatio());
- setTimeout(() => window.requestAnimationFrame(() => this.ensureAspectRatio()), 100);
+ setTimeout(
+ () => window.requestAnimationFrame(() => this.ensureAspectRatio()),
+ 100
+ );
});
resizeObserver.observe(document.body);
if (/Android|iPhone|iPod/i.test(navigator.userAgent)) {
- document.body.classList.add("scrollingDisabled");
+ document.body.classList.add('scrollingDisabled');
}
}
@@ -162,8 +172,8 @@ class GameLoader {
return;
}
- this.canvasElement.classList.add("active");
-
+ this.canvasElement.classList.add('active');
+
const maxWidth = window.innerWidth;
const maxHeight = window.innerHeight;
let newHeight: number, newWidth: number;
@@ -179,78 +189,86 @@ class GameLoader {
newHeight = newWidth / this.startingAspect!;
}
- this.canvasElement.style.height = "100%" //`${newHeight}px`;
- this.canvasElement.style.width = "100%" //`${newWidth}px`;
+ this.canvasElement.style.height = '100%'; //`${newHeight}px`;
+ this.canvasElement.style.width = '100%'; //`${newWidth}px`;
}
private async loadRunnerManifest(): Promise {
try {
- const res = await fetch("/runner.json", {
- credentials: "include", // keep Devvit context; same-origin
- cache: "no-cache" // avoid stale manifest after deploys
+ const res = await fetch('/runner.json', {
+ credentials: 'include', // keep Devvit context; same-origin
+ cache: 'no-cache', // avoid stale manifest after deploys
});
if (!res.ok) throw new Error(`runner.json HTTP ${res.status}`);
const manifest = (await res.json()) as RunnerManifest;
// Basic validation
- if (!Array.isArray(manifest.manifestFiles) || !Array.isArray(manifest.manifestFilesMD5)) {
- throw new Error("runner.json missing arrays");
+ if (
+ !Array.isArray(manifest.manifestFiles) ||
+ !Array.isArray(manifest.manifestFilesMD5)
+ ) {
+ throw new Error('runner.json missing arrays');
}
if (manifest.manifestFiles.length !== manifest.manifestFilesMD5.length) {
- console.warn("[runner.json] manifestFiles and manifestFilesMD5 length mismatch");
+ console.warn(
+ '[runner.json] manifestFiles and manifestFilesMD5 length mismatch'
+ );
}
// Wire the global getters from the manifest
- window.manifestFiles = () => manifest.manifestFiles.join(";");
+ window.manifestFiles = () => manifest.manifestFiles.join(';');
window.manifestFilesMD5 = () => manifest.manifestFilesMD5.slice(); // return a copy
-
} catch (e) {
- console.warn("Falling back to hardcoded manifest (runner.json not available):", e);
+ console.warn(
+ 'Falling back to hardcoded manifest (runner.json not available):',
+ e
+ );
// Fallback to current hardcoded values (this should never happen)
window.manifestFiles = () =>
[
- "runner.data",
- "runner.js",
- "runner.wasm",
- "audio-worklet.js",
- "game.unx"
- ].join(";");
-
- window.manifestFilesMD5 = () =>
- [
- "585214623b669175a702fed30de7d21d",
- "8669aa66d44cfb4f13a098cd6b0296e1",
- "d29ac123833b56dcfbe188f10e5ecb85",
- "e8f1e8db8cf996f8715a6f2164c2e44e",
- "00a26996df3ce310bb5836ef7f4b0e3c"
- ];
+ 'runner.data',
+ 'runner.js',
+ 'runner.wasm',
+ 'audio-worklet.js',
+ 'game.unx',
+ ].join(';');
+
+ window.manifestFilesMD5 = () => [
+ '585214623b669175a702fed30de7d21d',
+ '8669aa66d44cfb4f13a098cd6b0296e1',
+ 'd29ac123833b56dcfbe188f10e5ecb85',
+ 'e8f1e8db8cf996f8715a6f2164c2e44e',
+ '00a26996df3ce310bb5836ef7f4b0e3c',
+ ];
}
}
private setupGameMakerGlobals() {
-
// GameMaker async method support - make variables globally accessible
window.g_pAddAsyncMethod = -1;
window.setAddAsyncMethod = (asyncMethod: any) => {
window.g_pAddAsyncMethod = asyncMethod;
- console.log("setAddAsyncMethod called with:", asyncMethod);
+ console.log('setAddAsyncMethod called with:', asyncMethod);
};
// Exception handling - make variables globally accessible
window.g_pJSExceptionHandler = undefined;
window.setJSExceptionHandler = (exceptionHandler: any) => {
- if (typeof exceptionHandler === "function") {
+ if (typeof exceptionHandler === 'function') {
window.g_pJSExceptionHandler = exceptionHandler;
}
};
window.hasJSExceptionHandler = () => {
- return window.g_pJSExceptionHandler !== undefined && typeof window.g_pJSExceptionHandler === "function";
+ return (
+ window.g_pJSExceptionHandler !== undefined &&
+ typeof window.g_pJSExceptionHandler === 'function'
+ );
};
window.doJSExceptionHandler = (exceptionJSON: string) => {
- if (typeof window.g_pJSExceptionHandler === "function") {
+ if (typeof window.g_pJSExceptionHandler === 'function') {
const exception = JSON.parse(exceptionJSON);
window.g_pJSExceptionHandler(exception);
}
@@ -263,12 +281,12 @@ class GameLoader {
};
window.onFirstFrameRendered = () => {
- console.log("First frame rendered!");
+ console.log('First frame rendered!');
};
// Ad system stubs
window.triggerAd = (adId: string, ...callbacks: any[]) => {
- console.log("triggerAd called with adId:", adId);
+ console.log('triggerAd called with adId:', adId);
// For now, just call the callbacks to simulate ad completion
if (callbacks.length > 0 && typeof callbacks[0] === 'function') {
setTimeout(() => callbacks[0](), 100);
@@ -276,7 +294,7 @@ class GameLoader {
};
window.triggerPayment = (itemId: string, callback: any) => {
- console.log("triggerPayment called with itemId:", itemId);
+ console.log('triggerPayment called with itemId:', itemId);
// Simulate payment completion
if (typeof callback === 'function') {
setTimeout(() => callback({ id: itemId }), 1000);
@@ -292,31 +310,34 @@ class GameLoader {
};
// Multiplayer/networking stubs
+ // @ts-expect-error - here to show as an example
let acceptable_rollback_frames = 0;
window.set_acceptable_rollback = (frames: number) => {
acceptable_rollback_frames = frames;
- console.log("Set acceptable rollback frames:", frames);
+ console.log('Set acceptable rollback frames:', frames);
};
window.report_stats = (statsData: any) => {
- console.log("Game stats reported:", statsData);
+ console.log('Game stats reported:', statsData);
};
window.log_next_game_state = () => {
- console.log("Game state logging requested");
+ console.log('Game state logging requested');
};
window.wallpaper_update_config = (config: string) => {
- console.log("Wallpaper config update:", config);
+ console.log('Wallpaper config update:', config);
};
window.wallpaper_reset_config = () => {
- console.log("Wallpaper config reset");
+ console.log('Wallpaper config reset');
};
// Mock accelerometer API to prevent permissions policy violations
if (!('DeviceMotionEvent' in window)) {
- (window as any).DeviceMotionEvent = class MockDeviceMotionEvent extends Event {
+ (window as any).DeviceMotionEvent = class MockDeviceMotionEvent extends (
+ Event
+ ) {
constructor(type: string, eventInitDict?: any) {
super(type, eventInitDict);
}
@@ -324,11 +345,12 @@ class GameLoader {
}
if (!('DeviceOrientationEvent' in window)) {
- (window as any).DeviceOrientationEvent = class MockDeviceOrientationEvent extends Event {
- constructor(type: string, eventInitDict?: any) {
- super(type, eventInitDict);
- }
- };
+ (window as any).DeviceOrientationEvent =
+ class MockDeviceOrientationEvent extends Event {
+ constructor(type: string, eventInitDict?: any) {
+ super(type, eventInitDict);
+ }
+ };
}
}
@@ -336,28 +358,28 @@ class GameLoader {
try {
// First try to get initial data from the server
await this.fetchInitialData();
-
+
// Load manifest data that GameMaker runtime expects
await this.loadRunnerManifest();
// Setup required global functions before loading GameMaker script
this.setupGameMakerGlobals();
-
+
// Load the GameMaker runner script
const script = document.createElement('script');
script.src = '/runner.js';
script.async = true;
script.type = 'text/javascript';
-
+
script.onload = () => {
console.log('Game script loaded successfully');
};
-
+
script.onerror = (error) => {
console.error('Failed to load game script:', error);
this.statusElement.textContent = 'Failed to load game';
};
-
+
document.head.appendChild(script);
} catch (error) {
console.error('Error loading game:', error);
@@ -367,18 +389,20 @@ class GameLoader {
private async fetchInitialData() {
try {
- const response = await fetch("/api/init");
+ const response = await fetch('/api/init');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = (await response.json()) as InitResponse;
- if (data.type === "init") {
- console.log(`Game initialized for user: ${data.username}, post: ${data.postId}`);
+ if (data.type === 'init') {
+ console.log(
+ `Game initialized for user: ${data.username}, post: ${data.postId}`
+ );
} else {
- console.error("Invalid response type from /api/init", data);
+ console.error('Invalid response type from /api/init', data);
}
} catch (error) {
- console.error("Error fetching initial data:", error);
+ console.error('Error fetching initial data:', error);
}
}
}
diff --git a/src/client/splash/splash.css b/src/client/splash.css
similarity index 91%
rename from src/client/splash/splash.css
rename to src/client/splash.css
index ed5275e..10ab4a5 100644
--- a/src/client/splash/splash.css
+++ b/src/client/splash.css
@@ -2,8 +2,9 @@
box-sizing: border-box;
margin: 0;
padding: 0;
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica,
- Arial, sans-serif;
+ font-family:
+ -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial,
+ sans-serif;
color: #000000;
}
@@ -83,4 +84,4 @@
.divider {
color: rgba(0, 0, 0, 0.1);
-}
\ No newline at end of file
+}
diff --git a/src/client/splash.html b/src/client/splash.html
index abbd49f..9bdff13 100644
--- a/src/client/splash.html
+++ b/src/client/splash.html
@@ -1,7 +1,7 @@
-
+
-
+
- Edit src/client/splash/splash.ts to get
- started.
+ Edit src/client/splash.ts to get started.