177 lines
4.7 KiB
TypeScript
177 lines
4.7 KiB
TypeScript
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<GameState['playerCharacter']>,
|
|
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<typeof buildGoalHandoffFromState>;
|
|
} | 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<SetStateAction<GameState>>;
|
|
setCurrentStory: Dispatch<SetStateAction<StoryMoment | null>>;
|
|
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,
|
|
};
|
|
}
|