№ · 完成设计
CH.06.24
《同福客栈》道具系统代码技术文档
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 添加新道具类型
- 在
types.ts中定义新的道具接口 - 在
index.ts中添加新的道具数组和索引 - 实现相应的查询函数和类型守卫
10.2 添加新效果类型
- 在
types.ts的ItemEffectType中添加新类型 - 在
applyItemEffect函数中添加对应的处理逻辑 - 更新游戏状态管理系统以支持新效果
10.3 添加新获取途径
- 在
types.ts的ItemSource中添加新途径 - 在道具的
sources数组中使用新途径 - 在掉落配置中添加新途径的权重和规则
10.4 添加新商店类型
- 在
itemConfigs.ts中添加新的商店配置 - 实现商店刷新和购买逻辑
- 集成到UI系统中
11. 总结
本道具系统代码技术文档提供了完整的技术实现指南,包括:
- 数据结构:清晰的类型定义和接口设计
- 核心算法:掉落、价格、效果应用等关键算法
- 存储设计:运行时和存档数据结构
- 系统集成:与背包、装备、商店、事件等系统的集成接口
- 性能优化:索引、缓存、懒加载等优化策略
- 错误处理:完善的错误处理和边界条件处理
- 测试策略:单元测试和集成测试示例
- 扩展指南:如何扩展新功能
开发团队可以基于本文档快速实现和扩展道具系统,确保代码质量和可维护性。