import type { Dispatch, SetStateAction } from 'react'; import { hasEncounterEntity, interpolateEncounterTransitionState, } from '../../data/encounterTransition'; import { STORY_OPENING_CAMP_DIALOGUE_FUNCTION } from '../../data/functionCatalog'; import { CALL_OUT_ENTRY_X_METERS, RESOLVED_ENTITY_X_METERS, } from '../../data/sceneEncounterPreviews'; import { getWorldCampScenePreset } from '../../data/scenePresets'; import { sortStoryOptionsByPriority } from '../../data/stateFunctions'; import { generateNextStep } from '../../services/aiService'; import type { StoryGenerationContext } from '../../services/aiTypes'; import { createHistoryMoment } from '../../services/storyHistory'; import type { Character, Encounter, GameState, StoryMoment, StoryOption, } from '../../types'; const ENCOUNTER_ENTRY_DURATION_MS = 1800; const ENCOUNTER_ENTRY_TICK_MS = 180; const OPENING_CAMP_DIALOGUE_FUNCTION_ID = STORY_OPENING_CAMP_DIALOGUE_FUNCTION.id; export type PreparedOpeningAdventure = { encounterKey: string; actionText: string; resultText: string; fallbackText: string; openingOptions: StoryOption[]; }; export function buildPreparedOpeningAdventure({ state, character, getNpcEncounterKey, appendHistory, buildCampCompanionOpeningOptions, buildCampCompanionOpeningResultText, buildInitialCompanionDialogueText, }: { state: GameState; character: Character; getNpcEncounterKey: (encounter: Encounter) => string; appendHistory: ( state: GameState, actionText: string, resultText: string, ) => GameState['storyHistory']; buildCampCompanionOpeningOptions: ( state: GameState, character: Character, encounter: Encounter, ) => StoryOption[]; buildCampCompanionOpeningResultText: ( character: Character, encounter: Encounter, worldType: GameState['worldType'], ) => string; buildInitialCompanionDialogueText: ( character: Character, encounter: Encounter, worldType: GameState['worldType'], ) => string; }): PreparedOpeningAdventure | null { const encounter = state.currentEncounter; if ( !encounter || encounter.kind !== 'npc' || encounter.specialBehavior !== 'initial_companion' ) { return null; } const campScene = state.worldType ? getWorldCampScenePreset(state.worldType) : null; const actionText = '开始冒险'; const resultText = buildCampCompanionOpeningResultText( character, encounter, state.worldType, ); const dialogueText = buildInitialCompanionDialogueText( character, encounter, state.worldType, ); const resolvedEncounter: Encounter = { ...encounter, specialBehavior: 'camp_companion', xMeters: RESOLVED_ENTITY_X_METERS, }; const resolvedState: GameState = { ...state, currentScenePreset: campScene ?? state.currentScenePreset, currentEncounter: resolvedEncounter, npcInteractionActive: false, }; const nextHistory = appendHistory(state, actionText, resultText); const stateWithHistory: GameState = { ...resolvedState, storyHistory: nextHistory, }; return { encounterKey: getNpcEncounterKey(encounter), actionText, resultText, fallbackText: dialogueText, openingOptions: buildCampCompanionOpeningOptions( stateWithHistory, character, resolvedEncounter, ), }; } export async function playOpeningAdventureSequence({ gameState, encounter, preparedStory, setGameState, setCurrentStory, setAiError, setIsLoading, }: { gameState: GameState; character: Character; encounter: Encounter; preparedStory: PreparedOpeningAdventure; setGameState: Dispatch>; setCurrentStory: Dispatch>; setAiError: Dispatch>; setIsLoading: Dispatch>; buildDialogueStoryMoment: ( npcName: string, text: string, options: StoryOption[], streaming?: boolean, ) => StoryMoment; buildStoryContextFromState: ( state: GameState, extras?: { lastFunctionId?: string | null }, ) => StoryGenerationContext; getStoryGenerationHostileNpcs: ( state: GameState, ) => GameState['sceneHostileNpcs']; hasRenderableDialogueTurns: (text: string, npcName: string) => boolean; inferOpeningCampFollowupOptions: ( state: GameState, character: Character, baseOptions: StoryOption[], openingBackground: string, openingDialogue: string, ) => Promise; getTypewriterDelay: (char: string) => number; }) { const { fallbackText, openingOptions } = preparedStory; const campScene = gameState.worldType ? getWorldCampScenePreset(gameState.worldType) : null; const storyEncounter: Encounter = { ...encounter, xMeters: RESOLVED_ENTITY_X_METERS, specialBehavior: 'camp_companion', }; const resolvedState: GameState = { ...gameState, currentScenePreset: campScene ?? gameState.currentScenePreset, currentEncounter: storyEncounter, npcInteractionActive: true, }; setAiError(null); setIsLoading(false); try { setGameState(resolvedState); setCurrentStory({ text: fallbackText, options: sortStoryOptionsByPriority(openingOptions), displayMode: 'dialogue', dialogue: [ { speaker: 'npc', speakerName: encounter.npcName, text: fallbackText, }, ], streaming: false, npcChatState: { npcId: storyEncounter.id ?? storyEncounter.npcName, npcName: storyEncounter.npcName, turnCount: 0, customInputPlaceholder: '输入你想对 TA 说的话', }, }); } catch (error) { console.error('Failed to play opening adventure sequence:', error); setAiError(error instanceof Error ? error.message : '未知智能生成错误'); setGameState(resolvedState); setCurrentStory({ text: fallbackText, options: sortStoryOptionsByPriority(openingOptions), displayMode: 'dialogue', dialogue: [ { speaker: 'npc', speakerName: encounter.npcName, text: fallbackText, }, ], streaming: false, npcChatState: { npcId: storyEncounter.id ?? storyEncounter.npcName, npcName: storyEncounter.npcName, turnCount: 0, customInputPlaceholder: '输入你想对 TA 说的话', }, }); } finally { setIsLoading(false); } }