/* * 伤害计算 - 计算伤害 * */ import { eleBaseDmg, erTitle, breakBaseDmg, cryBaseDmg } from './DmgCalcMeta.js' import DmgMastery from './DmgMastery.js' import lodash from 'lodash' let DmgCalc = { calcRet (fnArgs = {}, data = {}) { let { pctNum, // 技能倍率 talent, // 天赋类型 ele, // 元素反应 basicNum, // 基础数值 mode, // 模式 dynamicData // 动态伤害计算数据 } = fnArgs let { dynamicDmg = 0, // 动态增伤 dynamicPhy = 0, // 动态物伤 dynamicCpct = 0, // 动态暴击率 dynamicCdmg = 0, // 动态暴击伤害 dynamicEnemydmg = 0 // 动态易伤 } = dynamicData let { ds, // 数据集 attr, // 属性 level, // 面板数据 enemyLv, // 敌人等级 showDetail = false, // 是否展示详情 game } = data let calc = ds.calc let { atk, dmg, phy, cdmg, cpct, enemydmg } = attr // 攻击区 let atkNum = calc(atk) // 倍率独立乘区 let multiNum = attr.multi / 100 // 增伤区 let dmgNum = (1 + dmg.base / 100 + dmg.plus / 100 + dynamicDmg / 100) if (ele === 'phy') { dmgNum = (1 + phy.base / 100 + phy.plus / 100 + dynamicPhy / 100) } if (/^scene,.*/.test(ele) || /.*,scene$/.test(ele) || ele === 'scene') { dmgNum = 1 if (ele !== 'scene') { ele = ele.replace(/(,)?scene(,)?/g, "") } } // 易伤区 let enemydmgNum = 1 if (game === 'sr') { enemydmgNum = 1 + enemydmg.base / 100 + enemydmg.plus / 100 + dynamicEnemydmg / 100 } // 暴击区 let cpctNum = cpct.base / 100 + cpct.plus / 100 + dynamicCpct / 100 // 爆伤区 let cdmgNum = cdmg.base / 100 + cdmg.plus / 100 + dynamicCdmg / 100 let enemyDef = attr.enemy.def / 100 let enemyIgnore = attr.enemy.ignore / 100 let plusNum = 0 pctNum = pctNum / 100 if (talent) { lodash.forEach(talent.split(','), (t) => { if (attr[t]) { let ds = attr[t] pctNum += ds.pct / 100 dmgNum += ds.dmg / 100 enemydmgNum += game === 'gs' ? 0 : ds.enemydmg / 100 cpctNum += ds.cpct / 100 cdmgNum += ds.cdmg / 100 enemyDef += ds.def / 100 enemyIgnore += ds.ignore / 100 multiNum += ds.multi / 100 plusNum += ds.plus } }) } // TODO if (ele === 'superBreak') { enemyIgnore += attr.superBreak.ignore / 100 } // 防御区 let defNum = (level + 100) / ((level + 100) + (enemyLv + 100) * (1 - enemyDef) * (1 - enemyIgnore)) if (game === 'sr') { let enemyDefdown = enemyDef + enemyIgnore <= 1 ? enemyDef + enemyIgnore : 1 defNum = (200 + level * 10) / ((200 + level * 10) + (200 + enemyLv * 10) * (1 - enemyDefdown)) } // 抗性区 let kx = attr.kx let kNum = 0.9 if (game === 'sr') { kNum = 1 + (kx / 100) } else { if (ele === 'swirl') { kx = attr.fykx } kx = 10 - (kx || 0) if (kx >= 75) { kNum = 1 / (1 + 3 * kx / 100) } else if (kx >= 0) { kNum = (100 - kx) / 100 } else { kNum = 1 - kx / 200 } } // 减伤区 let dmgReduceNum = 1 if (game === 'sr') { dmgReduceNum = 0.9 } cpctNum = Math.max(0, Math.min(1, cpctNum)) if (cpctNum === 0) { cdmgNum = 0 } const isEle = ele !== false && ele !== 'phy' && ele !== 'scene' // 反应区 let eleNum = 1 let eleBase = 1 if (game === 'gs') { eleNum = isEle ? DmgMastery.getBasePct(ele, attr.element) : 1 eleBase = isEle ? 1 + attr[ele] / 100 + DmgMastery.getMultiple(ele, calc(attr.mastery)) : 1 } let stanceNum = 1 if (game === 'sr') { switch (ele) { case 'shock': case 'burn': case 'windShear': case 'bleed': case 'entanglement': case 'lightningBreak': case 'fireBreak': case 'windBreak': case 'physicalBreak': case 'quantumBreak': case 'imaginaryBreak': case 'iceBreak': case 'superBreak':{ eleNum = DmgMastery.getBasePct(ele, attr.element) stanceNum = 1 + calc(attr.stance) / 100 break } default: break } } let dmgBase = (mode === 'basic') ? basicNum + plusNum : atkNum * pctNum * (1 + multiNum) + plusNum let ret = {} switch (ele) { case 'vaporize': case 'melt': { ret = { dmg: dmgBase * dmgNum * (1 + cdmgNum) * defNum * kNum * eleBase * eleNum, avg: dmgBase * dmgNum * (1 + cpctNum * cdmgNum) * defNum * kNum * eleBase * eleNum } break } case 'burning': case 'superConduct': case 'swirl': case 'electroCharged': case 'shatter': case 'overloaded': case 'bloom': case 'burgeon': case 'hyperBloom': { eleBase *= eleBaseDmg[level] ret = { avg: eleBase * eleNum * kNum } break } case 'crystallize': { eleBase *= cryBaseDmg[level] ret = { avg: eleBase * (calc(attr.shield) / 100) * (attr.shield.inc / 100) } break } case 'aggravate': case 'spread': { eleBase *= eleBaseDmg[level] dmgBase += eleBase * eleNum ret = { dmg: dmgBase * dmgNum * (1 + cdmgNum) * defNum * kNum, avg: dmgBase * dmgNum * (1 + cpctNum * cdmgNum) * defNum * kNum } break } // 技能持续伤害 = 伤害值乘区 * 增伤区 * 易伤区 * 防御区 * 抗性区 * 减伤区 case 'skillDot': { ret = { avg: dmgBase * dmgNum * enemydmgNum * defNum * kNum * dmgReduceNum } break } // 未计算:1. 层数(风化、纠缠) 2. 韧性条系数(击破、纠缠) 3. 削韧值(超击破)、超击破伤害提高 // 常规击破伤害均需要计算减伤区(即按韧性条存在处理) 特例:阮梅终结技/秘技击破伤害不计算减伤/波提欧 // 击破伤害 = 基础伤害 * 属性击破伤害系数 * (1+击破特攻%) * 易伤区 * 防御区 * 抗性区 * 减伤区 * (敌方韧性+2)/4 * 层数系数 // 击破持续伤害 = 基础伤害 * 属性持续伤害系数 * (1+击破特攻%) * 易伤区 * 防御区 * 抗性区 * 减伤区 * 层数系数 // 超击破伤害 = 基础伤害 * (1+击破特攻%) * 易伤区 * 防御区 * 抗性区 * 减伤区 * (1+超击破伤害提高) * 技能最终削韧值 // 技能最终削韧值 = 技能基础削韧值 ×(1+削韧值提高)×(1+弱点击破效率提高) // 超击破伤害提高 截至2.2版本该乘区仅能由同谐开拓者提供,与常规增伤区无关,暂时只在calc.js中手动计算 case 'shock': case 'burn': case 'windShear': case 'bleed': case 'entanglement': case 'lightningBreak': case 'fireBreak': case 'windBreak': case 'physicalBreak': case 'quantumBreak': case 'imaginaryBreak': case 'iceBreak': case 'superBreak': { let breakBase = 1 breakBase *= breakBaseDmg[level] ret = { avg: breakBase * eleNum * stanceNum * enemydmgNum * defNum * kNum * dmgReduceNum } break } default: { ret = { dmg: dmgBase * dmgNum * enemydmgNum * (1 + cdmgNum) * defNum * kNum * dmgReduceNum, avg: dmgBase * dmgNum * enemydmgNum * (1 + cpctNum * cdmgNum) * defNum * kNum * dmgReduceNum } } } if (showDetail) { console.log('Attr', attr) console.log({ mode, dmgBase, atkNum, pctNum, multiNum, plusNum, dmgNum, enemydmgNum, stanceNum, cpctNum, cdmgNum, defNum, eleNum, kNum, dmgReduceNum }) console.log('Ret', ret) } return ret }, getDmgFn (data) { let { showDetail, attr, ds, game } = data let { calc } = ds let dmgFn = function (pctNum = 0, talent = false, ele = false, basicNum = 0, mode = 'talent', dynamicData = false) { if (ele) { ele = erTitle[ele] || ele } if (game === 'sr') { // 星铁meta数据天赋为百分比前数字 pctNum = pctNum * 100 } return DmgCalc.calcRet({ pctNum, talent, ele, basicNum, mode, dynamicData }, data) } dmgFn.basic = function (basicNum = 0, talent = false, ele = false, dynamicData = false) { return dmgFn(0, talent, ele, basicNum, 'basic', dynamicData) } dmgFn.reaction = function (ele = false, talent = 'fy') { switch (ele) { // 击破持续伤害 case 'shock': case 'burn': case 'windShear': case 'bleed': { talent = 'dot' break } // 击破伤害 case 'superBreak': case 'lightningBreak': case 'fireBreak': case 'windBreak': case 'physicalBreak': case 'quantumBreak': case 'imaginaryBreak': case 'iceBreak': { talent = 'break' break } default: break } return dmgFn(0, talent, ele, 0, 'basic') } dmgFn.dynamic = function (pctNum = 0, talent = false, dynamicData = false, ele = false) { return dmgFn(pctNum, talent, ele, 0, 'talent', dynamicData) } // 计算治疗 dmgFn.heal = function (num) { if (showDetail) { console.log(num, calc(attr.heal), attr.heal.inc) } return { avg: num * (1 + calc(attr.heal) / 100 + attr.heal.inc / 100) } } // 计算护盾 dmgFn.shield = function (num) { if (showDetail) { console.log(num, calc(attr.shield), calc(attr.shield.inc)) } return { avg: num * (calc(attr.shield) / 100) * (attr.shield.inc / 100) } } // 扩散方法 dmgFn.swirl = function () { return dmgFn(0, 'fy', 'swirl') } return dmgFn } } export default DmgCalc