1
This commit is contained in:
197
server-node/src/modules/inventory/inventoryStoryActionService.ts
Normal file
197
server-node/src/modules/inventory/inventoryStoryActionService.ts
Normal file
@@ -0,0 +1,197 @@
|
||||
import type {
|
||||
RuntimeStoryActionRequest,
|
||||
RuntimeStoryPatch,
|
||||
} from '../../../../packages/shared/src/contracts/story.js';
|
||||
import { conflict, invalidRequest } from '../../errors.js';
|
||||
import {
|
||||
calculatePlayerBuildSnapshot,
|
||||
type RuntimeGameState as BuildRuntimeGameState,
|
||||
} from '../build/buildCalculationService.js';
|
||||
import {
|
||||
craftForgeRecipe,
|
||||
dismantleInventoryItem,
|
||||
equipInventoryItem,
|
||||
reforgeInventoryItem,
|
||||
unequipInventoryItem,
|
||||
useInventoryItem,
|
||||
type InventoryMutationFailure,
|
||||
type InventoryMutationSuccess,
|
||||
type RuntimeGameState as InventoryRuntimeGameState,
|
||||
} from './inventoryMutationService.js';
|
||||
import {
|
||||
replaceRuntimeSessionRawGameState,
|
||||
type RuntimeSession,
|
||||
} from '../story/runtimeSession.js';
|
||||
|
||||
const SUPPORTED_INVENTORY_STORY_FUNCTION_IDS = new Set<string>([
|
||||
'equipment_equip',
|
||||
'equipment_unequip',
|
||||
'forge_craft',
|
||||
'forge_dismantle',
|
||||
'forge_reforge',
|
||||
'inventory_use',
|
||||
]);
|
||||
|
||||
type InventoryStoryResolution = {
|
||||
actionText: string;
|
||||
resultText: string;
|
||||
patches: RuntimeStoryPatch[];
|
||||
toast?: string | null;
|
||||
};
|
||||
|
||||
type JsonRecord = Record<string, unknown>;
|
||||
|
||||
function isObject(value: unknown): value is JsonRecord {
|
||||
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function readPayload(request: RuntimeStoryActionRequest) {
|
||||
return isObject(request.action.payload) ? request.action.payload : {};
|
||||
}
|
||||
|
||||
function readString(value: unknown) {
|
||||
return typeof value === 'string' && value.trim() ? value.trim() : '';
|
||||
}
|
||||
|
||||
function readItemId(request: RuntimeStoryActionRequest) {
|
||||
const payload = readPayload(request);
|
||||
return (
|
||||
readString(payload.itemId) ||
|
||||
readString(payload.targetId) ||
|
||||
readString(request.action.targetId)
|
||||
);
|
||||
}
|
||||
|
||||
function readRecipeId(request: RuntimeStoryActionRequest) {
|
||||
const payload = readPayload(request);
|
||||
return (
|
||||
readString(payload.recipeId) ||
|
||||
readString(payload.targetId) ||
|
||||
readString(request.action.targetId)
|
||||
);
|
||||
}
|
||||
|
||||
function readEquipmentSlotId(request: RuntimeStoryActionRequest) {
|
||||
const payload = readPayload(request);
|
||||
const slotId =
|
||||
readString(payload.slotId) || readString(request.action.targetId);
|
||||
|
||||
if (slotId === 'weapon' || slotId === 'armor' || slotId === 'relic') {
|
||||
return slotId;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
function refreshSessionFromGameState(
|
||||
session: RuntimeSession,
|
||||
nextGameState: InventoryMutationSuccess['nextState'],
|
||||
) {
|
||||
replaceRuntimeSessionRawGameState(
|
||||
session,
|
||||
nextGameState as unknown as JsonRecord,
|
||||
);
|
||||
}
|
||||
|
||||
export function buildBuildToast(
|
||||
nextState: InventoryMutationSuccess['nextState'],
|
||||
) {
|
||||
const snapshot = calculatePlayerBuildSnapshot(
|
||||
nextState as BuildRuntimeGameState,
|
||||
);
|
||||
if (!snapshot.ok) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const buildMultiplier =
|
||||
snapshot.value.buildBreakdown.buildDamageMultiplier.toFixed(2);
|
||||
return `当前 Build 倍率 x${buildMultiplier}`;
|
||||
}
|
||||
|
||||
function throwMutationFailure(error: InventoryMutationFailure): never {
|
||||
switch (error.code) {
|
||||
case 'item_not_equippable':
|
||||
case 'recipe_not_available':
|
||||
throw invalidRequest(error.message);
|
||||
default:
|
||||
throw conflict(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
function resolveMutation(
|
||||
request: RuntimeStoryActionRequest,
|
||||
state: InventoryRuntimeGameState,
|
||||
) {
|
||||
switch (request.action.functionId) {
|
||||
case 'inventory_use': {
|
||||
const itemId = readItemId(request);
|
||||
if (!itemId) {
|
||||
throw invalidRequest('inventory_use 缺少 itemId');
|
||||
}
|
||||
return useInventoryItem(state, itemId);
|
||||
}
|
||||
case 'equipment_equip': {
|
||||
const itemId = readItemId(request);
|
||||
if (!itemId) {
|
||||
throw invalidRequest('equipment_equip 缺少 itemId');
|
||||
}
|
||||
return equipInventoryItem(state, itemId);
|
||||
}
|
||||
case 'equipment_unequip': {
|
||||
const slotId = readEquipmentSlotId(request);
|
||||
if (!slotId) {
|
||||
throw invalidRequest('equipment_unequip 缺少合法 slotId');
|
||||
}
|
||||
return unequipInventoryItem(state, slotId);
|
||||
}
|
||||
case 'forge_craft': {
|
||||
const recipeId = readRecipeId(request);
|
||||
if (!recipeId) {
|
||||
throw invalidRequest('forge_craft 缺少 recipeId');
|
||||
}
|
||||
return craftForgeRecipe(state, recipeId);
|
||||
}
|
||||
case 'forge_dismantle': {
|
||||
const itemId = readItemId(request);
|
||||
if (!itemId) {
|
||||
throw invalidRequest('forge_dismantle 缺少 itemId');
|
||||
}
|
||||
return dismantleInventoryItem(state, itemId);
|
||||
}
|
||||
case 'forge_reforge': {
|
||||
const itemId = readItemId(request);
|
||||
if (!itemId) {
|
||||
throw invalidRequest('forge_reforge 缺少 itemId');
|
||||
}
|
||||
return reforgeInventoryItem(state, itemId);
|
||||
}
|
||||
default:
|
||||
throw invalidRequest(`暂不支持的 Inventory 动作:${request.action.functionId}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function isSupportedInventoryStoryFunctionId(functionId: string) {
|
||||
return SUPPORTED_INVENTORY_STORY_FUNCTION_IDS.has(functionId);
|
||||
}
|
||||
|
||||
export function resolveInventoryStoryAction(
|
||||
session: RuntimeSession,
|
||||
request: RuntimeStoryActionRequest,
|
||||
): InventoryStoryResolution {
|
||||
const mutation = resolveMutation(
|
||||
request,
|
||||
session.rawGameState as InventoryRuntimeGameState,
|
||||
);
|
||||
if (!mutation.ok) {
|
||||
throwMutationFailure(mutation);
|
||||
}
|
||||
|
||||
refreshSessionFromGameState(session, mutation.nextState);
|
||||
|
||||
return {
|
||||
actionText: mutation.actionText,
|
||||
resultText: mutation.detailText,
|
||||
patches: [],
|
||||
toast: buildBuildToast(mutation.nextState),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user