This commit is contained in:
2026-04-19 20:33:18 +08:00
parent 692643136f
commit 67c584b4df
123 changed files with 11898 additions and 4082 deletions

View File

@@ -123,19 +123,12 @@ export function buildPreparedOpeningAdventure({
export async function playOpeningAdventureSequence({
gameState,
character,
encounter,
preparedStory,
setGameState,
setCurrentStory,
setAiError,
setIsLoading,
buildDialogueStoryMoment,
buildStoryContextFromState,
getStoryGenerationHostileNpcs,
hasRenderableDialogueTurns,
inferOpeningCampFollowupOptions,
getTypewriterDelay,
}: {
gameState: GameState;
character: Character;
@@ -168,160 +161,69 @@ export async function playOpeningAdventureSequence({
) => Promise<StoryOption[]>;
getTypewriterDelay: (char: string) => number;
}) {
const {
fallbackText,
openingOptions,
resultText: openingBackground,
} = preparedStory;
const actionText = `在营地与 ${encounter.npcName} 交换开场判断`;
const { fallbackText, openingOptions } = preparedStory;
const campScene = gameState.worldType
? getWorldCampScenePreset(gameState.worldType)
: null;
const entryState: GameState = {
...gameState,
currentScenePreset: campScene ?? gameState.currentScenePreset,
currentEncounter: {
...encounter,
xMeters: encounter.xMeters ?? CALL_OUT_ENTRY_X_METERS,
},
};
const resolvedEncounter: Encounter = {
const storyEncounter: Encounter = {
...encounter,
xMeters: RESOLVED_ENTITY_X_METERS,
};
const storyEncounter: Encounter = {
...resolvedEncounter,
specialBehavior: 'camp_companion',
};
const resolvedState: GameState = {
...gameState,
currentScenePreset: campScene ?? gameState.currentScenePreset,
currentEncounter: resolvedEncounter,
npcInteractionActive: false,
currentEncounter: storyEncounter,
npcInteractionActive: true,
};
setGameState(entryState);
setAiError(null);
setIsLoading(true);
setIsLoading(false);
try {
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 storyState: GameState = {
...resolvedState,
currentEncounter: storyEncounter,
npcInteractionActive: false,
};
setGameState(storyState);
setCurrentStory(buildDialogueStoryMoment(encounter.npcName, '', [], true));
let openingText = fallbackText;
let resolvedOpeningOptions = sortStoryOptionsByPriority(openingOptions);
try {
const response = await generateNextStep(
gameState.worldType!,
character,
getStoryGenerationHostileNpcs(storyState),
gameState.storyHistory,
actionText,
buildStoryContextFromState(storyState, {
lastFunctionId: OPENING_CAMP_DIALOGUE_FUNCTION_ID,
}),
setGameState(resolvedState);
setCurrentStory({
text: fallbackText,
options: sortStoryOptionsByPriority(openingOptions),
displayMode: 'dialogue',
dialogue: [
{
availableOptions: openingOptions,
speaker: 'npc',
speakerName: encounter.npcName,
text: fallbackText,
},
);
const generatedText = response.storyText.trim();
if (
generatedText &&
hasRenderableDialogueTurns(generatedText, encounter.npcName)
) {
openingText = generatedText;
}
if (response.options.length > 0) {
resolvedOpeningOptions = sortStoryOptionsByPriority(response.options);
}
} catch (error) {
console.error('Failed to infer opening camp dialogue:', error);
setAiError(error instanceof Error ? error.message : '未知智能生成错误');
}
const finalHistory = [
...gameState.storyHistory,
createHistoryMoment(actionText, 'action'),
createHistoryMoment(openingText, 'result', openingOptions),
];
const finalState: GameState = {
...storyState,
storyHistory: finalHistory,
};
setGameState(finalState);
const openingOptionsPromise = inferOpeningCampFollowupOptions(
finalState,
character,
resolvedOpeningOptions,
openingBackground,
openingText,
);
let displayedText = '';
for (const nextChar of openingText) {
displayedText += nextChar;
setCurrentStory(
buildDialogueStoryMoment(encounter.npcName, displayedText, [], true),
);
await new Promise((resolve) =>
window.setTimeout(resolve, getTypewriterDelay(nextChar)),
);
}
const finalOpeningOptions = await openingOptionsPromise;
setCurrentStory(
buildDialogueStoryMoment(
encounter.npcName,
openingText,
finalOpeningOptions,
false,
),
);
],
streaming: false,
npcChatState: {
npcId: storyEncounter.id ?? storyEncounter.npcName,
npcName: storyEncounter.npcName,
turnCount: 0,
customInputPlaceholder: '输入你想对 TA 说的话',
},
});
} catch (error) {
console.error('Failed to play opening adventure sequence:', error);
setAiError(error instanceof Error ? error.message : '未知智能生成错误');
setCurrentStory(
buildDialogueStoryMoment(
encounter.npcName,
fallbackText,
openingOptions,
false,
),
);
setGameState(resolvedState);
setCurrentStory({
text: fallbackText,
options: sortStoryOptionsByPriority(openingOptions),
displayMode: 'dialogue',
dialogue: [
{
speaker: 'npc',
speakerName: encounter.npcName,
text: fallbackText,
},
],
streaming: false,
npcChatState: {
npcId: storyEncounter.id ?? storyEncounter.npcName,
npcName: storyEncounter.npcName,
turnCount: 0,
customInputPlaceholder: '输入你想对 TA 说的话',
},
});
} finally {
setIsLoading(false);
}