№ · 完成设计 CH.06.24
主页 · ✅ 完成设计 · 《同福客栈》道具系统代码技术文档

《同福客栈》道具系统代码技术文档

📅 2026-06-24 · 📦 32.8 KB · ✅ 完成设计 · 📝 MD 文档 🏷️ 装备🏷️ Buff🏷️ 设计🏷️ 剧情

1. 文档概述

本文档详细描述《同福客栈》游戏道具系统的技术实现,包括数据结构、接口定义、核心算法、系统集成等内容。旨在为程序开发提供完整的技术实现指南。


2. 系统架构

2.1 模块结构

src/data/items/
├── types.ts              # 类型定义
├── consumables.ts        # 消耗品数据
├── equipment.ts          # 装备数据
├── storyItems.ts         # 剧情道具数据
├── legacyItems.ts        # 传承道具数据
├── itemConfigs.ts        # 配置数据(商店、合成、掉落)
└── index.ts              # 入口文件和工具函数

2.2 依赖关系

graph TD
    A[types.ts] --> B[consumables.ts]
    A --> C[equipment.ts]
    A --> D[storyItems.ts]
    A --> E[legacyItems.ts]
    A --> F[itemConfigs.ts]
    B --> G[index.ts]
    C --> G
    D --> G
    E --> G
    F --> G
    G --> H[App.tsx]
    G --> I[utils/itemGenerator.ts]
    G --> J[其他游戏系统]

3. 数据结构定义

3.1 基础类型

// 道具大类枚举
export type ItemCategory = 'consumable' | 'equipment' | 'story' | 'legacy'

// 道具稀有度
export type ItemRarity = 'common' | 'uncommon' | 'rare' | 'epic' | 'legendary'

// 道具效果类型
export type ItemEffectType =
  | 'restore_health'
  | 'restore_stamina'
  | 'restore_mood'
  | 'reduce_stress'
  | 'buff_reputation'
  | 'buff_cleanliness'
  | 'buff_safety'
  | 'buff_income'
  | 'buff_cooking'
  | 'buff_service'
  | 'unlock_story'
  | 'trigger_event'

// 道具效果目标
export type ItemEffectTarget = 'self' | 'staff' | 'inn' | 'specific_staff'

// 道具获取途径
export type ItemSource =
  | 'market'
  | 'event'
  | 'adventure'
  | 'auction'
  | 'craft'
  | 'quest'
  | 'legacy_shop'
  | 'initial'

3.2 核心接口

3.2.1 道具效果接口

export interface ItemEffect {
  type: ItemEffectType        // 效果类型
  value: number               // 效果数值
  target: ItemEffectTarget    // 作用目标
  targetId?: StaffId | string // 具体目标ID(如员工ID)
  duration?: number           // 持续时间(游戏时辰),-1表示永久
  description: string         // 效果描述
}

3.2.2 基础道具接口

export interface BaseItem {
  id: string                  // 道具唯一ID
  name: string                // 道具名称
  description: string         // 道具描述
  category: ItemCategory      // 道具类别
  rarity: ItemRarity          // 稀有度
  icon: string                // 图标标识
  maxStack: number            // 最大堆叠数
  value: number               // 基础价值(铜钱)
  sellable: boolean           // 是否可出售
  tradeable: boolean          // 是否可交易
  sources: ItemSource[]       // 获取途径
  effects: ItemEffect[]       // 道具效果列表
  flavorText: string          // 风味文本
  tags: string[]              // 标签列表
}

3.2.3 消耗品接口

export interface ConsumableItem extends BaseItem {
  category: 'consumable'
  consumable: true
  cooldown?: number           // 使用冷却时间(游戏时辰)
  usageCondition?: string     // 使用条件描述
}

3.2.4 装备接口

// 装备类型
export type EquipmentType =
  | 'weapon'
  | 'accessory'
  | 'ingredient'
  | 'book'
  | 'talisman'
  | 'tool'
  | 'furniture'
  | 'pet'

// 装备属性类型
export type EquipmentStat =
  | 'cookingSpeed'
  | 'cleanPower'
  | 'security'
  | 'incomeMultiplier'
  | 'passiveIncome'
  | 'reputationGain'
  | 'cleanlinessRecovery'
  | 'safetyRecovery'
  | 'foodEfficiency'
  | 'customerRetention'
  | 'staminaRegen'
  | 'stressRelief'

// 装备构建标签
export type BuildTag = 'chef' | 'management' | 'adventure' | 'defense'

export interface EquipmentItem extends BaseItem {
  category: 'equipment'
  equipmentType: EquipmentType  // 装备类型
  stat: EquipmentStat           // 主属性
  statValue: number             // 属性值
  level: number                 // 当前等级
  maxLevel: number              // 最大等级
  buildTag?: BuildTag           // 构建标签
  recommendedStaff?: StaffId[]  // 推荐装备的员工
  equippedTo?: StaffId | null   // 当前装备的员工
  setId?: string                // 套装ID
  setBonus?: string             // 套装效果描述
  refineCost?: {                // 精炼消耗
    coins: number
    guanzhongBills?: number
    banditLoot?: number
  }
}

3.2.5 剧情道具接口

export interface StoryItem extends BaseItem {
  category: 'story'
  storyEventId?: string       // 关联的剧情事件ID
  questId?: string            // 关联的任务ID
  unlockCondition?: string    // 解锁条件
  consumeOnUse: boolean       // 使用后是否消耗
  persistent: boolean         // 是否跨周目保留
}

3.2.6 传承道具接口

export interface LegacyItem extends BaseItem {
  category: 'legacy'
  legacyCost: number          // 江湖阅历消耗
  unlockCondition?: string    // 解锁条件
  persistent: true
  accountWide: true
  effects: ItemEffect[]       // 对全局账号的影响
}

3.2.7 道具联合类型

export type GameItem = ConsumableItem | EquipmentItem | StoryItem | LegacyItem

3.3 配置接口

3.3.1 道具掉落配置

export interface ItemDropConfig {
  itemId: string              // 道具ID
  weight: number              // 掉落权重
  minDay: number              // 最早出现天数
  maxDay?: number             // 最晚出现天数(可选)
  sources: ItemSource[]       // 获取途径
  rarityBoost?: number        // 稀有度提升系数
}

3.3.2 道具掉落池

export interface ItemDropPool {
  id: string                  // 掉落池ID
  name: string                // 掉落池名称
  description: string         // 掉落池描述
  drops: ItemDropConfig[]     // 掉落配置列表
  guaranteedDrops?: string[]  // 必定掉落的道具ID
  totalWeight: number         // 总权重
}

3.3.3 道具合成配方

export interface ItemRecipe {
  id: string                  // 配方ID
  name: string                // 配方名称
  description: string         // 配方描述
  ingredients: {              // 材料需求
    itemId: string
    quantity: number
  }[]
  result: {                   // 产出结果
    itemId: string
    quantity: number
  }
  unlockCondition?: string    // 解锁条件
  craftTime: number           // 制作时间(游戏时辰)
}

3.3.4 道具库存配置

export interface ItemStockConfig {
  itemId: string              // 道具ID
  initialStock: number        // 初始库存
  restockRate: number         // 每日补货数量
  maxStock: number            // 库存上限
  price: number               // 基础价格
  priceFluctuation: number    // 价格波动范围(百分比)
}

3.3.5 道具商店配置

export interface ItemShopConfig {
  id: string                  // 商店ID
  name: string                // 商店名称
  description: string         // 商店描述
  items: ItemStockConfig[]    // 商品列表
  refreshRate: number         // 刷新间隔(游戏天数)
  unlockCondition?: string    // 解锁条件
}

3.3.6 事件掉落配置

export interface ItemDropEventConfig {
  eventId: string             // 事件ID
  eventName: string           // 事件名称
  description: string         // 事件描述
  drops: {                    // 掉落配置
    itemId: string
    quantity: number
    probability: number
  }[]
  minDay: number              // 最早出现天数
  maxDay?: number             // 最晚出现天数
  requiredConditions?: string[] // 触发条件
}

4. 核心算法实现

4.1 道具掉落算法

/**
 * 从掉落池中随机获取道具
 * @param pool 掉落池配置
 * @param currentDay 当前游戏天数
 * @returns 随机获取的道具,如果没有可用道具则返回null
 */
export function getRandomItemFromPool(
  pool: ItemDropConfig[], 
  currentDay: number
): GameItem | null {
  // 1. 过滤出当前天数可用的道具
  const availableItems = pool.filter(config => currentDay >= config.minDay)
  
  if (availableItems.length === 0) {
    return null
  }
  
  // 2. 计算总权重
  const totalWeight = availableItems.reduce((sum, config) => sum + config.weight, 0)
  
  // 3. 加权随机选择
  let random = Math.random() * totalWeight
  for (const config of availableItems) {
    random -= config.weight
    if (random <= 0) {
      return ALL_ITEMS_MAP[config.itemId] || null
    }
  }
  
  return null
}

算法复杂度:O(n),其中n为掉落池中的道具数量

随机性保证

  • 使用 Math.random() 生成 [0, 1) 范围的随机数
  • 通过权重累加实现加权随机选择
  • 每次调用独立,无状态依赖

4.2 价格波动算法

/**
 * 计算道具的实际购买价格
 * @param basePrice 基础价格
 * @param fluctuation 波动范围(百分比)
 * @returns 实际价格
 */
export function calculateItemPrice(
  basePrice: number, 
  fluctuation: number
): number {
  // 生成 [-fluctuation%, +fluctuation%] 范围的随机波动
  const randomFactor = 1 + (Math.random() * 2 - 1) * (fluctuation / 100)
  return Math.round(basePrice * randomFactor)
}

算法特点

  • 价格在基础价格的 ±波动范围内随机波动
  • 每次购买独立计算,模拟市场价格波动
  • 结果取整,避免小数货币

4.3 道具效果应用算法

/**
 * 应用道具效果
 * @param item 道具对象
 * @param gameState 当前游戏状态
 * @param target 效果目标
 * @param targetId 具体目标ID
 */
export function applyItemEffect(
  item: GameItem,
  gameState: GameState,
  target: ItemEffectTarget,
  targetId?: string
): void {
  for (const effect of item.effects) {
    // 检查效果目标是否匹配
    if (effect.target !== target) continue
    if (effect.target === 'specific_staff' && effect.targetId !== targetId) continue
    
    switch (effect.type) {
      case 'restore_health':
        applyRestoreHealth(gameState, effect.value, targetId)
        break
      case 'restore_stamina':
        applyRestoreStamina(gameState, effect.value, targetId)
        break
      case 'restore_mood':
        applyRestoreMood(gameState, effect.value, targetId)
        break
      case 'reduce_stress':
        applyReduceStress(gameState, effect.value, targetId)
        break
      case 'buff_reputation':
        applyBuffReputation(gameState, effect.value)
        break
      case 'buff_cleanliness':
        applyBuffCleanliness(gameState, effect.value)
        break
      case 'buff_safety':
        applyBuffSafety(gameState, effect.value)
        break
      case 'buff_income':
        applyBuffIncome(gameState, effect.value, effect.duration)
        break
      case 'buff_cooking':
        applyBuffCooking(gameState, effect.value, effect.duration)
        break
      case 'buff_service':
        applyBuffService(gameState, effect.value, effect.duration)
        break
      case 'unlock_story':
        unlockStoryEvent(effect.value)
        break
      case 'trigger_event':
        triggerRandomEvent(effect.value)
        break
    }
  }
}

4.4 装备精炼算法

/**
 * 计算装备精炼后的属性值
 * @param item 装备对象
 * @returns 精炼后的属性值
 */
export function getRefinedItemValue(item: EquipmentItem): number {
  const RARITY_REFINE_VALUE_GAIN: Record<ItemRarity, number> = {
    common: 1,
    uncommon: 1.5,
    rare: 2,
    epic: 3,
    legendary: 4,
  }
  
  return item.statValue + RARITY_REFINE_VALUE_GAIN[item.rarity]
}

/**
 * 计算装备精炼消耗
 * @param item 装备对象
 * @returns 精炼消耗配置
 */
export function getItemRefineCost(item: EquipmentItem): {
  coins: number
  guanzhongBills: number
  banditLoot: number
} | null {
  const MAX_ITEM_LEVEL = 5
  const currentLevel = Math.max(1, item.level || 1)
  
  if (currentLevel >= MAX_ITEM_LEVEL) return null
  
  const RARITY_REFINE_COIN_BASE: Record<ItemRarity, number> = {
    common: 80,
    uncommon: 120,
    rare: 150,
    epic: 280,
    legendary: 460,
  }
  
  const baseCost = RARITY_REFINE_COIN_BASE[item.rarity] * currentLevel
  const tag = item.buildTag || 'management'
  const resourceStep = currentLevel
  const rarityResourceBonus = item.rarity === 'legendary' ? 1 : 0
  
  // 根据构建标签调整资源消耗
  if (tag === 'adventure') {
    return {
      coins: baseCost,
      guanzhongBills: resourceStep + rarityResourceBonus,
      banditLoot: resourceStep + rarityResourceBonus,
    }
  }
  
  if (tag === 'defense') {
    return {
      coins: baseCost,
      guanzhongBills: item.rarity === 'epic' || item.rarity === 'legendary' ? 1 : 0,
      banditLoot: resourceStep + rarityResourceBonus,
    }
  }
  
  if (tag === 'chef') {
    return {
      coins: baseCost,
      guanzhongBills: resourceStep + rarityResourceBonus,
      banditLoot: item.rarity === 'epic' || item.rarity === 'legendary' ? 1 : 0,
    }
  }
  
  // 默认管理流
  return {
    coins: baseCost,
    guanzhongBills: resourceStep + rarityResourceBonus,
    banditLoot: item.rarity === 'legendary' ? 1 : 0,
  }
}

4.5 道具查询算法

/**
 * 按类别获取道具
 * @param category 道具类别
 * @returns 该类别的所有道具
 */
export function getItemsByCategory(category: ItemCategory): GameItem[] {
  return ITEMS_BY_CATEGORY[category]
}

/**
 * 按稀有度获取道具
 * @param rarity 道具稀有度
 * @returns 该稀有度的所有道具
 */
export function getItemsByRarity(rarity: ItemRarity): GameItem[] {
  return ALL_ITEMS_BY_RARITY[rarity]
}

/**
 * 按标签获取道具
 * @param tag 道具标签
 * @returns 包含该标签的所有道具
 */
export function getItemsByTag(tag: string): GameItem[] {
  return ALL_ITEMS_BY_TAG[tag] || []
}

/**
 * 类型守卫:判断是否为消耗品
 */
export function isConsumableItem(item: GameItem): item is ConsumableItem {
  return item.category === 'consumable'
}

/**
 * 类型守卫:判断是否为装备
 */
export function isEquipmentItem(item: GameItem): item is EquipmentItem {
  return item.category === 'equipment'
}

/**
 * 类型守卫:判断是否为剧情道具
 */
export function isStoryItem(item: GameItem): item is StoryItem {
  return item.category === 'story'
}

/**
 * 类型守卫:判断是否为传承道具
 */
export function isLegacyItem(item: GameItem): item is LegacyItem {
  return item.category === 'legacy'
}

5. 数据存储设计

5.1 运行时背包数据结构

// 玩家单局背包结构
export interface Inventory {
  items: Record<string, number>     // { [itemId: string]: 数量 }
  equipped: Record<string, string>  // { [员工ID]: itemId }
}

// 道具运行时状态
export interface ItemRuntimeState {
  id: string                        // 道具ID
  quantity: number                  // 数量
  equippedTo?: StaffId | null       // 装备给谁
  level?: number                    // 当前等级
  acquiredDay?: number              // 获得时的游戏天数
  lastUsedDay?: number              // 上次使用的天数
}

5.2 存档数据结构

// 存档中的道具数据
export interface SaveItemData {
  inventory: Inventory              // 背包数据
  unlockedItems: string[]           // 已解锁的道具ID列表
  craftedRecipes: string[]          // 已解锁的配方ID列表
  shopStock: Record<string, number> // 商店库存状态
  dropHistory: {                    // 掉落历史记录
    itemId: string
    day: number
    source: ItemSource
  }[]
}

// 全局账号道具数据(跨周目)
export interface GlobalItemData {
  legacyItems: string[]             // 已拥有的传承道具ID列表
  unlockedRecipes: string[]         // 已解锁的配方ID列表
  totalItemsCollected: number       // 总收集道具数
  uniqueItemsCollected: number      // 唯一道具收集数
}

5.3 数据持久化方案

// 保存道具数据到localStorage
export function saveItemData(data: SaveItemData): void {
  const saveKey = 'tongfu_item_data'
  localStorage.setItem(saveKey, JSON.stringify(data))
}

// 从localStorage加载道具数据
export function loadItemData(): SaveItemData | null {
  const saveKey = 'tongfu_item_data'
  const data = localStorage.getItem(saveKey)
  return data ? JSON.parse(data) : null
}

// 保存全局道具数据
export function saveGlobalItemData(data: GlobalItemData): void {
  const saveKey = 'tongfu_global_item_data'
  localStorage.setItem(saveKey, JSON.stringify(data))
}

// 加载全局道具数据
export function loadGlobalItemData(): GlobalItemData | null {
  const saveKey = 'tongfu_global_item_data'
  const data = localStorage.getItem(saveKey)
  return data ? JSON.parse(data) : null
}

6. 系统集成接口

6.1 与背包系统集成

// 添加道具到背包
export function addItemToInventory(
  inventory: Inventory,
  itemId: string,
  quantity: number = 1
): Inventory {
  const currentQuantity = inventory.items[itemId] || 0
  const item = getItemById(itemId)
  
  if (!item) return inventory
  
  // 检查堆叠上限
  const maxStack = item.maxStack
  const newQuantity = Math.min(currentQuantity + quantity, maxStack)
  
  return {
    ...inventory,
    items: {
      ...inventory.items,
      [itemId]: newQuantity,
    },
  }
}

// 从背包移除道具
export function removeItemFromInventory(
  inventory: Inventory,
  itemId: string,
  quantity: number = 1
): Inventory {
  const currentQuantity = inventory.items[itemId] || 0
  const newQuantity = Math.max(0, currentQuantity - quantity)
  
  const newItems = { ...inventory.items }
  if (newQuantity === 0) {
    delete newItems[itemId]
  } else {
    newItems[itemId] = newQuantity
  }
  
  return {
    ...inventory,
    items: newItems,
  }
}

// 检查背包是否有足够道具
export function hasEnoughItems(
  inventory: Inventory,
  itemId: string,
  quantity: number
): boolean {
  return (inventory.items[itemId] || 0) >= quantity
}

6.2 与装备系统集成

// 装备道具
export function equipItem(
  inventory: Inventory,
  itemId: string,
  staffId: StaffId
): Inventory {
  const item = getItemById(itemId)
  
  if (!item || !isEquipmentItem(item)) return inventory
  
  // 检查是否已装备给其他员工
  if (item.equippedTo && item.equippedTo !== staffId) {
    // 先卸下原装备
    inventory = unequipItem(inventory, itemId, item.equippedTo)
  }
  
  return {
    ...inventory,
    equipped: {
      ...inventory.equipped,
      [staffId]: itemId,
    },
  }
}

// 卸下装备
export function unequipItem(
  inventory: Inventory,
  itemId: string,
  staffId: StaffId
): Inventory {
  const newEquipped = { ...inventory.equipped }
  
  if (newEquipped[staffId] === itemId) {
    delete newEquipped[staffId]
  }
  
  return {
    ...inventory,
    equipped: newEquipped,
  }
}

// 获取员工装备的道具
export function getStaffEquipment(
  inventory: Inventory,
  staffId: StaffId
): EquipmentItem | null {
  const itemId = inventory.equipped[staffId]
  if (!itemId) return null
  
  const item = getItemById(itemId)
  return item && isEquipmentItem(item) ? item : null
}

6.3 与商店系统集成

// 购买道具
export function purchaseItem(
  shopConfig: ItemShopConfig,
  itemId: string,
  quantity: number,
  playerCoins: number
): {
  success: boolean
  cost: number
  actualQuantity: number
  error?: string
} {
  const stockConfig = shopConfig.items.find(item => item.itemId === itemId)
  
  if (!stockConfig) {
    return { success: false, cost: 0, actualQuantity: 0, error: '商品不存在' }
  }
  
  // 计算实际价格
  const actualPrice = calculateItemPrice(stockConfig.price, stockConfig.priceFluctuation)
  const totalCost = actualPrice * quantity
  
  // 检查玩家资金
  if (playerCoins < totalCost) {
    return { success: false, cost: 0, actualQuantity: 0, error: '资金不足' }
  }
  
  // 检查库存
  const actualQuantity = Math.min(quantity, stockConfig.maxStock)
  if (actualQuantity <= 0) {
    return { success: false, cost: 0, actualQuantity: 0, error: '库存不足' }
  }
  
  return {
    success: true,
    cost: actualPrice * actualQuantity,
    actualQuantity,
  }
}

6.4 与合成系统集成

// 检查是否可以合成
export function canCraft(
  recipe: ItemRecipe,
  inventory: Inventory
): {
  canCraft: boolean
  missingItems: { itemId: string; required: number; available: number }[]
} {
  const missingItems: { itemId: string; required: number; available: number }[] = []
  
  for (const ingredient of recipe.ingredients) {
    const available = inventory.items[ingredient.itemId] || 0
    if (available < ingredient.quantity) {
      missingItems.push({
        itemId: ingredient.itemId,
        required: ingredient.quantity,
        available,
      })
    }
  }
  
  return {
    canCraft: missingItems.length === 0,
    missingItems,
  }
}

// 执行合成
export function craftItem(
  recipe: ItemRecipe,
  inventory: Inventory
): {
  success: boolean
  inventory: Inventory
  resultItem: GameItem | null
  error?: string
} {
  const { canCraft: canCraftResult, missingItems } = canCraft(recipe, inventory)
  
  if (!canCraftResult) {
    return {
      success: false,
      inventory,
      resultItem: null,
      error: `缺少材料: ${missingItems.map(m => `${m.itemId} x${m.required - m.available}`).join(', ')}`,
    }
  }
  
  // 消耗材料
  let newInventory = { ...inventory }
  for (const ingredient of recipe.ingredients) {
    newInventory = removeItemFromInventory(newInventory, ingredient.itemId, ingredient.quantity)
  }
  
  // 添加产出
  newInventory = addItemToInventory(newInventory, recipe.result.itemId, recipe.result.quantity)
  
  const resultItem = getItemById(recipe.result.itemId)
  
  return {
    success: true,
    inventory: newInventory,
    resultItem,
  }
}

6.5 与事件系统集成

// 处理事件掉落
export function handleEventDrop(
  eventConfig: ItemDropEventConfig,
  currentDay: number,
  gameState: GameState
): {
  droppedItems: GameItem[]
  triggeredEvents: string[]
} {
  const droppedItems: GameItem[] = []
  const triggeredEvents: string[] = []
  
  // 检查事件是否可触发
  if (currentDay < eventConfig.minDay) {
    return { droppedItems, triggeredEvents }
  }
  
  if (eventConfig.maxDay && currentDay > eventConfig.maxDay) {
    return { droppedItems, triggeredEvents }
  }
  
  // 检查触发条件
  if (eventConfig.requiredConditions) {
    const conditionsMet = checkEventConditions(eventConfig.requiredConditions, gameState)
    if (!conditionsMet) {
      return { droppedItems, triggeredEvents }
    }
  }
  
  // 处理掉落
  for (const drop of eventConfig.drops) {
    if (Math.random() < drop.probability) {
      const item = getItemById(drop.itemId)
      if (item) {
        droppedItems.push(item)
      }
    }
  }
  
  // 触发关联事件
  triggeredEvents.push(eventConfig.eventId)
  
  return { droppedItems, triggeredEvents }
}

// 检查事件条件
function checkEventConditions(
  conditions: string[],
  gameState: GameState
): boolean {
  // 实现条件检查逻辑
  // 例如:检查员工是否解锁、好感度是否达到阈值等
  return true
}

7. 性能优化

7.1 数据索引优化

// 预构建索引,避免运行时遍历
export const ALL_ITEMS_MAP: Record<string, GameItem> = {
  ...CONSUMABLE_ITEMS_MAP,
  ...EQUIPMENT_ITEMS_MAP,
  ...STORY_ITEMS_MAP,
  ...LEGACY_ITEMS_MAP,
}

export const ITEMS_BY_CATEGORY: Record<ItemCategory, GameItem[]> = {
  consumable: CONSUMABLE_ITEMS,
  equipment: EQUIPMENT_ITEMS,
  story: STORY_ITEMS,
  legacy: LEGACY_ITEMS,
}

export const ALL_ITEMS_BY_RARITY = {
  common: ALL_ITEMS.filter(item => item.rarity === 'common'),
  uncommon: ALL_ITEMS.filter(item => item.rarity === 'uncommon'),
  rare: ALL_ITEMS.filter(item => item.rarity === 'rare'),
  epic: ALL_ITEMS.filter(item => item.rarity === 'epic'),
  legendary: ALL_ITEMS.filter(item => item.rarity === 'legendary'),
}

export const ALL_ITEMS_BY_TAG = ALL_ITEMS.reduce((acc, item) => {
  item.tags.forEach(tag => {
    if (!acc[tag]) {
      acc[tag] = []
    }
    acc[tag].push(item)
  })
  return acc
}, {} as Record<string, GameItem[]>)

7.2 缓存策略

// 缓存计算结果
const refineCostCache = new Map<string, ReturnType<typeof getItemRefineCost>>()

export function getCachedRefineCost(item: EquipmentItem): ReturnType<typeof getItemRefineCost> {
  const cacheKey = `${item.id}_${item.level}_${item.rarity}`
  
  if (refineCostCache.has(cacheKey)) {
    return refineCostCache.get(cacheKey)!
  }
  
  const cost = getItemRefineCost(item)
  refineCostCache.set(cacheKey, cost)
  
  return cost
}

// 清除缓存(当道具数据更新时)
export function clearItemCaches(): void {
  refineCostCache.clear()
}

7.3 懒加载策略

// 按需加载道具数据
let consumablesLoaded = false
let equipmentLoaded = false
let storyItemsLoaded = false
let legacyItemsLoaded = false

export async function loadConsumablesIfNeeded(): Promise<void> {
  if (consumablesLoaded) return
  
  const { CONSUMABLE_ITEMS } = await import('./consumables')
  // 合并到全局数据
  consumablesLoaded = true
}

export async function loadEquipmentIfNeeded(): Promise<void> {
  if (equipmentLoaded) return
  
  const { EQUIPMENT_ITEMS } = await import('./equipment')
  // 合并到全局数据
  equipmentLoaded = true
}

// 类似地加载其他类型道具...

8. 错误处理

8.1 道具查找错误处理

export function getItemByIdSafe(id: string): GameItem | null {
  const item = ALL_ITEMS_MAP[id]
  
  if (!item) {
    console.warn(`[ItemSystem] 道具未找到: ${id}`)
    return null
  }
  
  return item
}

export function requireItemById(id: string): GameItem {
  const item = ALL_ITEMS_MAP[id]
  
  if (!item) {
    throw new Error(`[ItemSystem] 道具未找到: ${id}`)
  }
  
  return item
}

8.2 类型验证

export function validateItem(item: unknown): item is GameItem {
  if (typeof item !== 'object' || item === null) {
    return false
  }
  
  const obj = item as Record<string, unknown>
  
  return (
    typeof obj.id === 'string' &&
    typeof obj.name === 'string' &&
    typeof obj.description === 'string' &&
    typeof obj.category === 'string' &&
    typeof obj.rarity === 'string' &&
    Array.isArray(obj.effects)
  )
}

export function validateItemEffect(effect: unknown): effect is ItemEffect {
  if (typeof effect !== 'object' || effect === null) {
    return false
  }
  
  const obj = effect as Record<string, unknown>
  
  return (
    typeof obj.type === 'string' &&
    typeof obj.value === 'number' &&
    typeof obj.target === 'string' &&
    typeof obj.description === 'string'
  )
}

8.3 边界条件处理

export function clampItemQuantity(quantity: number, maxStack: number): number {
  return Math.max(0, Math.min(quantity, maxStack))
}

export function clampItemLevel(level: number, maxLevel: number): number {
  return Math.max(1, Math.min(level, maxLevel))
}

export function clampItemValue(value: number): number {
  return Math.max(0, value)
}

9. 测试策略

9.1 单元测试示例

// 测试道具掉落算法
describe('getRandomItemFromPool', () => {
  it('should return null for empty pool', () => {
    const result = getRandomItemFromPool([], 1)
    expect(result).toBeNull()
  })
  
  it('should return null when no items available for current day', () => {
    const pool: ItemDropConfig[] = [
      { itemId: 'item_firewood', weight: 10, minDay: 10, sources: ['market'] }
    ]
    const result = getRandomItemFromPool(pool, 5)
    expect(result).toBeNull()
  })
  
  it('should return item based on weight', () => {
    const pool: ItemDropConfig[] = [
      { itemId: 'item_firewood', weight: 100, minDay: 1, sources: ['market'] },
      { itemId: 'item_water', weight: 0, minDay: 1, sources: ['market'] }
    ]
    
    // 运行多次,确保权重为0的道具不会被选中
    for (let i = 0; i < 100; i++) {
      const result = getRandomItemFromPool(pool, 1)
      expect(result?.id).toBe('item_firewood')
    }
  })
})

// 测试道具查询函数
describe('Item queries', () => {
  it('should return all consumables', () => {
    const consumables = getItemsByCategory('consumable')
    expect(consumables.length).toBeGreaterThan(0)
    consumables.forEach(item => {
      expect(item.category).toBe('consumable')
    })
  })
  
  it('should return items by tag', () => {
    const staffTongItems = getItemsByTag('staff_tong')
    staffTongItems.forEach(item => {
      expect(item.tags).toContain('staff_tong')
    })
  })
})

9.2 集成测试示例

// 测试装备系统集成
describe('Equipment integration', () => {
  it('should equip and unequip item correctly', () => {
    let inventory: Inventory = { items: {}, equipped: {} }
    
    // 添加装备到背包
    inventory = addItemToInventory(inventory, 'item_iron_cleaver', 1)
    expect(inventory.items['item_iron_cleaver']).toBe(1)
    
    // 装备给员工
    inventory = equipItem(inventory, 'item_iron_cleaver', 'bai')
    expect(inventory.equipped['bai']).toBe('item_iron_cleaver')
    
    // 卸下装备
    inventory = unequipItem(inventory, 'item_iron_cleaver', 'bai')
    expect(inventory.equipped['bai']).toBeUndefined()
  })
})

// 测试合成系统集成
describe('Crafting integration', () => {
  it('should craft item with correct materials', () => {
    let inventory: Inventory = {
      items: {
        'item_rough_ingredient': 5,
        'item_water': 3,
      },
      equipped: {},
    }
    
    const recipe = ITEM_RECIPES_BY_RESULT['item_fine_ingredient']
    expect(recipe).toBeDefined()
    
    const { canCraft: canCraftResult } = canCraft(recipe!, inventory)
    expect(canCraftResult).toBe(true)
    
    const { success, inventory: newInventory } = craftItem(recipe!, inventory)
    expect(success).toBe(true)
    expect(newInventory.items['item_rough_ingredient']).toBe(2)
    expect(newInventory.items['item_water']).toBe(1)
    expect(newInventory.items['item_fine_ingredient']).toBe(1)
  })
})

10. 扩展指南

10.1 添加新道具类型

  1. types.ts 中定义新的道具接口
  2. index.ts 中添加新的道具数组和索引
  3. 实现相应的查询函数和类型守卫

10.2 添加新效果类型

  1. types.tsItemEffectType 中添加新类型
  2. applyItemEffect 函数中添加对应的处理逻辑
  3. 更新游戏状态管理系统以支持新效果

10.3 添加新获取途径

  1. types.tsItemSource 中添加新途径
  2. 在道具的 sources 数组中使用新途径
  3. 在掉落配置中添加新途径的权重和规则

10.4 添加新商店类型

  1. itemConfigs.ts 中添加新的商店配置
  2. 实现商店刷新和购买逻辑
  3. 集成到UI系统中

11. 总结

本道具系统代码技术文档提供了完整的技术实现指南,包括:

  • 数据结构:清晰的类型定义和接口设计
  • 核心算法:掉落、价格、效果应用等关键算法
  • 存储设计:运行时和存档数据结构
  • 系统集成:与背包、装备、商店、事件等系统的集成接口
  • 性能优化:索引、缓存、懒加载等优化策略
  • 错误处理:完善的错误处理和边界条件处理
  • 测试策略:单元测试和集成测试示例
  • 扩展指南:如何扩展新功能

开发团队可以基于本文档快速实现和扩展道具系统,确保代码质量和可维护性。