Files
Genarrative/src/hooks/rpg-runtime-story/sessionActions.ts
kdletters cbc27bad4a
Some checks failed
CI / verify (push) Has been cancelled
init with react+axum+spacetimedb
2026-04-26 18:06:23 +08:00

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,
};
}