Files
Genarrative/server-node/src/modules/runtime-item/treasureStoryActionService.ts
2026-04-21 18:27:46 +08:00

141 lines
4.0 KiB
TypeScript

import type {
RuntimeStoryActionRequest,
RuntimeStoryPatch,
} from '../../../../packages/shared/src/contracts/rpgRuntimeStoryState.js';
import { conflict, invalidRequest } from '../../errors.js';
import {
addInventoryItems,
appendStoryEngineCarrierMemory,
} from '../../bridges/legacyNpcTask6Bridge.js';
import {
buildTreasureResultText,
resolveTreasureReward,
} from '../../bridges/legacyTreasureRuntimeBridge.js';
import { buildBuildToast } from '../inventory/inventoryStoryActionService.js';
import {
replaceRuntimeSessionRawGameState,
} from '../rpg-runtime-story/RpgRuntimeSnapshotSync.js';
import type { RuntimeSession } from '../rpg-runtime-story/RpgRuntimeSessionLoader.js';
const SUPPORTED_TREASURE_STORY_FUNCTION_IDS = new Set<string>([
'treasure_inspect',
'treasure_leave',
'treasure_secure',
]);
type TreasureStoryResolution = {
actionText: string;
resultText: string;
patches: RuntimeStoryPatch[];
toast?: string | null;
};
type JsonRecord = Record<string, unknown>;
type RuntimeGameState = Parameters<typeof resolveTreasureReward>[0];
type RuntimeEncounter = Parameters<typeof resolveTreasureReward>[1];
function resolveTreasureAction(functionId: string) {
switch (functionId) {
case 'treasure_secure':
return 'secure';
case 'treasure_inspect':
return 'inspect';
case 'treasure_leave':
return 'leave';
default:
throw invalidRequest(`暂不支持的 Treasure 动作:${functionId}`);
}
}
function getTreasureEncounter(
session: RuntimeSession,
state: RuntimeGameState,
): RuntimeEncounter | null {
const rawEncounter = state.currentEncounter;
if (!rawEncounter || rawEncounter.kind !== 'treasure') {
return null;
}
return {
npcAvatar: '',
hostile: false,
...rawEncounter,
id: rawEncounter.id ?? session.currentEncounter?.id ?? rawEncounter.npcName,
} satisfies RuntimeEncounter;
}
export function isSupportedTreasureStoryFunctionId(functionId: string) {
return SUPPORTED_TREASURE_STORY_FUNCTION_IDS.has(functionId);
}
export function resolveTreasureStoryAction(
session: RuntimeSession,
request: RuntimeStoryActionRequest,
): TreasureStoryResolution {
const state = session.rawGameState as unknown as RuntimeGameState;
const encounter = getTreasureEncounter(session, state);
if (!encounter) {
throw conflict('当前没有可结算的宝藏遭遇。');
}
const action = resolveTreasureAction(request.action.functionId);
const reward =
action === 'leave' ? null : resolveTreasureReward(state, encounter, action);
let nextState = {
...state,
currentEncounter: null,
npcInteractionActive: false,
sceneHostileNpcs: [],
playerX: 0,
playerFacing: 'right' as const,
animationState: state.animationState,
scrollWorld: false,
inBattle: false,
playerHp: reward
? Math.min(state.playerMaxHp, state.playerHp + reward.hp)
: state.playerHp,
playerMana: reward
? Math.min(state.playerMaxMana, state.playerMana + reward.mana)
: state.playerMana,
playerCurrency: reward
? state.playerCurrency + reward.currency
: state.playerCurrency,
playerInventory: reward
? addInventoryItems(state.playerInventory, reward.items)
: state.playerInventory,
currentBattleNpcId: null,
currentNpcBattleMode: null,
currentNpcBattleOutcome: null,
sparReturnEncounter: null,
sparPlayerHpBefore: null,
sparPlayerMaxHpBefore: null,
sparStoryHistoryBefore: null,
} satisfies RuntimeGameState;
if (reward) {
nextState = appendStoryEngineCarrierMemory(nextState, reward.items);
}
replaceRuntimeSessionRawGameState(
session,
nextState as unknown as JsonRecord,
);
return {
actionText:
action === 'leave'
? '先记下位置'
: action === 'inspect'
? '仔细检查'
: '直接收取',
resultText: buildTreasureResultText(
encounter,
action,
reward ?? undefined,
state.worldType,
),
patches: [],
toast: reward ? buildBuildToast(nextState) : null,
};
}