/** * 物品系统 - 使用/装备/品质计算 * Phase 6 核心系统实现 */ import { ITEM_CONFIG } from '@/config/items.js' import { GAME_CONSTANTS } from '@/config/constants.js' import { SKILL_CONFIG } from '@/config/skills.js' /** * 计算装备品质后的属性 * 使用数学模型中的公式:品质倍率 = 1 + (品质 - 100) * 0.01 * @param {String} itemId - 物品ID * @param {Number} quality - 品质值 (0-250) * @param {Number} itemLevel - 物品等级(影响属性) * @returns {Object|null} 计算后的属性 */ export function calculateItemStats(itemId, quality = 100, itemLevel = 1) { const config = ITEM_CONFIG[itemId] if (!config) { return null } const qualityLevel = getQualityLevel(quality) // 品质倍率:1 + (品质 - 100) * 0.01 // 品质100 = 1.0倍,品质150 = 1.5倍,品质50 = 0.5倍 const qualityMultiplier = 1 + (quality - 100) * GAME_CONSTANTS.QUALITY.BASE_MULTIPLIER // 等级倍率:每级 +2% const levelMultiplier = 1 + (itemLevel - 1) * 0.02 // 综合倍率 const totalMultiplier = qualityMultiplier * levelMultiplier const stats = { id: itemId, name: config.name, type: config.type, subtype: config.subtype, description: config.description, icon: config.icon, quality: quality, itemLevel: itemLevel, qualityLevel: qualityLevel.level, qualityName: qualityLevel.name, qualityColor: qualityLevel.color } // 应用品质倍率到基础属性 if (config.baseDamage) { stats.baseDamage = config.baseDamage stats.finalDamage = Math.max(1, Math.floor(config.baseDamage * totalMultiplier)) } if (config.baseDefense) { stats.baseDefense = config.baseDefense stats.finalDefense = Math.max(1, Math.floor(config.baseDefense * totalMultiplier)) } if (config.baseShield) { stats.baseShield = config.baseShield stats.finalShield = Math.floor(config.baseShield * totalMultiplier) } if (config.attackSpeed) { stats.attackSpeed = config.attackSpeed } // 计算价值(品质影响) stats.baseValue = config.baseValue || 0 stats.finalValue = Math.floor(config.baseValue * totalMultiplier) // 消耗品效果 if (config.effect) { stats.effect = { ...config.effect } } // 书籍信息 if (config.type === 'book') { stats.readingTime = config.readingTime stats.expReward = { ...config.expReward } stats.completionBonus = config.completionBonus } // 解锁技能 if (config.unlockSkill) { stats.unlockSkill = config.unlockSkill } // 额外属性(来自物品配置) if (config.stats) { stats.stats = { ...config.stats } } // 堆叠信息 if (config.stackable !== undefined) { stats.stackable = config.stackable stats.maxStack = config.maxStack || 99 } return stats } /** * 获取品质等级 * @param {Number} quality - 品质值 * @returns {Object} 品质等级信息 */ export function getQualityLevel(quality) { // 确保品质在有效范围内 const clampedQuality = Math.max(0, Math.min(250, quality)) for (const [level, data] of Object.entries(GAME_CONSTANTS.QUALITY_LEVELS)) { if (clampedQuality >= data.range[0] && clampedQuality <= data.range[1]) { return { level: parseInt(level), ...data } } } // 默认返回垃圾品质 return GAME_CONSTANTS.QUALITY_LEVELS[1] } /** * 生成随机品质 * @param {Number} luck - 运气值 (0-100),影响高品质概率 * @returns {Number} 品质值 */ export function generateRandomQuality(luck = 0) { // 单次随机判定 const roll = Math.random() // 运气加成:每1点运气增加对应品质概率的 0.1% const luckBonus = luck * 0.001 // 传说品质:0.1% + 运气加成 if (roll < 0.001 + luckBonus) { return 200 + Math.floor(Math.random() * 51) // 200-250 } // 史诗品质:1% + 运气加成 if (roll < 0.01 + luckBonus * 2) { return 170 + Math.floor(Math.random() * 30) // 170-199 } // 稀有品质:5% + 运气加成 if (roll < 0.05 + luckBonus * 3) { return 140 + Math.floor(Math.random() * 30) // 140-169 } // 优秀品质:15% + 运气加成 if (roll < 0.15 + luckBonus * 4) { return 100 + Math.floor(Math.random() * 40) // 100-139 } // 普通品质:剩余大部分 if (roll < 0.85) { return 50 + Math.floor(Math.random() * 50) // 50-99 } // 垃圾品质:15% return Math.floor(Math.random() * 50) // 0-49 } /** * 使用物品 * @param {Object} playerStore - 玩家Store * @param {Object} gameStore - 游戏Store * @param {String} itemId - 物品ID * @param {Number} count - 使用数量 * @returns {Object} { success: boolean, message: string, effects: Object } */ export function useItem(playerStore, gameStore, itemId, count = 1) { const config = ITEM_CONFIG[itemId] if (!config) { return { success: false, message: '物品不存在' } } // 查找背包中的物品 const itemIndex = playerStore.inventory.findIndex(i => i.id === itemId) if (itemIndex === -1) { return { success: false, message: '背包中没有该物品' } } const inventoryItem = playerStore.inventory[itemIndex] // 检查数量 if (inventoryItem.count < count) { return { success: false, message: '物品数量不足' } } // 根据物品类型处理 if (config.type === 'consumable') { return useConsumable(playerStore, gameStore, config, inventoryItem, count, itemIndex) } if (config.type === 'book') { return useBook(playerStore, gameStore, config, inventoryItem) } return { success: false, message: '该物品无法使用' } } /** * 使用消耗品 */ function useConsumable(playerStore, gameStore, config, inventoryItem, count, itemIndex) { const effects = {} const appliedEffects = [] // 应用效果 if (config.effect) { if (config.effect.health) { const oldHealth = playerStore.currentStats.health playerStore.currentStats.health = Math.min( playerStore.currentStats.maxHealth, playerStore.currentStats.health + config.effect.health * count ) effects.health = playerStore.currentStats.health - oldHealth if (effects.health > 0) appliedEffects.push(`恢复${effects.health}生命`) } if (config.effect.stamina) { const oldStamina = playerStore.currentStats.stamina playerStore.currentStats.stamina = Math.min( playerStore.currentStats.maxStamina, playerStore.currentStats.stamina + config.effect.stamina * count ) effects.stamina = playerStore.currentStats.stamina - oldStamina if (effects.stamina > 0) appliedEffects.push(`恢复${effects.stamina}耐力`) } if (config.effect.sanity) { const oldSanity = playerStore.currentStats.sanity playerStore.currentStats.sanity = Math.min( playerStore.currentStats.maxSanity, playerStore.currentStats.sanity + config.effect.sanity * count ) effects.sanity = playerStore.currentStats.sanity - oldSanity if (effects.sanity > 0) appliedEffects.push(`恢复${effects.sanity}精神`) } } // 消耗物品 inventoryItem.count -= count if (inventoryItem.count <= 0) { playerStore.inventory.splice(itemIndex, 1) } // 记录日志 const effectText = appliedEffects.length > 0 ? appliedEffects.join('、') : '使用' if (gameStore.addLog) { gameStore.addLog(`使用了 ${config.name} x${count},${effectText}`, 'info') } return { success: true, message: effectText, effects } } /** * 使用书籍(阅读) */ function useBook(playerStore, gameStore, config, inventoryItem) { // 书籍不消耗,返回阅读任务信息 return { success: true, message: '开始阅读', readingTask: { itemId: config.id, bookName: config.name, readingTime: config.readingTime, expReward: config.expReward, completionBonus: config.completionBonus } } } /** * 装备物品 * @param {Object} playerStore - 玩家Store * @param {Object} gameStore - 游戏Store * @param {String} uniqueId - 物品唯一ID(背包中的物品) * @returns {Object} { success: boolean, message: string } */ export function equipItem(playerStore, gameStore, uniqueId) { const inventoryItem = playerStore.inventory.find(i => i.uniqueId === uniqueId) if (!inventoryItem) { return { success: false, message: '物品不存在' } } const config = ITEM_CONFIG[inventoryItem.id] if (!config) { return { success: false, message: '物品配置不存在' } } // 确定装备槽位 let slot = config.type if (config.subtype === 'one_handed' || config.subtype === 'two_handed') { slot = 'weapon' } else if (config.type === 'armor') { slot = 'armor' } else if (config.type === 'shield') { slot = 'shield' } else if (config.type === 'accessory') { slot = 'accessory' } if (!slot || !playerStore.equipment.hasOwnProperty(slot)) { return { success: false, message: '无法装备该物品' } } // 如果已有装备,先卸下 const currentlyEquipped = playerStore.equipment[slot] if (currentlyEquipped) { unequipItemBySlot(playerStore, gameStore, slot) } // 装备新物品 playerStore.equipment[slot] = inventoryItem inventoryItem.equipped = true // 应用装备属性 applyEquipmentStats(playerStore, slot, inventoryItem) // 记录日志 if (gameStore.addLog) { gameStore.addLog(`装备了 ${inventoryItem.name}`, 'info') } return { success: true, message: '装备成功' } } /** * 卸下装备 * @param {Object} playerStore - 玩家Store * @param {Object} gameStore - 游戏Store * @param {String} slot - 装备槽位 * @returns {Object} { success: boolean, message: string } */ export function unequipItemBySlot(playerStore, gameStore, slot) { const equipped = playerStore.equipment[slot] if (!equipped) { return { success: false, message: '该槽位没有装备' } } // 移除装备属性 removeEquipmentStats(playerStore, slot) // 标记为未装备 equipped.equipped = false // 清空槽位 playerStore.equipment[slot] = null // 记录日志 if (gameStore.addLog) { gameStore.addLog(`卸下了 ${equipped.name}`, 'info') } return { success: true, message: '卸下成功' } } /** * 应用装备属性到玩家 */ function applyEquipmentStats(playerStore, slot, item) { if (!playerStore.equipmentStats) { playerStore.equipmentStats = { attack: 0, defense: 0, shield: 0, critRate: 0, attackSpeed: 1 } } if (item.finalDamage) { playerStore.equipmentStats.attack += item.finalDamage } if (item.finalDefense) { playerStore.equipmentStats.defense += item.finalDefense } if (item.finalShield) { playerStore.equipmentStats.shield += item.finalShield } if (item.attackSpeed) { playerStore.equipmentStats.attackSpeed *= item.attackSpeed } } /** * 移除装备属性 */ function removeEquipmentStats(playerStore, slot) { const equipped = playerStore.equipment[slot] if (!equipped || !playerStore.equipmentStats) { return } if (equipped.finalDamage) { playerStore.equipmentStats.attack -= equipped.finalDamage } if (equipped.finalDefense) { playerStore.equipmentStats.defense -= equipped.finalDefense } if (equipped.finalShield) { playerStore.equipmentStats.shield -= equipped.finalShield } } /** * 添加物品到背包 * @param {Object} playerStore - 玩家Store * @param {String} itemId - 物品ID * @param {Number} count - 数量 * @param {Number} quality - 品质值(装备用,0-250) * @returns {Object} { success: boolean, item: Object } */ export function addItemToInventory(playerStore, itemId, count = 1, quality = null) { const config = ITEM_CONFIG[itemId] if (!config) { return { success: false, message: '物品不存在' } } // 素材和关键道具不需要品质,使用固定值 // 装备类物品才需要随机品质 const needsQuality = config.type === 'weapon' || config.type === 'armor' || config.type === 'shield' || config.type === 'accessory' const itemQuality = quality !== null ? quality : (needsQuality ? generateRandomQuality(playerStore.baseStats?.intuition || 0) : 100) // 计算物品属性(包含品质等级) const itemStats = calculateItemStats(itemId, itemQuality) // 素材类物品 - 只按ID堆叠 if (config.stackable) { const existingItem = playerStore.inventory.find(i => i.id === itemId) if (existingItem) { existingItem.count += count return { success: true, item: existingItem } } } // 装备类物品 - 不堆叠,每个装备都有独立的唯一ID // 这样可以正确跟踪每个装备的装备状态 if (needsQuality) { // 检查是否有完全相同品质的装备(用于显示堆叠数量,但各自独立) // 注意:装备不再真正堆叠,每个都是独立实例 // 生成唯一ID:itemId + quality + 时间戳 + 随机数 const uniqueId = `${itemId}_q${itemQuality}_${Date.now()}_${Math.random().toString(36).substring(2, 9)}` const newItem = { ...itemStats, uniqueId, count: 1, // 装备始终为1 equipped: false, obtainedAt: Date.now() } playerStore.inventory.push(newItem) return { success: true, item: newItem } } // 创建新物品(非装备类) const uniqueId = `${itemId}_${Date.now()}_${Math.random().toString(36).substring(2, 11)}` const newItem = { ...itemStats, uniqueId, count, equipped: false, obtainedAt: Date.now() } playerStore.inventory.push(newItem) return { success: true, item: newItem } } /** * 从背包移除物品 * @param {Object} playerStore - 玩家Store * @param {String} uniqueId - 物品唯一ID * @param {Number} count - 移除数量 * @returns {Object} { success: boolean, message: string } */ export function removeItemFromInventory(playerStore, uniqueId, count = 1) { const itemIndex = playerStore.inventory.findIndex(i => i.uniqueId === uniqueId) if (itemIndex === -1) { return { success: false, message: '物品不存在' } } const item = playerStore.inventory[itemIndex] if (item.count < count) { return { success: false, message: '物品数量不足' } } item.count -= count if (item.count <= 0) { // 如果已装备,先卸下 if (item.equipped) { for (const [slot, equipped] of Object.entries(playerStore.equipment)) { if (equipped && equipped.uniqueId === uniqueId) { playerStore.equipment[slot] = null break } } } playerStore.inventory.splice(itemIndex, 1) } return { success: true, message: '移除成功' } } /** * 检查是否拥有物品 * @param {Object} playerStore - 玩家Store * @param {String} itemId - 物品ID * @returns {Boolean} */ export function hasItem(playerStore, itemId) { return playerStore.inventory.some(i => i.id === itemId && i.count > 0) } /** * 获取物品数量 * @param {Object} playerStore - 玩家Store * @param {String} itemId - 物品ID * @returns {Number} */ export function getItemCount(playerStore, itemId) { const item = playerStore.inventory.find(i => i.id === itemId) return item ? item.count : 0 } // Note: checkSkillUnlock, calculateSellPrice, calculateBuyPrice, // getQualityColor, getQualityName are now test-only utilities // and are not exported for production use