Files
text-adventure-game/utils/itemSystem.js
Claude 021f6a54f5 fix: 修复制造系统耦合问题并优化物品堆叠逻辑
- 修复 craftingSystem.js 引用不存在的 inventorySystem.js
- 移除 recipes.js 中重复的 getItemCount 函数
- 装备类物品改为按品质等级(1-6)分组堆叠,而非精确品质值
- 统一品质计算,使用 GAME_CONSTANTS.QUALITY_LEVELS 范围
- 移除未使用的 createCraftedItem 函数

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 16:19:17 +08:00

528 lines
14 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 物品系统 - 使用/装备/品质计算
* 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 - 数量
* @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) {
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 }
}
}
// 创建新物品
// 装备类物品使用 itemId + qualityLevel 作为唯一标识
// 素材类物品使用 itemId + 时间戳虽然可堆叠但保留唯一ID用于其他用途
const uniqueId = needsQuality
? `${itemId}_q${itemStats.qualityLevel}`
: `${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