Newer
Older
rpg / apps / game / src / systems / DialogueSystem.ts
import {
    Container,
    DialogueBox,
    DialogueRunner,
    GameState,
    type DialogueBoxOptions,
    type DialogueGraph
} from '@rpg/engine';

/**
 * Диалоги игры: DialogueRunner движка ходит по графам и применяет флаги
 * к GameState, DialogueBox рисует реплики и варианты.
 */
export class DialogueSystem {
    private box: DialogueBox;
    private runner: DialogueRunner;

    /** Сюжетное событие «диалог завершён» — для триггеров/квестов. */
    onDialogueFinished: ((id: string) => void) | null = null;
    /** Звук на каждую новую реплику (колокольчик). */
    onLineShown: (() => void) | null = null;
    /** id стартованного диалога (для onFinish). */
    private currentId: string | null = null;

    constructor(uiRoot: Container, state: GameState, options: DialogueBoxOptions) {
        this.box = new DialogueBox({
            ...options,
            onChoice: (index) => this.runner.pick(index)
        });
        uiRoot.addChild(this.box.view);

        this.runner = new DialogueRunner(state);
        this.runner.setView({
            show: ({ speaker, text, choices }) => {
                this.box.show({ speaker, text, choices });
                this.onLineShown?.();
            },
            hide: () => this.box.hide()
        });
    }

    get active(): boolean {
        return this.runner.active;
    }

    /** Начать диалог по ключу из data/dialogues.ts. */
    start(graph: DialogueGraph, id: string): void {
        this.currentId = id;
        this.runner.onFinish = () => {
            const id = this.currentId;
            this.currentId = null;
            if (id) this.onDialogueFinished?.(id);
        };
        this.runner.start(graph);
    }

    /** Клик/пробел: следующая реплика (во время выбора игнорируется). */
    advance(): void {
        this.runner.advance();
    }
}