This commit is contained in:
292
src/hooks/rpg-runtime-story/choiceActions.ts
Normal file
292
src/hooks/rpg-runtime-story/choiceActions.ts
Normal file
@@ -0,0 +1,292 @@
|
||||
import type {
|
||||
Dispatch,
|
||||
SetStateAction,
|
||||
} from 'react';
|
||||
|
||||
import type { StoryGenerationContext } from '../../services/aiTypes';
|
||||
import { isRpgRuntimeServerFunctionId } from '../../services/rpg-runtime';
|
||||
import {
|
||||
type Character,
|
||||
type Encounter,
|
||||
type GameState,
|
||||
type StoryMoment,
|
||||
type StoryOption,
|
||||
} from '../../types';
|
||||
import type { EscapePlaybackSync } from '../combat/escapeFlow';
|
||||
import type { ResolvedChoiceState } from '../combat/resolvedChoice';
|
||||
import type {
|
||||
CommitGeneratedStateWithEncounterEntry,
|
||||
GenerateStoryForState,
|
||||
} from './progressionActions';
|
||||
import { runLocalStoryChoiceContinuation } from './storyChoiceContinuation';
|
||||
import {
|
||||
runCampTravelHomeChoice,
|
||||
runServerRuntimeChoiceAction,
|
||||
shouldOpenLocalRuntimeNpcModal,
|
||||
} from './storyChoiceRuntime';
|
||||
import type { BattleRewardSummary } from './uiTypes';
|
||||
|
||||
type RuntimeStatsIncrements = Partial<Pick<GameState['runtimeStats'], 'hostileNpcsDefeated' | 'questsAccepted' | 'itemsUsed' | 'scenesTraveled'>>;
|
||||
|
||||
type BuildFallbackStoryForState = (
|
||||
state: GameState,
|
||||
character: Character,
|
||||
fallbackText?: string,
|
||||
) => StoryMoment;
|
||||
|
||||
type BuildStoryFromResponse = (
|
||||
state: GameState,
|
||||
character: Character,
|
||||
response: StoryMoment,
|
||||
availableOptions: StoryOption[] | null,
|
||||
optionCatalog?: StoryOption[] | null,
|
||||
) => StoryMoment;
|
||||
|
||||
type BuildNpcStory = (
|
||||
state: GameState,
|
||||
character: Character,
|
||||
encounter: Encounter,
|
||||
overrideText?: string,
|
||||
) => StoryMoment;
|
||||
|
||||
type HandleNpcBattleConversationContinuation = (params: {
|
||||
nextState: GameState;
|
||||
encounter: Encounter;
|
||||
character: Character;
|
||||
actionText: string;
|
||||
resultText: string;
|
||||
battleMode: NonNullable<GameState['currentNpcBattleMode']>;
|
||||
}) => boolean;
|
||||
|
||||
type BuildStoryContextFromState = (
|
||||
state: GameState,
|
||||
extras?: {
|
||||
lastFunctionId?: string | null;
|
||||
observeSignsRequested?: boolean;
|
||||
recentActionResult?: string | null;
|
||||
},
|
||||
) => StoryGenerationContext;
|
||||
|
||||
type UpdateQuestLog = (
|
||||
state: GameState,
|
||||
updater: (quests: GameState['quests']) => GameState['quests'],
|
||||
) => GameState;
|
||||
|
||||
type IncrementRuntimeStats = (
|
||||
state: GameState,
|
||||
increments: RuntimeStatsIncrements,
|
||||
) => GameState;
|
||||
|
||||
export function createStoryChoiceActions({
|
||||
gameState,
|
||||
currentStory,
|
||||
isLoading,
|
||||
setGameState,
|
||||
setCurrentStory,
|
||||
setAiError,
|
||||
setIsLoading,
|
||||
setBattleReward,
|
||||
buildResolvedChoiceState,
|
||||
playResolvedChoice,
|
||||
buildStoryContextFromState,
|
||||
buildStoryFromResponse,
|
||||
buildFallbackStoryForState,
|
||||
generateStoryForState,
|
||||
getAvailableOptionsForState,
|
||||
getStoryGenerationHostileNpcs,
|
||||
getResolvedSceneHostileNpcs,
|
||||
buildNpcStory,
|
||||
handleNpcBattleConversationContinuation,
|
||||
updateQuestLog,
|
||||
incrementRuntimeStats,
|
||||
getCampCompanionTravelScene,
|
||||
enterNpcInteraction,
|
||||
handleNpcInteraction,
|
||||
handleTreasureInteraction,
|
||||
commitGeneratedStateWithEncounterEntry,
|
||||
finalizeNpcBattleResult,
|
||||
isContinueAdventureOption,
|
||||
isCampTravelHomeOption,
|
||||
isRegularNpcEncounter,
|
||||
isNpcEncounter,
|
||||
npcPreviewTalkFunctionId,
|
||||
fallbackCompanionName,
|
||||
turnVisualMs,
|
||||
}: {
|
||||
gameState: GameState;
|
||||
currentStory: StoryMoment | null;
|
||||
isLoading: boolean;
|
||||
setGameState: Dispatch<SetStateAction<GameState>>;
|
||||
setCurrentStory: Dispatch<SetStateAction<StoryMoment | null>>;
|
||||
setAiError: Dispatch<SetStateAction<string | null>>;
|
||||
setIsLoading: Dispatch<SetStateAction<boolean>>;
|
||||
setBattleReward: Dispatch<SetStateAction<BattleRewardSummary | null>>;
|
||||
buildResolvedChoiceState: (state: GameState, option: StoryOption, character: Character) => ResolvedChoiceState;
|
||||
playResolvedChoice: (
|
||||
state: GameState,
|
||||
option: StoryOption,
|
||||
character: Character,
|
||||
resolvedChoice: ResolvedChoiceState,
|
||||
sync?: EscapePlaybackSync,
|
||||
) => Promise<GameState>;
|
||||
buildStoryContextFromState: BuildStoryContextFromState;
|
||||
buildStoryFromResponse: BuildStoryFromResponse;
|
||||
buildFallbackStoryForState: BuildFallbackStoryForState;
|
||||
generateStoryForState: GenerateStoryForState;
|
||||
getAvailableOptionsForState: (state: GameState, character: Character) => StoryOption[] | null;
|
||||
getStoryGenerationHostileNpcs: (state: GameState) => GameState['sceneHostileNpcs'];
|
||||
getResolvedSceneHostileNpcs: (state: GameState) => GameState['sceneHostileNpcs'];
|
||||
buildNpcStory: BuildNpcStory;
|
||||
handleNpcBattleConversationContinuation: HandleNpcBattleConversationContinuation;
|
||||
updateQuestLog: UpdateQuestLog;
|
||||
incrementRuntimeStats: IncrementRuntimeStats;
|
||||
getCampCompanionTravelScene: (state: GameState, character: Character) => GameState['currentScenePreset'] | null;
|
||||
enterNpcInteraction: (encounter: Encounter, actionText: string) => boolean;
|
||||
handleNpcInteraction: (option: StoryOption) => boolean | Promise<boolean>;
|
||||
handleTreasureInteraction: (
|
||||
option: StoryOption,
|
||||
) => void | Promise<void> | boolean | Promise<boolean>;
|
||||
commitGeneratedStateWithEncounterEntry: CommitGeneratedStateWithEncounterEntry;
|
||||
finalizeNpcBattleResult: (
|
||||
state: GameState,
|
||||
character: Character,
|
||||
battleMode: NonNullable<GameState['currentNpcBattleMode']>,
|
||||
battleOutcome: GameState['currentNpcBattleOutcome'],
|
||||
) => { nextState: GameState; resultText: string } | null;
|
||||
isContinueAdventureOption: (option: StoryOption) => boolean;
|
||||
isCampTravelHomeOption: (option: StoryOption) => boolean;
|
||||
isRegularNpcEncounter: (encounter: GameState['currentEncounter']) => encounter is Encounter;
|
||||
isNpcEncounter: (encounter: GameState['currentEncounter']) => encounter is Encounter;
|
||||
npcPreviewTalkFunctionId: string;
|
||||
fallbackCompanionName: string;
|
||||
turnVisualMs: number;
|
||||
}) {
|
||||
const handleChoice = async (option: StoryOption) => {
|
||||
const character = gameState.playerCharacter;
|
||||
if (!gameState.worldType || !character || isLoading) return;
|
||||
if (option.disabled) return;
|
||||
|
||||
if (currentStory?.deferredOptions?.length && isContinueAdventureOption(option)) {
|
||||
if (currentStory.deferredRuntimeState) {
|
||||
setGameState({
|
||||
...gameState,
|
||||
currentEncounter: null,
|
||||
npcInteractionActive: false,
|
||||
sceneHostileNpcs: [],
|
||||
inBattle: false,
|
||||
currentBattleNpcId: null,
|
||||
currentNpcBattleMode: null,
|
||||
currentNpcBattleOutcome: null,
|
||||
currentScenePreset:
|
||||
currentStory.deferredRuntimeState.currentScenePreset ??
|
||||
gameState.currentScenePreset,
|
||||
storyEngineMemory:
|
||||
currentStory.deferredRuntimeState.storyEngineMemory ??
|
||||
gameState.storyEngineMemory,
|
||||
});
|
||||
}
|
||||
setCurrentStory({
|
||||
...currentStory,
|
||||
options: currentStory.deferredOptions,
|
||||
deferredOptions: undefined,
|
||||
deferredRuntimeState: undefined,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (isCampTravelHomeOption(option)) {
|
||||
await runCampTravelHomeChoice({
|
||||
gameState,
|
||||
option,
|
||||
character,
|
||||
setBattleReward,
|
||||
setAiError,
|
||||
setIsLoading,
|
||||
setGameState,
|
||||
incrementRuntimeStats,
|
||||
getCampCompanionTravelScene,
|
||||
commitGeneratedStateWithEncounterEntry,
|
||||
isNpcEncounter,
|
||||
fallbackCompanionName,
|
||||
turnVisualMs,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (shouldOpenLocalRuntimeNpcModal(option)) {
|
||||
setAiError(null);
|
||||
await handleNpcInteraction(option);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isRpgRuntimeServerFunctionId(option.functionId)) {
|
||||
await runServerRuntimeChoiceAction({
|
||||
gameState,
|
||||
currentStory,
|
||||
option,
|
||||
character,
|
||||
setBattleReward,
|
||||
setAiError,
|
||||
setIsLoading,
|
||||
setGameState,
|
||||
setCurrentStory: (story) => setCurrentStory(story),
|
||||
buildFallbackStoryForState,
|
||||
turnVisualMs,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
option.functionId === npcPreviewTalkFunctionId
|
||||
&& isRegularNpcEncounter(gameState.currentEncounter)
|
||||
&& !gameState.npcInteractionActive
|
||||
) {
|
||||
setAiError(null);
|
||||
enterNpcInteraction(gameState.currentEncounter, option.actionText);
|
||||
return;
|
||||
}
|
||||
|
||||
if (option.interaction?.kind === 'npc') {
|
||||
setAiError(null);
|
||||
await handleNpcInteraction(option);
|
||||
return;
|
||||
}
|
||||
|
||||
if (option.interaction?.kind === 'treasure') {
|
||||
setAiError(null);
|
||||
await handleTreasureInteraction(option);
|
||||
return;
|
||||
}
|
||||
|
||||
await runLocalStoryChoiceContinuation({
|
||||
gameState,
|
||||
currentStory,
|
||||
option,
|
||||
character,
|
||||
setGameState,
|
||||
setCurrentStory: (story) => setCurrentStory(story),
|
||||
setAiError,
|
||||
setIsLoading,
|
||||
setBattleReward,
|
||||
buildResolvedChoiceState,
|
||||
playResolvedChoice,
|
||||
buildStoryContextFromState,
|
||||
buildStoryFromResponse,
|
||||
buildFallbackStoryForState,
|
||||
generateStoryForState,
|
||||
getAvailableOptionsForState,
|
||||
getStoryGenerationHostileNpcs,
|
||||
getResolvedSceneHostileNpcs,
|
||||
buildNpcStory,
|
||||
handleNpcBattleConversationContinuation,
|
||||
updateQuestLog,
|
||||
incrementRuntimeStats,
|
||||
finalizeNpcBattleResult,
|
||||
isRegularNpcEncounter,
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
handleChoice,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user