212 lines
6.4 KiB
TypeScript
212 lines
6.4 KiB
TypeScript
import type { Dispatch, SetStateAction } from 'react';
|
||
|
||
import type { Character, Encounter, GameState, StoryOption } from '../../types';
|
||
import type { EscapePlaybackSync as ResolvedChoicePlaybackSync } from '../combat/escapeFlow';
|
||
import type { ResolvedChoiceState } from '../combat/resolvedChoice';
|
||
import { createStoryInteractionCoordinatorConfig } from './storyInteractionCoordinator';
|
||
import { sanitizeStoryOptions } from './storyPresentation';
|
||
import type { StoryRuntimeSupport } from './storyRuntimeSupport';
|
||
import { useRpgRuntimeInteractionFlow } from './useRpgRuntimeInteractionFlow';
|
||
import type { RpgRuntimeStoryControllerResult } from './useRpgRuntimeStoryController';
|
||
import { useRpgRuntimeStoryState } from './useRpgRuntimeStoryState';
|
||
import { useStoryGoalOptionCoordinator } from './useStoryGoalOptionCoordinator';
|
||
|
||
type RpgRuntimeStoryFlowParams = {
|
||
gameState: GameState;
|
||
setGameState: Dispatch<SetStateAction<GameState>>;
|
||
sceneTransitionPhase?: 'idle' | 'exiting' | 'entering';
|
||
buildResolvedChoiceState: (
|
||
state: GameState,
|
||
option: StoryOption,
|
||
character: Character,
|
||
) => ResolvedChoiceState;
|
||
playResolvedChoice: (
|
||
state: GameState,
|
||
option: StoryOption,
|
||
character: Character,
|
||
resolvedChoice: ResolvedChoiceState,
|
||
sync?: ResolvedChoicePlaybackSync,
|
||
) => Promise<GameState>;
|
||
getStoryGenerationHostileNpcs: (
|
||
state: GameState,
|
||
) => GameState['sceneHostileNpcs'];
|
||
getResolvedSceneHostileNpcs: (
|
||
state: GameState,
|
||
) => GameState['sceneHostileNpcs'];
|
||
runtimeController: RpgRuntimeStoryControllerResult;
|
||
runtimeSupport: StoryRuntimeSupport;
|
||
sortOptions: (options: StoryOption[]) => StoryOption[];
|
||
buildContinueAdventureOption: () => StoryOption;
|
||
resolveNpcInteractionDecision: (
|
||
state: GameState,
|
||
option: StoryOption,
|
||
) => { kind: string };
|
||
clearCharacterChatModal: () => void;
|
||
isContinueAdventureOption: (option: StoryOption) => boolean;
|
||
isCampTravelHomeOption: (option: StoryOption) => boolean;
|
||
isRegularNpcEncounter: (
|
||
encounter: GameState['currentEncounter'],
|
||
) => encounter is Encounter;
|
||
isNpcEncounter: (
|
||
encounter: GameState['currentEncounter'],
|
||
) => encounter is Encounter;
|
||
npcPreviewTalkFunctionId: string;
|
||
fallbackCompanionName: string;
|
||
turnVisualMs: number;
|
||
};
|
||
|
||
/**
|
||
* RPG runtime story 主编排层。
|
||
* 这里把 option 展示、正式交互分发和 story/session 状态动作收束成稳定出口。
|
||
*/
|
||
export function useRpgRuntimeStoryFlow({
|
||
gameState,
|
||
setGameState,
|
||
sceneTransitionPhase = 'idle',
|
||
buildResolvedChoiceState,
|
||
playResolvedChoice,
|
||
getStoryGenerationHostileNpcs,
|
||
getResolvedSceneHostileNpcs,
|
||
runtimeController,
|
||
runtimeSupport,
|
||
sortOptions,
|
||
buildContinueAdventureOption,
|
||
resolveNpcInteractionDecision,
|
||
clearCharacterChatModal,
|
||
isContinueAdventureOption,
|
||
isCampTravelHomeOption,
|
||
isRegularNpcEncounter,
|
||
isNpcEncounter,
|
||
npcPreviewTalkFunctionId,
|
||
fallbackCompanionName,
|
||
turnVisualMs,
|
||
}: RpgRuntimeStoryFlowParams) {
|
||
const {
|
||
currentStory,
|
||
setCurrentStory,
|
||
setAiError,
|
||
setIsLoading,
|
||
isLoading,
|
||
buildStoryContextFromState,
|
||
buildFallbackStoryForState,
|
||
buildDialogueStoryMoment,
|
||
generateStoryForState,
|
||
getAvailableOptionsForState,
|
||
getTypewriterDelay,
|
||
commitGeneratedState,
|
||
commitGeneratedStateWithEncounterEntry,
|
||
appendHistory,
|
||
buildOpeningCampChatContext,
|
||
resetPreparedOpeningAdventure,
|
||
} = runtimeController;
|
||
// 中文注释:interactionConfig 是“剧情交互协调器”的配置快照;
|
||
// 后续选项刷新、动作提交、fallback 叙事都会共用这套上下文。
|
||
const interactionConfig = createStoryInteractionCoordinatorConfig({
|
||
gameState,
|
||
setGameState,
|
||
setCurrentStory,
|
||
setAiError,
|
||
setIsLoading,
|
||
currentStory,
|
||
buildStoryContextFromState,
|
||
buildFallbackStoryForState,
|
||
buildDialogueStoryMoment,
|
||
generateStoryForState,
|
||
getAvailableOptionsForState,
|
||
getStoryGenerationHostileNpcs,
|
||
getTypewriterDelay,
|
||
runtimeSupport,
|
||
commitGeneratedState,
|
||
commitGeneratedStateWithEncounterEntry,
|
||
appendHistory,
|
||
buildOpeningCampChatContext,
|
||
sortOptions,
|
||
buildContinueAdventureOption,
|
||
sanitizeOptions: sanitizeStoryOptions,
|
||
resolveNpcInteractionDecision,
|
||
});
|
||
const {
|
||
displayedOptions,
|
||
canRefreshOptions,
|
||
handleRefreshOptions,
|
||
goalUi,
|
||
clearStoryGoalOptionUi,
|
||
} = useStoryGoalOptionCoordinator({
|
||
gameState,
|
||
currentStory,
|
||
});
|
||
// 中文注释:这一层把“战斗/NPC/背包/地图旅行”等具体交互入口分发到对应流程,
|
||
// 保证冒险面板只调用统一的 handleChoice / handleNpcChatInput 等接口。
|
||
const {
|
||
handleChoice,
|
||
battleRewardUi,
|
||
npcUi,
|
||
inventoryUi,
|
||
clearStoryInteractionUi,
|
||
handleNpcChatInput,
|
||
refreshNpcChatOptions,
|
||
exitNpcChat,
|
||
npcChatQuestOfferUi,
|
||
} = useRpgRuntimeInteractionFlow({
|
||
gameState,
|
||
isLoading,
|
||
sceneTransitionPhase,
|
||
interactionConfig,
|
||
runtimeSupport,
|
||
buildResolvedChoiceState,
|
||
playResolvedChoice,
|
||
buildStoryFromResponse: runtimeController.buildStoryFromResponse,
|
||
getResolvedSceneHostileNpcs,
|
||
getCampCompanionTravelScene: runtimeController.getCampCompanionTravelScene,
|
||
isContinueAdventureOption,
|
||
isCampTravelHomeOption,
|
||
isRegularNpcEncounter,
|
||
isNpcEncounter,
|
||
npcPreviewTalkFunctionId,
|
||
fallbackCompanionName,
|
||
turnVisualMs,
|
||
});
|
||
const { questUi, resetStoryState, hydrateStoryState, travelToSceneFromMap } =
|
||
useRpgRuntimeStoryState({
|
||
gameState,
|
||
isLoading,
|
||
setGameState,
|
||
setCurrentStory,
|
||
setAiError,
|
||
setIsLoading,
|
||
commitGeneratedState,
|
||
buildFallbackStoryForState,
|
||
resetPreparedOpeningAdventure,
|
||
clearStoryGoalOptionUi,
|
||
clearStoryInteractionUi,
|
||
clearCharacterChatModal,
|
||
});
|
||
|
||
// 中文注释:最终返回的是已经过目标选项协调、交互分发和 story state 收束后的稳定输出。
|
||
return {
|
||
displayedOptions,
|
||
canRefreshOptions,
|
||
handleRefreshOptions,
|
||
handleChoice,
|
||
resetStoryState,
|
||
hydrateStoryState,
|
||
travelToSceneFromMap,
|
||
battleRewardUi,
|
||
questUi,
|
||
goalUi,
|
||
npcUi,
|
||
inventoryUi,
|
||
handleNpcChatInput,
|
||
refreshNpcChatOptions,
|
||
exitNpcChat,
|
||
npcChatQuestOfferUi,
|
||
};
|
||
}
|
||
|
||
export type UseRpgRuntimeStoryFlowParams = Parameters<
|
||
typeof useRpgRuntimeStoryFlow
|
||
>[0];
|
||
export type RpgRuntimeStoryFlowResult = ReturnType<
|
||
typeof useRpgRuntimeStoryFlow
|
||
>;
|