Files
Genarrative/src/hooks/rpg-runtime-story/progressionActions.ts
2026-04-28 19:36:39 +08:00

182 lines
4.8 KiB
TypeScript

import type { Dispatch, SetStateAction } from 'react';
import {
hasEncounterEntity,
interpolateEncounterTransitionState,
} from '../../data/encounterTransition';
import { createHistoryMoment } from '../../services/storyHistory';
import type {
Character,
GameState,
StoryMoment,
StoryOption,
} from '../../types';
import type { CommitGeneratedState } from '../generatedState';
const ENCOUNTER_ENTRY_DURATION_MS = 1800;
const ENCOUNTER_ENTRY_TICK_MS = 180;
export type GenerateStoryForState = (params: {
state: GameState;
character: Character;
history: GameState['storyHistory'];
choice?: string;
lastFunctionId?: string | null;
optionCatalog?: StoryOption[] | null;
}) => Promise<StoryMoment>;
type BuildFallbackStoryForState = (
state: GameState,
character: Character,
fallbackText?: string,
) => StoryMoment;
export type CommitGeneratedStateWithEncounterEntry = (
entryState: GameState,
resolvedState: GameState,
character: Character,
actionText: string,
resultText: string,
lastFunctionId?: string,
) => Promise<void>;
export function appendStoryHistory(
state: GameState,
actionText: string,
resultText: string,
): GameState['storyHistory'] {
return [
...state.storyHistory,
createHistoryMoment(actionText, 'action'),
createHistoryMoment(resultText, 'result'),
];
}
export function createStoryProgressionActions({
gameState,
setGameState,
setCurrentStory,
setAiError,
setIsLoading,
generateStoryForState,
buildFallbackStoryForState,
}: {
gameState: GameState;
setGameState: Dispatch<SetStateAction<GameState>>;
setCurrentStory: Dispatch<SetStateAction<StoryMoment | null>>;
setAiError: Dispatch<SetStateAction<string | null>>;
setIsLoading: Dispatch<SetStateAction<boolean>>;
generateStoryForState: GenerateStoryForState;
buildFallbackStoryForState: BuildFallbackStoryForState;
}) {
const commitGeneratedState: CommitGeneratedState = async (
nextState,
character,
actionText,
resultText,
lastFunctionId,
) => {
const nextHistory = appendStoryHistory(gameState, actionText, resultText);
const stateWithHistory = {
...nextState,
storyHistory: nextHistory,
} as GameState;
setGameState(stateWithHistory);
setAiError(null);
setIsLoading(true);
try {
const nextStory = await generateStoryForState({
state: stateWithHistory,
character,
history: nextHistory,
choice: actionText,
lastFunctionId,
});
setGameState(stateWithHistory);
setCurrentStory(nextStory);
} catch (error) {
console.error('Failed to continue scripted story:', error);
setAiError(error instanceof Error ? error.message : '未知智能生成错误');
setCurrentStory(
buildFallbackStoryForState(stateWithHistory, character, resultText),
);
} finally {
setIsLoading(false);
}
};
const commitGeneratedStateWithEncounterEntry: CommitGeneratedStateWithEncounterEntry =
async (
entryState,
resolvedState,
character,
actionText,
resultText,
lastFunctionId,
) => {
setGameState(entryState);
setAiError(null);
setIsLoading(true);
if (hasEncounterEntity(resolvedState)) {
const runTicks = Math.max(
1,
Math.ceil(ENCOUNTER_ENTRY_DURATION_MS / ENCOUNTER_ENTRY_TICK_MS),
);
const tickDurationMs = Math.max(
1,
Math.round(ENCOUNTER_ENTRY_DURATION_MS / runTicks),
);
for (let tick = 1; tick <= runTicks; tick += 1) {
const progress = tick / runTicks;
setGameState(
interpolateEncounterTransitionState(
entryState,
resolvedState,
progress,
),
);
await new Promise((resolve) =>
window.setTimeout(resolve, tickDurationMs),
);
}
}
const nextHistory = appendStoryHistory(gameState, actionText, resultText);
const stateWithHistory = {
...resolvedState,
storyHistory: nextHistory,
} as GameState;
setGameState(stateWithHistory);
try {
const nextStory = await generateStoryForState({
state: stateWithHistory,
character,
history: nextHistory,
choice: actionText,
lastFunctionId,
});
setGameState(stateWithHistory);
setCurrentStory(nextStory);
} catch (error) {
console.error('Failed to continue encounter-entry story:', error);
setAiError(error instanceof Error ? error.message : '未知智能生成错误');
setCurrentStory(
buildFallbackStoryForState(stateWithHistory, character, resultText),
);
} finally {
setIsLoading(false);
}
};
return {
commitGeneratedState,
commitGeneratedStateWithEncounterEntry,
};
}