1
This commit is contained in:
@@ -1,14 +1,11 @@
|
||||
import { useMemo, type Dispatch, type SetStateAction } from 'react';
|
||||
import { type Dispatch, type SetStateAction, useEffect, useState } from 'react';
|
||||
|
||||
import type { RuntimeStoryInventoryActionView } from '../../../packages/shared/src/contracts/rpgRuntimeStoryState';
|
||||
import {
|
||||
EQUIPMENT_EQUIP_FUNCTION,
|
||||
EQUIPMENT_UNEQUIP_FUNCTION,
|
||||
FORGE_CRAFT_FUNCTION,
|
||||
FORGE_DISMANTLE_FUNCTION,
|
||||
FORGE_REFORGE_FUNCTION,
|
||||
INVENTORY_USE_FUNCTION,
|
||||
} from '../../data/functionCatalog';
|
||||
import { getForgeRecipeViews } from '../../data/forgeSystem';
|
||||
loadRpgRuntimeInventoryView,
|
||||
type RuntimeStoryChoicePayload,
|
||||
type RuntimeStoryInventoryView,
|
||||
} from '../../services/rpg-runtime';
|
||||
import type { Character, GameState, StoryMoment } from '../../types';
|
||||
import { resolveRpgRuntimeChoice } from '.';
|
||||
import type { InventoryFlowUi } from './uiTypes';
|
||||
@@ -41,20 +38,71 @@ export function useStoryInventoryActions({
|
||||
setIsLoading,
|
||||
buildFallbackStoryForState,
|
||||
} = runtime;
|
||||
const forgeRecipes = useMemo(
|
||||
() =>
|
||||
getForgeRecipeViews(
|
||||
gameState.playerInventory,
|
||||
gameState.playerCurrency,
|
||||
gameState.worldType,
|
||||
),
|
||||
[gameState.playerCurrency, gameState.playerInventory, gameState.worldType],
|
||||
);
|
||||
const [serverInventoryView, setServerInventoryView] =
|
||||
useState<RuntimeStoryInventoryView | null>(null);
|
||||
const runtimeSessionId = gameState.runtimeSessionId;
|
||||
const runtimeActionVersion = gameState.runtimeActionVersion;
|
||||
const currentScene = gameState.currentScene;
|
||||
const hasPlayerCharacter = Boolean(gameState.playerCharacter);
|
||||
|
||||
useEffect(() => {
|
||||
if (!hasPlayerCharacter || currentScene !== 'Story') {
|
||||
setServerInventoryView(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const controller = new AbortController();
|
||||
|
||||
void loadRpgRuntimeInventoryView(
|
||||
{
|
||||
gameState: {
|
||||
runtimeSessionId,
|
||||
runtimeActionVersion,
|
||||
},
|
||||
},
|
||||
{ signal: controller.signal },
|
||||
)
|
||||
.then((view) => {
|
||||
setServerInventoryView(view);
|
||||
})
|
||||
.catch((error) => {
|
||||
if (controller.signal.aborted) {
|
||||
return;
|
||||
}
|
||||
console.error('Failed to load inventory runtime view:', error);
|
||||
setAiError(error instanceof Error ? error.message : '背包视图同步失败');
|
||||
});
|
||||
|
||||
return () => {
|
||||
controller.abort();
|
||||
};
|
||||
}, [
|
||||
currentScene,
|
||||
hasPlayerCharacter,
|
||||
runtimeActionVersion,
|
||||
runtimeSessionId,
|
||||
setAiError,
|
||||
]);
|
||||
|
||||
const rejectInventoryAction = (message: string) => {
|
||||
setAiError(message);
|
||||
return false;
|
||||
};
|
||||
|
||||
const findBackpackItemView = (itemId: string) =>
|
||||
serverInventoryView?.backpackItems.find(
|
||||
(candidate) => candidate.item.id === itemId,
|
||||
) ?? null;
|
||||
|
||||
const findEquipmentSlotView = (slot: 'weapon' | 'armor' | 'relic') =>
|
||||
serverInventoryView?.equipmentSlots.find(
|
||||
(candidate) => candidate.slotId === slot,
|
||||
) ?? null;
|
||||
|
||||
const resolveServerInventoryAction = async (params: {
|
||||
functionId: string;
|
||||
actionText: string;
|
||||
payload: Record<string, unknown>;
|
||||
payload?: RuntimeStoryChoicePayload;
|
||||
}) => {
|
||||
const character = gameState.playerCharacter;
|
||||
if (
|
||||
@@ -69,7 +117,7 @@ export function useStoryInventoryActions({
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
const { hydratedSnapshot, nextStory } = await resolveRpgRuntimeChoice({
|
||||
const { response, hydratedSnapshot, nextStory } = await resolveRpgRuntimeChoice({
|
||||
gameState,
|
||||
currentStory,
|
||||
option: {
|
||||
@@ -81,6 +129,7 @@ export function useStoryInventoryActions({
|
||||
|
||||
setGameState(hydratedSnapshot.gameState);
|
||||
setCurrentStory(nextStory);
|
||||
setServerInventoryView(response.viewModel.inventory);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Failed to resolve inventory runtime action on the server:', error);
|
||||
@@ -94,100 +143,80 @@ export function useStoryInventoryActions({
|
||||
}
|
||||
};
|
||||
|
||||
const useInventoryItem = async (itemId: string) => {
|
||||
const item = gameState.playerInventory.find(
|
||||
(candidate) => candidate.id === itemId,
|
||||
const submitInventoryAction = async (
|
||||
action: RuntimeStoryInventoryActionView | undefined,
|
||||
fallbackReason: string,
|
||||
) => {
|
||||
if (!action) {
|
||||
return rejectInventoryAction(fallbackReason);
|
||||
}
|
||||
if (!action.enabled) {
|
||||
return rejectInventoryAction(action.reason ?? fallbackReason);
|
||||
}
|
||||
|
||||
return resolveServerInventoryAction({
|
||||
functionId: action.functionId,
|
||||
actionText: action.actionText,
|
||||
payload: action.payload as RuntimeStoryChoicePayload | undefined,
|
||||
});
|
||||
};
|
||||
|
||||
const useInventoryItem = async (itemId: string) =>
|
||||
submitInventoryAction(
|
||||
findBackpackItemView(itemId)?.actions.use,
|
||||
'后端背包视图尚未提供该物品的使用动作。',
|
||||
);
|
||||
if (!item) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return resolveServerInventoryAction({
|
||||
functionId: INVENTORY_USE_FUNCTION.id,
|
||||
actionText: `使用${item.name}`,
|
||||
payload: { itemId },
|
||||
});
|
||||
};
|
||||
|
||||
const equipInventoryItem = async (itemId: string) => {
|
||||
const item = gameState.playerInventory.find(
|
||||
(candidate) => candidate.id === itemId,
|
||||
const equipInventoryItem = async (itemId: string) =>
|
||||
submitInventoryAction(
|
||||
findBackpackItemView(itemId)?.actions.equip,
|
||||
'后端背包视图尚未提供该物品的装备动作。',
|
||||
);
|
||||
if (!item) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return resolveServerInventoryAction({
|
||||
functionId: EQUIPMENT_EQUIP_FUNCTION.id,
|
||||
actionText: `装备${item.name}`,
|
||||
payload: { itemId },
|
||||
});
|
||||
};
|
||||
|
||||
const unequipItem = async (slot: 'weapon' | 'armor' | 'relic') => {
|
||||
const equippedItem = gameState.playerEquipment[slot];
|
||||
if (!equippedItem) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return resolveServerInventoryAction({
|
||||
functionId: EQUIPMENT_UNEQUIP_FUNCTION.id,
|
||||
actionText: `卸下${equippedItem.name}`,
|
||||
payload: { slotId: slot },
|
||||
});
|
||||
};
|
||||
const unequipItem = async (slot: 'weapon' | 'armor' | 'relic') =>
|
||||
submitInventoryAction(
|
||||
findEquipmentSlotView(slot)?.unequip,
|
||||
'后端装备视图尚未提供该槽位的卸装动作。',
|
||||
);
|
||||
|
||||
const craftRecipe = async (recipeId: string) => {
|
||||
const recipe = forgeRecipes.find(
|
||||
const recipe = serverInventoryView?.forgeRecipes.find(
|
||||
(candidate) => candidate.id === recipeId,
|
||||
);
|
||||
if (!recipe) {
|
||||
return false;
|
||||
return rejectInventoryAction('后端锻造视图尚未提供该配方。');
|
||||
}
|
||||
if (!recipe.canCraft) {
|
||||
return rejectInventoryAction(
|
||||
recipe.disabledReason ?? recipe.action.reason ?? '当前配方不可制作。',
|
||||
);
|
||||
}
|
||||
|
||||
return resolveServerInventoryAction({
|
||||
functionId: FORGE_CRAFT_FUNCTION.id,
|
||||
actionText: `制作${recipe.resultLabel}`,
|
||||
payload: { recipeId },
|
||||
});
|
||||
return submitInventoryAction(recipe.action, '当前配方不可制作。');
|
||||
};
|
||||
|
||||
const dismantleItem = async (itemId: string) => {
|
||||
const item = gameState.playerInventory.find(
|
||||
(candidate) => candidate.id === itemId,
|
||||
const dismantleItem = async (itemId: string) =>
|
||||
submitInventoryAction(
|
||||
findBackpackItemView(itemId)?.actions.dismantle,
|
||||
'后端背包视图尚未提供该物品的拆解动作。',
|
||||
);
|
||||
if (!item) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return resolveServerInventoryAction({
|
||||
functionId: FORGE_DISMANTLE_FUNCTION.id,
|
||||
actionText: `拆解${item.name}`,
|
||||
payload: { itemId },
|
||||
});
|
||||
};
|
||||
|
||||
const reforgeItem = async (itemId: string) => {
|
||||
const item = gameState.playerInventory.find(
|
||||
(candidate) => candidate.id === itemId,
|
||||
const reforgeItem = async (itemId: string) =>
|
||||
submitInventoryAction(
|
||||
findBackpackItemView(itemId)?.actions.reforge,
|
||||
'后端背包视图尚未提供该物品的重铸动作。',
|
||||
);
|
||||
if (!item) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return resolveServerInventoryAction({
|
||||
functionId: FORGE_REFORGE_FUNCTION.id,
|
||||
actionText: `重铸${item.name}`,
|
||||
payload: { itemId },
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
inventoryUi: {
|
||||
useInventoryItem,
|
||||
equipInventoryItem,
|
||||
unequipItem,
|
||||
forgeRecipes,
|
||||
playerCurrency: serverInventoryView?.playerCurrency ?? null,
|
||||
currencyText: serverInventoryView?.currencyText ?? null,
|
||||
backpackItems: serverInventoryView?.backpackItems ?? [],
|
||||
equipmentSlots: serverInventoryView?.equipmentSlots ?? [],
|
||||
forgeRecipes: serverInventoryView?.forgeRecipes ?? [],
|
||||
craftRecipe,
|
||||
dismantleItem,
|
||||
reforgeItem,
|
||||
|
||||
Reference in New Issue
Block a user