miao-plugin/components/Calc.js

578 lines
15 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.

import fs from 'fs'
import lodash from 'lodash'
import Format from './Format.js'
import { Character } from './models.js'
import { eleBaseDmg, eleMap, attrMap } from './calc/calc-meta.js'
import { Mastery } from './calc/mastery.js'
let Calc = {
async getCharCalcRule (name) {
const _path = process.cwd()
const cfgPath = `${_path}/plugins/miao-plugin/resources/meta/character/${name}/calc.js`
let details; let buffs = []; let defParams = {}; let defDmgIdx = -1; let mainAttr = 'atk,cpct,cdmg'; let enemyName = '小宝'
if (fs.existsSync(cfgPath)) {
let fileData = await import(`file://${cfgPath}`)
details = fileData.details || false
buffs = fileData.buffs || []
defParams = fileData.defParams || {}
if (fileData.defDmgIdx) {
defDmgIdx = fileData.defDmgIdx
}
if (fileData.mainAttr) {
mainAttr = fileData.mainAttr
}
if (fileData.enemyName) {
enemyName = fileData.enemyName
}
}
if (details) {
return { details, buffs, defParams, defDmgIdx, mainAttr, enemyName }
}
return false
},
// 获取基础属性
attr (profile) {
let ret = {}
let { attr } = profile
ret.dataSource = profile.dataSource || 'miao'
// 基础属性
lodash.forEach('atk,def,hp'.split(','), (key) => {
ret[key] = {
base: attr[`${key}Base`] * 1 || 0,
plus: attr[key] * 1 - attr[`${key}Base`] * 1 || 0,
pct: 0
}
})
lodash.forEach('mastery,recharge'.split(','), (key) => {
ret[key] = {
base: attr[key] * 1 || 0,
plus: 0,
pct: 0
}
})
lodash.forEach({ cRate: 'cpct', cDmg: 'cdmg', hInc: 'heal' }, (val, key) => {
ret[val] = {
base: attr[key] * 1 || 0,
plus: 0,
pct: 0,
inc: 0
}
})
lodash.forEach('dmg,phy'.split(','), (key) => {
ret[key] = {
base: attr[key + 'Bonus'] * 1 || 0,
plus: 0,
pct: 0
}
})
// 技能属性记录
lodash.forEach('a,a2,a3,e,q'.split(','), (key) => {
ret[key] = {
pct: 0, // 倍率加成
multi: 0, // 独立倍率乘区加成
plus: 0, // 伤害值提高
dmg: 0, // 伤害提高
cpct: 0, // 暴击提高
cdmg: 0, // 爆伤提高
def: 0, // 防御降低
ignore: 0 // 无视防御
}
})
ret.enemy = {
def: 0, // 降低防御
ignore: 0, // 无视防御
phy: 0 // 物理防御
}
ret.shield = {
base: 100, // 基础
plus: 0, // 护盾强效
inc: 100 // 吸收倍率
}
let char = Character.get(profile)
ret.weapon = profile.weapon
ret.weaponType = char.weaponType
ret.element = eleMap[char.elem.toLowerCase()]
ret.refine = (profile.weapon.affix * 1 - 1) || 0
ret.multi = 0
ret.zf = 0
ret.rh = 0
ret.gd = 0
ret.ks = 0
ret.kx = 0
ret.fykx = 0
return ret
},
// 获取天赋数据
talent (profile, char) {
let ret = {}
let talentData = profile.talent || {}
lodash.forEach(['a', 'e', 'q'], (key) => {
let td = talentData[key] || {}
let lv = td.level_current * 1 || 1
let map = {}
lodash.forEach(char.talent[key].tables, (tr) => {
let val = tr.values[lv - 1]
val = val.replace(/[^\x00-\xff]/g, '').trim()
let valArr = []; let valArr2 = []
lodash.forEach(val.split('/'), (v, idx) => {
let valNum = 0
lodash.forEach(v.split('+'), (v) => {
v = v.split('*')
let v1 = v[0].replace('%', '').trim()
valNum += v1 * (v[1] || 1)
valArr2.push(v1)
})
valArr.push(valNum)
})
if (isNaN(valArr[0])) {
map[tr.name] = false
} else if (valArr.length === 1) {
map[tr.name] = valArr[0]
} else {
map[tr.name] = valArr
}
map[tr.name + '2'] = valArr2
})
ret[key] = map
})
return ret
},
getDs (attr, meta, params) {
return {
...meta,
attr,
params,
refine: attr.refine,
weaponType: attr.weaponType,
weapon: attr.weapon,
element: eleMap[attr.element] || attr.element,
calc (ds) {
return (ds.base || 0) + (ds.plus || 0) + ((ds.base || 0) * (ds.pct || 0) / 100)
}
}
},
calcAttr ({ originalAttr, buffs, meta, params = {}, incAttr = '', reduceAttr = '', talent = '' }) {
let attr = lodash.merge({}, originalAttr)
let msg = []
if (incAttr && attrMap[incAttr]) {
let aCfg = attrMap[incAttr]
attr[incAttr][aCfg.type] += aCfg.val
}
if (reduceAttr && attrMap[reduceAttr]) {
let aCfg = attrMap[reduceAttr]
attr[reduceAttr][aCfg.type] -= aCfg.val
}
lodash.forEach(buffs, (buff) => {
let ds = Calc.getDs(attr, meta, params)
ds.currentTalent = talent
// 如果存在rule则进行计算
if (buff.check && !buff.check(ds)) {
return
}
if (buff.cons) {
if (ds.cons * 1 < buff.cons * 1) {
return
}
}
let title = buff.title
if (buff.mastery) {
let mastery = Math.max(0, attr.mastery.base + attr.mastery.plus)
// let masteryNum = 2.78 * mastery / (mastery + 1400) * 100;
buff.data = buff.data || {}
lodash.forEach(buff.mastery.split(','), (key) => {
buff.data[key] = Mastery.getMultiple(key, mastery)
// buff.data[key] = masteryNum;
})
}
lodash.forEach(buff.data, (val, key) => {
if (lodash.isFunction(val)) {
val = val(ds)
}
title = title.replace(`[${key}]`, Format.comma(val, 1))
// 技能提高
let tRet = /^(a|a2|a3|e|q)(Def|Ignore|Dmg|Plus|Pct|Cpct|Cdmg|Multi)$/.exec(key)
if (tRet) {
attr[tRet[1]][tRet[2].toLowerCase()] += val * 1 || 0
return
}
let aRet = /^(hp|def|atk|mastery|cpct|cdmg|heal|recharge|dmg|phy|shield)(Plus|Pct|Inc)?$/.exec(key)
if (aRet) {
attr[aRet[1]][aRet[2] ? aRet[2].toLowerCase() : 'plus'] += val * 1 || 0
return
}
if (key === 'enemyDef') {
attr.enemy.def += val * 1 || 0
return
}
if (['zf', 'rh', 'kx', 'gd', 'ks', 'fykx'].includes(key)) {
attr[key] += val * 1 || 0
}
})
msg.push(title)
})
return {
attr, msg
}
},
async weapon (weaponName) {
const _path = process.cwd()
const cfgPath = `${_path}/plugins/miao-plugin/resources/meta/weapons/calc.js`
let weapons = {}
if (fs.existsSync(cfgPath)) {
let fileData = await import(`file://${cfgPath}`)
weapons = fileData.weapons || {}
}
let weaponCfg = weapons[weaponName] || []
if (lodash.isPlainObject(weaponCfg)) {
weaponCfg = [weaponCfg]
}
lodash.forEach(weaponCfg, (ds) => {
if (!//.test(ds.title)) {
ds.title = `${weaponName}${ds.title}`
}
if (ds.refine) {
ds.data = ds.data || {}
lodash.forEach(ds.refine, (r, key) => {
ds.data[key] = ({ refine }) => r[refine] * (ds.buffCount || 1)
})
}
})
return weaponCfg
},
async reliquaries (sets) {
const _path = process.cwd()
const cfgPath = `${_path}/plugins/miao-plugin/resources/meta/reliquaries/calc.js`
let buffs = {}
if (fs.existsSync(cfgPath)) {
let fileData = await import(`file://${cfgPath}`)
buffs = fileData.buffs || {}
}
let setMap = {}
lodash.forEach(sets, (set) => {
if (set && set.set) {
let name = set.set
setMap[name] = (setMap[name] || 0) + 1
}
})
let retBuffs = []
lodash.forEach(setMap, (count, setName) => {
if (count >= 2 && buffs[setName + 2]) {
retBuffs.push(buffs[setName + 2])
}
if (count >= 4 && buffs[setName + 4]) {
retBuffs.push(buffs[setName + 4])
}
})
return retBuffs
},
getDmgFn ({ ds, attr, profile, enemyLv, showDetail = false }) {
let { calc } = ds
let dmgFn = function (pctNum = 0, talent = false, ele = false, basicNum = 0, mode = 'talent') {
let { atk, dmg, phy, cdmg, cpct } = attr
// 攻击区
let atkNum = calc(atk)
// 倍率独立乘区
let multiNum = attr.multi / 100
// 增伤区
let dmgNum = (1 + dmg.base / 100 + dmg.plus / 100)
if (ele === 'phy') {
dmgNum = (1 + phy.base / 100 + phy.plus / 100)
}
// console.log({ base: Format.comma(dmg.base, 2), plus: Format.comma(dmg.plus, 2) })
let cpctNum = cpct.base / 100 + cpct.plus / 100
// 爆伤区
let cdmgNum = cdmg.base / 100 + cdmg.plus / 100
let enemyDef = attr.enemy.def / 100
let enemyIgnore = attr.enemy.ignore / 100
let plusNum = 0
if (talent && attr[talent]) {
pctNum = pctNum / 100
let ds = attr[talent]
pctNum += ds.pct / 100
dmgNum += ds.dmg / 100
cpctNum += ds.cpct / 100
cdmgNum += ds.cdmg / 100
enemyDef += ds.def / 100
enemyIgnore += ds.ignore / 100
multiNum += ds.multi / 100
plusNum += ds.plus
}
// 防御区
let lv = profile.lv
let defNum = (lv + 100) / ((lv + 100) + (enemyLv + 100) * (1 - enemyDef) * (1 - enemyIgnore))
// 抗性区
let kx = attr.kx
if (talent === 'fy') {
kx = attr.fykx
}
kx = 10 - (kx || 0)
let kNum = 0.9
if (kx >= 0) {
kNum = (100 - kx) / 100
} else {
kNum = 1 - kx / 200
}
// 反应区
let eleNum = 1; let eleBase = 0
if (ele === 'ks' || ele === 'gd') {
eleBase = eleBaseDmg[lv] || 0
}
if (ele === 'phy') {
// do nothing
} else if (ele) {
eleNum = Mastery.getBasePct(ele, attr.element)
if (attr[ele]) {
eleNum = eleNum * (1 + attr[ele] / 100)
}
}
cpctNum = Math.max(0, Math.min(1, cpctNum))
if (cpctNum === 0) {
cdmgNum = 0
}
let ret = {}
if (mode === 'basic') {
ret = {
dmg: basicNum * dmgNum * (1 + cdmgNum) * defNum * kNum * eleNum,
avg: basicNum * dmgNum * (1 + cpctNum * cdmgNum) * defNum * kNum * eleNum
}
} else if (eleBase) {
ret = {
avg: eleBase * kNum * eleNum
}
} else {
// 计算最终伤害
ret = {
dmg: (atkNum * pctNum * (1 + multiNum) + plusNum) * dmgNum * (1 + cdmgNum) * defNum * kNum * eleNum,
avg: (atkNum * pctNum * (1 + multiNum) + plusNum) * dmgNum * (1 + cpctNum * cdmgNum) * defNum * kNum * eleNum
}
}
if (showDetail) {
console.log(attr, { atkNum, pctNum, multiNum, plusNum, dmgNum, cpctNum, cdmgNum, defNum, eleNum, kNum }, ret)
}
return ret
}
dmgFn.basic = function (basicNum = 0, talent = false, ele = false) {
return dmgFn(0, talent, ele, basicNum, 'basic')
}
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) {
return {
avg: num * (calc(attr.shield) / 100) * (attr.shield.inc / 100)
}
}
dmgFn.ks = function () {
return dmgFn(0, 'fy', 'ks')
}
return dmgFn
},
async calcData ({ profile, char, enemyLv = 91, mode = 'profile', dmgIdx = 0 }) {
let charCalcData = await Calc.getCharCalcRule(char.name)
if (!charCalcData) {
return false
}
let talent = Calc.talent(profile, char)
let meta = {
cons: profile.cons * 1,
talent
}
let { buffs, details, defParams, mainAttr, defDmgIdx, enemyName } = charCalcData
defParams = defParams || {}
let originalAttr = Calc.attr(profile)
let weaponBuffs = await Calc.weapon(profile.weapon.name)
let reliBuffs = await Calc.reliquaries(profile.artis)
buffs = lodash.concat(buffs, weaponBuffs, reliBuffs)
lodash.forEach(buffs, (buff) => {
buff.sort = lodash.isUndefined(buff.sort) ? 1 : buff.sort
})
buffs = lodash.sortBy(buffs, ['sort'])
let { msg } = Calc.calcAttr({ originalAttr, buffs, meta, params: defParams || {} })
let ret = []; let detailMap = []; let dmgRet = []; let dmgDetail = {}
lodash.forEach(details, (detail, detailSysIdx) => {
if (lodash.isFunction(detail)) {
let { attr } = Calc.calcAttr({ originalAttr, buffs, meta })
let ds = lodash.merge({ talent }, Calc.getDs(attr, meta))
detail = detail({ ...ds, attr, profile })
}
let params = lodash.merge({}, defParams, detail.params || {})
let { attr } = Calc.calcAttr({ originalAttr, buffs, meta, params, talent: detail.talent || '' })
if (detail.check && !detail.check(Calc.getDs(attr, meta, params))) {
return
}
if (detail.cons && meta.cons * 1 < detail.cons * 1) {
return
}
let ds = lodash.merge({ talent }, Calc.getDs(attr, meta, params))
let dmg = Calc.getDmgFn({ ds, attr, profile, enemyLv, showDetail: detail.showDetail })
let basicDmgRet
if (detail.dmg) {
basicDmgRet = detail.dmg(ds, dmg)
detail.userIdx = detailMap.length
detailMap.push(detail)
ret.push({
title: detail.title,
...basicDmgRet
})
}
})
if (mode === 'dmg') {
let detail
if (dmgIdx && detailMap[dmgIdx - 1]) {
detail = detailMap[dmgIdx - 1]
} else if (!lodash.isUndefined(defDmgIdx) && details[defDmgIdx]) {
detail = details[defDmgIdx]
} else {
detail = detailMap[0]
}
dmgDetail = {
title: detail.title,
userIdx: detail.userIdx,
basicRet: lodash.merge({}, ret[detail.userIdx]),
attr: []
}
mainAttr = mainAttr.split(',')
let params = lodash.merge({}, defParams, detail.params || {})
let basicDmg = dmgDetail.basicRet
lodash.forEach(mainAttr, (reduceAttr) => {
dmgDetail.attr.push(attrMap[reduceAttr])
let rowData = []
lodash.forEach(mainAttr, (incAttr) => {
if (incAttr === reduceAttr) {
rowData.push({ type: 'na' })
return
}
let { attr } = Calc.calcAttr({
originalAttr,
buffs,
meta,
params,
incAttr,
reduceAttr,
talent: detail.talent || ''
})
let ds = lodash.merge({ talent }, Calc.getDs(attr, meta, params))
let dmg = Calc.getDmgFn({ ds, attr, profile, enemyLv })
if (detail.dmg) {
let dmgCalcRet = detail.dmg(ds, dmg)
rowData.push({
type: dmgCalcRet.avg === basicDmg.avg ? 'avg' : (dmgCalcRet.avg > basicDmg.avg ? 'gt' : 'lt'),
...dmgCalcRet
})
}
})
dmgRet.push(rowData)
})
}
return {
ret,
msg,
dmgRet,
enemyName,
dmgCfg: dmgDetail
}
}
}
export default Calc