miao-plugin/components/Calc.js
yoimiya-kokomi 89769be51a 增加扩散、感电的计算逻辑,感谢 **@49631073**的逻辑梳理
伤害计算增加 万叶ⁿᵉʷ、久岐忍ⁿᵉʷ,温迪增加扩散伤害计算
部分角色评分权重微调
2022-06-21 06:55:08 +08:00

591 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, buffs = [], defParams = {}, defDmgIdx = -1, mainAttr = "atk,cpct,cdmg", 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 = {},
{ 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 lv = talentData[key].level_current * 1 || 1,
lvKey = `Lv${lv}`;
let map = {};
lodash.forEach(char.talent[key].tables, (tr) => {
let val = tr.values[lv - 1];
val = val.replace(/[^\x00-\xff]/g, "").trim();
let valArr = [], 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, 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 = [], detailMap = [], dmgRet = [], 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 }),
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;