204 lines
5.8 KiB
TypeScript
204 lines
5.8 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>>;
|
|
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,
|
|
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;
|
|
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,
|
|
});
|
|
const {
|
|
handleChoice,
|
|
battleRewardUi,
|
|
npcUi,
|
|
inventoryUi,
|
|
clearStoryInteractionUi,
|
|
handleNpcChatInput,
|
|
refreshNpcChatOptions,
|
|
exitNpcChat,
|
|
npcChatQuestOfferUi,
|
|
} = useRpgRuntimeInteractionFlow({
|
|
gameState,
|
|
isLoading,
|
|
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,
|
|
});
|
|
|
|
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
|
|
>;
|