chore: sync latest changes
This commit is contained in:
@@ -5,6 +5,7 @@ import { StateManager } from './StateManager'
|
||||
import { ChoiceSystem } from '../systems/ChoiceSystem'
|
||||
import { QTESystem } from '../systems/QTESystem'
|
||||
import { AudioSystem } from '../systems/AudioSystem'
|
||||
import { AchievementSystem } from '../systems/AchievementSystem'
|
||||
|
||||
type EventHandler = (...args: any[]) => void
|
||||
|
||||
@@ -15,6 +16,7 @@ export class Engine {
|
||||
choiceSystem: ChoiceSystem
|
||||
qteSystem: QTESystem
|
||||
audioSystem: AudioSystem
|
||||
achievementSystem: AchievementSystem
|
||||
|
||||
private currentScene: SceneNode | null = null
|
||||
private events: Map<EngineEvent, Set<EventHandler>> = new Map()
|
||||
@@ -42,6 +44,11 @@ export class Engine {
|
||||
this.choiceSystem = new ChoiceSystem()
|
||||
this.qteSystem = new QTESystem()
|
||||
this.audioSystem = new AudioSystem()
|
||||
this.achievementSystem = new AchievementSystem()
|
||||
|
||||
this.stateManager.onAfterApply = (vars) => {
|
||||
this.achievementSystem.check(vars)
|
||||
}
|
||||
|
||||
this.videoManager.onTimeUpdate(this.onTimeUpdate)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ export class StateManager {
|
||||
variables: Record<string, number> = {}
|
||||
flags: Set<string> = new Set()
|
||||
history: ChoiceRecord[] = []
|
||||
onAfterApply: ((variables: Record<string, number>) => void) | null = null
|
||||
|
||||
init(initialVars: Record<string, number>) {
|
||||
this.variables = { ...initialVars }
|
||||
@@ -72,6 +73,7 @@ export class StateManager {
|
||||
break
|
||||
}
|
||||
}
|
||||
this.onAfterApply?.(this.variables)
|
||||
}
|
||||
|
||||
recordChoice(choice: ChoiceRecord) {
|
||||
|
||||
81
engine/systems/AchievementSystem.ts
Normal file
81
engine/systems/AchievementSystem.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import type { AchievementDef } from '../types'
|
||||
|
||||
type UnlockCallback = (achievement: AchievementDef) => void
|
||||
|
||||
export class AchievementSystem {
|
||||
private definitions: AchievementDef[] = []
|
||||
private unlockedIds: Set<string> = new Set()
|
||||
private onUnlock: UnlockCallback | null = null
|
||||
private toastQueue: string[] = []
|
||||
private toastActive = false
|
||||
|
||||
init(defs: AchievementDef[], alreadyUnlocked: string[]) {
|
||||
this.definitions = defs
|
||||
this.unlockedIds = new Set(alreadyUnlocked)
|
||||
this.toastQueue = []
|
||||
this.toastActive = false
|
||||
}
|
||||
|
||||
setUnlockCallback(cb: UnlockCallback) {
|
||||
this.onUnlock = cb
|
||||
}
|
||||
|
||||
check(variables: Record<string, number>) {
|
||||
for (const def of this.definitions) {
|
||||
if (this.unlockedIds.has(def.id)) continue
|
||||
|
||||
const cond = def.condition
|
||||
const val = variables[cond.variable] ?? 0
|
||||
let matched = false
|
||||
|
||||
switch (cond.op) {
|
||||
case '==': matched = val === cond.value; break
|
||||
case '!=': matched = val !== cond.value; break
|
||||
case '>': matched = val > (cond.value as number); break
|
||||
case '<': matched = val < (cond.value as number); break
|
||||
case '>=': matched = val >= (cond.value as number); break
|
||||
case '<=': matched = val <= (cond.value as number); break
|
||||
}
|
||||
|
||||
if (matched) {
|
||||
this.unlockedIds.add(def.id)
|
||||
this.onUnlock?.(def)
|
||||
this.enqueueToast(def.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enqueueToast(id: string) {
|
||||
this.toastQueue.push(id)
|
||||
if (!this.toastActive) {
|
||||
this.showNextToast()
|
||||
}
|
||||
}
|
||||
|
||||
private showNextToast() {
|
||||
if (this.toastQueue.length === 0) {
|
||||
this.toastActive = false
|
||||
return
|
||||
}
|
||||
this.toastActive = true
|
||||
// toast is shown by the UI layer watching toastQueue
|
||||
// UI calls toastDismissed() when the toast animation finishes
|
||||
}
|
||||
|
||||
getToastQueue(): string[] {
|
||||
return [...this.toastQueue]
|
||||
}
|
||||
|
||||
toastDismissed(id: string) {
|
||||
this.toastQueue = this.toastQueue.filter((i) => i !== id)
|
||||
this.showNextToast()
|
||||
}
|
||||
|
||||
getUnlockedIds(): string[] {
|
||||
return [...this.unlockedIds]
|
||||
}
|
||||
|
||||
getDefinitions(): AchievementDef[] {
|
||||
return this.definitions
|
||||
}
|
||||
}
|
||||
@@ -21,17 +21,23 @@ interface WatchedRecord {
|
||||
timestamp: number
|
||||
}
|
||||
|
||||
interface AchievementRecord {
|
||||
achievementId: string
|
||||
}
|
||||
|
||||
class SaveDB extends Dexie {
|
||||
saves!: Table<SaveRecord, number>
|
||||
unlocks!: Table<UnlockRecord, string>
|
||||
watched!: Table<WatchedRecord, string>
|
||||
achievements!: Table<AchievementRecord, string>
|
||||
|
||||
constructor() {
|
||||
super('MovieGameSaves')
|
||||
this.version(4).stores({
|
||||
this.version(5).stores({
|
||||
saves: '++id, slot',
|
||||
unlocks: 'chapterId',
|
||||
watched: 'sceneId',
|
||||
achievements: 'achievementId',
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -120,4 +126,16 @@ export class SaveSystem {
|
||||
const records = await db.watched.toArray()
|
||||
return records.map((r) => r.sceneId)
|
||||
}
|
||||
|
||||
async unlockAchievement(id: string) {
|
||||
const exists = await db.achievements.get(id)
|
||||
if (!exists) {
|
||||
await db.achievements.put({ achievementId: id })
|
||||
}
|
||||
}
|
||||
|
||||
async getUnlockedAchievements(): Promise<string[]> {
|
||||
const records = await db.achievements.toArray()
|
||||
return records.map((r) => r.achievementId)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,11 +79,21 @@ export interface ChapterInfo {
|
||||
defaultVariables?: Record<string, number>
|
||||
}
|
||||
|
||||
export interface AchievementDef {
|
||||
id: string
|
||||
title: string
|
||||
description: string
|
||||
icon?: string
|
||||
hidden?: boolean
|
||||
condition: Condition
|
||||
}
|
||||
|
||||
export interface GameData {
|
||||
scenes: Record<string, SceneNode>
|
||||
startScene: string
|
||||
variables: Record<string, number>
|
||||
chapters?: ChapterInfo[]
|
||||
achievements?: AchievementDef[]
|
||||
}
|
||||
|
||||
export interface ChoiceRecord {
|
||||
@@ -115,3 +125,4 @@ export type EngineEvent =
|
||||
| 'hotspotRequest'
|
||||
| 'hotspotUpdate'
|
||||
| 'chapterUnlock'
|
||||
| 'achievementUnlock'
|
||||
|
||||
Reference in New Issue
Block a user