74 lines
2.7 KiB
TypeScript
74 lines
2.7 KiB
TypeScript
import { create, type StoreApi, type UseBoundStore } from "zustand";
|
|
import {
|
|
createDungeonEncounterSnapshot,
|
|
reduceDungeonEncounter,
|
|
registerDungeonEncounterProvider,
|
|
resetDungeonEncounterSnapshot,
|
|
validateDungeonEncounterDefinition,
|
|
} from "./dungeonEncounterRuntime";
|
|
import type {
|
|
DungeonEncounterDefinition,
|
|
DungeonEncounterEffect,
|
|
DungeonEncounterEvent,
|
|
DungeonEncounterResetScope,
|
|
DungeonEncounterSnapshot,
|
|
} from "./dungeonEncounterTypes";
|
|
|
|
export interface DungeonEncounterStoreState extends DungeonEncounterSnapshot {
|
|
readonly dispatch: (event: DungeonEncounterEvent) => readonly DungeonEncounterEffect[];
|
|
readonly reset: (scope?: DungeonEncounterResetScope, now?: number) => void;
|
|
}
|
|
|
|
function serializableSnapshot(state: DungeonEncounterStoreState): DungeonEncounterSnapshot {
|
|
return {
|
|
schemaVersion: state.schemaVersion,
|
|
encounterId: state.encounterId,
|
|
phase: state.phase,
|
|
phaseStartedAt: state.phaseStartedAt,
|
|
flags: state.flags,
|
|
counters: state.counters,
|
|
timers: state.timers,
|
|
worldObjectStates: state.worldObjectStates,
|
|
transitionStates: state.transitionStates,
|
|
activeSpawnGroupIds: state.activeSpawnGroupIds,
|
|
completedObjectiveIds: state.completedObjectiveIds,
|
|
consumedActionIds: state.consumedActionIds,
|
|
checkpointId: state.checkpointId,
|
|
revision: state.revision,
|
|
};
|
|
}
|
|
|
|
export function createDungeonEncounterStore(
|
|
definition: DungeonEncounterDefinition,
|
|
): UseBoundStore<StoreApi<DungeonEncounterStoreState>> {
|
|
validateDungeonEncounterDefinition(definition);
|
|
const useStore = create<DungeonEncounterStoreState>((set, get) => ({
|
|
...createDungeonEncounterSnapshot(definition),
|
|
dispatch: (event) => {
|
|
const snapshot = serializableSnapshot(get());
|
|
const result = reduceDungeonEncounter(definition, snapshot, event);
|
|
if (result.snapshot !== snapshot) set(result.snapshot);
|
|
return result.effects;
|
|
},
|
|
reset: (scope = "dungeon", now = 0) => {
|
|
set(resetDungeonEncounterSnapshot(definition, serializableSnapshot(get()), scope, now));
|
|
},
|
|
}));
|
|
const provider = {
|
|
definition,
|
|
encounterId: definition.id,
|
|
dungeonId: definition.dungeonId,
|
|
getSnapshot: () => serializableSnapshot(useStore.getState()),
|
|
dispatch: (event: DungeonEncounterEvent) => useStore.getState().dispatch(event),
|
|
reset: (scope: DungeonEncounterResetScope, now = 0) => useStore.getState().reset(scope, now),
|
|
replaceSnapshot: (snapshot: DungeonEncounterSnapshot) => {
|
|
if (snapshot.encounterId !== definition.id) {
|
|
throw new Error(`Cannot hydrate ${definition.id} from ${snapshot.encounterId}`);
|
|
}
|
|
useStore.setState(snapshot);
|
|
},
|
|
};
|
|
registerDungeonEncounterProvider(provider);
|
|
return useStore;
|
|
}
|