diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..d89b905 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,29 @@ +{ + "name": "combat-hud-hub", + "version": "0.2.2", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "combat-hud-hub", + "version": "0.2.2", + "license": "MIT", + "devDependencies": { + "playwright-core": "^1.40.0" + } + }, + "node_modules/playwright-core": { + "version": "1.61.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.0.tgz", + "integrity": "sha512-caX7TrY3Ml6egyDX0WUcTHDxodl/b51y5wJOdCEA36QviK/s2g081hvmGs8eaE3DWb6NYZQ6BjO/QkNRPenoPA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + } + } +} diff --git a/package.json b/package.json index 2fd569e..0b3c438 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ }, "author": "Kaysser Taylor", "license": "MIT", - "dependencies": { - "foundry-hooks-lib": "^0.4.0" + "devDependencies": { + "playwright-core": "^1.40.0" } } \ No newline at end of file diff --git a/scripts/enable-in-world.mjs b/scripts/enable-in-world.mjs new file mode 100644 index 0000000..2cc910c --- /dev/null +++ b/scripts/enable-in-world.mjs @@ -0,0 +1,93 @@ +// One-shot: enable combat-hud-hub in the active world via the GM +// settings UI. Logs in as Gamemaster, opens Game Settings → Manage +// Modules, toggles combat-hud-hub on, saves, then refreshes. + +import { chromium } from "playwright-core"; + +const FOUNDRY_URL = process.env.FOUNDRY_URL ?? "http://localhost:30000"; + +const browser = await chromium.launch({ + headless: true, + args: ["--no-sandbox", "--disable-gpu"], +}); +const page = await browser.newPage(); +page.on("console", (m) => { + const t = m.text(); + if (m.type() === "error" || t.includes("[probe]") || t.includes("combat-hud-hub")) { + console.log(`[c.${m.type()}] ${t}`); + } +}); + +console.log("Joining as Gamemaster..."); +await page.goto(FOUNDRY_URL); +await page.waitForSelector("#join-game-form"); +const userOpts = await page.$$eval('select[name="userid"] option', (os) => + os.map((o) => ({ value: o.value, label: o.textContent, disabled: o.disabled })), +); +const gm = userOpts.find((o) => o.label === "Gamemaster" && !o.disabled && o.value); +if (!gm) { + console.error("No Gamemaster slot available."); + process.exit(1); +} +await page.selectOption('select[name="userid"]', gm.value); +await page.fill('input[name="password"]', ""); +await page.click('button[name="join"]'); +await page.waitForSelector("#ui-left", { timeout: 30_000 }); +console.log("Joined."); + +// Wait for the world to be ready. +await page.waitForFunction(() => game?.ready === true, { timeout: 30_000 }); + +// Check current state. +const before = await page.evaluate(() => { + const m = game.modules.get("combat-hud-hub"); + return m ? { active: m.active, found: true } : { found: false }; +}); +console.log("Before:", before); + +if (before.found && before.active) { + console.log("combat-hud-hub already enabled."); + await browser.close(); + process.exit(0); +} + +// Open Game Settings (the gear icon). +console.log("Opening Game Settings..."); +await page.evaluate(() => game.settings?.activateImportDialog ? null : null); +// The GM can call game.settings.sheet.render(true) — but better: click the menu. +const settingsOpened = await page.evaluate(() => { + // Try the menu path: Settings → Game Settings... → Modules + // The simplest API call is to manipulate core.moduleConfiguration directly. + // We can read the current config. + const cfg = game.settings.get("core", "moduleConfiguration"); + return cfg ?? {}; +}); +console.log("Current moduleConfiguration:", Object.keys(settingsOpened).filter(k => settingsOpened[k])); + +// Set the moduleConfiguration to include combat-hud-hub. +const result = await page.evaluate(() => { + const cfg = game.settings.get("core", "moduleConfiguration") ?? {}; + if (cfg["combat-hud-hub"]) return { ok: true, reason: "already enabled" }; + cfg["combat-hud-hub"] = true; + return game.settings.set("core", "moduleConfiguration", cfg) + .then(() => ({ ok: true })) + .catch((e) => ({ ok: false, reason: e.message })); +}); +console.log("Set result:", result); + +if (result.ok) { + console.log("combat-hud-hub enabled. The world needs a refresh for the new module to initialize."); + console.log("Refreshing in 2s..."); + await new Promise(r => setTimeout(r, 2000)); + await page.reload(); + await page.waitForSelector("#join-game-form", { timeout: 30_000 }); + console.log("Reloaded. Re-joining as Gamemaster..."); + await page.selectOption('select[name="userid"]', gm.value); + await page.fill('input[name="password"]', ""); + await page.click('button[name="join"]'); + await page.waitForSelector("#ui-left", { timeout: 30_000 }); + await page.waitForFunction(() => game?.modules?.get("combat-hud-hub")?.active === true, { timeout: 30_000 }); + console.log("combat-hud-hub is now active."); +} + +await browser.close(); \ No newline at end of file diff --git a/scripts/main.js b/scripts/main.js index c4d7651..6122276 100644 --- a/scripts/main.js +++ b/scripts/main.js @@ -1,4 +1,4 @@ -// combat-hud-hub — module entry point (v0.2.0). +// combat-hud-hub — module entry point (v0.2.2). // // Generic combat HUD host. Consumer modules register sections via // the public API; the hub aggregates built-in core sections + @@ -12,7 +12,7 @@ import { CombatHudHubApp, getHud, registerCoreSections } from "./hud.js"; const MODULE_ID = "combat-hud-hub"; -const MODULE_VERSION = "0.2.0"; +const MODULE_VERSION = "0.2.2"; const COMBAT_HOOK_NAMES = [ "combatStart", diff --git a/tests/verify-combat-hud-hub.mjs b/tests/verify-combat-hud-hub.mjs index bd62a84..bd62317 100644 --- a/tests/verify-combat-hud-hub.mjs +++ b/tests/verify-combat-hud-hub.mjs @@ -122,7 +122,8 @@ const mod = game.modules.get("combat-hud-hub"); assert("A.1 module registers at init", !!mod); assert("A.2 api surface present", !!mod?.api); assert("A.3 api.id is combat-hud-hub", mod.api.id === "combat-hud-hub"); -assert("A.4 api.version is 0.2.0", mod.api.version === "0.2.0"); +assert("A.4 api.version is 0.2.2", mod.api.version === "0.2.2", + `got=${mod.api.version}`); assert("A.5 addSection exported", typeof mod.api.addSection === "function"); assert("A.6 removeSection exported", typeof mod.api.removeSection === "function"); assert("A.7 pushFeedEntry exported", typeof mod.api.pushFeedEntry === "function");