import type { Dispatch, SetStateAction } from 'react'; import { hasEncounterEntity, interpolateEncounterTransitionState, } from '../../data/encounterTransition'; import { createHistoryMoment } from '../../services/storyHistory'; import type { Character, GameState, StoryMoment, StoryOption, } from '../../types'; import type { CommitGeneratedState } from '../generatedState'; const ENCOUNTER_ENTRY_DURATION_MS = 1800; const ENCOUNTER_ENTRY_TICK_MS = 180; export type GenerateStoryForState = (params: { state: GameState; character: Character; history: GameState['storyHistory']; choice?: string; lastFunctionId?: string | null; optionCatalog?: StoryOption[] | null; }) => Promise; type BuildFallbackStoryForState = ( state: GameState, character: Character, fallbackText?: string, ) => StoryMoment; export type CommitGeneratedStateWithEncounterEntry = ( entryState: GameState, resolvedState: GameState, character: Character, actionText: string, resultText: string, lastFunctionId?: string, ) => Promise; export function appendStoryHistory( state: GameState, actionText: string, resultText: string, ): GameState['storyHistory'] { return [ ...state.storyHistory, createHistoryMoment(actionText, 'action'), createHistoryMoment(resultText, 'result'), ]; } export function createStoryProgressionActions({ gameState, setGameState, setCurrentStory, setAiError, setIsLoading, generateStoryForState, buildFallbackStoryForState, }: { gameState: GameState; setGameState: Dispatch>; setCurrentStory: Dispatch>; setAiError: Dispatch>; setIsLoading: Dispatch>; generateStoryForState: GenerateStoryForState; buildFallbackStoryForState: BuildFallbackStoryForState; }) { const commitGeneratedState: CommitGeneratedState = async ( nextState, character, actionText, resultText, lastFunctionId, ) => { const nextHistory = appendStoryHistory(gameState, actionText, resultText); const stateWithHistory = { ...nextState, storyHistory: nextHistory, } as GameState; setGameState(stateWithHistory); setAiError(null); setIsLoading(true); try { const nextStory = await generateStoryForState({ state: stateWithHistory, character, history: nextHistory, choice: actionText, lastFunctionId, }); setGameState(stateWithHistory); setCurrentStory(nextStory); } catch (error) { console.error('Failed to continue scripted story:', error); setAiError(error instanceof Error ? error.message : '未知智能生成错误'); setCurrentStory( buildFallbackStoryForState(stateWithHistory, character, resultText), ); } finally { setIsLoading(false); } }; const commitGeneratedStateWithEncounterEntry: CommitGeneratedStateWithEncounterEntry = async ( entryState, resolvedState, character, actionText, resultText, lastFunctionId, ) => { setGameState(entryState); setAiError(null); setIsLoading(true); if (hasEncounterEntity(resolvedState)) { const runTicks = Math.max( 1, Math.ceil(ENCOUNTER_ENTRY_DURATION_MS / ENCOUNTER_ENTRY_TICK_MS), ); const tickDurationMs = Math.max( 1, Math.round(ENCOUNTER_ENTRY_DURATION_MS / runTicks), ); for (let tick = 1; tick <= runTicks; tick += 1) { const progress = tick / runTicks; setGameState( interpolateEncounterTransitionState( entryState, resolvedState, progress, ), ); await new Promise((resolve) => window.setTimeout(resolve, tickDurationMs), ); } } const nextHistory = appendStoryHistory(gameState, actionText, resultText); const stateWithHistory = { ...resolvedState, storyHistory: nextHistory, } as GameState; setGameState(stateWithHistory); try { const nextStory = await generateStoryForState({ state: stateWithHistory, character, history: nextHistory, choice: actionText, lastFunctionId, }); setGameState(stateWithHistory); setCurrentStory(nextStory); } catch (error) { console.error('Failed to continue encounter-entry story:', error); setAiError(error instanceof Error ? error.message : '未知智能生成错误'); setCurrentStory( buildFallbackStoryForState(stateWithHistory, character, resultText), ); } finally { setIsLoading(false); } }; return { commitGeneratedState, commitGeneratedStateWithEncounterEntry, }; }