This commit is contained in:
140
src/hooks/rpg-runtime-story/rpgRuntimeStoryGateway.ts
Normal file
140
src/hooks/rpg-runtime-story/rpgRuntimeStoryGateway.ts
Normal file
@@ -0,0 +1,140 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user