Files
text-adventure-game/utils/itemSystem.js

547 lines
15 KiB
JavaScript
Raw Normal View History

/**
* 物品系统 - 使用/装备/品质计算
* 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) {
// 检查是否有完全相同品质的装备(用于显示堆叠数量,但各自独立)
// 注意:装备不再真正堆叠,每个都是独立实例
// 生成唯一IDitemId + 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