/** * 游戏数值模型配置 * * 本文件定义了游戏中所有数值计算的核心公式 * 包括:角色属性、装备属性、技能效果、怪物属性等 */ // ==================== 核心常量 ==================== export const GAME_FORMULAS = { // 经验曲线系数 EXP_BASE: 100, // 基础经验值 EXP_GROWTH: 1.15, // 每级经验增长倍数 // 属性缩放系数 STAT_SCALING: { STRENGTH_TO_ATTACK: 1.0, // 1力量 = 1攻击力 AGILITY_TO_DODGE: 0.5, // 1敏捷 = 0.5%闪避 DEXTERITY_TO_CRIT: 0.3, // 1灵巧 = 0.3%暴击 INTUITION_TO_QUALITY: 0.5, // 1智力 = 0.5品质 VITALITY_TO_HP: 10, // 1体质 = 10生命 VITALITY_TO_REGEN: 0.05 // 1体质 = 0.05 HP/秒回复 }, // 装备品质系统 QUALITY: { BASE_MULTIPLIER: 0.01, // 品质每1点 = 1%属性 LEVEL_BONUS: 2, // 每级额外+2品质 MAX_QUALITY: 250, MIN_QUALITY: 0 }, // 词缀系统 AFFIX: { MAX_COUNT: 3, // 每件装备最多词缀数量 COMMON_CHANCE: 0.60, // 普通词缀概率 RARE_CHANCE: 0.30, // 稀有词缀概率 EPIC_CHANCE: 0.09, // 史诗词缀概率 LEGENDARY_CHANCE: 0.01 // 传说词缀概率 }, // 怪物缩放 MONSTER: { HP_SCALING: 1.12, // 每级HP增长12% ATTACK_SCALING: 1.08, // 每级攻击增长8% DEFENSE_SCALING: 1.06, // 每级防御增长6% EXP_SCALING: 1.10, // 每级经验奖励增长10% LEVEL_VARANCE: 0.2 // 等级浮动±20% } } // ==================== 角色属性计算 ==================== /** * 计算角色基础属性 * @param {Object} baseStats - 基础属性 {strength, agility, dexterity, intuition, vitality} * @param {Number} level - 角色等级 * @returns {Object} 计算后的属性 */ export function calculateCharacterStats(baseStats, level = 1) { const s = GAME_FORMULAS.STAT_SCALING // 基础生命值 = 100 + 体质 * 10 + 等级 * 5 const baseHP = 100 + (baseStats.vitality || 10) * s.VITALITY_TO_HP + level * 5 // 基础耐力 = 80 + 体质 * 5 + 等级 * 3 const baseStamina = 80 + (baseStats.vitality || 10) * 5 + level * 3 // 基础精神 = 100 + 智力 * 3 const baseSanity = 100 + (baseStats.intuition || 10) * 3 // 基础攻击力 = 力量 * 1 + 等级 * 2 const baseAttack = (baseStats.strength || 10) * s.STRENGTH_TO_ATTACK + level * 2 // 基础防御 = 敏捷 * 0.3 + 体质 * 0.2 + 等级 const baseDefense = (baseStats.agility || 8) * 0.3 + (baseStats.vitality || 10) * 0.2 + level // 基础暴击率 = 灵巧 * 0.3% (基础值5%) const baseCritRate = 5 + (baseStats.dexterity || 8) * s.DEXTERITY_TO_CRIT // 基础闪避率 = 敏捷 * 0.5% const baseDodgeRate = (baseStats.agility || 8) * s.AGILITY_TO_DODGE return { hp: Math.floor(baseHP), stamina: Math.floor(baseStamina), sanity: Math.floor(baseSanity), attack: Math.floor(baseAttack), defense: Math.floor(baseDefense), critRate: Math.floor(baseCritRate * 10) / 10, dodgeRate: Math.floor(baseDodgeRate * 10) / 10 } } /** * 计算装备后的最终属性 * @param {Object} baseStats - 基础属性 * @param {Object} equipmentStats - 装备提供的属性 * @returns {Object} 最终属性 */ export function calculateFinalStats(baseStats, equipmentStats = {}) { const final = { ...baseStats } // 装备属性加成(百分比和固定值混合) if (equipmentStats.attackBonus) { final.attack += Math.floor(baseStats.attack * (equipmentStats.attackBonus / 100)) final.attack += equipmentStats.attack || 0 } if (equipmentStats.defenseBonus) { final.defense += Math.floor(baseStats.defense * (equipmentStats.defenseBonus / 100)) final.defense += equipmentStats.defense || 0 } if (equipmentStats.critRate) { final.critRate += equipmentStats.critRate } if (equipmentStats.dodgeRate) { final.dodgeRate += equipmentStats.dodgeRate } if (equipmentStats.maxHP) { final.hp += equipmentStats.maxHP } if (equipmentStats.maxStamina) { final.stamina += equipmentStats.maxStamina } // 限制范围 final.critRate = Math.min(95, Math.max(0, final.critRate)) final.dodgeRate = Math.min(80, Math.max(0, final.dodgeRate)) return final } // ==================== 装备属性计算 ==================== /** * 计算装备的最终属性 * @param {Object} baseItem - 基础物品配置 * @param {Number} quality - 品质值 (0-250) * @param {Number} itemLevel - 物品等级 * @param {Array} affixes - 词缀列表 * @returns {Object} 计算后的装备属性 */ export function calculateEquipmentStats(baseItem, quality = 100, itemLevel = 1, affixes = []) { const q = GAME_FORMULAS.QUALITY const stats = { ...baseItem } // 1. 品质倍率计算 // 品质倍率 = 1 + (品质 - 100) * 0.01 // 品质100 = 1.0倍,品质150 = 1.5倍,品质50 = 0.5倍 const qualityMultiplier = 1 + (quality - 100) * q.BASE_MULTIPLIER // 2. 等级倍率 // 每级增加2%属性 const levelMultiplier = 1 + (itemLevel - 1) * 0.02 // 3. 综合倍率 const totalMultiplier = qualityMultiplier * levelMultiplier // 4. 计算基础属性 if (baseItem.baseDamage) { stats.finalDamage = Math.max(1, Math.floor(baseItem.baseDamage * totalMultiplier)) } if (baseItem.baseDefense) { stats.finalDefense = Math.max(1, Math.floor(baseItem.baseDefense * totalMultiplier)) } if (baseItem.blockRate) { stats.finalBlockRate = Math.min(75, baseItem.blockRate + quality * 0.02) } // 5. 应用词缀 const affixStats = calculateAffixStats(affixes, itemLevel) Object.assign(stats, affixStats) // 6. 计算价值 stats.finalValue = Math.floor(baseItem.baseValue * totalMultiplier * (1 + affixes.length * 0.2)) // 7. 品质信息 stats.quality = quality stats.itemLevel = itemLevel stats.qualityLevel = getQualityLevel(quality) stats.affixes = affixes return stats } /** * 计算词缀属性 * @param {Array} affixes - 词缀列表 * @param {Number} itemLevel - 物品等级 * @returns {Object} 词缀提供的属性 */ export function calculateAffixStats(affixes, itemLevel = 1) { const stats = {} for (const affix of affixes) { // 词缀属性随物品等级缩放 const scale = 1 + (itemLevel - 1) * 0.1 switch (affix.type) { case 'attack': stats.attackBonus = (stats.attackBonus || 0) + affix.value * scale break case 'defense': stats.defenseBonus = (stats.defenseBonus || 0) + affix.value * scale break case 'critRate': stats.critRate = (stats.critRate || 0) + affix.value * scale break case 'dodge': stats.dodgeRate = (stats.dodgeRate || 0) + affix.value * scale break case 'hp': stats.maxHP = (stats.maxHP || 0) + affix.value * scale break case 'stamina': stats.maxStamina = (stats.maxStamina || 0) + affix.value * scale break case 'fireDamage': stats.fireDamage = (stats.fireDamage || 0) + affix.value * scale break case 'speed': stats.attackSpeed = (stats.attackSpeed || 1) + affix.value * 0.01 * scale break } } return stats } /** * 获取品质等级 * 与 constants.js 中的 QUALITY_LEVELS 保持一致 */ export function getQualityLevel(quality) { if (quality < 50) return { level: 1, name: '垃圾', color: '#6b7280', multiplier: 0.5 } if (quality < 100) return { level: 2, name: '普通', color: '#ffffff', multiplier: 1.0 } if (quality < 140) return { level: 3, name: '优秀', color: '#22c55e', multiplier: 1.2 } if (quality < 170) return { level: 4, name: '稀有', color: '#3b82f6', multiplier: 1.5 } if (quality < 200) return { level: 5, name: '史诗', color: '#a855f7', multiplier: 2.0 } return { level: 6, name: '传说', color: '#f59e0b', multiplier: 3.0 } } // ==================== 词缀生成 ==================== /** * 词缀池定义 */ export const AFFIX_POOL = { // 武器词缀 weapon: [ { id: 'sharp', name: '锋利', type: 'attack', value: 5, rarity: 'common' }, { id: 'keen', name: '锐利', type: 'critRate', value: 3, rarity: 'common' }, { id: 'swift', name: '轻盈', type: 'speed', value: 10, rarity: 'common' }, { id: 'vicious', name: '残暴', type: 'attack', value: 12, rarity: 'rare' }, { id: 'deadly', name: '致命', type: 'critRate', value: 8, rarity: 'rare' }, { id: 'flaming', name: '烈焰', type: 'fireDamage', value: 10, rarity: 'epic' }, { id: 'godslayer', name: '弑神', type: 'attack', value: 25, rarity: 'legendary' } ], // 防具词缀 armor: [ { id: 'sturdy', name: '坚固', type: 'defense', value: 5, rarity: 'common' }, { id: 'agile', name: '灵活', type: 'dodge', value: 3, rarity: 'common' }, { id: 'healthy', name: '健康', type: 'hp', value: 20, rarity: 'common' }, { id: 'fortified', name: '强化', type: 'defense', value: 12, rarity: 'rare' }, { id: 'enduring', name: '耐久', type: 'stamina', value: 15, rarity: 'rare' }, { id: 'ironclad', name: '铁壁', type: 'defense', value: 25, rarity: 'epic' }, { id: 'immortal', name: '不朽', type: 'hp', value: 100, rarity: 'legendary' } ] } /** * 生成词缀 * @param {String} itemType - 物品类型 (weapon/armor) * @param {Number} quality - 品质值 * @returns {Array} 词缀数组 */ export function generateAffixes(itemType, quality) { const pool = AFFIX_POOL[itemType] || [] if (pool.length === 0) return [] // 根据品质决定最大词缀数量 const maxAffixes = quality < 90 ? 0 : quality < 130 ? 1 : quality < 160 ? 2 : GAME_FORMULAS.AFFIX.MAX_COUNT if (maxAffixes === 0) return [] const affixes = [] const used = new Set() // 决定每个词缀的稀有度 for (let i = 0; i < maxAffixes; i++) { const roll = Math.random() let rarity if (roll < GAME_FORMULAS.AFFIX.LEGENDARY_CHANCE) rarity = 'legendary' else if (roll < GAME_FORMULAS.AFFIX.LEGENDARY_CHANCE + GAME_FORMULAS.AFFIX.EPIC_CHANCE) rarity = 'epic' else if (roll < GAME_FORMULAS.AFFIX.LEGENDARY_CHANCE + GAME_FORMULAS.AFFIX.EPIC_CHANCE + GAME_FORMULAS.AFFIX.RARE_CHANCE) rarity = 'rare' else rarity = 'common' // 从对应稀有度池中选择词缀 const available = pool.filter(a => a.rarity === rarity && !used.has(a.id)) // 如果该稀有度没有可用词缀,降级选择 const fallback = available.length > 0 ? available : pool.filter(a => !used.has(a.id)) if (fallback.length > 0) { const affix = fallback[Math.floor(Math.random() * fallback.length)] affixes.push(affix) used.add(affix.id) } } return affixes } // ==================== 怪物属性计算 ==================== /** * 计算怪物属性 * @param {Object} baseMonster - 基础怪物配置 * @param {Number} level - 怪物等级 * @param {Number} difficulty - 难度系数 (0.8 - 1.5) * @returns {Object} 计算后的怪物属性 */ export function calculateMonsterStats(baseMonster, level = 1, difficulty = 1.0) { const m = GAME_FORMULAS.MONSTER // 等级缩放公式:基础值 * (1 + 增长率)^(等级-1) const levelBonus = Math.pow(m.HP_SCALING, level - 1) const attackBonus = Math.pow(m.ATTACK_SCALING, level - 1) const defenseBonus = Math.pow(m.DEFENSE_SCALING, level - 1) const expBonus = Math.pow(m.EXP_SCALING, level - 1) return { ...baseMonster, level, // HP = 基础HP * 等级倍率 * 难度系数 hp: Math.floor(baseMonster.hp * levelBonus * difficulty), maxHp: Math.floor(baseMonster.hp * levelBonus * difficulty), // 攻击 = 基础攻击 * 等级倍率 * 难度系数 attack: Math.floor(baseMonster.attack * attackBonus * difficulty), // 防御 = 基础防御 * 等级倍率 * 难度系数 defense: Math.floor(baseMonster.defense * defenseBonus * difficulty), // 经验奖励 = 基础经验 * 等级倍率 expReward: Math.floor(baseMonster.expReward * expBonus), // 暴击率随等级略微提升 critRate: Math.min(30, (baseMonster.critRate || 0) + level * 0.5), // 速度随等级略微提升 speed: baseMonster.speed + level * 0.5 } } /** * 计算怪物等级范围 * @param {Number} baseLevel - 基础等级 * @returns {Object} { min, max } */ export function getMonsterLevelRange(baseLevel) { const v = GAME_FORMULAS.MONSTER.LEVEL_VARANCE const variance = Math.max(1, Math.floor(baseLevel * v)) return { min: Math.max(1, baseLevel - variance), max: baseLevel + variance } } // ==================== 经验计算 ==================== /** * 计算升级所需经验 * 与 levelingSystem.js 中的公式保持一致 * @param {Number} currentLevel - 当前等级 * @returns {Number} 所需经验 */ export function getExpForLevel(currentLevel) { const f = GAME_FORMULAS if (currentLevel <= 1) return 0 // 混合曲线公式: base * (1.15 ^ (level - 2)) * (level - 1) * 0.8 // 与 levelingSystem.js 的 HYBRID 曲线保持一致 return Math.floor(f.EXP_BASE * Math.pow(f.EXP_GROWTH, currentLevel - 2) * (currentLevel - 1) * 0.8) } /** * 计算战斗经验 * @param {Number} monsterLevel - 怪物等级 * @param {Number} playerLevel - 玩家等级 * @param {Number} baseExp - 基础经验 * @returns {Number} 实际获得经验 */ export function calculateCombatExp(monsterLevel, playerLevel, baseExp) { // 等级差惩罚 const levelDiff = monsterLevel - playerLevel let multiplier = 1.0 if (levelDiff > 5) { // 怪物高5级以上,额外奖励 multiplier = 1 + (levelDiff - 5) * 0.1 } else if (levelDiff < -5) { // 怪物低5级以上,大幅衰减 multiplier = Math.max(0.1, 0.5 + (levelDiff + 5) * 0.1) } else if (levelDiff < 0) { // 怪物等级略低,小幅衰减 multiplier = 1 + levelDiff * 0.05 } return Math.max(1, Math.floor(baseExp * multiplier)) } /** * 计算技能经验 * @param {Number} skillLevel - 技能当前等级 * @param {Number} actionExp - 动作基础经验 * @returns {Number} 实际获得经验 */ export function calculateSkillExp(skillLevel, actionExp) { // 等级越高,获得经验越难 const decay = Math.pow(0.95, skillLevel - 1) return Math.max(1, Math.floor(actionExp * decay)) } // ==================== 伤害计算 ==================== /** * 计算普通伤害 * @param {Number} attack - 攻击力 * @param {Number} defense - 防御力 * @param {Number} level - 等级加成 * @returns {Number} 伤害值 */ export function calculateDamage(attack, defense, level = 1) { // 基础伤害 = 攻击力 - 防御力 * 0.5 // 最小伤害为攻击力的20% const baseDamage = attack - defense * 0.5 const minDamage = attack * 0.2 // 加入等级加成 const levelBonus = 1 + level * 0.02 return Math.max(minDamage, baseDamage) * levelBonus } /** * 计算暴击伤害 * @param {Number} baseDamage - 基础伤害 * @param {Number} critMultiplier - 暴击倍率 * @returns {Number} 暴击伤害 */ export function calculateCritDamage(baseDamage, critMultiplier = 1.5) { return Math.floor(baseDamage * critMultiplier) } /** * 计算是否暴击 * @param {Number} critRate - 暴击率 (0-100) * @returns {Boolean} */ export function rollCrit(critRate) { return Math.random() * 100 < critRate } /** * 计算是否命中 * @param {Number} hitRate - 命中率 (0-100) * @param {Number} dodgeRate - 闪避率 (0-100) * @returns {Boolean} */ export function rollHit(hitRate = 90, dodgeRate = 0) { const actualHitRate = Math.max(5, Math.min(95, hitRate - dodgeRate)) return Math.random() * 100 < actualHitRate } // ==================== 价值计算 ==================== /** * 计算物品出售价格 * @param {Number} baseValue - 基础价值 * @param {Number} quality - 品质 * @param {Number} affixCount - 词缀数量 * @returns {Number} 出售价格 */ export function calculateSellPrice(baseValue, quality = 100, affixCount = 0) { const qualityMultiplier = 1 + (quality - 100) * 0.005 const affixMultiplier = 1 + affixCount * 0.15 return Math.floor(baseValue * qualityMultiplier * affixMultiplier) } /** * 计算物品购买价格 * @param {Number} sellPrice - 出售价格 * @param {Number} merchantRate - 商人倍率 (通常 2.0 - 3.0) * @returns {Number} 购买价格 */ export function calculateBuyPrice(sellPrice, merchantRate = 2.0) { return Math.floor(sellPrice * merchantRate) }