197 lines
5.0 KiB
TypeScript
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,
|
|
};
|
|
}
|