From c32579ab631273388238ccd435ab20249259e79c Mon Sep 17 00:00:00 2001 From: yoimiya-kokomi <592981798@qq.com> Date: Thu, 5 May 2022 04:53:20 +0800 Subject: [PATCH] =?UTF-8?q?*=20=E5=A2=9E=E5=8A=A0=20`#=E6=B7=B1=E6=B8=8A?= =?UTF-8?q?=E9=85=8D=E9=98=9F`=20=E5=8A=9F=E8=83=BD=20=20=20*=20=E6=A0=B9?= =?UTF-8?q?=E6=8D=AE=E5=BD=93=E5=89=8D=E8=B4=A6=E5=8F=B7=E7=9A=84=E8=A7=92?= =?UTF-8?q?=E8=89=B2=E7=BB=83=E5=BA=A6=E5=8F=8A=E6=9C=AC=E6=9C=9F=E6=B7=B1?= =?UTF-8?q?=E6=B8=8A=E5=87=BA=E5=9C=BA=E6=95=B0=E6=8D=AE=EF=BC=8C=E6=8E=A8?= =?UTF-8?q?=E8=8D=90=E8=BE=83=E5=8C=B9=E9=85=8D=E7=9A=84=E9=85=8D=E9=98=9F?= =?UTF-8?q?=E6=96=B9=E6=A1=88=20=20=20*=20=E6=B7=B1=E6=B8=8A=E5=87=BA?= =?UTF-8?q?=E5=9C=BA=E6=95=B0=E6=8D=AE=E6=9D=A5=E8=87=AA=E8=83=A1=E6=A1=83?= =?UTF-8?q?API=EF=BC=8C=E4=B8=BASnapGenshin=E7=94=A8=E6=88=B7=E8=87=AA?= =?UTF-8?q?=E4=B8=BB=E4=B8=8A=E4=BC=A0=E7=9A=84=E6=B7=B1=E6=B8=8A=E6=8C=91?= =?UTF-8?q?=E6=88=98=E8=AE=B0=E5=BD=95=EF=BC=8C=E6=84=9F=E8=B0=A2SG?= =?UTF-8?q?=E5=9B=A2=E9=98=9F=20=20=20*=20=E9=85=8D=E9=98=9F=E6=96=B9?= =?UTF-8?q?=E6=A1=88=E4=BB=85=E4=BE=9B=E5=8F=82=E8=80=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 6 +- apps/stat.js | 238 +++++++++++++++++- components/models/Character.js | 24 ++ components/models/HutaoApi.js | 16 +- index.js | 10 +- resources/common/common.css | 120 +++++++++ resources/meta/character/神里绫人/calc.js | 2 +- resources/stat/abyss-team.css | 115 +++++++++ resources/stat/abyss-team.html | 74 ++++++ 9 files changed, 590 insertions(+), 15 deletions(-) create mode 100644 resources/stat/abyss-team.css create mode 100644 resources/stat/abyss-team.html diff --git a/CHANGELOG.md b/CHANGELOG.md index 128139a1..023f01b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ -# 1.3.2 +# 1.4.0 +* 增加 `#深渊配队` 功能 + * 根据当前账号的角色练度及本期深渊出场数据,推荐较匹配的配队方案 + * 深渊出场数据来自胡桃API,为SnapGenshin用户自主上传的深渊挑战记录,感谢SG团队 + * 配队方案仅供参考 * `#角色面板` 伤害计算新增部分角色 * 目前支持:雷神、胡桃、魈、神子、甘雨、宵宫、公子、绫人、绫华、心海、钟离 * `#角色面板` 一些功能升级与调整 diff --git a/apps/stat.js b/apps/stat.js index 12787b5d..9da3f98d 100644 --- a/apps/stat.js +++ b/apps/stat.js @@ -6,6 +6,7 @@ import { HutaoApi, Character } from "../components/models.js"; import { Cfg } from "../components/index.js"; import lodash from "lodash"; import { segment } from "oicq"; +import fs from "fs"; export async function consStat(e, { render }) { if (Cfg.isDisable(e, "wiki.abyss")) { @@ -116,9 +117,6 @@ export async function abyssPct(e, { render }) { } }); - console.log('floor', chooseFloor); - - lodash.forEach(abyssData.data, (floorData) => { let floor = { floor: floorData.floor, @@ -130,7 +128,7 @@ export async function abyssPct(e, { render }) { if (char) { avatars.push({ name: char.name, - star: char.star, + star: char.rarity, value: ds.value * 8 }) } @@ -163,4 +161,236 @@ export async function abyssPct(e, { render }) { } return true; +} + +async function getTalentData(e, isUpdate = false) { + + // 技能查询缓存 + let cachePath = `./data/cache/`; + if (!fs.existsSync(cachePath)) { + fs.mkdirSync(cachePath); + } + cachePath += "talentList/"; + if (!fs.existsSync(cachePath)) { + fs.mkdirSync(cachePath); + } + + let avatarRet = []; + let uid = e.selfUser.uid; + + let hasCache = await redis.get(`cache:uid-talent-new:${uid}`); + if (hasCache) { + // 有缓存优先使用缓存 + let jsonRet = fs.readFileSync(cachePath + `${uid}.json`, "utf8"); + avatarRet = JSON.parse(jsonRet); + return avatarRet; + } else if (!isUpdate) { + e.noReplyTalentList = true; + await YunzaiApps.mysInfo.talentList(e); + return await getTalentData(e, true); + } + return false; +} + +export async function abyssTeam(e, { render }) { + + + let MysApi = await e.getMysApi({ + auth: "cookie", // 所有用户均可查询 + targetType: "self", // 被查询用户可以是任意用户 + cookieType: "self" // cookie可以是任意可用cookie + }); + + if (!MysApi || !MysApi.selfUser || !MysApi.selfUser.uid) { + return true; + } + + let abyssData = await HutaoApi.getAbyssTeam(); + if (!abyssData || !abyssData.data) { + e.reply("暂时无法查询"); + return true; + } + abyssData = abyssData.data; + let talentData = await getTalentData(e); + if (!talentData || talentData.length === 0) { + e.reply("暂时无法获得角色信息"); + return true; + } + + let avatarRet = {}; + let data = {}; + + let noAvatar = {}; + + lodash.forEach(talentData, (avatar) => { + avatarRet[avatar.id] = Math.min(avatar.level, avatar.weapon_level) * 100 + Math.max(avatar.a_original, avatar.e_original, avatar.q_original) * 1000 + }); + + + let getTeamCfg = (str) => { + let teams = str.split(","); + teams.sort(); + let teamMark = 0; + lodash.forEach(teams, (a) => { + if (!avatarRet[a]) { + teamMark = -1; + noAvatar[a] = true; + } + if (teamMark !== -1) { + teamMark += avatarRet[a] * 1; + } + }) + if (teamMark === -1) { + teamMark = 1; + } + + return { + key: teams.join(","), + mark: teamMark + }; + } + + let hasSame = function (team1, team2) { + for (let idx = 0; idx < team1.length; idx++) { + if (team2.includes(team1[idx])) { + return true; + } + } + return false; + } + + lodash.forEach(abyssData, (ds) => { + let floor = ds.level.floor; + if (!data[floor]) { + data[floor] = { + up: {}, + down: {}, + teams: [] + }; + } + lodash.forEach(ds.teams, (ds) => { + lodash.forEach(['up', 'down'], (halfKey) => { + let teamCfg = getTeamCfg(ds.id[`${halfKey}Half`]); + if (teamCfg) { + if (!data[floor][halfKey][teamCfg.key]) { + data[floor][halfKey][teamCfg.key] = { + count: 0, + mark: 0, + hasTeam: teamCfg.mark > 1 + }; + } + data[floor][halfKey][teamCfg.key].count += ds.value; + data[floor][halfKey][teamCfg.key].mark += ds.value * teamCfg.mark; + } + + }) + }); + + let temp = []; + lodash.forEach(['up', 'down'], (halfKey) => { + lodash.forEach(data[floor][halfKey], (ds, team) => { + temp.push({ + team, + teamArr: team.split(","), + half: halfKey, + count: ds.count, + mark: ds.mark, + mark2: 1, + hasTeam: ds.hasTeam + }) + }) + temp = lodash.sortBy(temp, "mark") + data[floor].teams = temp.reverse(); + }); + }); + + + let ret = {}; + + lodash.forEach(data, (floorData, floor) => { + ret[floor] = {} + let ds = ret[floor]; + lodash.forEach(floorData.teams, (t1) => { + if (t1.mark2 <= 0) { + return; + } + lodash.forEach(floorData.teams, (t2) => { + if (t1.mark2 <= 0) { + return false; + } + if (t1.half === t2.half || t2.mark2 <= 0) { + return; + } + + let teamKey = t1.half === "up" ? (t1.team + "+" + t2.team) : (t2.team + "+" + t1.team); + if (ds[teamKey]) { + return; + } + if (hasSame(t1.teamArr, t2.teamArr)) { + return; + } + + ds[teamKey] = { + up: t1.half === "up" ? t1 : t2, + down: t1.half === "up" ? t2 : t1, + count: Math.min(t1.count, t2.count), + mark: t1.hasTeam && t2.hasTeam ? t1.mark + t2.mark : t1.count + t2.count // 如果不存在组队则进行评分惩罚 + } + t1.mark2--; + t2.mark2--; + return false; + }); + if (lodash.keys(ds).length >= 20) { + return false; + } + }) + }); + + lodash.forEach(ret, (ds, floor) => { + ds = lodash.sortBy(lodash.values(ds), 'mark'); + ds = ds.reverse(); + ds = ds.slice(0, 4); + + lodash.forEach(ds, (team) => { + team.up.teamArr = Character.sortIds(team.up.teamArr); + team.down.teamArr = Character.sortIds(team.down.teamArr); + }) + + ret[floor] = ds; + }) + + let avatarMap = {}; + + lodash.forEach(talentData, (ds) => { + avatarMap[ds.id] = { + id: ds.id, + name: ds.name, + star: ds.rarity, + level: ds.level, + cons: ds.cons + } + }) + + lodash.forEach(noAvatar, (d, id) => { + let char = Character.get(id); + avatarMap[id] = { + id, + name: char.name, + star: char.star, + level: 0, + cons: 0, + } + }) + + + let base64 = await render("stat", "abyss-team", { + teams: ret, + avatars: avatarMap, + cfgScale: Cfg.scale(1.5) + }); + if (base64) { + e.reply(segment.image(`base64://${base64}`)); + } + + return true; } \ No newline at end of file diff --git a/components/models/Character.js b/components/models/Character.js index a815ed94..cdae3759 100644 --- a/components/models/Character.js +++ b/components/models/Character.js @@ -142,4 +142,28 @@ Character.getRandomImg = function (type) { return lodash.sample(ret); } + +let charPosIdx = { + 1: '宵宫,雷神,胡桃,甘雨,优菈,一斗,绫人,魈,可莉,迪卢克,凝光,刻晴,辛焱,烟绯,雷泽', + 2: '夜兰,八重,九条,行秋,香菱,安柏,凯亚,丽莎,北斗,菲谢尔,重云,罗莎莉亚,埃洛伊', + 3: '申鹤,莫娜,早柚,云堇,久岐忍,五郎,砂糖,万叶,温迪', + 4: '班尼特,心海,琴,芭芭拉,七七,迪奥娜,托马,空,荧,阿贝多,钟离' +} + +let idSort = {}; +lodash.forEach(charPosIdx, (chars, pos) => { + chars = chars.split(","); + lodash.forEach(chars, (name, idx) => { + let id = YunzaiApps.mysInfo['roleIdToName'](name); + if (id) { + idSort[id] = pos * 100 + idx; + } + }) +}) + + +Character.sortIds = function (arr) { + return arr.sort((a, b) => (idSort[a] || 300) - (idSort[b] || 300)); +} + export default Character; diff --git a/components/models/HutaoApi.js b/components/models/HutaoApi.js index f8d371ca..c4764425 100644 --- a/components/models/HutaoApi.js +++ b/components/models/HutaoApi.js @@ -2,10 +2,8 @@ * 胡桃API Miao-Plugin 封装 * https://github.com/DGP-Studio/DGP.Genshin.HutaoAPI * -* * */ -import Base from "./Base.js"; import fetch from "node-fetch"; const host = "http://49.232.91.210:88/miaoPlugin/hutaoApi"; @@ -27,11 +25,12 @@ let HutaoApi = { method: "GET", }); let retData = await response.json(); - let d = new Date(); - retData.lastUpdate = `${d.toLocaleDateString()} ${d.toTimeString().substr(0, 5)}`; - await redis.set(`hutao:${url}`, JSON.stringify(retData), { EX: 3600 }); + if (retData && retData.data) { + let d = new Date(); + retData.lastUpdate = `${d.toLocaleDateString()} ${d.toTimeString().substr(0, 5)}`; + await redis.set(`hutao:${url}`, JSON.stringify(retData), { EX: 3600 }); + } return retData; - }, // 角色持有及命座分布 @@ -41,8 +40,11 @@ let HutaoApi = { async getAbyssPct() { return await HutaoApi.req("/Statistics/AvatarParticipation"); - } + }, + async getAbyssTeam() { + return await HutaoApi.req("/Statistics/TeamCombination"); + } }; diff --git a/index.js b/index.js index 495325eb..7aae861c 100644 --- a/index.js +++ b/index.js @@ -8,7 +8,7 @@ import { getProfileAll, profileHelp } from "./apps/character.js"; -import { consStat, abyssPct } from "./apps/stat.js"; +import { consStat, abyssPct, abyssTeam } from "./apps/stat.js"; import { wiki } from "./apps/wiki.js"; import { help, versionInfo } from "./apps/help.js"; import lodash from "lodash"; @@ -21,6 +21,7 @@ export { wife, consStat, abyssPct, + abyssTeam, wiki, updateRes, updateMiaoPlugin, @@ -37,7 +38,8 @@ export { let rule = { character: { - reg: "^#(喵喵)?(更新)?(.*)(详情|详细|面板|面版|伤害[1-7]?)?(更新)?$", + //reg: "^#(喵喵)?(更新)?(.*)(详情|详细|面板|面版|伤害[1-7]?)?(更新)?$", + reg: "noCheck", describe: "【#角色】角色详情", }, getArtis: { @@ -64,6 +66,10 @@ let rule = { reg: "^#(喵喵)?深渊(第?.{1,2}层)?(角色)?出场(率|统计)*$", describe: "【#统计】 #深渊出场率 #深渊12层出场率", }, + abyssTeam: { + reg: "#深渊(组队|配队)", + describe: "【#角色】 #深渊组队", + }, wiki: { reg: "^(#|喵喵)?.*(天赋|技能|命座|命之座|资料|照片|写真|图片|插画)$", describe: "【#资料】 #神里天赋 #夜兰命座", diff --git a/resources/common/common.css b/resources/common/common.css index ab33e645..af1f44bc 100644 --- a/resources/common/common.css +++ b/resources/common/common.css @@ -198,4 +198,124 @@ body { .elem-pyro .elem-bg { background-image: url(../common/bg/bg-pyro.jpg); +} + + +/* cont */ + +.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; + color: #fff; + font-size: 16px; +} + + +.cont-title { + background: rgba(0, 0, 0, .4); + box-shadow: 0 0 1px 0 #fff; + color: #d3bc8e; + font-family: YS; + padding: 10px 20px; + text-align: left; + border-radius: 10px 10px 0 0; +} + +.cont-title span { + font-size: 12px; + color: #aaa; + margin-left: 10px; + font-family: Number, YS; + font-weight: normal; +} + +.cont-body { + padding: 10px 15px; + font-size: 12px; + background: rgba(0, 0, 0, 0.5); + box-shadow: 0 0 1px 0 #fff; + font-family: YS; + font-weight: normal; +} + +ul.cont-msg { + padding-left: 15px; +} + +ul.cont-msg li { + margin: 5px 0; +} + +ul.cont-msg li strong { + font-weight: normal; + margin: 0 2px; + color: #d3bc8e; +} + +.cont-footer { + padding: 10px 15px; + font-size: 12px; + background: rgba(0, 0, 0, 0.5); + font-family: YS; + font-weight: normal; +} + +.cont-table { + display: table; + width: 100%; +} + +.cont-table .tr { + display: table-row; +} + +.cont-table .tr:nth-child(even) { + background: rgba(0, 0, 0, .4); +} + +.cont-table .tr:nth-child(odd) { + background: rgba(50, 50, 50, .4); +} + +.cont-table .tr > div { + display: table-cell; + box-shadow: 0 0 1px 0 #fff; +} + +.cont-table .tr > div.value-full { + display: table; + width: 200%; +} + +.cont-table .tr > div.value-none { + box-shadow: none; +} + +.cont-table .thead { + text-align: center; +} + +.cont-table .thead > div { + font-family: YS; + color: #d3bc8e; + background: rgba(0, 0, 0, .4); + line-height: 40px; + height: 40px; +} + + +.cont-table .title, +.cont-table .th { + font-family: YS; + color: #d3bc8e; + padding-right: 15px; + text-align: right; + background: rgba(0, 0, 0, .4); + min-width: 100px; + vertical-align: middle; } \ No newline at end of file diff --git a/resources/meta/character/神里绫人/calc.js b/resources/meta/character/神里绫人/calc.js index 548063c5..5debf9a5 100644 --- a/resources/meta/character/神里绫人/calc.js +++ b/resources/meta/character/神里绫人/calc.js @@ -23,7 +23,7 @@ export const buffs = [{ check: ({ cons }) => cons < 2, title: "4层浪闪:提高瞬水剑伤害[aPlus]", data: { - aPlus: ({ attr, calc, talent }) => calc(attr.hp) * talent.e['浪闪伤害值提高'] / 100 * 4 + aPlus: ({ attr, calc, talent }) => calc(attr.hp) * talent.e['浪闪伤害值提高'][0] / 100 * 4 } }, { cons: 2, diff --git a/resources/stat/abyss-team.css b/resources/stat/abyss-team.css new file mode 100644 index 00000000..5d22317e --- /dev/null +++ b/resources/stat/abyss-team.css @@ -0,0 +1,115 @@ +.head-box { + margin-top: 0; + margin-bottom: 15px; +} + +.cont { + font-family: YS; + margin-top: 10px; +} + +.cont-table .title { + min-width: 30px; + padding-right: 5px; + padding-left: 5px; + text-align: center; +} + +.cont-table .title .team-count { + display: block; + font-size: 12px; + color: #555; +} + +.card-list { + display: flex; + flex-wrap: wrap; + justify-content: center; + padding: 5px 0; +} + +.card-list .card { + margin: 3px; + border-radius: 5px; + box-shadow: 0 2px 6px 0 rgb(132 93 90 / 30%); + position: relative; + overflow: hidden; + background: #e7e5d9; + font-size: 12px; + font-family: Number; + color: #000; + text-align: center; +} + +.card-list .has-character .for-no { + display: none; +} + +.card-list .no-character .for-has { + display: none; +} + +.card-list .no-character { + opacity: .5; +} + +.card-list .no-label { + font-size: 12px; + font-family: YS; + color: #555; +} + +.card-list .card img { + width: 50px; + height: 50px; + border-radius: 5px 5px 10px 0; + background-size: 100%; + background-repeat: no-repeat; + display: block; +} + +.card-list .card.star5 img { + background-image: url(../common/item/bg5.png); + + /*filter: brightness(1.1);*/ + +} + +.card-list .card.star4 img { + background-image: url(../common/item/bg4.png); +} + +.card-list .card .cons { + position: absolute; + top: 0; + right: 0; + padding: 3px; +} + +.card-list .card .num { + position: absolute; + top: 0px; + right: 0px; + z-index: 9; + font-size: 18px; + text-align: center; + color: #fff; + border-radius: 3px; + padding: 1px 5px; + background: rgb(0 0 0 / 50%); + font-family: Number; +} + +.card-list .card .name, +.card-list .card .num_name { + position: absolute; + top: 70px; + left: 0px; + z-index: 9; + font-size: 12px; + text-align: center; + width: 100%; + height: 16px; + line-height: 18px; +} + diff --git a/resources/stat/abyss-team.html b/resources/stat/abyss-team.html new file mode 100644 index 00000000..312c2b94 --- /dev/null +++ b/resources/stat/abyss-team.html @@ -0,0 +1,74 @@ + + +
+ + + + + + + + +