| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import Phaser from 'phaser'; |
| import { type DialogueEntry } from '../scenes/BaseChapterScene'; |
|
|
| export class DialogueManager extends Phaser.Events.EventEmitter { |
| private scene: Phaser.Scene; |
| private dialogues: DialogueEntry[] = []; |
| private currentIndex: number = 0; |
| private isPlaying: boolean = false; |
| private isWaitingForInput: boolean = false; |
| private isChoiceActive: boolean = false; |
| |
| private isAutoAdvancing: boolean = false; |
|
|
| constructor(scene: Phaser.Scene) { |
| super(); |
| this.scene = scene; |
| } |
|
|
| |
|
|
| |
| loadDialogues(dialogues: DialogueEntry[]): void { |
| this.dialogues = [...dialogues]; |
| this.currentIndex = 0; |
| this.isPlaying = false; |
| this.isWaitingForInput = false; |
| this.isChoiceActive = false; |
| this.isAutoAdvancing = false; |
| } |
|
|
| |
| start(fromIndex?: number): void { |
| if (this.dialogues.length === 0) { |
| this.emit('dialogueComplete'); |
| return; |
| } |
| this.currentIndex = fromIndex ?? 0; |
| this.isPlaying = true; |
| this.processEntry(this.dialogues[this.currentIndex]); |
| } |
|
|
| |
| advance(): void { |
| |
| |
| if (!this.isPlaying || this.isChoiceActive || this.isAutoAdvancing) return; |
|
|
| this.isWaitingForInput = false; |
| this.currentIndex++; |
| if (this.currentIndex >= this.dialogues.length) { |
| this.isPlaying = false; |
| this.emit('dialogueComplete'); |
| return; |
| } |
| this.processEntry(this.dialogues[this.currentIndex]); |
| } |
|
|
| |
| selectChoice(optionIndex: number): void { |
| if (!this.isChoiceActive) return; |
| const entry = this.dialogues[this.currentIndex]; |
| if (!entry || entry.type !== 'choice' || !entry.options) return; |
|
|
| |
| const visible = (entry.options ?? []).filter( |
| (opt) => !opt.condition || opt.condition(), |
| ); |
| const selected = visible[optionIndex]; |
| if (!selected) return; |
|
|
| this.isChoiceActive = false; |
| this.emit('choiceSelected', entry.id ?? '', selected, optionIndex); |
|
|
| |
| this.advance(); |
| } |
|
|
| |
| skipAll(): void { |
| this.isPlaying = false; |
| this.isWaitingForInput = false; |
| this.isChoiceActive = false; |
| this.currentIndex = this.dialogues.length; |
| this.emit('dialogueComplete'); |
| } |
|
|
| |
|
|
| isActive(): boolean { |
| return this.isPlaying; |
| } |
|
|
| getCurrentEntry(): DialogueEntry | undefined { |
| return this.dialogues[this.currentIndex]; |
| } |
|
|
| getProgress(): number { |
| return this.dialogues.length > 0 |
| ? this.currentIndex / this.dialogues.length |
| : 0; |
| } |
|
|
| isWaiting(): boolean { |
| return this.isWaitingForInput; |
| } |
|
|
| |
|
|
| private processEntry(entry: DialogueEntry): void { |
| switch (entry.type) { |
| case 'text': |
| this.processTextEntry(entry); |
| break; |
| case 'choice': |
| this.processChoiceEntry(entry); |
| break; |
| case 'event': |
| this.processEventEntry(entry); |
| break; |
| case 'character': |
| this.processCharacterEntry(entry); |
| break; |
| case 'branch': |
| this.processBranchEntry(entry); |
| break; |
| case 'wait': |
| this.processWaitEntry(entry); |
| break; |
| default: |
| this.advance(); |
| break; |
| } |
| } |
|
|
| private processTextEntry(entry: DialogueEntry): void { |
| this.isWaitingForInput = true; |
| this.emit( |
| 'showText', |
| entry.speaker ?? '', |
| entry.text ?? '', |
| entry.expression, |
| ); |
| this.emit('waitingForInput'); |
| } |
|
|
| private processChoiceEntry(entry: DialogueEntry): void { |
| const visible = (entry.options ?? []).filter( |
| (opt) => !opt.condition || opt.condition(), |
| ); |
| |
| if (visible.length === 0) { |
| this.advance(); |
| return; |
| } |
| this.isChoiceActive = true; |
| this.emit('showChoice', entry.id ?? '', entry.prompt ?? '', visible); |
| } |
|
|
| private processEventEntry(entry: DialogueEntry): void { |
| this.emit('triggerEvent', entry.action ?? '', entry.data); |
| |
| this.advance(); |
| } |
|
|
| private processCharacterEntry(entry: DialogueEntry): void { |
| this.emit( |
| 'characterAction', |
| entry.characterId ?? '', |
| entry.action ?? '', |
| entry.position, |
| ); |
| |
| |
| this.isAutoAdvancing = true; |
| const delay = entry.action === 'enter' ? 300 : 50; |
| this.scene.time.delayedCall(delay, () => { |
| this.isAutoAdvancing = false; |
| this.advance(); |
| }); |
| } |
|
|
| private processBranchEntry(entry: DialogueEntry): void { |
| const result = entry.condition?.() ?? true; |
| const branch = result ? entry.trueBranch : entry.falseBranch; |
| if (branch && branch.length > 0) { |
| |
| this.dialogues.splice(this.currentIndex + 1, 0, ...branch); |
| } |
| this.advance(); |
| } |
|
|
| private processWaitEntry(entry: DialogueEntry): void { |
| |
| this.isAutoAdvancing = true; |
| this.scene.time.delayedCall(entry.duration ?? 1000, () => { |
| this.isAutoAdvancing = false; |
| this.advance(); |
| }); |
| } |
| } |
|
|