Files
Genarrative/src/hooks/story/goalFlow.ts
2026-04-10 15:37:02 +08:00

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,
};
}