* 增加 #深渊配队 功能

* 根据当前账号的角色练度及本期深渊出场数据,推荐较匹配的配队方案
  * 深渊出场数据来自胡桃API,为SnapGenshin用户自主上传的深渊挑战记录,感谢SG团队
  * 配队方案仅供参考
This commit is contained in:
yoimiya-kokomi 2022-05-05 04:53:20 +08:00
parent 59329e1b74
commit c32579ab63
9 changed files with 590 additions and 15 deletions

View File

@ -1,5 +1,9 @@
# 1.3.2 # 1.4.0
* 增加 `#深渊配队` 功能
* 根据当前账号的角色练度及本期深渊出场数据,推荐较匹配的配队方案
* 深渊出场数据来自胡桃API为SnapGenshin用户自主上传的深渊挑战记录感谢SG团队
* 配队方案仅供参考
* `#角色面板` 伤害计算新增部分角色 * `#角色面板` 伤害计算新增部分角色
* 目前支持:雷神、胡桃、魈、神子、甘雨、宵宫、公子、绫人、绫华、心海、钟离 * 目前支持:雷神、胡桃、魈、神子、甘雨、宵宫、公子、绫人、绫华、心海、钟离
* `#角色面板` 一些功能升级与调整 * `#角色面板` 一些功能升级与调整

View File

@ -6,6 +6,7 @@ import { HutaoApi, Character } from "../components/models.js";
import { Cfg } from "../components/index.js"; import { Cfg } from "../components/index.js";
import lodash from "lodash"; import lodash from "lodash";
import { segment } from "oicq"; import { segment } from "oicq";
import fs from "fs";
export async function consStat(e, { render }) { export async function consStat(e, { render }) {
if (Cfg.isDisable(e, "wiki.abyss")) { 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) => { lodash.forEach(abyssData.data, (floorData) => {
let floor = { let floor = {
floor: floorData.floor, floor: floorData.floor,
@ -130,7 +128,7 @@ export async function abyssPct(e, { render }) {
if (char) { if (char) {
avatars.push({ avatars.push({
name: char.name, name: char.name,
star: char.star, star: char.rarity,
value: ds.value * 8 value: ds.value * 8
}) })
} }
@ -164,3 +162,235 @@ export async function abyssPct(e, { render }) {
return true; 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;
}

View File

@ -142,4 +142,28 @@ Character.getRandomImg = function (type) {
return lodash.sample(ret); 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; export default Character;

View File

@ -2,10 +2,8 @@
* 胡桃API Miao-Plugin 封装 * 胡桃API Miao-Plugin 封装
* https://github.com/DGP-Studio/DGP.Genshin.HutaoAPI * https://github.com/DGP-Studio/DGP.Genshin.HutaoAPI
* *
*
* */ * */
import Base from "./Base.js";
import fetch from "node-fetch"; import fetch from "node-fetch";
const host = "http://49.232.91.210:88/miaoPlugin/hutaoApi"; const host = "http://49.232.91.210:88/miaoPlugin/hutaoApi";
@ -27,11 +25,12 @@ let HutaoApi = {
method: "GET", method: "GET",
}); });
let retData = await response.json(); let retData = await response.json();
if (retData && retData.data) {
let d = new Date(); let d = new Date();
retData.lastUpdate = `${d.toLocaleDateString()} ${d.toTimeString().substr(0, 5)}`; retData.lastUpdate = `${d.toLocaleDateString()} ${d.toTimeString().substr(0, 5)}`;
await redis.set(`hutao:${url}`, JSON.stringify(retData), { EX: 3600 }); await redis.set(`hutao:${url}`, JSON.stringify(retData), { EX: 3600 });
}
return retData; return retData;
}, },
// 角色持有及命座分布 // 角色持有及命座分布
@ -41,8 +40,11 @@ let HutaoApi = {
async getAbyssPct() { async getAbyssPct() {
return await HutaoApi.req("/Statistics/AvatarParticipation"); return await HutaoApi.req("/Statistics/AvatarParticipation");
} },
async getAbyssTeam() {
return await HutaoApi.req("/Statistics/TeamCombination");
}
}; };

View File

@ -8,7 +8,7 @@ import {
getProfileAll, getProfileAll,
profileHelp profileHelp
} from "./apps/character.js"; } 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 { wiki } from "./apps/wiki.js";
import { help, versionInfo } from "./apps/help.js"; import { help, versionInfo } from "./apps/help.js";
import lodash from "lodash"; import lodash from "lodash";
@ -21,6 +21,7 @@ export {
wife, wife,
consStat, consStat,
abyssPct, abyssPct,
abyssTeam,
wiki, wiki,
updateRes, updateRes,
updateMiaoPlugin, updateMiaoPlugin,
@ -37,7 +38,8 @@ export {
let rule = { let rule = {
character: { character: {
reg: "^#(喵喵)?(更新)?(.*)(详情|详细|面板|面版|伤害[1-7]?)?(更新)?$", //reg: "^#(喵喵)?(更新)?(.*)(详情|详细|面板|面版|伤害[1-7]?)?(更新)?$",
reg: "noCheck",
describe: "【#角色】角色详情", describe: "【#角色】角色详情",
}, },
getArtis: { getArtis: {
@ -64,6 +66,10 @@ let rule = {
reg: "^#(喵喵)?深渊(第?.{1,2}层)?(角色)?出场(率|统计)*$", reg: "^#(喵喵)?深渊(第?.{1,2}层)?(角色)?出场(率|统计)*$",
describe: "【#统计】 #深渊出场率 #深渊12层出场率", describe: "【#统计】 #深渊出场率 #深渊12层出场率",
}, },
abyssTeam: {
reg: "#深渊(组队|配队)",
describe: "【#角色】 #深渊组队",
},
wiki: { wiki: {
reg: "^(#|喵喵)?.*(天赋|技能|命座|命之座|资料|照片|写真|图片|插画)$", reg: "^(#|喵喵)?.*(天赋|技能|命座|命之座|资料|照片|写真|图片|插画)$",
describe: "【#资料】 #神里天赋 #夜兰命座", describe: "【#资料】 #神里天赋 #夜兰命座",

View File

@ -199,3 +199,123 @@ body {
.elem-pyro .elem-bg { .elem-pyro .elem-bg {
background-image: url(../common/bg/bg-pyro.jpg); 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;
}

View File

@ -23,7 +23,7 @@ export const buffs = [{
check: ({ cons }) => cons < 2, check: ({ cons }) => cons < 2,
title: "4层浪闪提高瞬水剑伤害[aPlus]", title: "4层浪闪提高瞬水剑伤害[aPlus]",
data: { 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, cons: 2,

View File

@ -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;
}

View File

@ -0,0 +1,74 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8"/>
<link rel="shortcut icon" href="#"/>
<link rel="stylesheet" type="text/css" href="{{_res_path}}/common/common.css?v=1.0"/>
<link rel="stylesheet" type="text/css" href="{{_res_path}}/stat/common.css?v=1.0"/>
<link rel="stylesheet" type="text/css" href="{{_res_path}}/stat/abyss-team.css?v=1.0"/>
<link rel="preload" href="{{_res_path}}/font/tttgbnumber.ttf" as="font">
</head>
<body id="container" class="body_box" {{cfgScale}}>
<div class="container">
<div class="info_box">
<div class="head-box type{{bgType}}">
<div class="title">深渊配队建议</div>
<img class="genshin_logo" src="{{_sys_res_path}}/genshin/roleAll/原神.png"/>
</div>
<div class="cont">
<div class="cont-title">深渊配队说明</div>
<div class="cont-body">
<ul class="cont-msg">
<li>根据<strong>当前账号的角色练度</strong>及本期深渊出场数据,推荐较匹配的配队方案</li>
<li>若当前记录可用配队方案少于4组时会使用未持有角色的方案进行补充</li>
<li>深渊出场数据来自<strong>胡桃API</strong>为SnapGenshin用户自主上传的深渊挑战记录感谢SG团队</li>
<li>月初及月中深渊刚刷新后挑战数据可能不足,请等待几天之后数据会逐步稳定</li>
<li>配队列表<strong>仅供参考</strong>,可根据账号实际情况及个人倾向进行灵活调整</li>
</ul>
</div>
</div>
{{each teams floorTeam floor}}
<div class="cont">
<div class="cont-table">
<div class="tr thead">
<div>{{floor}}层</div>
<div>上半</div>
<div>下半</div>
</div>
{{each floorTeam team idx}}
<div class="tr">
<div class="title">配队{{idx+1}}<span class="team-count">{{team.count}}场记录</span></div>
{{each team teamData key}}
{{if key === "up" || key === "down"}}
<div>
<div class="card-list">
{{each teamData.teamArr id}}
<div
class="card star{{avatars[id].star == 4 ? 4:5}} {{avatars[id].level*1 === 0 ? 'no-character':'has-character'}}">
<img class="role"
src="{{_sys_res_path}}/genshin/logo/role/{{avatars[id].name}}.png"
/>
<span class="cons cons-{{avatars[id].cons}} for-has">{{avatars[id].cons}}</span>
<span class="lvl for-has">Lv.{{avatars[id].level}}</span>
<span class="for-no no-label">暂无</span>
</div>
{{/each}}
</div>
</div>
{{/if}}
{{/each}}
</div>
{{/each}}
</div>
</div>
{{/each}}
<div class="copyright"> Created By Yunzai-Bot & Miao-Plugin</div>
</div>
</div>
</body>
</html>