2026-01-21 17:13:51 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 物品系统 - 使用/装备/品质计算
|
|
|
|
|
|
* Phase 6 核心系统实现
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
import { ITEM_CONFIG } from '@/config/items.js'
|
|
|
|
|
|
import { GAME_CONSTANTS } from '@/config/constants.js'
|
|
|
|
|
|
import { SKILL_CONFIG } from '@/config/skills.js'
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 计算装备品质后的属性
|
|
|
|
|
|
* @param {String} itemId - 物品ID
|
|
|
|
|
|
* @param {Number} quality - 品质值 (0-250)
|
|
|
|
|
|
* @returns {Object|null} 计算后的属性
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function calculateItemStats(itemId, quality = 100) {
|
|
|
|
|
|
const config = ITEM_CONFIG[itemId]
|
|
|
|
|
|
if (!config) {
|
|
|
|
|
|
return null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const qualityLevel = getQualityLevel(quality)
|
|
|
|
|
|
const qualityMultiplier = qualityLevel.multiplier
|
|
|
|
|
|
|
|
|
|
|
|
const stats = {
|
|
|
|
|
|
id: itemId,
|
|
|
|
|
|
name: config.name,
|
|
|
|
|
|
type: config.type,
|
|
|
|
|
|
subtype: config.subtype,
|
|
|
|
|
|
description: config.description,
|
|
|
|
|
|
icon: config.icon,
|
|
|
|
|
|
quality: quality,
|
|
|
|
|
|
qualityLevel: qualityLevel.level,
|
|
|
|
|
|
qualityName: qualityLevel.name,
|
|
|
|
|
|
qualityColor: qualityLevel.color
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 应用品质百分比到基础属性
|
|
|
|
|
|
const qualityRatio = quality / 100
|
|
|
|
|
|
|
|
|
|
|
|
if (config.baseDamage) {
|
|
|
|
|
|
stats.baseDamage = config.baseDamage
|
|
|
|
|
|
stats.finalDamage = Math.floor(config.baseDamage * qualityRatio * qualityMultiplier)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (config.baseDefense) {
|
|
|
|
|
|
stats.baseDefense = config.baseDefense
|
|
|
|
|
|
stats.finalDefense = Math.floor(config.baseDefense * qualityRatio * qualityMultiplier)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (config.baseShield) {
|
|
|
|
|
|
stats.baseShield = config.baseShield
|
|
|
|
|
|
stats.finalShield = Math.floor(config.baseShield * qualityRatio * qualityMultiplier)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (config.attackSpeed) {
|
|
|
|
|
|
stats.attackSpeed = config.attackSpeed
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 计算价值
|
|
|
|
|
|
stats.baseValue = config.baseValue || 0
|
|
|
|
|
|
stats.finalValue = Math.floor(config.baseValue * qualityRatio * qualityMultiplier)
|
|
|
|
|
|
|
|
|
|
|
|
// 消耗品效果
|
|
|
|
|
|
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.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) {
|
|
|
|
|
|
// 基础品质范围 50-150
|
|
|
|
|
|
const baseMin = 50
|
|
|
|
|
|
const baseMax = 150
|
|
|
|
|
|
|
|
|
|
|
|
// 运气加成:每1点运气增加0.5品质
|
|
|
|
|
|
const luckBonus = luck * 0.5
|
|
|
|
|
|
|
|
|
|
|
|
// 随机品质
|
|
|
|
|
|
let quality = Math.floor(Math.random() * (baseMax - baseMin + 1)) + baseMin + luckBonus
|
|
|
|
|
|
|
|
|
|
|
|
// 小概率生成高品质(传说)
|
|
|
|
|
|
if (Math.random() < 0.01 + luck / 10000) {
|
|
|
|
|
|
quality = 180 + Math.floor(Math.random() * 70) // 180-250
|
|
|
|
|
|
}
|
|
|
|
|
|
// 史诗品质
|
|
|
|
|
|
else if (Math.random() < 0.05 + luck / 2000) {
|
|
|
|
|
|
quality = 160 + Math.floor(Math.random() * 40) // 160-199
|
|
|
|
|
|
}
|
|
|
|
|
|
// 稀有品质
|
|
|
|
|
|
else if (Math.random() < 0.15 + luck / 500) {
|
|
|
|
|
|
quality = 130 + Math.floor(Math.random() * 30) // 130-159
|
|
|
|
|
|
}
|
|
|
|
|
|
// 优秀品质
|
|
|
|
|
|
else if (Math.random() < 0.3 + luck / 200) {
|
|
|
|
|
|
quality = 100 + Math.floor(Math.random() * 30) // 100-129
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return Math.min(250, Math.max(0, Math.floor(quality)))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 使用物品
|
|
|
|
|
|
* @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 - 数量
|
2026-01-23 16:19:17 +08:00
|
|
|
|
* @param {Number} quality - 品质值(装备用,0-250)
|
2026-01-21 17:13:51 +08:00
|
|
|
|
* @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)
|
|
|
|
|
|
|
2026-01-23 16:19:17 +08:00
|
|
|
|
// 计算物品属性(包含品质等级)
|
2026-01-21 17:13:51 +08:00
|
|
|
|
const itemStats = calculateItemStats(itemId, itemQuality)
|
|
|
|
|
|
|
2026-01-23 16:19:17 +08:00
|
|
|
|
// 素材类物品 - 只按ID堆叠
|
2026-01-21 17:13:51 +08:00
|
|
|
|
if (config.stackable) {
|
|
|
|
|
|
const existingItem = playerStore.inventory.find(i => i.id === itemId)
|
|
|
|
|
|
if (existingItem) {
|
|
|
|
|
|
existingItem.count += count
|
|
|
|
|
|
return { success: true, item: existingItem }
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-23 16:19:17 +08:00
|
|
|
|
// 装备类物品 - 按物品ID + 品质等级堆叠
|
|
|
|
|
|
if (needsQuality) {
|
|
|
|
|
|
const qualityLevel = itemStats.qualityLevel
|
|
|
|
|
|
const existingItem = playerStore.inventory.find(
|
|
|
|
|
|
i => i.id === itemId && i.qualityLevel === qualityLevel
|
|
|
|
|
|
)
|
|
|
|
|
|
if (existingItem) {
|
|
|
|
|
|
existingItem.count += count
|
|
|
|
|
|
return { success: true, item: existingItem }
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-21 17:13:51 +08:00
|
|
|
|
// 创建新物品
|
2026-01-23 16:19:17 +08:00
|
|
|
|
// 装备类物品使用 itemId + qualityLevel 作为唯一标识
|
|
|
|
|
|
// 素材类物品使用 itemId + 时间戳(虽然可堆叠,但保留唯一ID用于其他用途)
|
|
|
|
|
|
const uniqueId = needsQuality
|
|
|
|
|
|
? `${itemId}_q${itemStats.qualityLevel}`
|
|
|
|
|
|
: `${itemId}_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`
|
|
|
|
|
|
|
2026-01-21 17:13:51 +08:00
|
|
|
|
const newItem = {
|
|
|
|
|
|
...itemStats,
|
2026-01-23 16:19:17 +08:00
|
|
|
|
uniqueId,
|
2026-01-21 17:13:51 +08:00
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-23 16:19:17 +08:00
|
|
|
|
// Note: checkSkillUnlock, calculateSellPrice, calculateBuyPrice,
|
|
|
|
|
|
// getQualityColor, getQualityName are now test-only utilities
|
|
|
|
|
|
// and are not exported for production use
|