This commit is contained in:
176
src/hooks/rpg-runtime-story/sessionActions.ts
Normal file
176
src/hooks/rpg-runtime-story/sessionActions.ts
Normal file
@@ -0,0 +1,176 @@
|
||||
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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user