Refactor platform UI and data flow across core pages
All checks were successful
Build Org Website / build (push) Successful in 50s

This commit is contained in:
gitea-actions
2026-07-30 12:22:44 +01:00
parent fe8acac707
commit a0c78929b3
45 changed files with 1168 additions and 348 deletions

86
assets/scripts/pages/princess-lima-game.js Normal file → Executable file
View File

@@ -7,6 +7,7 @@
const Scenes = window.PrincessLimaScenes;
const UI = window.PrincessLimaUI;
const Audio = window.PrincessLimaAudio;
const Intro = window.PrincessLimaIntro;
const controller = {
root: null,
@@ -14,15 +15,16 @@
scene: null,
state: null,
audio: null,
intro: null,
ui: null,
locked: true,
transitioning: false,
move: { up: false, down: false, left: false, right: false },
lastSaveAt: 0,
systemReducedMotion: window.matchMedia("(prefers-reduced-motion: reduce)").matches,
getState: () => controller.state,
reducedMotion: () => controller.state && controller.state.settings.reducedMotion !== null
? controller.state.settings.reducedMotion : controller.systemReducedMotion,
debugCollision: false,
devWarn: (message) => {
if (["localhost", "127.0.0.1"].includes(window.location.hostname)) console.warn(`[Princess Lima RPG] ${message}`);
}
@@ -32,19 +34,37 @@
function init() {
const root = document.querySelector("[data-princess-lima-rpg]");
if (!root || !Data || !State || !Systems || !Scenes || !UI || !Audio || !window.Phaser) return gracefulFailure();
if (!root || !Data || !State || !Systems || !Scenes || !UI || !Audio || !Intro || !window.PrincessLimaMaps || !window.Phaser) return gracefulFailure();
controller.root = root;
controller.state = loadState();
controller.audio = Audio.create(() => controller.state);
controller.debugCollision = localDebug();
controller.ui = UI.create(root, {
getState: () => controller.state,
onLock: (value) => { controller.locked = value; },
onUseTonic: useTonic,
onSetting: updateSetting,
onFullscreen: toggleFullscreen,
onReplayIntro: () => {
controller.ui.close();
startIntro(true);
},
onReset: resetSave,
onRespawn: respawn
});
controller.intro = Intro.create(root, {
onLock: (value) => { controller.locked = value; },
onAudioUnlock: () => controller.audio.unlock(),
onComplete: completeIntro,
playVoice: (key) => controller.audio.playVoice(key),
stopVoice: () => controller.audio.stopVoice(),
playEffect: (key, volume) => controller.audio.play(key, volume),
narrationEnabled: () => !controller.state || controller.state.settings.narrationEnabled,
subtitlesEnabled: () => !controller.state || controller.state.settings.subtitles,
soundEnabled: () => Boolean(controller.state && controller.state.settings.soundEnabled),
toggleSound,
status
});
bindInterface();
startEngine();
window.addEventListener("error", (event) => {
@@ -85,14 +105,14 @@
const classes = Scenes.createSceneClasses(controller);
controller.game = new Phaser.Game({
type: Phaser.AUTO,
width: Data.WIDTH,
width: 1000,
height: Data.HEIGHT,
parent: "princess-lima-game",
pixelArt: true,
roundPixels: true,
backgroundColor: "#0a0b12",
physics: { default: "arcade", arcade: { debug: localDebug(), gravity: { x: 0, y: 0 } } },
scale: { mode: Phaser.Scale.FIT, autoCenter: Phaser.Scale.CENTER_BOTH, width: Data.WIDTH, height: Data.HEIGHT },
scale: { mode: Phaser.Scale.FIT, autoCenter: Phaser.Scale.CENTER_BOTH, width: 1000, height: Data.HEIGHT },
scene: classes,
render: { antialias: false, pixelArt: true }
});
@@ -116,7 +136,16 @@
const root = controller.root;
root.querySelector("[data-lima-new]").addEventListener("click", () => openSetup());
root.querySelector("[data-lima-continue]").addEventListener("click", () => {
if (controller.state) beginAdventure();
if (controller.state) {
controller.audio.unlock();
if (controller.state.introSeen) beginAdventure();
else startIntro(false);
}
});
root.querySelector("[data-lima-replay-intro]").addEventListener("click", () => startIntro(true));
root.querySelector("[data-lima-menu-settings]").addEventListener("click", () => {
if (controller.state) openPanel("settings");
else controller.ui.show("settings-help", "Settings", "<p>Create a traveller to save audio and accessibility preferences. The introduction always includes subtitles and can be muted or skipped.</p>");
});
root.querySelector("[data-lima-credits]").addEventListener("click", () => controller.ui.show(
"credits", "Credits",
@@ -124,18 +153,6 @@
));
root.querySelector("[data-lima-setup-form]").addEventListener("submit", submitSetup);
root.querySelector("[data-lima-setup-cancel]").addEventListener("click", closeSetup);
root.querySelectorAll("[data-lima-move]").forEach((button) => {
const direction = button.dataset.limaMove;
const on = (event) => { event.preventDefault(); controller.move[direction] = true; focusGame(); };
const off = (event) => { event.preventDefault(); controller.move[direction] = false; };
button.addEventListener("pointerdown", on);
button.addEventListener("pointerup", off);
button.addEventListener("pointercancel", off);
button.addEventListener("pointerleave", off);
});
bindButton("[data-lima-attack]", () => controller.scene && controller.scene.attack(controller.scene.time.now));
bindButton("[data-lima-interact]", () => controller.scene && controller.scene.interact());
bindButton("[data-lima-item]", useTonic);
bindButton("[data-lima-pause]", () => openPanel("pause"));
bindButton("[data-lima-inventory]", () => openPanel("inventory"));
bindButton("[data-lima-quests]", () => openPanel("quests"));
@@ -172,20 +189,41 @@
controller.state = State.fresh(name, appearance);
save();
closeSetup();
beginAdventure();
startIntro(false);
}
function beginAdventure() {
controller.audio.unlock();
controller.root.querySelector("[data-lima-menu]").hidden = true;
controller.root.querySelector("[data-lima-intro]").hidden = true;
controller.root.querySelector("[data-lima-hud]").hidden = false;
controller.root.querySelector("[data-lima-touch]").hidden = false;
controller.root.querySelector("[data-lima-status-stack]").hidden = false;
controller.locked = false;
controller.game.scene.start("LimaWorld", { region: controller.state.region });
focusGame();
}
function startIntro(replay) {
controller.audio.unlock();
controller.root.querySelector("[data-lima-menu]").hidden = true;
controller.root.querySelector("[data-lima-hud]").hidden = true;
controller.root.querySelector("[data-lima-status-stack]").hidden = true;
controller.intro.start({ replay });
}
function completeIntro(result) {
if (controller.state && !result.replay) {
controller.state.introSeen = true;
controller.state = State.normalize(controller.state);
save();
beginAdventure();
return;
}
controller.locked = true;
controller.root.querySelector("[data-lima-menu]").hidden = false;
controller.root.querySelector("[data-lima-menu] button").focus();
}
function travel(region, spawn) {
if (controller.transitioning || !Data.MAPS[region]) return;
controller.transitioning = true;
@@ -311,12 +349,20 @@
text("[data-lima-chapter]", `Chapter ${controller.state.chapter}`);
text("[data-lima-objective]", Systems.currentObjective(controller.state));
text("[data-lima-tonics]", `Tonic ×${Systems.quantity(controller.state, "healing_tonic")}`);
text("[data-lima-weapon]", controller.state.equipment.weapon ? Data.ITEMS[controller.state.equipment.weapon].name : "Unarmed");
text("[data-lima-armour]", controller.state.equipment.armour ? Data.ITEMS[controller.state.equipment.armour].name : "None");
text("[data-lima-selected-item]", `Healing Tonic ×${Systems.quantity(controller.state, "healing_tonic")}`);
text("[data-lima-currency]", String(Systems.quantity(controller.state, "moon_coin")));
text("[data-lima-sound]", controller.state.settings.soundEnabled ? "Sound On" : "Sound Muted");
const healthBar = controller.root.querySelector(".lima-hud__healthbar i");
if (healthBar) healthBar.style.width = `${controller.state.health / controller.state.maxHealth * 100}%`;
controller.root.classList.toggle("is-high-contrast", controller.state.settings.highContrast);
}
function cycleItem() {
status("Healing Tonic selected. Press Q to use it.");
}
function prompt(message) {
text("[data-lima-prompt]", message || "Explore the road ahead.");
}
@@ -358,6 +404,6 @@
Object.assign(controller, {
ready, travel, reloadRegion, persistPosition, setState, updateHud, status, prompt, boss,
openDialogue, openPanel, gameOver, openEnding, useTonic
openDialogue, openPanel, gameOver, openEnding, useTonic, toggleFullscreen, toggleSound, cycleItem
});
}());