PlotManager.ts 8.81 KB
import { Emitter } from "simba-eventkit";
import { ActionManager } from "./ActionManager";
import { EditorEvents } from "./EditorEvents";
import { GameRecord } from "./game-data/GameRecord";
import { getPlot, initPlotsData, initCustomPlots, addCustomPlotData, getChapterPlots } from "./game-data/PlotsData";
import { PlotJumpType, ReadonlyPlot, ReadonlyPlots, SpecialPlotId, Plot } from "./model/PlotModel";
import { evalConditionExpr } from "./utils/PlotUtils";

export namespace PlotManager {
    let emitter = new Emitter;
    let currentPlots: ReadonlyPlot[] = [];

    export function getCurrentPlots(): ReadonlyPlots {
        return currentPlots;
    }

    // 剧情开始,需要在特定场景展现、执行Action等
    export const PlotStartEvent = emitter.createEvent<(plots: ReadonlyPlots) => void>();

    // 剧情将要开始,可根据将要开始的剧情修改存档数据保存
    export const PlotWillStart = emitter.createEvent<(plot: ReadonlyPlot | undefined, branch: number) => void>();

    export const PlotsRollbackEvent = emitter.createEvent<(plots: ReadonlyPlots) => void>();

    let customPlotId = SpecialPlotId.CustomPlotStart;

    let plotSelections: { [key: number]: number };

    export function getPlotSelection(plotId: number) {
        return plotSelections[plotId];
    }

    export async function init() {
        await initPlotsData();
        GameRecord.initRecords();
        customPlotId = GameRecord.globalVariables.customPlotId;
        initCustomPlots(GameRecord.globalVariables.customPlots);

        EditorEvents.START_PLOT_BRANCH.on((param: string) => {
            let plotId = parseInt(param);
            return startNewPlotBranch(plotId);
        });
    }

    // Add custom plot and return plot id
    export function addCustomPlot(plot: Plot): void {
        plot.id = customPlotId--;
        addCustomPlotData(plot);
        GameRecord.globalVariables.customPlotId = customPlotId;
        GameRecord.globalVariables.customPlots = getChapterPlots(-100);
        GameRecord.autoSave();
    }

    export function addCustomPlots(plots: Plot[]): void {
        for (let plot of plots) {
            plot.id = customPlotId--;
            addCustomPlotData(plot);
        }
        GameRecord.globalVariables.customPlotId = customPlotId;
        GameRecord.globalVariables.customPlots = getChapterPlots(-100);
        GameRecord.autoSave();
    }

    export async function start(recordIndex: number = 0) {
        let plots = GameRecord.startGameWithRecord(recordIndex);
        currentPlots = await Promise.all(plots.map(v => getPlot(v))) as ReadonlyPlot[];

        plotSelections = {};

        let items = GameRecord.getCurrentRecordItems();

        for (let p of items) {
            if (p.s) {
                for (let i = 0; i < p.p.length; i++) {
                    if (p.s[i] !== undefined) {
                        plotSelections[p.p[i]] = p.s[i];
                    }
                }
            }
        }

        // 检查未完待续剧情,如有新剧情修改存档数据
        for (let i = 0; i < plots.length; i++) {
            let plotId = plots[i];
            if (plotId === SpecialPlotId.ToBeContinued) {
                let index = items.length - 2;
                for (; index >= 0; index--) {
                    if (items[index].p[i] >= 0) {
                        plotId = items[index].p[i];
                        break;
                    }
                }
                if (plotId < 0) throw new Error("Record error.");
                let plot = (await getPlot(plotId))!;
                let sels = items[index].s;
                let nextPlot = (await getNextPlot(plot, sels ? sels[i] : undefined))!;
                if (nextPlot.id >= 0) {
                    for (let j = index + 1; j < items.length; j++) { // 修改存档数据
                        (items[index].p as number[])[i] = nextPlot.id;
                    }
                }
                currentPlots[i] = nextPlot as ReadonlyPlot;
            }
        }

        PlotStartEvent.emit(currentPlots);
        return currentPlots;
    }

    export async function startNewPlotBranch(plotId: number) {
        let index = currentPlots.findIndex(v => v && v.id === plotId);
        if (index >= 0) {
            console.warn("startNewPlotBranch: plot already exist.", plotId);
            return -1;
        }
        let plot = await getPlot(plotId);
        await Promise.all(PlotWillStart.emit(plot, index));
        let history = GameRecord.getCurrentRecordItems();
        index = history[history.length - 1].p.length;
        let plots = GameRecord.startPlot(plotId, index);
        currentPlots = await Promise.all(plots.map(v => getPlot(v))) as ReadonlyPlot[];
        PlotStartEvent.emit(currentPlots);
        return index;
    }

    export async function getNextPlot(plot: ReadonlyPlot, selectOption?: number) {
        let id = getNextPlotId(plot, selectOption);
        return getPlot(id);
    }

    function getNextPlotId(plot: ReadonlyPlot, selectOption?: number | string, saveToRecord?: boolean) {
        let nextPlotId: number;
        if (selectOption !== undefined && selectOption !== null) {
            if (plot.jump.type !== PlotJumpType.Select) {
                throw new Error("Plot jump type error");
            }
            let selectIndex: number;
            if (typeof selectOption === "string") {
                for (let i = 0; i < plot.jump.jumps.length; i++) {
                    if (plot.jump.jumps[i].id == selectOption) {
                        selectIndex = i;
                    }
                }
            } else {
                selectIndex = selectOption;
            }
            if (selectIndex! >= plot.jump.jumps.length) {
                throw new Error("Invalid option");

            }
            if (saveToRecord) {
                GameRecord.setSelectOptionForPlot(plot.id, selectIndex!);
            }
            nextPlotId = plot.jump.jumps[selectIndex!].toPlot;
        } else {
            if (plot.jump.type !== PlotJumpType.Condition) {
                throw new Error("Plot jump type error");
            }
            let conditions = plot.jump.conditionBranches;
            if (conditions) {
                try {
                    for (let cond of conditions) {
                        if (cond.condition && evalConditionExpr(cond.condition)) {
                            nextPlotId = cond.toPlot;
                            break;
                        }
                    }
                } catch (e) {

                }
            }
            if (nextPlotId! === undefined) nextPlotId = plot.jump.toPlot;
        }
        return nextPlotId;
    }

    export async function completePlot(plot: ReadonlyPlot, selectOption?: number | string, customData?: object) {
        let plotIndex = currentPlots.findIndex(v => v && v.id === plot.id);
        if (plotIndex < 0) throw new Error("Invalid plot");
        GameRecord.startTransaction();
        if (selectOption !== undefined) {
            await Promise.all(EditorEvents.emitter.emit("SELECT_OPTION_" + ((typeof selectOption === "number") ? selectOption + 1 : selectOption)));
        }
        await Promise.all(EditorEvents.PLOT_END.emit(plot.id.toString()));
        ActionManager.stop();
        let nextPlotId: number = getNextPlotId(plot, selectOption, true);
        if (customData) {
            GameRecord.setPlotCustomData(plotIndex, customData);
        }
        let nextPlot = await getPlot(nextPlotId);
        await Promise.all(PlotWillStart.emit(nextPlot, plotIndex));
        let plots = GameRecord.startPlot(nextPlotId, plotIndex);
        GameRecord.endTransaction();
        if (typeof selectOption === "number") {
            plotSelections[plot.id] = selectOption;
        }
        currentPlots = await Promise.all(plots.map(v => getPlot(v))) as ReadonlyPlot[];
        PlotStartEvent.emit(currentPlots);
        return currentPlots;
    }

    export async function rollbackToPlot(plot: number, reverseFind = false, preserveVars?: string[]) {
        GameRecord.rollbackToPlot(plot, reverseFind, preserveVars);
        return rollbackFinished();
    }

    export function rollbackToIndex(index: number, preserveVars?: string[]) {
        GameRecord.rollbackToIndex(index, preserveVars);
        return rollbackFinished();
    }

    async function rollbackFinished() {
        let recordItems = GameRecord.getCurrentRecordItems();
        plotSelections = {};
        for (let p of recordItems) {
            if (p.s) {
                for (let i = 0; i < p.p.length; i++) {
                    if (p.s[i] !== undefined) {
                        plotSelections[p.p[i]] = p.s[i];
                    }
                }
            }
        }
        let plots = recordItems[recordItems.length - 1].p;
        currentPlots = await Promise.all(plots.map(v => getPlot(v))) as ReadonlyPlot[];
        PlotsRollbackEvent.emit(currentPlots);
        return currentPlots;
    }
}