Enka_Panel_Bot/gspanel/data_convert.py
2023-01-14 21:59:43 +08:00

676 lines
29 KiB
Python
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 json
from time import time
from typing import Dict, List, Tuple
# from nonebot.log import logger
from ci import logger
from gspanel.__utils__ import (
CALC_RULES,
CHAR_DATA,
ELEM,
GROW_VALUE,
HASH_TRANS,
MAIN_AFFIXS,
POS,
PROP,
RANK_MAP,
RELIC_APPEND,
SKILL,
SUB_AFFIXS,
getServer,
kStr,
vStr,
)
async def getRelicConfig(char: str, base: Dict = {}) -> Tuple[Dict, Dict, Dict]:
"""
指定角色圣遗物计算配置获取,包括词条评分权重、词条数值原始权重、各位置圣遗物总分理论最高分和主词条理论最高得分
* ``param char: str`` 角色名
* ``param base: Dict = {}`` 角色的基础数值,可由 Enka 返回获得,格式为 ``{"生命值": 1, "攻击力": 1, "防御力": 1}``
- ``return: Tuple[Dict, Dict, Dict]`` 词条评分权重、词条数值原始权重、各位置圣遗物最高得分
"""
affixWeight = CALC_RULES.get(char, {"攻击力百分比": 75, "暴击率": 100, "暴击伤害": 100})
# 词条评分权重的 key 排序影响最优主词条选择
# 通过特定排序使同等权重时生命攻击防御固定值词条优先级最低
# key 的原始排序为 生命值攻击力防御力百分比、暴击率、暴击伤害、元素精通、元素伤害加成、物理伤害加成、元素充能效率
# 注:已经忘了最初为什么这样写了,但总之就是顺序有影响+现在这样写能用
affixWeight = dict(
sorted(
affixWeight.items(),
key=lambda item: (
item[1],
"暴击" in item[0],
"加成" in item[0],
"元素" in item[0],
),
reverse=True,
)
)
# 计算词条数值原始权重
# 是一种与词条数值的乘积在百位数级别的东西,后续据此计算最终得分
# 非百分比的生命攻击防御词条也按百分比词条的 affixWeight 权重计算
pointMark = {k: v / GROW_VALUE[k] for k, v in affixWeight.items()}
if pointMark.get("攻击力百分比"):
pointMark["攻击力"] = pointMark["攻击力百分比"] / base.get("攻击力", 1020) * 100
if pointMark.get("防御力百分比"):
pointMark["防御力"] = pointMark["防御力百分比"] / base.get("防御力", 300) * 100
if pointMark.get("生命值百分比"):
pointMark["生命值"] = pointMark["生命值百分比"] / base.get("生命值", 400) * 100
# 各位置圣遗物的总分理论最高分、主词条理论最高得分
maxMark = {"1": {}, "2": {}, "3": {}, "4": {}, "5": {}}
for posIdx in range(1, 6):
# 主词条最高得分
if posIdx <= 2:
# 花和羽不计算主词条得分
mainAffix = "生命值" if posIdx == 1 else "攻击力"
maxMark[str(posIdx)]["main"] = 0
maxMark[str(posIdx)]["total"] = 0
else:
# 沙杯头计算该位置评分权重最高的词条得分
avalMainAffix = {
k: v for k, v in affixWeight.items() if k in MAIN_AFFIXS[str(posIdx)]
}
# logger.debug(
# "{} 的主词条推荐顺序为:\n{}".format(
# list(POS.values())[posIdx - 1],
# " / ".join(f"{k}[{v}]" for k, v in avalMainAffix.items()),
# )
# )
mainAffix = list(avalMainAffix)[0]
maxMark[str(posIdx)]["main"] = affixWeight[mainAffix]
maxMark[str(posIdx)]["total"] = affixWeight[mainAffix] * 2
# 副词条最高得分
maxSubAffixs = {
k: v
for k, v in affixWeight.items()
if k in SUB_AFFIXS and k != mainAffix and affixWeight.get(k)
}
# logger.debug(
# "{} 的副词条推荐顺序为:\n{}".format(
# list(POS.values())[posIdx - 1],
# " / ".join(f"{k}[{v}]" for k, v in maxSubAffixs.items()),
# )
# )
# 副词条中评分权重最高的词条得分大幅提升
maxMark[str(posIdx)]["total"] += sum(
affixWeight[k] * (1 if kIdx else 6)
for kIdx, k in enumerate(list(maxSubAffixs)[0:4])
)
logger.info(
(
"{}」圣遗物评分依据:"
"\n\t词条评分权重 affixWeight\n\t{}"
"\n\t词条数值原始权重 pointMark\n\t{}"
"\n\t各位置圣遗物最高得分 maxMark\n\t{}"
).format(
char,
" / ".join(f"{k}[{v}]" for k, v in affixWeight.items()),
" / ".join(f"{k}[{v}]" for k, v in pointMark.items()),
" / ".join(
f"{list(POS.values())[int(k)-1]}>主词条[{v['main']}]总分[{v['total']}]"
for k, v in maxMark.items()
),
)
)
return affixWeight, pointMark, maxMark
def getRelicRank(score: float) -> str:
"""圣遗物评级获取"""
# 在角色等级较低(基础数值较低)时评级可能显示为 "ERR"
# 注:角色等级较低时不为 "ERR" 的评分也有可能出错
return [r[0] for r in RANK_MAP if score <= r[1]][0] if score <= 66 else "ERR"
async def calcRelicMark(
relicData: Dict, charElement: str, affixWeight: Dict, pointMark: Dict, maxMark: Dict
) -> Dict:
"""
指定角色圣遗物评分计算
* ``param relicData: Dict`` 圣遗物数据
* ``param charElement: str`` 角色的中文元素属性
* ``param affixWeight: Dict`` 角色的词条评分权重,由 ``getRelicConfig()`` 获取
* ``param pointMark: Dict`` 角色的词条数值原始权重,由 ``getRelicConfig()`` 获取
* ``param maxMark: Dict`` 角色的各位置圣遗物最高得分,由 ``getRelicConfig()`` 获取
- ``return: Dict`` 圣遗物评分结果
"""
posIdx, relicLevel = str(relicData["pos"]), relicData["level"]
mainProp, subProps = relicData["main"], relicData["sub"]
# 主词条得分、主词条收益系数(百分数)
if posIdx in ["1", "2"]:
calcMain, calcMainPct = 0.0, 100
else:
# 角色元素属性与伤害属性不同时权重为 0不影响物理伤害得分
_mainPointMark: float = pointMark.get(
mainProp["prop"].replace(charElement, ""), 0
)
_point: float = _mainPointMark * mainProp["value"]
# 主词条与副词条的得分计算规则一致,但只取 25%
calcMain = _point * 46.6 / 6 / 100 / 4
# 主词条收益系数用于沙杯头位置主词条不正常时的圣遗物总分惩罚,最多扣除 50% 总分
_punishPct: float = _point / maxMark[posIdx]["main"] / 2 / 4
calcMainPct = 100 - 50 * (1 - _punishPct)
# 副词条得分
calcSubs = []
for s in subProps:
_subPointMark: float = pointMark.get(s["prop"], 0)
calcSub: float = _subPointMark * s["value"] * 46.6 / 6 / 100
# 副词条 CSS 样式
_awKey = f"{s['prop']}百分比" if s["prop"] in ["生命值", "攻击力", "防御力"] else s["prop"]
_subAffixWeight: int = affixWeight.get(_awKey, 0)
subStyleClass = (
("great" if _subAffixWeight > 79 else "use") if calcSub else "unuse"
)
# [词条名, 词条数值, 词条得分]
calcSubs.append([subStyleClass, calcSub])
# 总分对齐系数(百分数),按满分 66 对齐各位置圣遗物的总分
calcTotalPct: float = 66 / (maxMark[posIdx]["total"] * 46.6 / 6 / 100) * 100
# 最终圣遗物总分
_total = calcMain + sum(s[1] for s in calcSubs)
calcTotal = _total * calcMainPct * calcTotalPct / 10000
# 强化歪次数
realAppendPropIdList: List[int] = (
relicData["_appendPropIdList"][-(relicLevel // 4) :] if (relicLevel // 4) else []
)
# logger.debug(
# "{} 强化记录:\n{}".format(
# list(POS.values())[int(posIdx) - 1],
# " / ".join(
# PROP.get(RELIC_APPEND[str(x)], RELIC_APPEND[str(x)])
# for x in realAppendPropIdList
# ),
# )
# )
notHit = len(
[
x
for x in realAppendPropIdList
if not pointMark.get(PROP.get(RELIC_APPEND[str(x)], RELIC_APPEND[str(x)]))
]
)
return {
"rank": getRelicRank(calcTotal),
"total": calcTotal,
"nohit": notHit,
"main": round(calcMain, 1),
"sub": [{"style": subRes[0], "goal": round(subRes[1], 1)} for subRes in calcSubs],
"main_pct": round(calcMainPct, 1),
"total_pct": round(calcTotalPct, 1),
}
async def transFromEnka(avatarInfo: Dict, ts: int = 0) -> Dict:
"""
转换 Enka.Network 角色查询数据为内部格式
* ``param avatarInfo: Dict`` Enka.Network 角色查询数据,取自 ``data["avatarInfoList"]`` 列表
* ``param ts: int = 0`` 数据时间戳
- ``return: Dict`` 内部格式角色数据,用于本地缓存等
"""
charData = CHAR_DATA[str(avatarInfo["avatarId"])]
res = {
"id": avatarInfo["avatarId"], # type: int
"rarity": 5 if "QUALITY_ORANGE" in charData["QualityType"] else 4,
"name": charData["NameCN"],
"slogan": charData["Slogan"],
"element": ELEM[charData["Element"]], # 中文单字
"cons": len(avatarInfo.get("talentIdList", [])), # type: int
"fetter": avatarInfo["fetterInfo"]["expLevel"], # type: int
"level": int(avatarInfo["propMap"]["4001"]["val"]), # type: int
"icon": charData["iconName"],
"gachaAvatarImg": charData["Costumes"][str(avatarInfo["costumeId"])]["art"]
if avatarInfo.get("costumeId")
else charData["iconName"].replace("UI_AvatarIcon_", "UI_Gacha_AvatarImg_"),
"baseProp": { # type: float
"生命值": avatarInfo["fightPropMap"]["1"],
"攻击力": avatarInfo["fightPropMap"]["4"],
"防御力": avatarInfo["fightPropMap"]["7"],
},
"fightProp": { # type: float
"生命值": avatarInfo["fightPropMap"]["2000"],
# "攻击力": avatarInfo["fightPropMap"]["2001"],
"攻击力": avatarInfo["fightPropMap"]["4"]
* (1 + avatarInfo["fightPropMap"].get("6", 0))
+ avatarInfo["fightPropMap"].get("5", 0),
"防御力": avatarInfo["fightPropMap"]["2002"],
"暴击率": avatarInfo["fightPropMap"]["20"] * 100,
"暴击伤害": avatarInfo["fightPropMap"]["22"] * 100,
"治疗加成": avatarInfo["fightPropMap"]["26"] * 100,
"元素精通": avatarInfo["fightPropMap"]["28"],
"元素充能效率": avatarInfo["fightPropMap"]["23"] * 100,
"物理伤害加成": avatarInfo["fightPropMap"]["30"] * 100,
"火元素伤害加成": avatarInfo["fightPropMap"]["40"] * 100,
"水元素伤害加成": avatarInfo["fightPropMap"]["42"] * 100,
"风元素伤害加成": avatarInfo["fightPropMap"]["44"] * 100,
"雷元素伤害加成": avatarInfo["fightPropMap"]["41"] * 100,
"草元素伤害加成": avatarInfo["fightPropMap"]["43"] * 100,
"冰元素伤害加成": avatarInfo["fightPropMap"]["46"] * 100,
"岩元素伤害加成": avatarInfo["fightPropMap"]["45"] * 100,
},
"skills": {},
"consts": [],
"weapon": {},
"relics": [],
"relicSet": {},
"relicCalc": {},
"damage": {}, # 预留
"time": ts or int(time()),
}
# 技能数据
skills = {"a": {}, "e": {}, "q": {}}
extraLevels = {
k[-1]: v for k, v in avatarInfo.get("proudSkillExtraLevelMap", {}).items()
}
for idx, skillId in enumerate(charData["SkillOrder"]):
# 实际技能等级、显示技能等级
level = avatarInfo["skillLevelMap"][str(skillId)]
currentLvl = level + extraLevels.get(list(SKILL)[idx], 0)
skills[list(SKILL.values())[idx]] = {
"style": "extra" if currentLvl > level else "",
"icon": charData["Skills"][str(skillId)],
"level": currentLvl,
"originLvl": level,
}
res["skills"] = skills
# 命座数据
consts = []
for cIdx, consImgName in enumerate(charData["Consts"]):
consts.append(
{
"style": "off" if cIdx + 1 > res["cons"] else "",
"icon": consImgName,
}
)
res["consts"] = consts
# 装备数据
affixWeight, pointMark, maxMark = await getRelicConfig(
charData["NameCN"], res["baseProp"]
)
relicsMark, relicsCnt, relicSet = 0.0, 0, {}
for equip in avatarInfo["equipList"]:
if equip["flat"]["itemType"] == "ITEM_WEAPON":
weaponSub: str = equip["flat"]["weaponStats"][-1]["appendPropId"]
weaponSubValue = equip["flat"]["weaponStats"][-1]["statValue"]
res["weapon"] = {
"id": equip["itemId"],
"rarity": equip["flat"]["rankLevel"], # type: int
"name": HASH_TRANS.get(equip["flat"]["nameTextMapHash"], "缺少翻译"),
"affix": list(equip["weapon"].get("affixMap", {"_": 0}).values())[0] + 1,
"level": equip["weapon"]["level"], # type: int
"icon": equip["flat"]["icon"],
"main": equip["flat"]["weaponStats"][0]["statValue"], # type: int
"sub": {
"prop": PROP[weaponSub].replace("百分比", ""),
"value": f"{weaponSubValue}{'' if weaponSub.endswith('ELEMENT_MASTERY') else '%'}",
}
if weaponSub != "FIGHT_PROP_BASE_ATTACK"
else {},
}
elif equip["flat"]["itemType"] == "ITEM_RELIQUARY":
mainProp: Dict = equip["flat"]["reliquaryMainstat"]
subProps: List = equip["flat"].get("reliquarySubstats", [])
posIdx = list(POS.keys()).index(equip["flat"]["equipType"]) + 1
relicData = {
"pos": posIdx,
"rarity": equip["flat"]["rankLevel"],
"name": HASH_TRANS.get(equip["flat"]["nameTextMapHash"], "缺少翻译"),
"setName": HASH_TRANS.get(equip["flat"]["setNameTextMapHash"], "缺少翻译"),
"level": equip["reliquary"]["level"] - 1,
"main": {
"prop": PROP[mainProp["mainPropId"]],
"value": mainProp["statValue"],
},
"sub": [
{"prop": PROP[s["appendPropId"]], "value": s["statValue"]}
for s in subProps
],
"calc": {},
"icon": equip["flat"]["icon"],
"_appendPropIdList": equip["reliquary"].get("appendPropIdList", []),
}
relicData["calc"] = await calcRelicMark(
relicData, res["element"], affixWeight, pointMark, maxMark
)
# 分数计算完毕后再将词条名称、数值转为适合 HTML 渲染的格式
relicData["main"]["value"] = vStr(
relicData["main"]["prop"], relicData["main"]["value"]
)
relicData["main"]["prop"] = kStr(relicData["main"]["prop"])
relicData["sub"] = [
{"prop": kStr(s["prop"]), "value": vStr(s["prop"], s["value"])}
for s in relicData["sub"]
]
# 额外数据处理
relicData["calc"]["total"] = round(relicData["calc"]["total"], 1)
relicData.pop("_appendPropIdList")
relicSet[relicData["setName"]] = relicSet.get(relicData["setName"], 0) + 1
res["relics"].append(relicData)
# 累积圣遗物套装评分和计数器
relicsMark += relicData["calc"]["total"]
relicsCnt += 1
# 圣遗物套装
res["relicSet"] = relicSet
res["relicCalc"] = {
"rank": getRelicRank(relicsMark / relicsCnt) if relicsCnt else "NaN",
"total": round(relicsMark, 1),
}
return res
async def transToTeyvat(avatarsData: List[Dict], uid: str) -> Dict:
"""
转换内部格式角色数据为 Teyvat Helper 请求格式
* ``param avatarsData: List[Dict]`` 内部格式角色数据,由 ``transFromEnka()`` 获取
* ``param uid: str`` 角色所属用户 UID
- ``return: Dict`` Teyvat Helper 请求格式角色数据
"""
res = {"uid": uid, "role_data": []}
if uid[0] not in ["1", "2"]:
res["server"] = getServer(uid)
for avatarData in avatarsData:
name = avatarData["name"]
cons = avatarData["cons"]
weapon = avatarData["weapon"]
baseProp = avatarData["baseProp"]
fightProp = avatarData["fightProp"]
skills = avatarData["skills"]
relics = avatarData["relics"]
relicSet = avatarData["relicSet"]
# dataFix from https://github.com/yoimiya-kokomi/miao-plugin/blob/ac27075276154ef5a87a458697f6e5492bd323bd/components/profile-data/enka-data.js#L186
if name == "雷电将军":
_thunderDmg = fightProp["雷元素伤害加成"]
_recharge = fightProp["元素充能效率"]
fightProp["雷元素伤害加成"] = max(0, _thunderDmg - (_recharge - 100) * 0.4)
if name == "莫娜":
_waterDmg = fightProp["水元素伤害加成"]
_recharge = fightProp["元素充能效率"]
fightProp["水元素伤害加成"] = max(0, _waterDmg - _recharge * 0.2)
if name == "妮露" and cons == 6:
_count = float(fightProp["生命值"] / 1000)
_crit = fightProp["暴击率"]
_critDmg = fightProp["暴击伤害"]
fightProp["暴击率"] = max(5, _crit - min(30, _count * 0.6))
fightProp["暴击伤害"] = max(50, _critDmg - min(60, _count * 1.2))
if weapon["name"] in ["息灾", "波乱月白经津", "雾切之回光", "猎人之径"]:
for elem in ["", "", "", "", "", "", ""]:
_origin = fightProp[f"{elem}元素伤害加成"]
fightProp[f"{elem}元素伤害加成"] = max(
0, _origin - 12 - 12 * (weapon["affix"] - 1) / 4
)
# 圣遗物数据
artifacts = []
for a in relics:
tData = {
"artifacts_name": a["name"],
"artifacts_type": list(POS.values())[a["pos"] - 1],
"level": a["level"],
"maintips": kStr(a["main"]["prop"], reverse=True),
"mainvalue": a["main"]["value"],
}
tData.update(
{
f"tips{sIdx + 1}": "{}+{}".format(
kStr(s["prop"], reverse=True), s["value"]
)
for sIdx, s in enumerate(a["sub"])
}
)
artifacts.append(tData)
# 单个角色最终结果
res["role_data"].append(
{
"uid": uid,
"role": name,
"role_class": cons,
"level": int(avatarData["level"]),
"weapon": weapon["name"],
"weapon_level": weapon["level"],
"weapon_class": f"精炼{weapon['affix']}",
"hp": int(fightProp["生命值"]),
"base_hp": int(baseProp["生命值"]),
"attack": int(fightProp["攻击力"]),
"base_attack": int(baseProp["攻击力"]),
"defend": int(fightProp["防御力"]),
"base_defend": int(baseProp["防御力"]),
"element": round(fightProp["元素精通"]),
"crit": f"{round(fightProp['暴击率'], 1)}%",
"crit_dmg": f"{round(fightProp['暴击伤害'], 1)}%",
"heal": f"{round(fightProp['治疗加成'], 1)}%",
"recharge": f"{round(fightProp['元素充能效率'], 1)}%",
"fire_dmg": f"{round(fightProp['火元素伤害加成'], 1)}%",
"water_dmg": f"{round(fightProp['水元素伤害加成'], 1)}%",
"thunder_dmg": f"{round(fightProp['雷元素伤害加成'], 1)}%",
"wind_dmg": f"{round(fightProp['风元素伤害加成'], 1)}%",
"ice_dmg": f"{round(fightProp['冰元素伤害加成'], 1)}%",
"rock_dmg": f"{round(fightProp['岩元素伤害加成'], 1)}%",
"grass_dmg": f"{round(fightProp['草元素伤害加成'], 1)}%",
"physical_dmg": f"{round(fightProp['物理伤害加成'], 1)}%",
"artifacts": "+".join(
f"{k}{4 if v >= 4 else (2 if v >= 2 else 1)}"
for k, v in relicSet.items()
if (v >= 2) or ("之人" in k)
),
"ability1": skills["a"]["level"],
"ability2": skills["e"]["level"],
"ability3": skills["q"]["level"],
"artifacts_detail": artifacts,
}
)
return res
async def simplDamageRes(damage: Dict) -> Dict:
"""
转换角色伤害计算请求数据为精简格式
* ``param damage: Dict`` 角色伤害计算请求数据,由 ``queryDamageApi()["result"][int]`` 获取
- ``return: Dict`` 精简格式伤害数据,出错时返回 ``{}``
"""
res = {"level": damage["zdl_result"] or "NaN", "data": [], "buff": []}
for key in ["damage_result_arr", "damage_result_arr2"]:
for dmgDetail in damage[key]:
dmgTitle = "{}{}".format(
f"[{damage['zdl_result2']}]<br>" if key == "damage_result_arr2" else "",
dmgDetail["title"],
)
if "期望" in str(dmgDetail["value"]) or not dmgDetail.get("expect"):
dmgCrit, dmgExp = "-", str(dmgDetail["value"]).replace("期望", "")
else:
dmgCrit = str(dmgDetail["value"])
dmgExp = str(dmgDetail["expect"]).replace("期望", "")
res["data"].append([dmgTitle, dmgCrit, dmgExp])
for buff in damage["bonus"]:
# damage["bonus"]: {"0": {}, "2": {}, ...}
# damage["bonus"]: [{}, {}, ...]
intro = damage["bonus"][buff]["intro"] if isinstance(buff, str) else buff["intro"]
buffTitle, buffDetail = intro.split("")
if buffTitle not in ["", "备注"]:
res["buff"].append([buffTitle, buffDetail])
return res
async def simplFightProp(
fightProp: Dict, baseProp: Dict, char: str, element: str
) -> Dict[str, Dict]:
"""
转换角色面板数据为 HTML 模板需求格式
* ``param fightProp: Dict`` 角色面板数据,由 ``transFromEnka()["fightProp"]`` 获取
* ``param baseProp: Dict`` 角色基础数据,由 ``transFromEnka()["baseProp"]`` 获取
* ``param element: str`` 角色元素属性
- ``return: Dict[str, Dict]`` HTML 模板需求格式面板数据
"""
affixWeight = CALC_RULES.get(char, {"攻击力百分比": 75, "暴击率": 100, "暴击伤害": 100})
# 排列伤害加成
prefer = (
element if affixWeight.get("元素伤害加成", 0) > affixWeight.get("物理伤害加成", 0) else ""
)
damages = sorted(
[{"k": k, "v": v} for k, v in fightProp.items() if str(k).endswith("伤害加成")],
key=lambda x: (x["v"], x["k"][0] == prefer),
reverse=True,
)
for unuseDmg in damages[1:]:
fightProp.pop(unuseDmg["k"])
# 生成模板渲染所需数据
res = {}
for propTitle, propValue in fightProp.items():
# 跳过无效治疗加成
if propTitle == "治疗加成" and not propValue and not affixWeight.get(propTitle):
continue
# 整理渲染数据
res[propTitle] = {
"value": f"{round(propValue, 1)}%"
if propTitle not in ["生命值", "攻击力", "防御力", "元素精通"]
else round(propValue),
"weight": max(affixWeight.get("元素伤害加成", 0), affixWeight.get("物理伤害加成", 0))
if propTitle.endswith("伤害加成")
else affixWeight.get(propTitle) or affixWeight.get(f"{propTitle}百分比", 0),
}
# 补充基础数值
if propTitle in ["生命值", "攻击力", "防御力"]:
res[propTitle]["detail"] = [
round(baseProp[propTitle]),
round(res[propTitle]["value"] - baseProp[propTitle]),
]
# 标记异常伤害加成
if propTitle.endswith("伤害加成"):
if (
propTitle[0] not in ["", element]
or affixWeight.get(propTitle[-6:], 0) != res[propTitle]["weight"]
):
res[propTitle]["error"] = True
return res
async def simplTeamDamageRes(raw: Dict, rolesData: Dict) -> Dict:
"""
转换队伍伤害计算请求数据为精简格式
* ``param raw: Dict`` 队伍伤害计算请求数据,由 ``queryDamageApi(*, "team")["result"]`` 获取
* ``param rolesData: Dict`` 角色数据,键为角色中文名,值为内部格式
- ``return: Dict`` 精简格式伤害数据。出错时返回 ``{"error": "错误信息"}``
"""
s = (
str(raw["zdl_tips0"])
.replace("你的队伍", "")
.replace("秒内造成总伤害", "-")
.replace("DPS为:", "")
)
tm, total = s.split("-")
pieData, pieColor = [], []
for x in raw["chart_data"]:
char, damage = x["name"].split("\n")
pieData.append({"char": char, "damage": float(damage.replace("W", ""))})
pieColor.append(x["label"]["color"])
pieData = sorted(pieData, key=lambda x: x["damage"], reverse=True)
# 寻找伤害最高的角色元素属性,跳过绽放等伤害来源
elem = [
rolesData[_source["char"]]["element"]
for _source in pieData
if rolesData.get(_source["char"])
][0]
avatars = {}
for role in raw["role_list"]:
panelData = rolesData[role["role"]]
avatars[role["role"]] = {
"rarity": role["role_star"],
"icon": panelData["icon"],
"name": role["role"],
"elem": panelData["element"],
"cons": role["role_class"],
"level": role["role_level"].replace("Lv", ""),
"weapon": {
"icon": panelData["weapon"]["icon"],
"level": panelData["weapon"]["level"],
"rarity": panelData["weapon"]["rarity"],
"affix": panelData["weapon"]["affix"],
},
"sets": {
[r for r in panelData["relics"] if r["setName"] == k][0]["icon"].split(
"_"
)[-2]: (2 if v < 4 else 4)
for k, v in panelData["relicSet"].items()
if v >= 2 # 暂未排版 祭X之人 单件套装
},
"cp": round(panelData["fightProp"]["暴击率"], 1),
"cd": round(panelData["fightProp"]["暴击伤害"], 1),
"key_prop": role["key_ability"],
"key_value": role["key_value"],
"skills": [
{"icon": skill["icon"], "style": skill["style"], "level": skill["level"]}
for _, skill in panelData["skills"].items()
],
}
for rechargeData in raw["recharge_info"]:
name, tmp = rechargeData["recharge"].split("共获取同色球")
same, diff = tmp.split("个,异色球")
if len(diff.split("个,无色球")) == 2:
# 暂未排版无色球
diff = diff.split("个,无色球")[0]
avatars[name]["recharge"] = {
"pct": rechargeData["rate"],
"same": round(float(same), 1),
"diff": round(float(diff.replace("", "")), 1),
}
damages = []
for step in raw["advice"]:
if not step.get("content"):
logger.error(f"奇怪的伤害:{step}")
continue
# content: "4.2s 雷神e协同暴击:3016,不暴击:1565,期望:2343"
t, s = step["content"].split(" ")
if len(s.split("")) == 1:
# "3.89s 万叶q染色为:雷"
a = s.split("")[0]
d = ["-", "-", "-"]
else:
a, dmgs = s.split("")
if len(dmgs.split(",")) == 1:
d = ["-", "-", dmgs.split(",")[0].split("")[-1]]
else:
d = [dd.split(":")[-1] for dd in dmgs.split(",")]
damages.append([t.replace("s", ""), a.upper(), *d])
buffs = []
for buff in raw["buff"]:
if not buff.get("content"):
logger.error(f"奇怪的 Buff{buff}")
continue
# buff: "1.5s 风套-怪物雷抗减少-40%"
t, tmp = buff["content"].split(" ", 1)
b, bd = tmp.split("-", 1)
buffs.append([t.replace("s", ""), b.upper(), bd.upper()])
return {
"uid": raw["uid"],
"elem": elem,
"rank": raw["zdl_tips2"],
"dps": raw["zdl_result"],
"tm": tm,
"total": total,
"pie_data": json.dumps(pieData, ensure_ascii=False),
"pie_color": json.dumps(pieColor),
"avatars": avatars,
"actions": raw["combo_intro"].split(","),
"damages": damages,
"buffs": buffs,
}