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

570 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'
/**
* 计算装备品质后的属性
* @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 - 品质装备用
* @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 }
}
}
// 创建新物品
const newItem = {
...itemStats,
uniqueId: `${itemId}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
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
}
/**
* 检查装备是否解锁技能
* @param {Object} playerStore - 玩家Store
* @param {String} itemId - 物品ID
* @returns {String|null} 解锁的技能ID
*/
export function checkSkillUnlock(playerStore, itemId) {
const config = ITEM_CONFIG[itemId]
if (!config || !config.unlockSkill) {
return null
}
const skillId = config.unlockSkill
// 检查技能是否已解锁
if (playerStore.skills[skillId] && playerStore.skills[skillId].unlocked) {
return null
}
return skillId
}
/**
* 计算物品出售价格
* @param {Object} item - 物品对象
* @param {Number} marketRate - 市场价格倍率 (0.1-2.0)
* @returns {Number} 出售价格铜币
*/
export function calculateSellPrice(item, marketRate = 0.3) {
const basePrice = item.finalValue || item.baseValue || 0
return Math.max(1, Math.floor(basePrice * marketRate))
}
/**
* 计算物品购买价格
* @param {Object} item - 物品对象
* @param {Number} marketRate - 市场价格倍率 (1.0-3.0)
* @returns {Number} 购买价格铜币
*/
export function calculateBuyPrice(item, marketRate = 2.0) {
const basePrice = item.finalValue || item.baseValue || 0
return Math.max(1, Math.floor(basePrice * marketRate))
}
/**
* 获取品质颜色
* @param {Number} quality - 品质值
* @returns {String} 颜色代码
*/
export function getQualityColor(quality) {
const level = getQualityLevel(quality)
return level.color
}
/**
* 获取品质名称
* @param {Number} quality - 品质值
* @returns {String} 品质名称
*/
export function getQualityName(quality) {
const level = getQualityLevel(quality)
return level.name
}