141 lines
4.5 KiB
TypeScript
141 lines
4.5 KiB
TypeScript
import { rehydrateSavedSnapshot } from '../../persistence/runtimeSnapshot';
|
|
import type { HydratedSavedGameSnapshot } from '../../persistence/runtimeSnapshotTypes';
|
|
import {
|
|
getRpgRuntimeClientVersion,
|
|
getRpgRuntimeSessionId,
|
|
getRpgRuntimeStoryState,
|
|
resolveRpgRuntimeStoryAction,
|
|
type RuntimeStorySnapshotRequest,
|
|
resolveRpgRuntimeStoryMoment,
|
|
type RuntimeStoryChoicePayload,
|
|
type RuntimeStoryResponse,
|
|
} from '../../services/rpg-runtime/rpgRuntimeStoryClient';
|
|
import type { GameState, StoryMoment, StoryOption } from '../../types';
|
|
|
|
function getRuntimeResponseOptions(response: RuntimeStoryResponse) {
|
|
return response.viewModel.availableOptions.length > 0
|
|
? response.viewModel.availableOptions
|
|
: response.presentation.options;
|
|
}
|
|
|
|
function buildRuntimeSnapshotRequest(
|
|
gameState: GameState,
|
|
currentStory: StoryMoment | null,
|
|
): RuntimeStorySnapshotRequest {
|
|
return {
|
|
gameState,
|
|
bottomTab: 'adventure',
|
|
currentStory,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 前端访问服务端 runtime story 的统一网关。
|
|
* 统一处理 option catalog 拉取、继续游戏恢复与正式动作结算。
|
|
*/
|
|
export async function loadServerRuntimeOptionCatalog(params: {
|
|
gameState: GameState;
|
|
currentStory: StoryMoment | null;
|
|
}) {
|
|
const response = await getRpgRuntimeStoryState({
|
|
sessionId: getRpgRuntimeSessionId(params.gameState),
|
|
clientVersion: getRpgRuntimeClientVersion(params.gameState),
|
|
snapshot: buildRuntimeSnapshotRequest(params.gameState, params.currentStory),
|
|
});
|
|
const options = resolveRpgRuntimeStoryMoment({
|
|
response,
|
|
hydratedSnapshot: response.snapshot,
|
|
fallbackGameState: params.gameState,
|
|
fallbackStoryText: response.presentation.storyText,
|
|
}).options;
|
|
|
|
return options.length > 0 ? options : null;
|
|
}
|
|
|
|
export async function resumeServerRuntimeStory(
|
|
snapshot: HydratedSavedGameSnapshot,
|
|
) {
|
|
const hydratedSnapshot = rehydrateSavedSnapshot(snapshot);
|
|
const shouldRefreshFromServer =
|
|
hydratedSnapshot.gameState.currentScene === 'Story' &&
|
|
Boolean(hydratedSnapshot.gameState.worldType) &&
|
|
Boolean(hydratedSnapshot.gameState.playerCharacter);
|
|
|
|
if (!shouldRefreshFromServer) {
|
|
return {
|
|
hydratedSnapshot,
|
|
nextStory: hydratedSnapshot.currentStory,
|
|
};
|
|
}
|
|
|
|
const response = await getRpgRuntimeStoryState({
|
|
sessionId: getRpgRuntimeSessionId(hydratedSnapshot.gameState),
|
|
});
|
|
const resumedSnapshot = rehydrateSavedSnapshot(response.snapshot);
|
|
const runtimeOptions = getRuntimeResponseOptions(response);
|
|
const nextStory =
|
|
response.presentation.storyText || runtimeOptions.length > 0
|
|
? resolveRpgRuntimeStoryMoment({
|
|
response,
|
|
hydratedSnapshot: resumedSnapshot,
|
|
fallbackGameState: hydratedSnapshot.gameState,
|
|
fallbackStoryText:
|
|
response.presentation.storyText ||
|
|
resumedSnapshot.currentStory?.text ||
|
|
hydratedSnapshot.currentStory?.text ||
|
|
'',
|
|
})
|
|
: resumedSnapshot.currentStory;
|
|
|
|
return {
|
|
hydratedSnapshot: resumedSnapshot,
|
|
nextStory,
|
|
};
|
|
}
|
|
|
|
export async function resolveServerRuntimeChoice(params: {
|
|
gameState: GameState;
|
|
currentStory: StoryMoment | null;
|
|
option: Pick<StoryOption, 'functionId' | 'actionText'> &
|
|
Partial<Pick<StoryOption, 'interaction'>>;
|
|
payload?: RuntimeStoryChoicePayload;
|
|
}) {
|
|
const response = await resolveRpgRuntimeStoryAction({
|
|
sessionId: getRpgRuntimeSessionId(params.gameState),
|
|
clientVersion: getRpgRuntimeClientVersion(params.gameState),
|
|
option: params.option,
|
|
targetId:
|
|
params.option.interaction?.kind === 'npc'
|
|
? params.option.interaction.npcId
|
|
: undefined,
|
|
payload: params.payload,
|
|
snapshot: buildRuntimeSnapshotRequest(params.gameState, params.currentStory),
|
|
});
|
|
const hydratedSnapshot = rehydrateSavedSnapshot(response.snapshot);
|
|
|
|
return {
|
|
response,
|
|
hydratedSnapshot,
|
|
nextStory: resolveRpgRuntimeStoryMoment({
|
|
response,
|
|
hydratedSnapshot,
|
|
fallbackGameState: params.gameState,
|
|
fallbackStoryText:
|
|
response.presentation.storyText ||
|
|
hydratedSnapshot.currentStory?.text ||
|
|
params.option.actionText,
|
|
}),
|
|
};
|
|
}
|
|
|
|
export type LoadRpgRuntimeOptionCatalogParams = Parameters<
|
|
typeof loadServerRuntimeOptionCatalog
|
|
>[0];
|
|
export type ResolveRpgRuntimeChoiceParams = Parameters<
|
|
typeof resolveServerRuntimeChoice
|
|
>[0];
|
|
|
|
export const loadRpgRuntimeOptionCatalog = loadServerRuntimeOptionCatalog;
|
|
export const resumeRpgRuntimeStory = resumeServerRuntimeStory;
|
|
export const resolveRpgRuntimeChoice = resolveServerRuntimeChoice;
|