182 lines
4.8 KiB
TypeScript
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,
|
|
};
|
|
}
|