diff --git a/apps/admin.js b/apps/admin.js index d184124a..116f0196 100644 --- a/apps/admin.js +++ b/apps/admin.js @@ -131,7 +131,7 @@ export async function updateRes(e) { } }); } else { - command = `git clone https://gitee.com/yoimiya-kokomi/miao-res-plus.git ${resPath}/miao-res-plus/`; + command = `git clone https://gitee.com/yoimiya-kokomi/miao-res-plus.git '${resPath}/miao-res-plus/'`; e.reply("开始尝试安装图片加量包,可能会需要一段时间,请耐心等待~"); exec(command, function (error, stdout, stderr) { if (error) { diff --git a/apps/character.js b/apps/character.js index 43b60958..122d376c 100644 --- a/apps/character.js +++ b/apps/character.js @@ -5,8 +5,9 @@ import { Cfg } from "../components/index.js"; import Profile from "../components/Profile.js"; import Format from "../components/Format.js" import Reliquaries from "../components/models/Reliquaries.js"; +import Calc from "../components/Calc.js"; import fs from "fs"; -import sizeOf from "image-size"; + //角色昵称 @@ -603,7 +604,6 @@ export async function renderProfile(e, char, render) { let reliquaries = [], totalMark = 0, totalMaxMark = 0; - const maxMark = Reliquaries.getMaxMark(char.name); let { titles: usefulTitles, mark: usefulMark } = Reliquaries.getUseful(avatar.name); lodash.forEach(avatar.reliquaries, (ds) => { @@ -620,7 +620,8 @@ export async function renderProfile(e, char, render) { ds.attrs = Profile.formatArti(arti.attrs); } posIdx[pos].data = ds; - }) + }); + lodash.forEach(posIdx, (ds) => { if (ds && ds.data) { reliquaries.push(ds.data); @@ -629,6 +630,16 @@ export async function renderProfile(e, char, render) { } }); + let dmgMsg = [], dmgData = []; + let dmgCalc = await Calc.calcData(profile, char, avatar); + if (dmgCalc && dmgCalc.ret) { + lodash.forEach(dmgCalc.ret, (ds) => { + ds.dmg = Format.comma(ds.dmg, 1); + ds.avg = Format.comma(ds.avg, 1); + dmgData.push(ds); + }) + dmgMsg = dmgCalc.msg; + } let base64 = await render("character", "detail", { save_id: uid, @@ -640,6 +651,8 @@ export async function renderProfile(e, char, render) { cons: char.cons, name: char.name, elem: char.elem, + dmgData, + dmgMsg, reliquaries, totalMark: c(totalMark, 1), totalMaxMark, diff --git a/components/Calc.js b/components/Calc.js new file mode 100644 index 00000000..55717350 --- /dev/null +++ b/components/Calc.js @@ -0,0 +1,344 @@ +import fs from "fs"; +import lodash from "lodash"; +import Format from "./Format.js"; +import { buffs } from "../resources/meta/reliquaries/calc.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 = {}; + if (fs.existsSync(cfgPath)) { + let fileData = await import (`file://${cfgPath}`); + details = fileData.details || false; + buffs = fileData.buffs || []; + defParams = fileData.defParams || {}; + } + + if (details) { + return { details, buffs, defParams } + } + return false; + }, + + // 获取基础属性 + attr(profile, avatar) { + let ret = {}, + { attr } = profile; + + // 基础属性 + 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 + } + }) + + lodash.forEach("dmg,phy".split(","), (key) => { + ret[key] = { + base: attr[key + "Bonus"] * 1 || 0, + plus: 0, + pct: 0 + } + }) + + // a + lodash.forEach("a,a2,a3,e,q".split(","), (key) => { + ret[key] = { + pct: 0, // 倍率加成 + def: 0, // 防御降低 + ignore: 0, // 无视防御 + plus: 0, // 伤害值提高 + dmg: 0, // 伤害提高 + cpct: 0,// 暴击提高 + cdmg: 0 //爆伤提高 + } + }) + + ret.enemy = { + def: 0, // 降低防御 + ignore: 0, // 无视防御 + phy: 0 // 物理防御 + } + + ret.weaponType = avatar.weapon.type_name; + ret.element = avatar.element; + ret.refine = (profile.weapon.refine * 1 - 1) || 0; + + return ret; + + }, + + // 获取天赋数据 + talent(profile, char) { + let ret = {}; + + lodash.forEach(['a', 'e', 'q'], (key) => { + let lv = profile.talent[key] * 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 = []; + lodash.forEach(val.split("/"), (v, idx) => { + let valNum = 0; + lodash.forEach(v.split("+"), (v) => { + valNum += v.replace("%", "").trim() * 1; + }) + 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; + } + }) + ret[key] = map; + }) + return ret; + }, + + calcAttr(originalAttr, buffs, meta, params = {}) { + let attr = lodash.merge({}, originalAttr); + + let msg = []; + + + lodash.forEach(buffs, (buff) => { + let ds = { + ...meta, + attr, + params, + refine: attr.refine + }; + + // 如果存在rule,则进行计算 + if (buff.rule && !buff.rule(ds)) { + return; + } + + let title = buff.title; + 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)$/.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)(Plus|Pct)?$/.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; + } + }); + 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) => { + 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.name + 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; + }, + async calcData(profile, char, avatar) { + let charCalcData = await Calc.getCharCalcRule(char.name); + + //avatar.element; + + if (!charCalcData) { + return false; + } + let talent = Calc.talent(profile, char); + + let meta = { + cons: profile.cons * 1, + ...profile.talent, + talent, + } + + let { buffs, details, defParams } = charCalcData; + + defParams = defParams || {}; + + let originalAttr = Calc.attr(profile, avatar); + + let weaponBuffs = await Calc.weapon(profile.weapon.name); + let reliBuffs = await Calc.reliquaries(avatar.reliquaries); + 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, defParams || {}); + + let ret = []; + + lodash.forEach(details, (detail) => { + + let params = lodash.merge({}, defParams, detail.params || {}); + + let { attr } = Calc.calcAttr(originalAttr, buffs, meta, params); + + let dmg = function (pctNum = 0, talent = false) { + let { atk, dmg, cdmg, cpct } = attr; + // 攻击区 + let atkNum = (atk.base + atk.plus + atk.base * atk.pct / 100); + + // 增伤区 + let dmgNum = (1 + dmg.base + dmg.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; + + pctNum = pctNum / 100; + + if (talent && attr[talent]) { + 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; + } + + // 防御区 + let enemyLv = 86, lv = 90; + let defNum = (lv + 100) / ((lv + 100) + (enemyLv + 100) * (1 - enemyDef) * (1 - enemyIgnore)); + + // 抗性区 + let kNum = 0.9; + + // 计算最终伤害 + return { + dmg: atkNum * pctNum * dmgNum * (1 + cdmgNum) * defNum * kNum, + avg: atkNum * pctNum * dmgNum * (1 + cpctNum * cdmgNum) * defNum * kNum + } + }; + + if (detail.dmg) { + let dmgRet = detail.dmg({ attr, talent }, dmg); + ret.push({ + title: detail.title, + ...dmgRet + }) + } + }) + + return { + ret, + msg + } + } + + +} + +export default Calc; \ No newline at end of file diff --git a/components/Profile.js b/components/Profile.js index d41c9c5a..f9ca5238 100644 --- a/components/Profile.js +++ b/components/Profile.js @@ -69,6 +69,24 @@ const artifactMap = { } +let posIdx = { + "生之花": { + idx: 1 + }, + "死之羽": { + idx: 2 + }, + "时之沙": { + idx: 3 + }, + "空之杯": { + idx: 4 + }, + "理之冠": { + idx: 5 + } +}; + let Data = { getData(uid, data) { let ret = { @@ -238,6 +256,8 @@ let Profile = { e.reply(`请求失败:${data.msg || "未知错误"}`); return false; } + // 请求成功Bot侧对该uid冷却10分钟 + // 请勿将时间改短,10分钟之内若发起请求会命中服务侧的uid缓存,返回之前的数据,并导致服务侧重新计时 await redis.set(`miao:role-all:${uid}`, 'pending', { EX: 600 }); data = data.data; let userData = {}; @@ -262,6 +282,7 @@ let Profile = { fs.writeFileSync(userFile, JSON.stringify(userData), "", " "); return data; }, + get(uid, charId) { const userFile = `${userPath}/${uid}.json`; let userData = {}; @@ -273,6 +294,19 @@ let Profile = { } return false; }, + + getAll(uid) { + const userFile = `${userPath}/${uid}.json`; + let userData = {}; + if (fs.existsSync(userFile)) { + userData = JSON.parse(fs.readFileSync(userFile, "utf8")) || {}; + } + if (userData && userData.chars) { + return userData.chars; + } + return false; + }, + formatArti(ds) { if (lodash.isArray(ds[0])) { let ret = []; @@ -301,6 +335,7 @@ let Profile = { } return [title, val]; }, + getArtiMark(data, ds) { Reliquaries.getMark(data) let total = 0; @@ -313,6 +348,43 @@ let Profile = { total += 20; } return total; + }, + + getArtiDetail(profile, avatar) { + + let reliquaries = [], + totalMark = 0, + totalMaxMark = 0; + + lodash.forEach(avatar.reliquaries, (ds) => { + let pos = ds.pos_name; + let arti = profile.artis[`arti${posIdx[pos].idx}`]; + if (arti) { + let mark = Reliquaries.getMark(avatar.name, arti.attrs); + let maxMark = Reliquaries.getMaxMark(avatar.name, arti.main[0] || ""); + totalMark += mark; + totalMaxMark += maxMark; + ds.mark = Format.comma(mark, 1); + ds.markType = Reliquaries.getMarkScore(mark, maxMark); + ds.main = Profile.formatArti(arti.main); + ds.attrs = Profile.formatArti(arti.attrs); + } + posIdx[pos].data = ds; + }) + lodash.forEach(posIdx, (ds) => { + if (ds && ds.data) { + reliquaries.push(ds.data); + } else { + reliquaries.push({}); + } + }); + + return { + reliquaries, + totalMark, + totalMaxMark, + markScore: Reliquaries.getMarkScore(totalMark * 1.05, totalMaxMark) + } } }; export default Profile; diff --git a/resources/character/detail.css b/resources/character/detail.css index 977770cb..426a0ac4 100644 --- a/resources/character/detail.css +++ b/resources/character/detail.css @@ -10,13 +10,19 @@ body { overflow: hidden; } -.container:after { + +.basic { + position: relative; + margin-bottom: 10px; +} + +.basic:after { content: ""; display: block; position: absolute; left: 8px; top: 115px; - height: 410px; + bottom: 0; right: 8px; box-shadow: 0 0 2px 0 #fff; border-radius: 5px; @@ -63,11 +69,13 @@ body { .detail li { width: 300px; - font-size: 18px; + font-size: 17px; list-style: none; - padding: 5px 100px 5px 35px; + padding: 0 100px 0 35px; position: relative; font-family: YS; + height: 32px; + line-height: 32px; font-weight: 400; text-shadow: 0 0 1px rgba(0, 0, 0, .5); } @@ -213,14 +221,14 @@ body { .char-talents { display: flex; width: 300px; - margin-bottom: 15px; + margin-bottom: 10px; } .char-cons { display: flex; width: 250px; position: absolute; - top: 465px; + bottom: 5px; left: 20px; } @@ -295,13 +303,87 @@ body { } +/*** dmg ***/ + +.dmg-cont { + border-radius: 10px; + background: url("../common/cont/card-bg.png") top left repeat-x; + background-size: auto 100%; + margin: 5px 15px 5px 10px; + position: relative; + box-shadow: 0 0 1px 0 #ccc, 2px 2px 4px 0 rgba(50, 50, 50, .8); + overflow: hidden; + display: table; + width: calc(100% - 25px); + color: #fff; + font-size: 14px; +} + +.dmg-cont .tr { + display: table-row; +} + +.dmg-cont .tr:nth-child(even) { + background: rgba(0, 0, 0, .4); +} + +.dmg-cont .tr:nth-child(odd) { + background: rgba(50, 50, 50, .4); +} + +.dmg-cont .tr > div { + display: table-cell; + box-shadow: 0 0 1px 0 #fff; +} + +.dmg-cont .th { + text-align: center; +} + +.dmg-cont .th > div { + font-family: YS; + color: #d3bc8e; + background: rgba(0, 0, 0, .4); + line-height: 40px; + height: 40px; +} + + +.dmg-cont .title { + font-family: YS; + color: #d3bc8e; + padding-right: 15px; + text-align: right; + background: rgba(0, 0, 0, .4); +} + + +.dmg .value { + text-align: center; + color: #fff; + display: block; + height: 40px; + font-size: 18px; + font-family: Number; + line-height: 40px; + width: 33.333% +} + +.dmg-notice { + font-size: 12px; + text-align: right; + color: #eee; + margin-right: 15px; +} + + +/*** artis***/ .artis { display: flex; width: 600px; flex-wrap: wrap; margin-bottom: 5px; padding: 5px; - margin-top: 40px; } .artis .item { @@ -365,7 +447,7 @@ body { } .mark-ACE, -.mark-ACE²{ +.mark-ACE² { color: #e85656; font-weight: bold; } @@ -396,11 +478,13 @@ body { } .artis ul.detail li { - padding: 3px; + padding: 0 3px; font-size: 14px; position: initial; width: 100%; display: table; + line-height: 26px; + height: 26px; } @@ -410,7 +494,7 @@ body { .artis ul.detail li.arti-main { font-size: 16px; - padding: 8px 3px; + padding: 3px 3px; font-weight: bold; } @@ -479,7 +563,8 @@ body { right: 0; left: 0; text-align: right; - padding: 8px 12px 10px 0; + padding: 13px 12px 13px 0; + z-index: 3; background-image: linear-gradient(to right, rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0.7)); } @@ -566,9 +651,11 @@ body { .char-优菈 .main-pic { margin-left: -175px; } + .char-烟绯 .main-pic { margin-left: -135px; } + .char-香菱 .main-pic { margin-left: -195px; } \ No newline at end of file diff --git a/resources/character/detail.html b/resources/character/detail.html index a6da6932..13c7acce 100644 --- a/resources/character/detail.html +++ b/resources/character/detail.html @@ -48,6 +48,23 @@ {{/each}} + {{if dmgData.length > 0}} +