import { getForwardScenePreset, getScenePresetById, getScenePresetsByWorld, getTravelScenePreset, } from '../../data/scenePresets'; import { getFunctionEffect } from '../../data/stateFunctions'; import type { Character, GameState, StoryOption, WorldType, } from '../../types'; import { classifyCombatOption } from '../combatStoryUtils'; import { buildIdleAfterSequence } from '../idleAdventureFlow'; import { applyRecoveryEffectToState, type BattlePlan, } from './battlePlan'; import { buildEscapeAfterSequence } from './escapeFlow'; type OptionKind = 'battle' | 'escape' | 'idle'; export type ResolvedChoiceState = { optionKind: OptionKind; battlePlan: BattlePlan | null; afterSequence: GameState; }; function getShiftedScenePreset( worldType: WorldType | null, currentScenePreset: GameState['currentScenePreset'], sceneShift: number | undefined, ): GameState['currentScenePreset'] { if (!worldType || sceneShift === undefined || sceneShift === 0) return currentScenePreset; if (!currentScenePreset) return getScenePresetsByWorld(worldType)[0] ?? null; if (sceneShift > 0) { let nextScene = currentScenePreset; for (let index = 0; index < sceneShift; index += 1) { nextScene = getForwardScenePreset(worldType, nextScene.id) ?? nextScene; } return nextScene; } const scenes = getScenePresetsByWorld(worldType); if (scenes.length === 0) return currentScenePreset; const currentIndex = scenes.findIndex(scene => scene.id === currentScenePreset.id); const safeIndex = currentIndex >= 0 ? currentIndex : 0; const nextIndex = ((safeIndex + sceneShift) % scenes.length + scenes.length) % scenes.length; return scenes[nextIndex] ?? null; } function getSceneTargetForFunction( worldType: WorldType | null, currentScenePreset: GameState['currentScenePreset'], option: StoryOption, ): GameState['currentScenePreset'] { if (!worldType) return currentScenePreset; const targetSceneId = typeof option.runtimePayload?.targetSceneId === 'string' ? option.runtimePayload.targetSceneId : typeof option.runtimePayload?.escapeTargetSceneId === 'string' ? option.runtimePayload.escapeTargetSceneId : null; if (targetSceneId) { return getScenePresetById(worldType, targetSceneId) ?? currentScenePreset; } if (option.functionId === 'idle_travel_next_scene') { return getTravelScenePreset(worldType, currentScenePreset?.id) ?? currentScenePreset; } return getShiftedScenePreset( worldType, currentScenePreset, getFunctionEffect(option.functionId).sceneShift, ); } export function buildResolvedChoiceState(params: { state: GameState; option: StoryOption; character: Character; buildBattlePlan: (state: GameState, option: StoryOption, character: Character) => BattlePlan; }) { const { state, option, character, buildBattlePlan, } = params; const nextScenePreset = getSceneTargetForFunction( state.worldType, state.currentScenePreset, option, ); const optionKind = classifyCombatOption(option); if (optionKind === 'battle') { const battlePlan = buildBattlePlan(state, option, character); const battleResult: GameState = { ...battlePlan.finalState, currentScenePreset: nextScenePreset ?? battlePlan.finalState.currentScenePreset, }; return { optionKind, battlePlan, afterSequence: battleResult, } satisfies ResolvedChoiceState; } if (optionKind === 'escape') { return { optionKind, battlePlan: null, afterSequence: buildEscapeAfterSequence(state, option, nextScenePreset), } satisfies ResolvedChoiceState; } return { optionKind, battlePlan: null, afterSequence: buildIdleAfterSequence({ state, option, character, nextScenePreset, applyRecoveryEffectToState, }), } satisfies ResolvedChoiceState; }