import type { Dispatch, SetStateAction, } from 'react'; import { addInventoryItems } from '../../data/npcInteractions'; import { findQuestById, markQuestCompletionNotified, markQuestTurnedIn, } from '../../data/questFlow'; import { advanceChapterState, resolveCurrentChapterState, } from '../../services/storyEngine/chapterDirector'; import { appendStoryEngineCarrierMemory } from '../../services/storyEngine/echoMemory'; import { buildGoalHandoffFromState } from '../../services/storyEngine/goalDirector'; import { createEmptyStoryEngineMemoryState } from '../../services/storyEngine/visibilityEngine'; import type { GameState, StoryMoment, } from '../../types'; import type { CommitGeneratedState } from '../generatedState'; import { buildMapTravelResolution } from './storyGenerationState'; type BuildFallbackStoryForState = ( state: GameState, character: NonNullable, fallbackText?: string, ) => StoryMoment; export function acknowledgeQuestCompletionState( state: GameState, questId: string, ): GameState { return { ...state, quests: markQuestCompletionNotified(state.quests, questId), }; } export function applyQuestRewardClaim( state: GameState, questId: string, ): { nextState: GameState; handoff: ReturnType; } | null { const quest = findQuestById(state.quests, questId); if (!quest || (quest.status !== 'completed' && quest.status !== 'ready_to_turn_in')) { return null; } const issuerNpcState = state.npcStates[quest.issuerNpcId]; const nextState = appendStoryEngineCarrierMemory({ ...state, quests: markQuestTurnedIn(state.quests, questId), playerCurrency: state.playerCurrency + quest.reward.currency, playerInventory: addInventoryItems(state.playerInventory, quest.reward.items), npcStates: issuerNpcState ? { ...state.npcStates, [quest.issuerNpcId]: { ...issuerNpcState, affinity: issuerNpcState.affinity + quest.reward.affinityBonus, }, } : state.npcStates, }, quest.reward.items); const chapterState = advanceChapterState({ previousChapter: nextState.chapterState ?? nextState.storyEngineMemory?.currentChapter ?? null, nextChapter: resolveCurrentChapterState({ state: nextState, }), }); const storyEngineMemory = nextState.storyEngineMemory ?? createEmptyStoryEngineMemoryState(); const synchronizedNextState: GameState = { ...nextState, chapterState, storyEngineMemory: { ...storyEngineMemory, currentChapter: chapterState, }, }; return { nextState: synchronizedNextState, handoff: buildGoalHandoffFromState(synchronizedNextState), }; } export function createStorySessionActions({ gameState, isLoading, setGameState, setCurrentStory, clearStoryRuntimeUi, commitGeneratedState, buildFallbackStoryForState, }: { gameState: GameState; isLoading: boolean; setGameState: Dispatch>; setCurrentStory: Dispatch>; clearStoryRuntimeUi: () => void; commitGeneratedState: CommitGeneratedState; buildFallbackStoryForState: BuildFallbackStoryForState; }) { const acknowledgeQuestCompletion = (questId: string) => { setGameState(currentState => acknowledgeQuestCompletionState(currentState, questId)); }; const claimQuestReward = (questId: string) => { const rewardClaim = applyQuestRewardClaim(gameState, questId); if (!rewardClaim) { return null; } setGameState(rewardClaim.nextState); return { questId, handoff: rewardClaim.handoff, }; }; const resetStoryState = () => { setCurrentStory(null); clearStoryRuntimeUi(); }; const hydrateStoryState = (story: StoryMoment | null) => { setCurrentStory(story); clearStoryRuntimeUi(); }; const travelToSceneFromMap = (sceneId: string) => { if (!gameState.playerCharacter || isLoading || gameState.inBattle) { return false; } const travelResolution = buildMapTravelResolution(gameState, sceneId); if (!travelResolution) { return false; } setCurrentStory( buildFallbackStoryForState( travelResolution.nextState, gameState.playerCharacter, travelResolution.travelResultText, ), ); void commitGeneratedState( travelResolution.nextState, gameState.playerCharacter, travelResolution.actionText, travelResolution.travelResultText, 'idle_travel_next_scene', ); return true; }; return { acknowledgeQuestCompletion, claimQuestReward, resetStoryState, hydrateStoryState, travelToSceneFromMap, }; }