1
This commit is contained in:
@@ -1,9 +1,3 @@
|
||||
import type {
|
||||
RuntimeStoryActionRequest,
|
||||
RuntimeStoryActionResponse,
|
||||
RuntimeStoryOptionView,
|
||||
RuntimeStoryStateRequest,
|
||||
} from '../../../packages/shared/src/contracts/rpgRuntimeStoryState';
|
||||
import type {
|
||||
RuntimeStoryChoicePayload,
|
||||
ServerRuntimeFunctionId,
|
||||
@@ -13,6 +7,13 @@ import {
|
||||
SERVER_RUNTIME_FUNCTION_IDS,
|
||||
TASK5_RUNTIME_FUNCTION_IDS,
|
||||
} from '../../../packages/shared/src/contracts/rpgRuntimeStoryAction';
|
||||
import type {
|
||||
RuntimeStoryActionRequest,
|
||||
RuntimeStoryActionResponse,
|
||||
RuntimeStoryBootstrapRequest,
|
||||
RuntimeStoryBootstrapResponse,
|
||||
RuntimeStoryOptionView,
|
||||
} from '../../../packages/shared/src/contracts/rpgRuntimeStoryState';
|
||||
import { rehydrateSavedSnapshot } from '../../persistence/runtimeSnapshot';
|
||||
import type { HydratedSavedGameSnapshot } from '../../persistence/runtimeSnapshotTypes';
|
||||
import type { GameState, StoryMoment, StoryOption } from '../../types';
|
||||
@@ -44,11 +45,13 @@ export type RuntimeStoryResponse = RuntimeStoryActionResponse<
|
||||
GameState,
|
||||
StoryMoment
|
||||
>;
|
||||
export type { RuntimeStoryChoicePayload };
|
||||
export type RuntimeStorySnapshotRequest = RuntimeStoryStateRequest<
|
||||
export type RuntimeStoryBootstrapResult = RuntimeStoryBootstrapResponse<
|
||||
GameState,
|
||||
StoryMoment
|
||||
>['snapshot'];
|
||||
>;
|
||||
export type RuntimeStoryInventoryView =
|
||||
RuntimeStoryResponse['viewModel']['inventory'];
|
||||
export type { RuntimeStoryChoicePayload };
|
||||
|
||||
function requestRuntimeStoryJson<T>(
|
||||
path: string,
|
||||
@@ -56,6 +59,8 @@ function requestRuntimeStoryJson<T>(
|
||||
fallbackMessage: string,
|
||||
options: RpgRuntimeStoryClientOptions = {},
|
||||
) {
|
||||
// 中文注释:runtime story 请求默认带一层轻量重试,
|
||||
// 因为这里既有 state 拉取,也有动作结算,请求失败会直接影响当前回合体验。
|
||||
return requestJson<T>(
|
||||
`${RUNTIME_STORY_API_BASE}${path}`,
|
||||
{
|
||||
@@ -71,6 +76,8 @@ function createRuntimeStoryOption(
|
||||
option: RuntimeStoryOptionView,
|
||||
_gameState?: Pick<GameState, 'currentEncounter'>,
|
||||
): StoryOption {
|
||||
// 中文注释:服务端 viewModel 当前只返回动作层字段,
|
||||
// 前端在这里补齐 StoryOption 所需的基础表现字段,保持冒险面板消费接口稳定。
|
||||
return {
|
||||
functionId: option.functionId,
|
||||
actionText: option.actionText,
|
||||
@@ -118,6 +125,8 @@ export function isServerRuntimeFunctionId(
|
||||
}
|
||||
|
||||
export function shouldUseServerRuntimeOptions(options: StoryOption[] | null) {
|
||||
// 中文注释:只有当整组选项都已经切换到服务端 function id 体系时,
|
||||
// 前端才把这轮视为“纯服务端 runtime 选项”,避免本地/服务端动作混用。
|
||||
return Boolean(
|
||||
options?.length &&
|
||||
options.every((option) => isServerRuntimeFunctionId(option.functionId)),
|
||||
@@ -152,6 +161,8 @@ export function resolveRuntimeStoryMoment(params: {
|
||||
fallbackGameState?: Pick<GameState, 'currentEncounter'>;
|
||||
fallbackStoryText?: string;
|
||||
}) {
|
||||
// 中文注释:对话态 story 往往包含 deferredOptions / dialogue 结构,
|
||||
// 这类内容如果已经存进快照,应优先使用快照,避免被普通 presentation 选项覆盖。
|
||||
if (shouldPreferSnapshotStory(params.hydratedSnapshot.currentStory)) {
|
||||
return params.hydratedSnapshot.currentStory!;
|
||||
}
|
||||
@@ -178,32 +189,18 @@ export async function getRuntimeStoryState(
|
||||
params: {
|
||||
sessionId: string;
|
||||
clientVersion?: number;
|
||||
snapshot?: RuntimeStorySnapshotRequest;
|
||||
},
|
||||
options: RpgRuntimeStoryClientOptions = {},
|
||||
) {
|
||||
const normalizedSessionId = params.sessionId || DEFAULT_SESSION_ID;
|
||||
const response = params.snapshot
|
||||
? await requestRuntimeStoryJson<RuntimeStoryResponse>(
|
||||
'/state/resolve',
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
sessionId: normalizedSessionId,
|
||||
clientVersion: params.clientVersion,
|
||||
snapshot: params.snapshot,
|
||||
} satisfies RuntimeStoryStateRequest<GameState, StoryMoment>),
|
||||
},
|
||||
'读取运行时故事状态失败',
|
||||
options,
|
||||
)
|
||||
: await requestRuntimeStoryJson<RuntimeStoryResponse>(
|
||||
`/state/${encodeURIComponent(normalizedSessionId)}`,
|
||||
{ method: 'GET' },
|
||||
'读取运行时故事状态失败',
|
||||
options,
|
||||
);
|
||||
// 中文注释:runtime story 状态读取只按服务端持久化 sessionId 拉取,
|
||||
// 不再允许前端上传本地 GameState 快照参与解析。
|
||||
const response = await requestRuntimeStoryJson<RuntimeStoryResponse>(
|
||||
`/state/${encodeURIComponent(normalizedSessionId)}`,
|
||||
{ method: 'GET' },
|
||||
'读取运行时故事状态失败',
|
||||
options,
|
||||
);
|
||||
|
||||
return {
|
||||
...response,
|
||||
@@ -213,6 +210,51 @@ export async function getRuntimeStoryState(
|
||||
} satisfies RuntimeStoryResponse;
|
||||
}
|
||||
|
||||
export async function loadRuntimeInventoryView(
|
||||
params: {
|
||||
gameState: Pick<GameState, 'runtimeSessionId' | 'runtimeActionVersion'>;
|
||||
},
|
||||
options: RpgRuntimeStoryClientOptions = {},
|
||||
) {
|
||||
// 中文注释:背包 / 装备 / 锻造 view 只读取后端已持久化的 runtime session;
|
||||
// 前端不再用本地背包、货币或装备状态重算配方可用性。
|
||||
const response = await getRuntimeStoryState(
|
||||
{
|
||||
sessionId: getRuntimeSessionId(params.gameState),
|
||||
clientVersion: getRuntimeClientVersion(params.gameState),
|
||||
},
|
||||
options,
|
||||
);
|
||||
|
||||
return response.viewModel.inventory;
|
||||
}
|
||||
|
||||
export async function beginRuntimeStorySession(
|
||||
params: RuntimeStoryBootstrapRequest<
|
||||
GameState['customWorldProfile'],
|
||||
NonNullable<GameState['playerCharacter']>
|
||||
>,
|
||||
options: RpgRuntimeStoryClientOptions = {},
|
||||
) {
|
||||
const response = await requestRuntimeStoryJson<RuntimeStoryBootstrapResult>(
|
||||
'/sessions',
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(params),
|
||||
},
|
||||
'初始化运行时开局失败',
|
||||
options,
|
||||
);
|
||||
|
||||
return {
|
||||
...response,
|
||||
snapshot: rehydrateSavedSnapshot(
|
||||
response.snapshot as HydratedSavedGameSnapshot,
|
||||
),
|
||||
} satisfies RuntimeStoryBootstrapResult;
|
||||
}
|
||||
|
||||
export async function resolveRuntimeStoryAction(
|
||||
params: {
|
||||
sessionId?: string;
|
||||
@@ -220,10 +262,11 @@ export async function resolveRuntimeStoryAction(
|
||||
option: Pick<StoryOption, 'functionId' | 'actionText'>;
|
||||
targetId?: string;
|
||||
payload?: RuntimeStoryChoicePayload;
|
||||
snapshot?: RuntimeStorySnapshotRequest;
|
||||
},
|
||||
options: RpgRuntimeStoryClientOptions = {},
|
||||
) {
|
||||
// 中文注释:story_choice 是当前前端统一提交给服务端的动作包裹格式,
|
||||
// optionText 会一起带上,方便服务端日志、提示词和调试链查看用户当轮选择。
|
||||
const response = await requestRuntimeStoryJson<RuntimeStoryResponse>(
|
||||
'/actions/resolve',
|
||||
{
|
||||
@@ -241,7 +284,6 @@ export async function resolveRuntimeStoryAction(
|
||||
...(params.payload ?? {}),
|
||||
},
|
||||
},
|
||||
snapshot: params.snapshot,
|
||||
} satisfies RuntimeStoryActionRequest),
|
||||
},
|
||||
'执行运行时动作失败',
|
||||
@@ -260,19 +302,23 @@ export function getRuntimeActionSnapshot(response: RuntimeStoryResponse) {
|
||||
return rehydrateSavedSnapshot(response.snapshot as HydratedSavedGameSnapshot);
|
||||
}
|
||||
|
||||
export const beginRpgRuntimeStorySession = beginRuntimeStorySession;
|
||||
export const getRpgRuntimeActionSnapshot = getRuntimeActionSnapshot;
|
||||
export const getRpgRuntimeClientVersion = getRuntimeClientVersion;
|
||||
export const getRpgRuntimeSessionId = getRuntimeSessionId;
|
||||
export const getRpgRuntimeStoryState = getRuntimeStoryState;
|
||||
export const isRpgRuntimeServerFunctionId = isServerRuntimeFunctionId;
|
||||
export const isRpgRuntimeTaskFunctionId = isTask5RuntimeFunctionId;
|
||||
export const loadRpgRuntimeInventoryView = loadRuntimeInventoryView;
|
||||
export const resolveRpgRuntimeStoryAction = resolveRuntimeStoryAction;
|
||||
export const resolveRpgRuntimeStoryMoment = resolveRuntimeStoryMoment;
|
||||
export const shouldUseRpgRuntimeServerOptions = shouldUseServerRuntimeOptions;
|
||||
|
||||
export const rpgRuntimeStoryClient = {
|
||||
beginSession: beginRpgRuntimeStorySession,
|
||||
getActionSnapshot: getRpgRuntimeActionSnapshot,
|
||||
getClientVersion: getRpgRuntimeClientVersion,
|
||||
getInventoryView: loadRpgRuntimeInventoryView,
|
||||
getSessionId: getRpgRuntimeSessionId,
|
||||
getState: getRpgRuntimeStoryState,
|
||||
resolveAction: resolveRpgRuntimeStoryAction,
|
||||
|
||||
Reference in New Issue
Block a user