import type { Dispatch, SetStateAction, } from 'react'; import type { StoryGenerationContext } from '../../services/aiTypes'; import { isRpgRuntimeServerFunctionId } from '../../services/rpg-runtime'; import { type Character, type Encounter, type GameState, type StoryMoment, type StoryOption, } from '../../types'; import type { EscapePlaybackSync } from '../combat/escapeFlow'; import type { ResolvedChoiceState } from '../combat/resolvedChoice'; import type { CommitGeneratedStateWithEncounterEntry, GenerateStoryForState, } from './progressionActions'; import { runLocalStoryChoiceContinuation } from './storyChoiceContinuation'; import { runCampTravelHomeChoice, runServerRuntimeChoiceAction, shouldOpenLocalRuntimeNpcModal, } from './storyChoiceRuntime'; import type { BattleRewardSummary } from './uiTypes'; type RuntimeStatsIncrements = Partial>; type BuildFallbackStoryForState = ( state: GameState, character: Character, fallbackText?: string, ) => StoryMoment; type BuildStoryFromResponse = ( state: GameState, character: Character, response: StoryMoment, availableOptions: StoryOption[] | null, optionCatalog?: StoryOption[] | null, ) => StoryMoment; type BuildNpcStory = ( state: GameState, character: Character, encounter: Encounter, overrideText?: string, ) => StoryMoment; type HandleNpcBattleConversationContinuation = (params: { nextState: GameState; encounter: Encounter; character: Character; actionText: string; resultText: string; battleMode: NonNullable; }) => boolean; type BuildStoryContextFromState = ( state: GameState, extras?: { lastFunctionId?: string | null; observeSignsRequested?: boolean; recentActionResult?: string | null; }, ) => StoryGenerationContext; type UpdateQuestLog = ( state: GameState, updater: (quests: GameState['quests']) => GameState['quests'], ) => GameState; type IncrementRuntimeStats = ( state: GameState, increments: RuntimeStatsIncrements, ) => GameState; export function createStoryChoiceActions({ gameState, currentStory, isLoading, setGameState, setCurrentStory, setAiError, setIsLoading, setBattleReward, buildResolvedChoiceState, playResolvedChoice, buildStoryContextFromState, buildStoryFromResponse, buildFallbackStoryForState, generateStoryForState, getAvailableOptionsForState, getStoryGenerationHostileNpcs, getResolvedSceneHostileNpcs, buildNpcStory, handleNpcBattleConversationContinuation, updateQuestLog, incrementRuntimeStats, getCampCompanionTravelScene, enterNpcInteraction, handleNpcInteraction, handleTreasureInteraction, commitGeneratedStateWithEncounterEntry, finalizeNpcBattleResult, isContinueAdventureOption, isCampTravelHomeOption, isRegularNpcEncounter, isNpcEncounter, npcPreviewTalkFunctionId, fallbackCompanionName, turnVisualMs, }: { gameState: GameState; currentStory: StoryMoment | null; isLoading: boolean; setGameState: Dispatch>; setCurrentStory: Dispatch>; setAiError: Dispatch>; setIsLoading: Dispatch>; setBattleReward: Dispatch>; buildResolvedChoiceState: (state: GameState, option: StoryOption, character: Character) => ResolvedChoiceState; playResolvedChoice: ( state: GameState, option: StoryOption, character: Character, resolvedChoice: ResolvedChoiceState, sync?: EscapePlaybackSync, ) => Promise; buildStoryContextFromState: BuildStoryContextFromState; buildStoryFromResponse: BuildStoryFromResponse; buildFallbackStoryForState: BuildFallbackStoryForState; generateStoryForState: GenerateStoryForState; getAvailableOptionsForState: (state: GameState, character: Character) => StoryOption[] | null; getStoryGenerationHostileNpcs: (state: GameState) => GameState['sceneHostileNpcs']; getResolvedSceneHostileNpcs: (state: GameState) => GameState['sceneHostileNpcs']; buildNpcStory: BuildNpcStory; handleNpcBattleConversationContinuation: HandleNpcBattleConversationContinuation; updateQuestLog: UpdateQuestLog; incrementRuntimeStats: IncrementRuntimeStats; getCampCompanionTravelScene: (state: GameState, character: Character) => GameState['currentScenePreset'] | null; enterNpcInteraction: (encounter: Encounter, actionText: string) => boolean; handleNpcInteraction: (option: StoryOption) => boolean | Promise; handleTreasureInteraction: ( option: StoryOption, ) => void | Promise | boolean | Promise; commitGeneratedStateWithEncounterEntry: CommitGeneratedStateWithEncounterEntry; finalizeNpcBattleResult: ( state: GameState, character: Character, battleMode: NonNullable, battleOutcome: GameState['currentNpcBattleOutcome'], ) => { nextState: GameState; resultText: string } | null; 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; }) { const handleChoice = async (option: StoryOption) => { const character = gameState.playerCharacter; if (!gameState.worldType || !character || isLoading) return; if (option.disabled) return; if (currentStory?.deferredOptions?.length && isContinueAdventureOption(option)) { if (currentStory.deferredRuntimeState) { setGameState({ ...gameState, currentEncounter: null, npcInteractionActive: false, sceneHostileNpcs: [], inBattle: false, currentBattleNpcId: null, currentNpcBattleMode: null, currentNpcBattleOutcome: null, currentScenePreset: currentStory.deferredRuntimeState.currentScenePreset ?? gameState.currentScenePreset, }); } setCurrentStory({ ...currentStory, options: currentStory.deferredOptions, deferredOptions: undefined, deferredRuntimeState: undefined, }); return; } if (isCampTravelHomeOption(option)) { await runCampTravelHomeChoice({ gameState, option, character, setBattleReward, setAiError, setIsLoading, setGameState, incrementRuntimeStats, getCampCompanionTravelScene, commitGeneratedStateWithEncounterEntry, isNpcEncounter, fallbackCompanionName, turnVisualMs, }); return; } if (shouldOpenLocalRuntimeNpcModal(option)) { setAiError(null); await handleNpcInteraction(option); return; } if (isRpgRuntimeServerFunctionId(option.functionId)) { await runServerRuntimeChoiceAction({ gameState, currentStory, option, character, setBattleReward, setAiError, setIsLoading, setGameState, setCurrentStory: (story) => setCurrentStory(story), buildFallbackStoryForState, turnVisualMs, }); return; } if ( option.functionId === npcPreviewTalkFunctionId && isRegularNpcEncounter(gameState.currentEncounter) && !gameState.npcInteractionActive ) { setAiError(null); enterNpcInteraction(gameState.currentEncounter, option.actionText); return; } if (option.interaction?.kind === 'npc') { setAiError(null); await handleNpcInteraction(option); return; } if (option.interaction?.kind === 'treasure') { setAiError(null); await handleTreasureInteraction(option); return; } await runLocalStoryChoiceContinuation({ gameState, currentStory, option, character, setGameState, setCurrentStory: (story) => setCurrentStory(story), setAiError, setIsLoading, setBattleReward, buildResolvedChoiceState, playResolvedChoice, buildStoryContextFromState, buildStoryFromResponse, buildFallbackStoryForState, generateStoryForState, getAvailableOptionsForState, getStoryGenerationHostileNpcs, getResolvedSceneHostileNpcs, buildNpcStory, handleNpcBattleConversationContinuation, updateQuestLog, incrementRuntimeStats, finalizeNpcBattleResult, isRegularNpcEncounter, }); }; return { handleChoice, }; }