import { useMemo, useState } from 'react'; import type { RuntimeStoryEquipmentSlotView, RuntimeStoryForgeRecipeView, RuntimeStoryInventoryItemView, } from '../../packages/shared/src/contracts/rpgRuntimeStoryState'; import { formatCurrency } from '../data/economy'; import { buildInitialPlayerInventory } from '../data/npcInteractions'; import { Character, InventoryItem, NarrativeCodexSection, NarrativeQaReport, WorldType, } from '../types'; import { InventoryItemDetailModal, InventoryItemGrid, } from './InventoryItemViews'; interface InventoryPanelProps { playerCharacter: Character; worldType: WorldType | null; playerInventory: InventoryItem[]; playerCurrency: number; playerHp: number; playerMaxHp: number; playerMana: number; playerMaxMana: number; inBattle: boolean; currencyText?: string | null; backpackItems?: RuntimeStoryInventoryItemView[]; equipmentSlots?: RuntimeStoryEquipmentSlotView[]; onUseItem: (itemId: string) => Promise; onEquipItem: (itemId: string) => Promise; forgeRecipes: RuntimeStoryForgeRecipeView[]; onCraftRecipe: (recipeId: string) => Promise; onDismantleItem: (itemId: string) => Promise; onReforgeItem: (itemId: string) => Promise; narrativeCodex?: NarrativeCodexSection[]; narrativeQaReport?: NarrativeQaReport | null; } export function InventoryPanel({ playerCharacter, worldType, playerInventory, playerCurrency, inBattle, currencyText = null, backpackItems = [], equipmentSlots: _equipmentSlots = [], onUseItem: _onUseItem, onEquipItem: _onEquipItem, forgeRecipes, onCraftRecipe, onDismantleItem: _onDismantleItem, onReforgeItem: _onReforgeItem, narrativeCodex = [], narrativeQaReport = null, }: InventoryPanelProps) { const [selectedItem, setSelectedItem] = useState(null); const [forgeActionKey, setForgeActionKey] = useState(null); const serverInventoryItems = useMemo( () => backpackItems .map((view) => view.item as unknown as InventoryItem) .filter( (item) => typeof item.id === 'string' && typeof item.name === 'string', ), [backpackItems], ); const inventoryItems = useMemo( () => serverInventoryItems.length > 0 ? serverInventoryItems : playerInventory.length > 0 ? playerInventory : buildInitialPlayerInventory(playerCharacter, worldType), [playerCharacter, playerInventory, serverInventoryItems, worldType], ); const documentItems = useMemo( () => inventoryItems.filter((item) => item.category === '文书' || item.tags.includes('document')), [inventoryItems], ); return (
{documentItems.length > 0 && (
文书与证据
{documentItems.map((item) => ( ))}
)} {(narrativeCodex.length > 0 || narrativeQaReport) && (
故事档案
{narrativeQaReport && (
QA:{narrativeQaReport.summary}
)}
{narrativeCodex.slice(0, 3).map((section) => (
{section.title}
{section.entries.slice(0, 3).map((entry) => (
{entry.title} {':'} {entry.summary}
))}
))}
)}
工坊 {currencyText ?? formatCurrency(playerCurrency, worldType)}
{forgeRecipes.map((recipe) => (
{recipe.name}
{recipe.description}
产物:{recipe.resultLabel}
花费:{recipe.currencyText}
{recipe.requirements.map((requirement) => ( = requirement.quantity ? 'border-emerald-400/20 bg-emerald-500/10 text-emerald-100' : 'border-white/10 bg-black/20 text-zinc-400' }`} > {requirement.label} {requirement.owned}/ {requirement.quantity} ))}
{(!recipe.canCraft || !recipe.action.enabled) && (recipe.disabledReason || recipe.action.reason) && (
{recipe.disabledReason ?? recipe.action.reason}
)}
))}
setSelectedItem(null)} />
); }