89 lines
2.6 KiB
TypeScript
89 lines
2.6 KiB
TypeScript
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
|
|
import {
|
|
buildGoalStackState,
|
|
createGoalPulseSnapshot,
|
|
deriveGoalPulseEvent,
|
|
} from '../../services/storyEngine/goalDirector';
|
|
import type { GameState } from '../../types';
|
|
import type { GoalFlowUi } from './uiTypes';
|
|
|
|
export function useStoryGoalFlow(gameState: GameState) {
|
|
const [goalPulse, setGoalPulse] = useState<GoalFlowUi['pulse']>(null);
|
|
const previousGoalPulseSnapshotRef =
|
|
useRef<ReturnType<typeof createGoalPulseSnapshot> | null>(null);
|
|
|
|
const runtimeGoalStack = useMemo(
|
|
() =>
|
|
buildGoalStackState({
|
|
quests: gameState.quests,
|
|
worldType: gameState.worldType,
|
|
currentSceneId: gameState.currentScenePreset?.id ?? null,
|
|
chapterState:
|
|
gameState.chapterState ??
|
|
gameState.storyEngineMemory?.currentChapter ??
|
|
null,
|
|
journeyBeat: gameState.storyEngineMemory?.currentJourneyBeat ?? null,
|
|
setpieceDirective:
|
|
gameState.storyEngineMemory?.currentSetpieceDirective ?? null,
|
|
currentCampEvent:
|
|
gameState.storyEngineMemory?.currentCampEvent ?? null,
|
|
currentSceneName: gameState.currentScenePreset?.name ?? null,
|
|
}),
|
|
[
|
|
gameState.chapterState,
|
|
gameState.currentScenePreset?.id,
|
|
gameState.currentScenePreset?.name,
|
|
gameState.quests,
|
|
gameState.storyEngineMemory?.currentCampEvent,
|
|
gameState.storyEngineMemory?.currentChapter,
|
|
gameState.storyEngineMemory?.currentJourneyBeat,
|
|
gameState.storyEngineMemory?.currentSetpieceDirective,
|
|
gameState.worldType,
|
|
],
|
|
);
|
|
|
|
useEffect(() => {
|
|
const currentSnapshot = createGoalPulseSnapshot(
|
|
gameState.quests,
|
|
runtimeGoalStack,
|
|
);
|
|
const previousSnapshot = previousGoalPulseSnapshotRef.current;
|
|
|
|
if (!previousSnapshot) {
|
|
previousGoalPulseSnapshotRef.current = currentSnapshot;
|
|
return;
|
|
}
|
|
|
|
const nextPulse = deriveGoalPulseEvent({
|
|
previous: previousSnapshot,
|
|
quests: gameState.quests,
|
|
goalStack: runtimeGoalStack,
|
|
});
|
|
if (nextPulse) {
|
|
setGoalPulse(nextPulse);
|
|
}
|
|
|
|
previousGoalPulseSnapshotRef.current = currentSnapshot;
|
|
}, [gameState.quests, runtimeGoalStack]);
|
|
|
|
const dismissGoalPulse = useCallback(() => {
|
|
setGoalPulse(null);
|
|
}, []);
|
|
|
|
const resetGoalPulseTracking = useCallback(() => {
|
|
previousGoalPulseSnapshotRef.current = null;
|
|
setGoalPulse(null);
|
|
}, []);
|
|
|
|
return {
|
|
runtimeGoalStack,
|
|
goalUi: {
|
|
goalStack: runtimeGoalStack,
|
|
pulse: goalPulse,
|
|
dismissPulse: dismissGoalPulse,
|
|
} satisfies GoalFlowUi,
|
|
resetGoalPulseTracking,
|
|
};
|
|
}
|