№ · 玩法设计 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.ts 的 ItemEffectType 中添加新类型
  2. 在 applyItemEffect 函数中添加对应的处理逻辑
  3. 更新游戏状态管理系统以支持新效果

10.3 添加新获取途径

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

10.4 添加新商店类型

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

11. 总结

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

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

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