diff --git a/src/composables/useGameEngine.ts b/src/composables/useGameEngine.ts index 879e992..22a6fea 100644 --- a/src/composables/useGameEngine.ts +++ b/src/composables/useGameEngine.ts @@ -15,105 +15,112 @@ export function useGameEngine(videoEls: () => [HTMLVideoElement | null, HTMLVide ;(window as any).__store = store } + engine.setChapterUnlockHandler(async (chapterId) => { + await saveSystem.unlockChapter(chapterId) + store.addUnlockedChapter(chapterId) + }) + + engine.on('sceneChange', (scene) => { + store.setScene(scene) + store.clearChoices() + store.clearTimer() + store.clearHotspots() + store.setIsImageScene(scene.type === 'image') + saveGame(0) + }) + + engine.on('choiceRequest', (choiceList) => { + store.setChoices(choiceList) + }) + + engine.on('choiceTimer', (timerState) => { + store.setTimer(timerState.total, timerState.remaining) + }) + + engine.on('choiceTimeout', () => { + store.clearChoices() + store.clearTimer() + }) + + engine.on('hotspotRequest', (list) => { + store.setHotspots(list) + }) + + engine.on('hotspotUpdate', (list) => { + store.setHotspots(list) + }) + + engine.on('videoEnd', () => { + try { + const video = engine.videoManager.getActiveVideoElement() + if (video && video.readyState >= 2) { + const canvas = document.createElement('canvas') + canvas.width = 320 + canvas.height = 180 + const ctx = canvas.getContext('2d') + if (ctx) { + ctx.drawImage(video, 0, 0, 320, 180) + lastThumbnail = canvas.toDataURL('image/jpeg', 0.6) + } + } + } catch { + /* ignore */ + } + }) + + engine.on('gameEnd', () => { + store.setGameEnded(true) + engine.choiceSystem.stop() + }) + + engine.on('qteTrigger', (qte) => { + store.showQTE(qte) + }) + + engine.on('qteTimer', ({ remaining }) => { + store.updateQTE(remaining) + }) + + engine.on('qteResult', ({ success }) => { + store.resolveQTE(success) + }) + + engine.videoManager.onTimeUpdate((t: number) => { + store.setVideoTime(t) + }) + async function loadGame(dataUrl: string) { const resp = await fetch(dataUrl) const data: GameData = await resp.json() engine.sceneManager.load(data) engine.stateManager.init(data.variables) store.setChapters(data.chapters || []) - const unlocked = await saveSystem.getUnlockedChapters() store.setUnlockedChapters(unlocked) } - function registerEvents() { - engine.setChapterUnlockHandler(async (chapterId) => { - await saveSystem.unlockChapter(chapterId) - store.addUnlockedChapter(chapterId) - }) - - engine.on('sceneChange', (scene) => { - store.setScene(scene) - store.clearChoices() - store.clearTimer() - store.clearHotspots() - store.setIsImageScene(scene.type === 'image') - saveGame(0) - }) - - engine.on('choiceRequest', (choiceList) => { - store.setChoices(choiceList) - }) - - engine.on('choiceTimer', (timerState) => { - store.setTimer(timerState.total, timerState.remaining) - }) - - engine.on('choiceTimeout', () => { - store.clearChoices() - store.clearTimer() - }) - - engine.on('hotspotRequest', (list) => { - store.setHotspots(list) - }) - - engine.on('hotspotUpdate', (list) => { - store.setHotspots(list) - }) - - engine.on('videoEnd', () => { - try { - const video = engine.videoManager.getActiveVideoElement() - if (video && video.readyState >= 2) { - const canvas = document.createElement('canvas') - canvas.width = 320 - canvas.height = 180 - const ctx = canvas.getContext('2d') - if (ctx) { - ctx.drawImage(video, 0, 0, 320, 180) - lastThumbnail = canvas.toDataURL('image/jpeg', 0.6) - } - } - } catch { /* ignore */ } - }) - - engine.on('gameEnd', () => { - store.setGameEnded(true) - engine.choiceSystem.stop() - }) - - engine.on('qteTrigger', (qte) => { - store.showQTE(qte) - }) - - engine.on('qteTimer', ({ remaining }) => { - store.updateQTE(remaining) - }) - - engine.on('qteResult', ({ success }) => { - store.resolveQTE(success) - }) - - engine.videoManager.onTimeUpdate((t: number) => { - store.setVideoTime(t) - }) + function ensureVideo() { + const [elA, elB] = videoEls() + if (elA && elB) engine.videoManager.attach(elA, elB) } function start() { - const [elA, elB] = videoEls() - if (elA && elB) engine.videoManager.attach(elA, elB) - registerEvents() + ensureVideo() engine.start() } async function resumeAutoSave(): Promise { - const [elA, elB] = videoEls() - if (elA && elB) engine.videoManager.attach(elA, elB) - registerEvents() + ensureVideo() + store.setGameEnded(false) return await loadGameFromSlot(0) } + function startChapter(chapterId: string) { + ensureVideo() + store.setGameEnded(false) + engine.startChapter(chapterId) + } + function makeChoice(index: number) { const scene = store.currentScene if (!scene?.choices) return @@ -131,17 +138,9 @@ export function useGameEngine(videoEls: () => [HTMLVideoElement | null, HTMLVide } } - function startChapter(chapterId: string) { - const [elA, elB] = videoEls() - if (elA && elB) engine.videoManager.attach(elA, elB) - store.setGameEnded(false) - engine.startChapter(chapterId) - } - async function saveGame(slot: number) { const state = engine.stateManager const currentScene = store.currentScene - await saveSystem.save(slot, { timestamp: Date.now(), currentScene: currentScene?.id ?? '', @@ -156,7 +155,6 @@ export function useGameEngine(videoEls: () => [HTMLVideoElement | null, HTMLVide async function loadGameFromSlot(slot: number): Promise { const data = await saveSystem.load(slot) if (!data) return false - store.setGameEnded(false) engine.resumeScene(data.currentScene, { variables: data.variables, @@ -179,6 +177,17 @@ export function useGameEngine(videoEls: () => [HTMLVideoElement | null, HTMLVide destroy() }) - return { loadGame, start, resumeAutoSave, makeChoice, clickHotspot, startChapter, - saveGame, loadGameFromSlot, refreshSaves, engine, saveSystem } + return { + loadGame, + start, + resumeAutoSave, + makeChoice, + clickHotspot, + startChapter, + saveGame, + loadGameFromSlot, + refreshSaves, + engine, + saveSystem, + } }