Files
Genarrative/src/hooks/story/inventoryActions.ts
2026-04-10 15:37:02 +08:00

197 lines
5.0 KiB
TypeScript

import { useMemo, type Dispatch, type SetStateAction } from 'react';
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';
import type { Character, GameState, StoryMoment } from '../../types';
import { resolveServerRuntimeChoice } from './runtimeStoryCoordinator';
import type { InventoryFlowUi } from './uiTypes';
type BuildFallbackStoryForState = (
state: GameState,
character: Character,
fallbackText?: string,
) => StoryMoment;
export function useStoryInventoryActions({
gameState,
runtime,
}: {
gameState: GameState;
runtime: {
currentStory: StoryMoment | null;
setGameState: Dispatch<SetStateAction<GameState>>;
setCurrentStory: Dispatch<SetStateAction<StoryMoment | null>>;
setAiError: Dispatch<SetStateAction<string | null>>;
setIsLoading: Dispatch<SetStateAction<boolean>>;
buildFallbackStoryForState: BuildFallbackStoryForState;
};
}) {
const {
currentStory,
setGameState,
setCurrentStory,
setAiError,
setIsLoading,
buildFallbackStoryForState,
} = runtime;
const forgeRecipes = useMemo(
() =>
getForgeRecipeViews(
gameState.playerInventory,
gameState.playerCurrency,
gameState.worldType,
),
[gameState.playerCurrency, gameState.playerInventory, gameState.worldType],
);
const resolveServerInventoryAction = async (params: {
functionId: string;
actionText: string;
payload: Record<string, unknown>;
}) => {
const character = gameState.playerCharacter;
if (
!character ||
!gameState.worldType ||
gameState.currentScene !== 'Story'
) {
return false;
}
setAiError(null);
setIsLoading(true);
try {
const { hydratedSnapshot, nextStory } = await resolveServerRuntimeChoice({
gameState,
currentStory,
option: {
functionId: params.functionId,
actionText: params.actionText,
},
payload: params.payload,
});
setGameState(hydratedSnapshot.gameState);
setCurrentStory(nextStory);
return true;
} catch (error) {
console.error('Failed to resolve inventory runtime action on the server:', error);
setAiError(error instanceof Error ? error.message : '背包动作执行失败');
if (!currentStory) {
setCurrentStory(buildFallbackStoryForState(gameState, character));
}
return false;
} finally {
setIsLoading(false);
}
};
const useInventoryItem = async (itemId: string) => {
const item = gameState.playerInventory.find(
(candidate) => candidate.id === itemId,
);
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,
);
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 craftRecipe = async (recipeId: string) => {
const recipe = forgeRecipes.find(
(candidate) => candidate.id === recipeId,
);
if (!recipe) {
return false;
}
return resolveServerInventoryAction({
functionId: FORGE_CRAFT_FUNCTION.id,
actionText: `制作${recipe.resultLabel}`,
payload: { recipeId },
});
};
const dismantleItem = async (itemId: string) => {
const item = gameState.playerInventory.find(
(candidate) => candidate.id === itemId,
);
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,
);
if (!item) {
return false;
}
return resolveServerInventoryAction({
functionId: FORGE_REFORGE_FUNCTION.id,
actionText: `重铸${item.name}`,
payload: { itemId },
});
};
return {
inventoryUi: {
useInventoryItem,
equipInventoryItem,
unequipItem,
forgeRecipes,
craftRecipe,
dismantleItem,
reforgeItem,
} satisfies InventoryFlowUi,
};
}