✨ Support starrail player_card
11
.github/dependabot.yml
vendored
@ -1,11 +0,0 @@
|
|||||||
version: 2
|
|
||||||
updates:
|
|
||||||
- package-ecosystem: pip
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: daily
|
|
||||||
time: "18:00"
|
|
||||||
timezone: Asia/Shanghai
|
|
||||||
open-pull-requests-limit: 10
|
|
||||||
allow:
|
|
||||||
- dependency-type: "direct"
|
|
@ -19,6 +19,8 @@ ASSETS_PATH.mkdir(exist_ok=True, parents=True)
|
|||||||
DATA_MAP = {
|
DATA_MAP = {
|
||||||
"avatar": WikiModel.BASE_URL + "avatar_icons.json",
|
"avatar": WikiModel.BASE_URL + "avatar_icons.json",
|
||||||
"light_cone": WikiModel.BASE_URL + "light_cone_icons.json",
|
"light_cone": WikiModel.BASE_URL + "light_cone_icons.json",
|
||||||
|
"avatar_eidolon": WikiModel.BASE_URL + "avatar_eidolon_icons.json",
|
||||||
|
"avatar_skill": WikiModel.BASE_URL + "skill/info.json",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -75,17 +77,28 @@ class _AvatarAssets(_AssetsService):
|
|||||||
async def initialize(self):
|
async def initialize(self):
|
||||||
logger.info("正在初始化角色素材图标")
|
logger.info("正在初始化角色素材图标")
|
||||||
html = await self.client.get(DATA_MAP["avatar"])
|
html = await self.client.get(DATA_MAP["avatar"])
|
||||||
|
eidolons = await self.client.get(DATA_MAP["avatar_eidolon"])
|
||||||
|
eidolons_data = eidolons.json()
|
||||||
|
skills = await self.client.get(DATA_MAP["avatar_skill"])
|
||||||
|
skills_data = skills.json()
|
||||||
self.data = [AvatarIcon(**data) for data in html.json()]
|
self.data = [AvatarIcon(**data) for data in html.json()]
|
||||||
self.name_map = {icon.name: icon for icon in self.data}
|
self.name_map = {icon.name: icon for icon in self.data}
|
||||||
self.id_map = {icon.id: icon for icon in self.data}
|
self.id_map = {icon.id: icon for icon in self.data}
|
||||||
tasks = []
|
tasks = []
|
||||||
for icon in self.data:
|
for icon in self.data:
|
||||||
|
eidolons_s_data = eidolons_data.get(str(icon.id), [])
|
||||||
|
skills_s_data = [f"{i}.png" for i in skills_data if i.startswith(str(icon.id) + "_")]
|
||||||
base_path = self.path / f"{icon.id}"
|
base_path = self.path / f"{icon.id}"
|
||||||
base_path.mkdir(exist_ok=True, parents=True)
|
base_path.mkdir(exist_ok=True, parents=True)
|
||||||
gacha_path = base_path / "gacha.webp"
|
gacha_path = base_path / "gacha.webp"
|
||||||
icon_path = base_path / "icon.webp"
|
icon_path = base_path / "icon.webp"
|
||||||
normal_path = base_path / "normal.webp"
|
normal_path = base_path / "normal.webp"
|
||||||
square_path = base_path / "square.png"
|
square_path = base_path / "square.png"
|
||||||
|
eidolons_paths = [(base_path / f"eidolon_{eidolon_id}.webp") for eidolon_id in range(1, 7)]
|
||||||
|
skills_paths = []
|
||||||
|
for i in skills_s_data:
|
||||||
|
temp_end = "_".join(i.split("_")[1:])
|
||||||
|
skills_paths.append(base_path / f"skill_{temp_end}")
|
||||||
if not gacha_path.exists():
|
if not gacha_path.exists():
|
||||||
tasks.append(self._download(icon.gacha, gacha_path))
|
tasks.append(self._download(icon.gacha, gacha_path))
|
||||||
if not icon_path.exists():
|
if not icon_path.exists():
|
||||||
@ -94,6 +107,12 @@ class _AvatarAssets(_AssetsService):
|
|||||||
tasks.append(self._download(icon.normal, normal_path))
|
tasks.append(self._download(icon.normal, normal_path))
|
||||||
if not square_path.exists() and icon.square:
|
if not square_path.exists() and icon.square:
|
||||||
tasks.append(self._download(icon.square, square_path))
|
tasks.append(self._download(icon.square, square_path))
|
||||||
|
for index, eidolon in enumerate(eidolons_paths):
|
||||||
|
if not eidolon.exists():
|
||||||
|
tasks.append(self._download(eidolons_s_data[index], eidolon))
|
||||||
|
for index, skill in enumerate(skills_paths):
|
||||||
|
if not skill.exists():
|
||||||
|
tasks.append(self._download(WikiModel.BASE_URL + "skill/" + skills_s_data[index], skill))
|
||||||
if len(tasks) >= 100:
|
if len(tasks) >= 100:
|
||||||
await asyncio.gather(*tasks)
|
await asyncio.gather(*tasks)
|
||||||
tasks = []
|
tasks = []
|
||||||
@ -145,6 +164,46 @@ class _AvatarAssets(_AssetsService):
|
|||||||
raise AssetsCouldNotFound("角色素材图标不存在", target)
|
raise AssetsCouldNotFound("角色素材图标不存在", target)
|
||||||
return path
|
return path
|
||||||
|
|
||||||
|
def eidolons(self, target: StrOrInt, second_target: StrOrInt = None) -> List[Path]:
|
||||||
|
"""星魂"""
|
||||||
|
icon = self.get_target(target, second_target)
|
||||||
|
return [self.get_path(icon, f"eidolon_{i}") for i in range(1, 7)]
|
||||||
|
|
||||||
|
def skill_basic_atk(self, target: StrOrInt, second_target: StrOrInt = None) -> Path:
|
||||||
|
"""普攻 001"""
|
||||||
|
icon = self.get_target(target, second_target)
|
||||||
|
return self.get_path(icon, "skill_basic_atk", "png")
|
||||||
|
|
||||||
|
def skill_skill(self, target: StrOrInt, second_target: StrOrInt = None) -> Path:
|
||||||
|
"""战技 002"""
|
||||||
|
icon = self.get_target(target, second_target)
|
||||||
|
return self.get_path(icon, "skill_skill", "png")
|
||||||
|
|
||||||
|
def skill_ultimate(self, target: StrOrInt, second_target: StrOrInt = None) -> Path:
|
||||||
|
"""终结技 003"""
|
||||||
|
icon = self.get_target(target, second_target)
|
||||||
|
return self.get_path(icon, "skill_ultimate", "png")
|
||||||
|
|
||||||
|
def skill_talent(self, target: StrOrInt, second_target: StrOrInt = None) -> Path:
|
||||||
|
"""天赋 004"""
|
||||||
|
icon = self.get_target(target, second_target)
|
||||||
|
return self.get_path(icon, "skill_talent", "png")
|
||||||
|
|
||||||
|
def skill_technique(self, target: StrOrInt, second_target: StrOrInt = None) -> Path:
|
||||||
|
"""秘技 007"""
|
||||||
|
icon = self.get_target(target, second_target)
|
||||||
|
return self.get_path(icon, "skill_technique", "png")
|
||||||
|
|
||||||
|
def skills(self, target: StrOrInt, second_target: StrOrInt = None) -> List[Path]:
|
||||||
|
icon = self.get_target(target, second_target)
|
||||||
|
return [
|
||||||
|
self.get_path(icon, "skill_basic_atk", "png"),
|
||||||
|
self.get_path(icon, "skill_skill", "png"),
|
||||||
|
self.get_path(icon, "skill_ultimate", "png"),
|
||||||
|
self.get_path(icon, "skill_talent", "png"),
|
||||||
|
self.get_path(icon, "skill_technique", "png"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class _LightConeAssets(_AssetsService):
|
class _LightConeAssets(_AssetsService):
|
||||||
path: Path
|
path: Path
|
||||||
|
@ -3,109 +3,120 @@ from __future__ import annotations
|
|||||||
import functools
|
import functools
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from metadata.genshin import WEAPON_DATA
|
__all__ = [
|
||||||
|
"roles",
|
||||||
__all__ = ["roles", "light_cones", "roleToId", "roleToName", "lightConeToName", "lightConeToId", "not_real_roles", "roleToTag"]
|
"light_cones",
|
||||||
|
"roleToId",
|
||||||
|
"roleToName",
|
||||||
|
"idToRole",
|
||||||
|
"lightConeToName",
|
||||||
|
"lightConeToId",
|
||||||
|
"not_real_roles",
|
||||||
|
"roleToTag",
|
||||||
|
]
|
||||||
|
|
||||||
# noinspection SpellCheckingInspection
|
# noinspection SpellCheckingInspection
|
||||||
roles = {
|
roles = {
|
||||||
1001: ['三月七'],
|
8001: ["开拓者"],
|
||||||
1002: ['丹恒'],
|
8002: ["开拓者"],
|
||||||
1003: ['姬子'],
|
8003: ["开拓者"],
|
||||||
1004: ['瓦尔特'],
|
8004: ["开拓者"],
|
||||||
1005: ['卡芙卡'],
|
1001: ["三月七"],
|
||||||
1006: ['银狼'],
|
1002: ["丹恒"],
|
||||||
1008: ['阿兰'],
|
1003: ["姬子"],
|
||||||
1009: ['艾丝妲'],
|
1004: ["瓦尔特"],
|
||||||
1013: ['黑塔'],
|
1005: ["卡芙卡"],
|
||||||
1101: ['布洛妮娅'],
|
1006: ["银狼"],
|
||||||
1102: ['希儿'],
|
1008: ["阿兰"],
|
||||||
1103: ['希露瓦'],
|
1009: ["艾丝妲"],
|
||||||
1104: ['杰帕德'],
|
1013: ["黑塔"],
|
||||||
1105: ['娜塔莎'],
|
1101: ["布洛妮娅"],
|
||||||
1106: ['佩拉'],
|
1102: ["希儿"],
|
||||||
1107: ['克拉拉'],
|
1103: ["希露瓦"],
|
||||||
1108: ['桑博'],
|
1104: ["杰帕德"],
|
||||||
1109: ['虎克'],
|
1105: ["娜塔莎"],
|
||||||
1201: ['青雀'],
|
1106: ["佩拉"],
|
||||||
1202: ['停云'],
|
1107: ["克拉拉"],
|
||||||
1203: ['罗刹'],
|
1108: ["桑博"],
|
||||||
1204: ['景元'],
|
1109: ["虎克"],
|
||||||
1206: ['素裳'],
|
1201: ["青雀"],
|
||||||
1209: ['彦卿'],
|
1202: ["停云"],
|
||||||
1211: ['白露'],
|
1203: ["罗刹"],
|
||||||
8004: ['开拓者'],
|
1204: ["景元"],
|
||||||
|
1206: ["素裳"],
|
||||||
|
1209: ["彦卿"],
|
||||||
|
1211: ["白露"],
|
||||||
}
|
}
|
||||||
not_real_roles = []
|
not_real_roles = []
|
||||||
light_cones = {
|
light_cones = {
|
||||||
20000: ['锋镝'],
|
20000: ["锋镝"],
|
||||||
20001: ['物穰'],
|
20001: ["物穰"],
|
||||||
20002: ['天倾'],
|
20002: ["天倾"],
|
||||||
20003: ['琥珀'],
|
20003: ["琥珀"],
|
||||||
20004: ['幽邃'],
|
20004: ["幽邃"],
|
||||||
20005: ['齐颂'],
|
20005: ["齐颂"],
|
||||||
20006: ['智库'],
|
20006: ["智库"],
|
||||||
20007: ['离弦'],
|
20007: ["离弦"],
|
||||||
20008: ['嘉果'],
|
20008: ["嘉果"],
|
||||||
20009: ['乐圮'],
|
20009: ["乐圮"],
|
||||||
20010: ['戍御'],
|
20010: ["戍御"],
|
||||||
20011: ['渊环'],
|
20011: ["渊环"],
|
||||||
20012: ['轮契'],
|
20012: ["轮契"],
|
||||||
20013: ['灵钥'],
|
20013: ["灵钥"],
|
||||||
20014: ['相抗'],
|
20014: ["相抗"],
|
||||||
20015: ['蕃息'],
|
20015: ["蕃息"],
|
||||||
20016: ['俱殁'],
|
20016: ["俱殁"],
|
||||||
20017: ['开疆'],
|
20017: ["开疆"],
|
||||||
20018: ['匿影'],
|
20018: ["匿影"],
|
||||||
20019: ['调和'],
|
20019: ["调和"],
|
||||||
20020: ['睿见'],
|
20020: ["睿见"],
|
||||||
21000: ['一场术后对话'],
|
21000: ["一场术后对话"],
|
||||||
21001: ['晚安与睡颜'],
|
21001: ["晚安与睡颜"],
|
||||||
21002: ['余生的第一天'],
|
21002: ["余生的第一天"],
|
||||||
21003: ['唯有沉默'],
|
21003: ["唯有沉默"],
|
||||||
21004: ['记忆中的模样'],
|
21004: ["记忆中的模样"],
|
||||||
21005: ['鼹鼠党欢迎你'],
|
21005: ["鼹鼠党欢迎你"],
|
||||||
21006: ['「我」的诞生'],
|
21006: ["「我」的诞生"],
|
||||||
21007: ['同一种心情'],
|
21007: ["同一种心情"],
|
||||||
21008: ['猎物的视线'],
|
21008: ["猎物的视线"],
|
||||||
21009: ['朗道的选择'],
|
21009: ["朗道的选择"],
|
||||||
21010: ['论剑'],
|
21010: ["论剑"],
|
||||||
21011: ['与行星相会'],
|
21011: ["与行星相会"],
|
||||||
21012: ['秘密誓心'],
|
21012: ["秘密誓心"],
|
||||||
21013: ['别让世界静下来'],
|
21013: ["别让世界静下来"],
|
||||||
21014: ['此时恰好'],
|
21014: ["此时恰好"],
|
||||||
21015: ['决心如汗珠般闪耀'],
|
21015: ["决心如汗珠般闪耀"],
|
||||||
21016: ['宇宙市场趋势'],
|
21016: ["宇宙市场趋势"],
|
||||||
21017: ['点个关注吧!'],
|
21017: ["点个关注吧!"],
|
||||||
21018: ['舞!舞!舞!'],
|
21018: ["舞!舞!舞!"],
|
||||||
21019: ['在蓝天下'],
|
21019: ["在蓝天下"],
|
||||||
21020: ['天才们的休憩'],
|
21020: ["天才们的休憩"],
|
||||||
21021: ['等价交换'],
|
21021: ["等价交换"],
|
||||||
21022: ['延长记号'],
|
21022: ["延长记号"],
|
||||||
21023: ['我们是地火'],
|
21023: ["我们是地火"],
|
||||||
21024: ['春水初生'],
|
21024: ["春水初生"],
|
||||||
21025: ['过往未来'],
|
21025: ["过往未来"],
|
||||||
21026: ['汪!散步时间!'],
|
21026: ["汪!散步时间!"],
|
||||||
21027: ['早餐的仪式感'],
|
21027: ["早餐的仪式感"],
|
||||||
21028: ['暖夜不会漫长'],
|
21028: ["暖夜不会漫长"],
|
||||||
21029: ['后会有期'],
|
21029: ["后会有期"],
|
||||||
21030: ['这就是我啦!'],
|
21030: ["这就是我啦!"],
|
||||||
21031: ['重返幽冥'],
|
21031: ["重返幽冥"],
|
||||||
21032: ['镂月裁云之意'],
|
21032: ["镂月裁云之意"],
|
||||||
21033: ['无处可逃'],
|
21033: ["无处可逃"],
|
||||||
21034: ['今日亦是和平的一日'],
|
21034: ["今日亦是和平的一日"],
|
||||||
23000: ['银河铁道之夜'],
|
23000: ["银河铁道之夜"],
|
||||||
23001: ['于夜色中'],
|
23001: ["于夜色中"],
|
||||||
23002: ['无可取代的东西'],
|
23002: ["无可取代的东西"],
|
||||||
23003: ['但战斗还未结束'],
|
23003: ["但战斗还未结束"],
|
||||||
23004: ['以世界之名'],
|
23004: ["以世界之名"],
|
||||||
23005: ['制胜的瞬间'],
|
23005: ["制胜的瞬间"],
|
||||||
23010: ['拂晓之前'],
|
23010: ["拂晓之前"],
|
||||||
23012: ['如泥酣眠'],
|
23012: ["如泥酣眠"],
|
||||||
23013: ['时节不居'],
|
23013: ["时节不居"],
|
||||||
24000: ['记一位星神的陨落'],
|
24000: ["记一位星神的陨落"],
|
||||||
24001: ['星海巡航'],
|
24001: ["星海巡航"],
|
||||||
24002: ['记忆的质料']
|
24002: ["记忆的质料"],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -125,6 +136,13 @@ def roleToId(name: str) -> int | None:
|
|||||||
return next((key for key, value in roles.items() for n in value if n == name), None)
|
return next((key for key, value in roles.items() for n in value if n == name), None)
|
||||||
|
|
||||||
|
|
||||||
|
# noinspection PyPep8Naming
|
||||||
|
@functools.lru_cache()
|
||||||
|
def idToRole(aid: int) -> str | None:
|
||||||
|
"""获取角色名"""
|
||||||
|
return roles.get(aid, [None])[0]
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyPep8Naming
|
# noinspection PyPep8Naming
|
||||||
@functools.lru_cache()
|
@functools.lru_cache()
|
||||||
def lightConeToName(shortname: str) -> str:
|
def lightConeToName(shortname: str) -> str:
|
||||||
|
@ -3,8 +3,6 @@ from datetime import datetime, timedelta
|
|||||||
from typing import List, Tuple, Optional, Dict, Union, TYPE_CHECKING
|
from typing import List, Tuple, Optional, Dict, Union, TYPE_CHECKING
|
||||||
|
|
||||||
from httpx import AsyncClient
|
from httpx import AsyncClient
|
||||||
|
|
||||||
from metadata.genshin import AVATAR_DATA
|
|
||||||
from metadata.shortname import roleToId
|
from metadata.shortname import roleToId
|
||||||
from modules.apihelper.client.components.remote import Remote
|
from modules.apihelper.client.components.remote import Remote
|
||||||
from modules.apihelper.models.genshin.calendar import Date, FinalAct, ActEnum, ActDetail, ActTime, BirthChar
|
from modules.apihelper.models.genshin.calendar import Date, FinalAct, ActEnum, ActDetail, ActTime, BirthChar
|
||||||
|
162
modules/apihelper/client/components/player_cards.py
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
from typing import List, Optional, Union, Dict
|
||||||
|
|
||||||
|
import ujson
|
||||||
|
from httpx import AsyncClient, TimeoutException
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from modules.playercards.fight_prop import EquipmentsStats
|
||||||
|
from modules.wiki.base import WikiModel
|
||||||
|
from modules.wiki.models.relic_affix import RelicAffixAll
|
||||||
|
from utils.enkanetwork import RedisCache
|
||||||
|
from modules.playercards.file import PlayerCardsFile
|
||||||
|
|
||||||
|
|
||||||
|
class Behavior(BaseModel):
|
||||||
|
BehaviorID: int
|
||||||
|
Level: int
|
||||||
|
|
||||||
|
|
||||||
|
class Equipment(BaseModel):
|
||||||
|
ID: Optional[int] = 0
|
||||||
|
Level: Optional[int] = 0
|
||||||
|
Promotion: Optional[int] = 3
|
||||||
|
"""星级"""
|
||||||
|
Rank: Optional[int] = 0
|
||||||
|
"""叠影"""
|
||||||
|
|
||||||
|
|
||||||
|
class SubAffix(BaseModel):
|
||||||
|
Cnt: Optional[int] = 1
|
||||||
|
Step: Optional[int] = 0
|
||||||
|
SubAffixID: int
|
||||||
|
|
||||||
|
|
||||||
|
class Relic(BaseModel):
|
||||||
|
ID: int
|
||||||
|
Level: Optional[int] = 0
|
||||||
|
MainAffixID: int
|
||||||
|
RelicSubAffix: Optional[List[SubAffix]]
|
||||||
|
Type: int
|
||||||
|
|
||||||
|
|
||||||
|
class Avatar(BaseModel):
|
||||||
|
AvatarID: int
|
||||||
|
BehaviorList: List[Behavior]
|
||||||
|
EquipmentID: Optional[Equipment]
|
||||||
|
Level: int
|
||||||
|
Promotion: Optional[int] = 4
|
||||||
|
Rank: Optional[int] = 0
|
||||||
|
RelicList: Optional[List[Relic]]
|
||||||
|
|
||||||
|
|
||||||
|
class ChallengeData(BaseModel):
|
||||||
|
MazeGroupID: Optional[int]
|
||||||
|
MazeGroupIndex: Optional[int]
|
||||||
|
PreMazeGroupIndex: Optional[int]
|
||||||
|
|
||||||
|
|
||||||
|
class PlayerSpaceInfo(BaseModel):
|
||||||
|
AchievementCount: Optional[int] = 0
|
||||||
|
AvatarCount: Optional[int] = 0
|
||||||
|
ChallengeData: ChallengeData
|
||||||
|
LightConeCount: Optional[int] = 0
|
||||||
|
PassAreaProgress: Optional[int] = 0
|
||||||
|
|
||||||
|
|
||||||
|
class PlayerInfo(BaseModel):
|
||||||
|
Birthday: Optional[int]
|
||||||
|
CurFriendCount: Optional[int]
|
||||||
|
AvatarList: List[Avatar]
|
||||||
|
HeadIconID: Optional[int]
|
||||||
|
IsDisplayAvatarList: bool
|
||||||
|
Level: int
|
||||||
|
NickName: str
|
||||||
|
PlayerSpaceInfo: PlayerSpaceInfo
|
||||||
|
Signature: Optional[str]
|
||||||
|
UID: int
|
||||||
|
WorldLevel: Optional[int]
|
||||||
|
|
||||||
|
|
||||||
|
class PlayerCardsError(Exception):
|
||||||
|
def __init__(self, msg):
|
||||||
|
self.msg = msg
|
||||||
|
|
||||||
|
|
||||||
|
class PlayerCards:
|
||||||
|
url = "https://mhy.fuckmys.tk/sr_info/"
|
||||||
|
prop_url = f"{WikiModel.BASE_URL}relic_config.json"
|
||||||
|
|
||||||
|
def __init__(self, redis):
|
||||||
|
self.cache = RedisCache(redis.client, key="plugin:player_cards:fake_enka_network", ex=60)
|
||||||
|
self.client = AsyncClient()
|
||||||
|
self.player_cards_file = PlayerCardsFile()
|
||||||
|
self.init = False
|
||||||
|
self.relic_datas_map: Dict[int, RelicAffixAll] = {}
|
||||||
|
|
||||||
|
async def async_init(self):
|
||||||
|
if self.init:
|
||||||
|
return
|
||||||
|
self.relic_datas_map.clear()
|
||||||
|
req = await self.client.get(self.prop_url)
|
||||||
|
data = req.json()
|
||||||
|
for i in data:
|
||||||
|
self.relic_datas_map[i["id"]] = RelicAffixAll(**i)
|
||||||
|
self.init = True
|
||||||
|
|
||||||
|
async def update_data(self, uid: str) -> Union[PlayerInfo, str]:
|
||||||
|
try:
|
||||||
|
data = await self.cache.get(uid)
|
||||||
|
if data is not None:
|
||||||
|
return PlayerInfo.parse_obj(data)
|
||||||
|
user = await self.client.get(self.url + uid, timeout=15)
|
||||||
|
if user.status_code != 200:
|
||||||
|
raise PlayerCardsError(f"请求异常,错误代码 {user.status_code}")
|
||||||
|
data = ujson.loads(user.text)
|
||||||
|
error_code = data.get("ErrCode", 0)
|
||||||
|
if error_code:
|
||||||
|
raise PlayerCardsError(f"请求异常,错误代码 {error_code}")
|
||||||
|
data = data.get("PlayerDetailInfo", {})
|
||||||
|
data = await self.player_cards_file.merge_info(uid, data)
|
||||||
|
await self.cache.set(uid, data)
|
||||||
|
return PlayerInfo.parse_obj(data)
|
||||||
|
except TimeoutException:
|
||||||
|
error = "服务请求超时,请稍后重试"
|
||||||
|
except PlayerCardsError as e:
|
||||||
|
error = e.msg
|
||||||
|
return error
|
||||||
|
|
||||||
|
def get_affix_by_id(self, cid: int) -> RelicAffixAll:
|
||||||
|
return self.relic_datas_map.get(cid)
|
||||||
|
|
||||||
|
def get_set_by_id(self, cid: int) -> int:
|
||||||
|
if affix := self.get_affix_by_id(cid):
|
||||||
|
return affix.set_id
|
||||||
|
return 101
|
||||||
|
|
||||||
|
def get_affix(self, relic: Relic, main: bool = True, sub: bool = True) -> List[EquipmentsStats]:
|
||||||
|
affix = self.get_affix_by_id(relic.ID)
|
||||||
|
if not affix:
|
||||||
|
return []
|
||||||
|
main_affix = affix.main_affix[str(relic.MainAffixID)]
|
||||||
|
datas = (
|
||||||
|
[
|
||||||
|
EquipmentsStats(
|
||||||
|
prop_id=main_affix.property,
|
||||||
|
prop_value=main_affix.get_value(relic.Level),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
if main
|
||||||
|
else []
|
||||||
|
)
|
||||||
|
if not sub:
|
||||||
|
return datas
|
||||||
|
if relic.RelicSubAffix:
|
||||||
|
for sub_a in relic.RelicSubAffix:
|
||||||
|
sub_affix = affix.sub_affix[str(sub_a.SubAffixID)]
|
||||||
|
datas.append(
|
||||||
|
EquipmentsStats(
|
||||||
|
prop_id=sub_affix.property,
|
||||||
|
prop_value=sub_affix.get_value(sub_a.Step, sub_a.Cnt),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return datas
|
@ -205,7 +205,6 @@ class GachaLog:
|
|||||||
except GachaLogMixedProvider as e:
|
except GachaLogMixedProvider as e:
|
||||||
raise GachaLogMixedProvider from e
|
raise GachaLogMixedProvider from e
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
breakpoint()
|
|
||||||
raise GachaLogException from exc
|
raise GachaLogException from exc
|
||||||
|
|
||||||
async def get_gacha_log_data(self, user_id: int, client: Client, authkey: str) -> int:
|
async def get_gacha_log_data(self, user_id: int, client: Client, authkey: str) -> int:
|
||||||
|
@ -1,68 +1,87 @@
|
|||||||
import enum
|
import functools
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from modules.wiki.models.enums import RelicAffix
|
||||||
|
|
||||||
|
relic_affix_map = {
|
||||||
|
RelicAffix.AttackAddedRatio: "攻击力百分比",
|
||||||
|
RelicAffix.AttackDelta: "攻击力",
|
||||||
|
RelicAffix.BreakDamageAddedRatioBase: "击破特攻",
|
||||||
|
RelicAffix.CriticalChanceBase: "暴击率百分比",
|
||||||
|
RelicAffix.CriticalDamageBase: "暴击伤害百分比",
|
||||||
|
RelicAffix.DefenceAddedRatio: "防御力百分比",
|
||||||
|
RelicAffix.DefenceDelta: "防御力",
|
||||||
|
RelicAffix.FireAddedRatio: "火属性伤害提高百分比",
|
||||||
|
RelicAffix.HPAddedRatio: "生命值百分比",
|
||||||
|
RelicAffix.HPDelta: "生命值",
|
||||||
|
RelicAffix.HealRatioBase: "治疗量加成百分比",
|
||||||
|
RelicAffix.IceAddedRatio: "冰属性伤害提高百分比",
|
||||||
|
RelicAffix.ImaginaryAddedRatio: "虚数属性伤害提高百分比",
|
||||||
|
RelicAffix.PhysicalAddedRatio: "物理属性伤害提高百分比",
|
||||||
|
RelicAffix.QuantumAddedRatio: "量子属性伤害提高百分比",
|
||||||
|
RelicAffix.SpeedDelta: "速度",
|
||||||
|
RelicAffix.SPRatioBase: "能量恢复效率百分比",
|
||||||
|
RelicAffix.StatusProbabilityBase: "效果命中百分比",
|
||||||
|
RelicAffix.StatusResistanceBase: "效果抵抗百分比",
|
||||||
|
RelicAffix.ThunderAddedRatio: "雷属性伤害提高百分比",
|
||||||
|
RelicAffix.WindAddedRatio: "风属性伤害提高百分比",
|
||||||
|
}
|
||||||
|
relic_affix_name_map = {v: k for k, v in relic_affix_map.items()}
|
||||||
|
relic_affix_score_map = {
|
||||||
|
RelicAffix.AttackAddedRatio: 1.0,
|
||||||
|
RelicAffix.AttackDelta: 0.5,
|
||||||
|
RelicAffix.BreakDamageAddedRatioBase: 1.0,
|
||||||
|
RelicAffix.CriticalChanceBase: 2.0,
|
||||||
|
RelicAffix.CriticalDamageBase: 2.0,
|
||||||
|
RelicAffix.DefenceAddedRatio: 1.0,
|
||||||
|
RelicAffix.DefenceDelta: 0.5,
|
||||||
|
RelicAffix.FireAddedRatio: 1.0,
|
||||||
|
RelicAffix.HPAddedRatio: 1.0,
|
||||||
|
RelicAffix.HPDelta: 0.5,
|
||||||
|
RelicAffix.HealRatioBase: 1.0,
|
||||||
|
RelicAffix.IceAddedRatio: 1.0,
|
||||||
|
RelicAffix.ImaginaryAddedRatio: 1.0,
|
||||||
|
RelicAffix.PhysicalAddedRatio: 1.0,
|
||||||
|
RelicAffix.QuantumAddedRatio: 1.0,
|
||||||
|
RelicAffix.SpeedDelta: 1.0,
|
||||||
|
RelicAffix.SPRatioBase: 1.0,
|
||||||
|
RelicAffix.StatusProbabilityBase: 1.0,
|
||||||
|
RelicAffix.StatusResistanceBase: 1.0,
|
||||||
|
RelicAffix.ThunderAddedRatio: 1.0,
|
||||||
|
RelicAffix.WindAddedRatio: 1.0,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class FightProp(enum.Enum):
|
# noinspection PyPep8Naming
|
||||||
BASE_HP = "基础血量"
|
@functools.lru_cache()
|
||||||
FIGHT_PROP_BASE_ATTACK = "基础攻击力"
|
def FightProp(prop: RelicAffix, percent: bool = True) -> str:
|
||||||
FIGHT_PROP_BASE_DEFENSE = "基础防御力"
|
name = relic_affix_map.get(prop)
|
||||||
FIGHT_PROP_BASE_HP = "基础血量"
|
return name if percent else name.replace("百分比", "")
|
||||||
FIGHT_PROP_ATTACK = "攻击力"
|
|
||||||
FIGHT_PROP_ATTACK_PERCENT = "攻击力百分比"
|
|
||||||
FIGHT_PROP_HP = "生命值"
|
|
||||||
FIGHT_PROP_HP_PERCENT = "生命值百分比"
|
|
||||||
FIGHT_PROP_DEFENSE = "防御力"
|
|
||||||
FIGHT_PROP_DEFENSE_PERCENT = "防御力百分比"
|
|
||||||
FIGHT_PROP_ELEMENT_MASTERY = "元素精通"
|
|
||||||
FIGHT_PROP_CRITICAL = "暴击率"
|
|
||||||
FIGHT_PROP_CRITICAL_HURT = "暴击伤害"
|
|
||||||
FIGHT_PROP_CHARGE_EFFICIENCY = "元素充能效率"
|
|
||||||
FIGHT_PROP_FIRE_SUB_HURT = "火元素抗性"
|
|
||||||
FIGHT_PROP_ELEC_SUB_HURT = "雷元素抗性"
|
|
||||||
FIGHT_PROP_ICE_SUB_HURT = "冰元素抗性"
|
|
||||||
FIGHT_PROP_WATER_SUB_HURT = "水元素抗性"
|
|
||||||
FIGHT_PROP_WIND_SUB_HURT = "风元素抗性"
|
|
||||||
FIGHT_PROP_ROCK_SUB_HURT = "岩元素抗性"
|
|
||||||
FIGHT_PROP_GRASS_SUB_HURT = "草元素抗性"
|
|
||||||
FIGHT_PROP_FIRE_ADD_HURT = "火元素伤害加成"
|
|
||||||
FIGHT_PROP_ELEC_ADD_HURT = "雷元素伤害加成"
|
|
||||||
FIGHT_PROP_ICE_ADD_HURT = "冰元素伤害加成"
|
|
||||||
FIGHT_PROP_WATER_ADD_HURT = "水元素伤害加成"
|
|
||||||
FIGHT_PROP_WIND_ADD_HURT = "风元素伤害加成"
|
|
||||||
FIGHT_PROP_ROCK_ADD_HURT = "岩元素伤害加成"
|
|
||||||
FIGHT_PROP_GRASS_ADD_HURT = "草元素伤害加成"
|
|
||||||
FIGHT_PROP_PHYSICAL_ADD_HURT = "物理伤害加成"
|
|
||||||
FIGHT_PROP_HEAL_ADD = "治疗加成"
|
|
||||||
|
|
||||||
|
|
||||||
class FightPropScore(enum.Enum):
|
# noinspection PyPep8Naming
|
||||||
_value_: float
|
@functools.lru_cache()
|
||||||
value: float
|
def nameToFightProp(name: str) -> RelicAffix:
|
||||||
FIGHT_PROP_BASE_ATTACK = 1
|
return relic_affix_name_map.get(name)
|
||||||
FIGHT_PROP_BASE_DEFENSE = 1
|
|
||||||
FIGHT_PROP_BASE_HP = 1
|
|
||||||
FIGHT_PROP_ATTACK = 662 / 3110 # 攻击力
|
# noinspection PyPep8Naming
|
||||||
FIGHT_PROP_ATTACK_PERCENT = 4 / 3 # 攻击力百分比
|
@functools.lru_cache()
|
||||||
FIGHT_PROP_HP = 662 / 47800 # 生命
|
def FightPropScore(prop) -> float:
|
||||||
FIGHT_PROP_HP_PERCENT = 4 / 3 # 生命百分比
|
return relic_affix_score_map.get(prop)
|
||||||
FIGHT_PROP_DEFENSE = 662 / 3890 # 防御力
|
|
||||||
FIGHT_PROP_DEFENSE_PERCENT = 662 / 583 # 防御力百分比
|
|
||||||
FIGHT_PROP_ELEMENT_MASTERY = 1 / 3 # 元素精通
|
class EquipmentsStats(BaseModel):
|
||||||
FIGHT_PROP_CRITICAL = 2 # 暴击率
|
prop_id: RelicAffix
|
||||||
FIGHT_PROP_CRITICAL_HURT = 1 # 暴击伤害
|
prop_value: float
|
||||||
FIGHT_PROP_CHARGE_EFFICIENCY = 662 / 518 # 元素充能效率
|
|
||||||
FIGHT_PROP_FIRE_SUB_HURT = 1
|
@property
|
||||||
FIGHT_PROP_ELEC_SUB_HURT = 1
|
def name(self) -> str:
|
||||||
FIGHT_PROP_ICE_SUB_HURT = 1
|
return FightProp(self.prop_id, False)
|
||||||
FIGHT_PROP_WATER_SUB_HURT = 1
|
|
||||||
FIGHT_PROP_WIND_SUB_HURT = 1
|
@property
|
||||||
FIGHT_PROP_ROCK_SUB_HURT = 1
|
def value(self) -> str:
|
||||||
FIGHT_PROP_GRASS_SUB_HURT = 1
|
return (
|
||||||
FIGHT_PROP_FIRE_ADD_HURT = 1
|
str(round(self.prop_value, 1)) if self.prop_value > 1 else str(str(round(self.prop_value * 100.0, 1)) + "%")
|
||||||
FIGHT_PROP_ELEC_ADD_HURT = 1
|
)
|
||||||
FIGHT_PROP_ICE_ADD_HURT = 1
|
|
||||||
FIGHT_PROP_WATER_ADD_HURT = 1
|
|
||||||
FIGHT_PROP_WIND_ADD_HURT = 1
|
|
||||||
FIGHT_PROP_ROCK_ADD_HURT = 1
|
|
||||||
FIGHT_PROP_GRASS_ADD_HURT = 1
|
|
||||||
FIGHT_PROP_PHYSICAL_ADD_HURT = 1
|
|
||||||
FIGHT_PROP_HEAL_ADD = 1
|
|
||||||
|
@ -63,12 +63,25 @@ class PlayerCardsFile:
|
|||||||
async with self._lock:
|
async with self._lock:
|
||||||
old_data = await self.load_history_info(uid)
|
old_data = await self.load_history_info(uid)
|
||||||
if old_data is None:
|
if old_data is None:
|
||||||
await self.save_json(self.get_file_path(uid), data)
|
old_data = {}
|
||||||
return data
|
avatars = []
|
||||||
data["avatarInfoList"] = data.get("avatarInfoList", [])
|
avatar_ids = []
|
||||||
characters = [i.get("avatarId", 0) for i in data["avatarInfoList"]]
|
assist_avatar = data.get("AssistAvatar", None)
|
||||||
for i in old_data.get("avatarInfoList", []):
|
if assist_avatar:
|
||||||
if i.get("avatarId", 0) not in characters:
|
avatars.append(assist_avatar)
|
||||||
data["avatarInfoList"].append(i)
|
avatar_ids.append(assist_avatar.get("AvatarID", 0))
|
||||||
|
for avatar in data.get("DisplayAvatarList", []):
|
||||||
|
if avatar.get("AvatarID", 0) in avatar_ids:
|
||||||
|
continue
|
||||||
|
avatars.append(avatar)
|
||||||
|
avatar_ids.append(avatar.get("AvatarID", 0))
|
||||||
|
data["AvatarList"] = avatars
|
||||||
|
if "AssistAvatar" in data:
|
||||||
|
del data["AssistAvatar"]
|
||||||
|
if "DisplayAvatarList" in data:
|
||||||
|
del data["DisplayAvatarList"]
|
||||||
|
for i in old_data.get("AvatarList", []):
|
||||||
|
if i.get("AvatarID", 0) not in avatar_ids:
|
||||||
|
data["AvatarList"].append(i)
|
||||||
await self.save_json(self.get_file_path(uid), data)
|
await self.save_json(self.get_file_path(uid), data)
|
||||||
return data
|
return data
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
import ujson as json
|
import ujson as json
|
||||||
from enkanetwork import EquipmentsStats
|
|
||||||
|
|
||||||
from modules.playercards.fight_prop import FightProp, FightPropScore
|
from modules.playercards.fight_prop import FightPropScore, EquipmentsStats, nameToFightProp
|
||||||
|
from modules.wiki.models.enums import RelicAffix
|
||||||
|
|
||||||
_project_path = os.path.dirname(__file__)
|
_project_path = os.path.dirname(__file__)
|
||||||
_fight_prop_rule_file = os.path.join(_project_path, "metadata", "FightPropRule.json")
|
_fight_prop_rule_file = os.path.join(_project_path, "metadata", "FightPropRule.json")
|
||||||
@ -15,32 +15,23 @@ class ArtifactStatsTheory:
|
|||||||
def __init__(self, character_name: str):
|
def __init__(self, character_name: str):
|
||||||
self.character_name = character_name
|
self.character_name = character_name
|
||||||
fight_prop_rule_list = fight_prop_rule_data.get(self.character_name, [])
|
fight_prop_rule_list = fight_prop_rule_data.get(self.character_name, [])
|
||||||
self.main_prop = [FightProp(fight_prop_rule) for fight_prop_rule in fight_prop_rule_list]
|
self.main_prop = [nameToFightProp(fight_prop_rule) for fight_prop_rule in fight_prop_rule_list]
|
||||||
if not self.main_prop:
|
if not self.main_prop:
|
||||||
self.main_prop = [
|
self.main_prop = [
|
||||||
FightProp.FIGHT_PROP_CRITICAL,
|
RelicAffix.CriticalChanceBase,
|
||||||
FightProp.FIGHT_PROP_CRITICAL_HURT,
|
RelicAffix.CriticalDamageBase,
|
||||||
FightProp.FIGHT_PROP_ATTACK_PERCENT,
|
RelicAffix.AttackAddedRatio,
|
||||||
]
|
]
|
||||||
# 修正要评分的数值词条
|
|
||||||
if FightProp.FIGHT_PROP_ATTACK_PERCENT in self.main_prop and FightProp.FIGHT_PROP_ATTACK not in self.main_prop:
|
|
||||||
self.main_prop.append(FightProp.FIGHT_PROP_ATTACK)
|
|
||||||
if FightProp.FIGHT_PROP_HP_PERCENT in self.main_prop and FightProp.FIGHT_PROP_HP not in self.main_prop:
|
|
||||||
self.main_prop.append(FightProp.FIGHT_PROP_HP)
|
|
||||||
if (
|
|
||||||
FightProp.FIGHT_PROP_DEFENSE_PERCENT in self.main_prop
|
|
||||||
and FightProp.FIGHT_PROP_DEFENSE not in self.main_prop
|
|
||||||
):
|
|
||||||
self.main_prop.append(FightProp.FIGHT_PROP_DEFENSE)
|
|
||||||
|
|
||||||
def theory(self, sub_stats: EquipmentsStats) -> float:
|
def theory(self, sub_stats: EquipmentsStats) -> float:
|
||||||
"""圣遗物副词条评分
|
"""圣遗物词条评分
|
||||||
Args:
|
Args:
|
||||||
sub_stats: 圣遗物对象
|
sub_stats: 圣遗物对象
|
||||||
Returns:
|
Returns:
|
||||||
返回得分
|
返回得分
|
||||||
"""
|
"""
|
||||||
score: float = 0
|
score: float = 0
|
||||||
if sub_stats.prop_id in map(lambda x: x.name, self.main_prop):
|
if sub_stats.prop_id in self.main_prop:
|
||||||
score = float(FightPropScore[sub_stats.prop_id].value) * sub_stats.value
|
base_value = 100.0 if sub_stats.prop_value < 1 else 1.0
|
||||||
|
score = float(FightPropScore(sub_stats.prop_id) * sub_stats.prop_value * base_value)
|
||||||
return round(score, 1)
|
return round(score, 1)
|
||||||
|
@ -1,411 +1,125 @@
|
|||||||
{
|
{
|
||||||
"旅行者": [
|
"丹恒": [
|
||||||
"攻击力百分比",
|
"攻击力百分比",
|
||||||
"暴击率",
|
"暴击率百分比",
|
||||||
"暴击伤害",
|
"暴击伤害百分比",
|
||||||
"元素充能效率",
|
"风属性伤害提高百分比"
|
||||||
"元素精通"
|
|
||||||
],
|
],
|
||||||
"安柏": [
|
"彦卿": [
|
||||||
"攻击力百分比",
|
"攻击力百分比",
|
||||||
"暴击率",
|
"暴击率百分比",
|
||||||
"暴击伤害",
|
"暴击伤害百分比",
|
||||||
"元素精通"
|
"冰属性伤害提高百分比"
|
||||||
],
|
],
|
||||||
"凯亚": [
|
"希儿": [
|
||||||
"攻击力百分比",
|
"攻击力百分比",
|
||||||
"暴击率",
|
"暴击率百分比",
|
||||||
"暴击伤害",
|
"暴击伤害百分比",
|
||||||
"元素充能效率"
|
"量子属性伤害提高百分比",
|
||||||
|
"速度"
|
||||||
],
|
],
|
||||||
"丽莎": [
|
"姬子": [
|
||||||
"攻击力百分比",
|
"攻击力百分比",
|
||||||
"暴击率",
|
"暴击率百分比",
|
||||||
"暴击伤害",
|
"暴击伤害百分比",
|
||||||
"元素精通"
|
"火属性伤害提高百分比"
|
||||||
],
|
],
|
||||||
"芭芭拉": [
|
"阿兰": [
|
||||||
"生命值百分比",
|
|
||||||
"元素充能效率",
|
|
||||||
"元素精通"
|
|
||||||
],
|
|
||||||
"芭芭拉-核爆": [
|
|
||||||
"攻击力百分比",
|
"攻击力百分比",
|
||||||
"暴击率",
|
"暴击率百分比",
|
||||||
"暴击伤害",
|
"暴击伤害百分比",
|
||||||
"元素精通"
|
"雷属性伤害提高百分比"
|
||||||
],
|
],
|
||||||
"雷泽": [
|
"黑塔": [
|
||||||
"攻击力百分比",
|
"攻击力百分比",
|
||||||
"暴击率",
|
"暴击率百分比",
|
||||||
"暴击伤害",
|
"暴击伤害百分比",
|
||||||
"物理伤害加成"
|
"冰属性伤害提高百分比"
|
||||||
],
|
],
|
||||||
"香菱": [
|
"希露瓦": [
|
||||||
"攻击力百分比",
|
"攻击力百分比",
|
||||||
"暴击率",
|
"暴击率百分比",
|
||||||
"暴击伤害",
|
"暴击伤害百分比",
|
||||||
"元素精通",
|
"雷属性伤害提高百分比"
|
||||||
"元素充能效率"
|
|
||||||
],
|
],
|
||||||
"北斗": [
|
"克拉拉": [
|
||||||
"攻击力百分比",
|
"攻击力百分比",
|
||||||
"暴击率",
|
"暴击率百分比",
|
||||||
"暴击伤害",
|
"暴击伤害百分比",
|
||||||
"元素充能效率"
|
"物理属性伤害提高百分比"
|
||||||
],
|
],
|
||||||
"行秋": [
|
"虎克": [
|
||||||
"攻击力百分比",
|
"攻击力百分比",
|
||||||
"暴击率",
|
"暴击率百分比",
|
||||||
"暴击伤害",
|
"暴击伤害百分比",
|
||||||
"元素充能效率"
|
"火属性伤害提高百分比"
|
||||||
],
|
],
|
||||||
"凝光": [
|
"青雀": [
|
||||||
"攻击力百分比",
|
"攻击力百分比",
|
||||||
"暴击率",
|
"暴击率百分比",
|
||||||
"暴击伤害",
|
"暴击伤害百分比",
|
||||||
"元素充能效率"
|
"量子属性伤害提高百分比"
|
||||||
],
|
],
|
||||||
"菲谢尔": [
|
"素裳": [
|
||||||
"攻击力百分比",
|
"攻击力百分比",
|
||||||
"暴击率",
|
"暴击率百分比",
|
||||||
"暴击伤害",
|
"暴击伤害百分比",
|
||||||
"元素精通"
|
"物理属性伤害提高百分比"
|
||||||
],
|
],
|
||||||
"班尼特": [
|
"杰帕德": [
|
||||||
"生命值百分比",
|
|
||||||
"元素充能效率",
|
|
||||||
"治疗加成"
|
|
||||||
],
|
|
||||||
"诺艾尔": [
|
|
||||||
"攻击力百分比",
|
|
||||||
"防御力百分比",
|
"防御力百分比",
|
||||||
"暴击率",
|
"防御力",
|
||||||
"暴击伤害",
|
"能量恢复效率百分比"
|
||||||
"元素充能效率"
|
|
||||||
],
|
],
|
||||||
"重云": [
|
"三月七": [
|
||||||
"攻击力百分比",
|
|
||||||
"暴击率",
|
|
||||||
"暴击伤害",
|
|
||||||
"元素精通",
|
|
||||||
"元素充能效率"
|
|
||||||
],
|
|
||||||
"砂糖": [
|
|
||||||
"攻击力百分比",
|
|
||||||
"暴击率",
|
|
||||||
"暴击伤害",
|
|
||||||
"元素精通",
|
|
||||||
"元素充能效率"
|
|
||||||
],
|
|
||||||
"琴": [
|
|
||||||
"攻击力百分比",
|
|
||||||
"暴击率",
|
|
||||||
"暴击伤害",
|
|
||||||
"物理伤害加成",
|
|
||||||
"元素充能效率",
|
|
||||||
"治疗加成"
|
|
||||||
],
|
|
||||||
"迪卢克": [
|
|
||||||
"攻击力百分比",
|
|
||||||
"暴击率",
|
|
||||||
"暴击伤害",
|
|
||||||
"元素精通"
|
|
||||||
],
|
|
||||||
"七七": [
|
|
||||||
"攻击力百分比",
|
|
||||||
"暴击率",
|
|
||||||
"暴击伤害",
|
|
||||||
"物理伤害加成",
|
|
||||||
"元素充能效率",
|
|
||||||
"治疗加成"
|
|
||||||
],
|
|
||||||
"莫娜": [
|
|
||||||
"攻击力百分比",
|
|
||||||
"暴击率",
|
|
||||||
"暴击伤害",
|
|
||||||
"元素精通",
|
|
||||||
"元素充能效率"
|
|
||||||
],
|
|
||||||
"刻晴": [
|
|
||||||
"攻击力百分比",
|
|
||||||
"暴击率",
|
|
||||||
"暴击伤害",
|
|
||||||
"元素精通",
|
|
||||||
"物理伤害加成"
|
|
||||||
],
|
|
||||||
"温迪": [
|
|
||||||
"攻击力百分比",
|
|
||||||
"暴击率",
|
|
||||||
"暴击伤害",
|
|
||||||
"元素精通",
|
|
||||||
"元素充能效率"
|
|
||||||
],
|
|
||||||
"可莉": [
|
|
||||||
"攻击力百分比",
|
|
||||||
"暴击率",
|
|
||||||
"暴击伤害",
|
|
||||||
"元素精通",
|
|
||||||
"元素充能效率"
|
|
||||||
],
|
|
||||||
"迪奥娜": [
|
|
||||||
"生命值百分比",
|
|
||||||
"元素充能效率",
|
|
||||||
"治疗加成"
|
|
||||||
],
|
|
||||||
"达达利亚": [
|
|
||||||
"攻击力百分比",
|
|
||||||
"暴击率",
|
|
||||||
"暴击伤害",
|
|
||||||
"元素精通",
|
|
||||||
"元素充能效率"
|
|
||||||
],
|
|
||||||
"辛焱": [
|
|
||||||
"防御力百分比",
|
"防御力百分比",
|
||||||
"攻击力百分比",
|
"防御力",
|
||||||
"暴击率",
|
"能量恢复效率百分比"
|
||||||
"暴击伤害",
|
|
||||||
"物理伤害加成"
|
|
||||||
],
|
],
|
||||||
"钟离": [
|
"瓦尔特": [
|
||||||
|
"虚数属性伤害提高百分比",
|
||||||
|
"速度",
|
||||||
|
"能量恢复效率百分比",
|
||||||
|
"效果命中百分比"
|
||||||
|
],
|
||||||
|
"桑博": [
|
||||||
|
"速度",
|
||||||
|
"能量恢复效率百分比",
|
||||||
|
"效果命中百分比",
|
||||||
|
"风属性伤害提高百分比"
|
||||||
|
],
|
||||||
|
"佩拉": [
|
||||||
|
"冰属性伤害提高百分比",
|
||||||
|
"速度",
|
||||||
|
"能量恢复效率百分比",
|
||||||
|
"效果命中百分比"
|
||||||
|
],
|
||||||
|
"停云": [
|
||||||
|
"攻击力百分比",
|
||||||
|
"速度",
|
||||||
|
"能量恢复效率百分比",
|
||||||
|
"雷属性伤害提高百分比"
|
||||||
|
],
|
||||||
|
"艾丝妲": [
|
||||||
|
"攻击力百分比",
|
||||||
|
"火属性伤害提高百分比",
|
||||||
|
"速度",
|
||||||
|
"能量恢复效率百分比"
|
||||||
|
],
|
||||||
|
"白露": [
|
||||||
"生命值百分比",
|
"生命值百分比",
|
||||||
"攻击力百分比",
|
"生命值",
|
||||||
"暴击率",
|
"治疗量加成百分比"
|
||||||
"暴击伤害",
|
|
||||||
"物理伤害加成",
|
|
||||||
"元素充能效率"
|
|
||||||
],
|
],
|
||||||
"钟离-安如磐石": [
|
"娜塔莎": [
|
||||||
"生命值百分比",
|
"生命值百分比",
|
||||||
"暴击率"
|
"生命值",
|
||||||
|
"治疗量加成百分比"
|
||||||
],
|
],
|
||||||
"阿贝多": [
|
"布洛妮娅": [
|
||||||
"防御力百分比",
|
"暴击伤害百分比",
|
||||||
"暴击率",
|
"速度",
|
||||||
"暴击伤害"
|
"能量恢复效率百分比",
|
||||||
],
|
"风属性伤害提高百分比"
|
||||||
"甘雨": [
|
|
||||||
"攻击力百分比",
|
|
||||||
"暴击率",
|
|
||||||
"暴击伤害",
|
|
||||||
"元素精通"
|
|
||||||
],
|
|
||||||
"甘雨-永冻": [
|
|
||||||
"攻击力百分比",
|
|
||||||
"暴击率",
|
|
||||||
"暴击伤害",
|
|
||||||
"元素充能效率"
|
|
||||||
],
|
|
||||||
"魈": [
|
|
||||||
"攻击力百分比",
|
|
||||||
"暴击率",
|
|
||||||
"暴击伤害",
|
|
||||||
"元素充能效率"
|
|
||||||
],
|
|
||||||
"胡桃": [
|
|
||||||
"生命值百分比",
|
|
||||||
"暴击率",
|
|
||||||
"暴击伤害",
|
|
||||||
"元素精通"
|
|
||||||
],
|
|
||||||
"罗莎莉亚": [
|
|
||||||
"攻击力百分比",
|
|
||||||
"暴击率",
|
|
||||||
"暴击伤害",
|
|
||||||
"物理伤害加成",
|
|
||||||
"元素充能效率"
|
|
||||||
],
|
|
||||||
"烟绯": [
|
|
||||||
"攻击力百分比",
|
|
||||||
"暴击率",
|
|
||||||
"暴击伤害",
|
|
||||||
"元素精通",
|
|
||||||
"元素充能效率"
|
|
||||||
],
|
|
||||||
"优菈": [
|
|
||||||
"攻击力百分比",
|
|
||||||
"暴击率",
|
|
||||||
"暴击伤害",
|
|
||||||
"物理伤害加成",
|
|
||||||
"元素充能效率"
|
|
||||||
],
|
|
||||||
"枫原万叶": [
|
|
||||||
"攻击力百分比",
|
|
||||||
"暴击率",
|
|
||||||
"暴击伤害",
|
|
||||||
"元素精通",
|
|
||||||
"元素充能效率"
|
|
||||||
],
|
|
||||||
"神里绫华": [
|
|
||||||
"攻击力百分比",
|
|
||||||
"暴击率",
|
|
||||||
"暴击伤害",
|
|
||||||
"元素充能效率"
|
|
||||||
],
|
|
||||||
"早柚": [
|
|
||||||
"攻击力百分比",
|
|
||||||
"暴击率",
|
|
||||||
"暴击伤害",
|
|
||||||
"元素精通",
|
|
||||||
"元素充能效率",
|
|
||||||
"治疗加成"
|
|
||||||
],
|
|
||||||
"宵宫": [
|
|
||||||
"攻击力百分比",
|
|
||||||
"暴击率",
|
|
||||||
"暴击伤害",
|
|
||||||
"元素精通"
|
|
||||||
],
|
|
||||||
"埃洛伊": [
|
|
||||||
"攻击力百分比",
|
|
||||||
"暴击率",
|
|
||||||
"暴击伤害"
|
|
||||||
],
|
|
||||||
"九条裟罗": [
|
|
||||||
"攻击力百分比",
|
|
||||||
"暴击率",
|
|
||||||
"暴击伤害",
|
|
||||||
"元素充能效率"
|
|
||||||
],
|
|
||||||
"雷电将军": [
|
|
||||||
"攻击力百分比",
|
|
||||||
"暴击率",
|
|
||||||
"暴击伤害",
|
|
||||||
"元素充能效率",
|
|
||||||
"元素精通"
|
|
||||||
],
|
|
||||||
"珊瑚宫心海": [
|
|
||||||
"生命值百分比",
|
|
||||||
"攻击力百分比",
|
|
||||||
"元素充能效率",
|
|
||||||
"治疗加成",
|
|
||||||
"元素精通"
|
|
||||||
],
|
|
||||||
"托马": [
|
|
||||||
"生命值百分比",
|
|
||||||
"暴击率",
|
|
||||||
"元素充能效率",
|
|
||||||
"元素精通"
|
|
||||||
],
|
|
||||||
"五郎": [
|
|
||||||
"防御力百分比",
|
|
||||||
"暴击率",
|
|
||||||
"元素充能效率"
|
|
||||||
],
|
|
||||||
"荒泷一斗": [
|
|
||||||
"防御力百分比",
|
|
||||||
"暴击率",
|
|
||||||
"暴击伤害",
|
|
||||||
"元素充能效率"
|
|
||||||
],
|
|
||||||
"云堇": [
|
|
||||||
"防御力百分比",
|
|
||||||
"暴击率",
|
|
||||||
"元素充能效率"
|
|
||||||
],
|
|
||||||
"申鹤": [
|
|
||||||
"攻击力百分比",
|
|
||||||
"暴击率",
|
|
||||||
"暴击伤害",
|
|
||||||
"元素充能效率"
|
|
||||||
],
|
|
||||||
"八重神子": [
|
|
||||||
"攻击力百分比",
|
|
||||||
"暴击率",
|
|
||||||
"暴击伤害",
|
|
||||||
"元素精通",
|
|
||||||
"元素充能效率"
|
|
||||||
],
|
|
||||||
"神里绫人": [
|
|
||||||
"生命值百分比",
|
|
||||||
"攻击力百分比",
|
|
||||||
"暴击率",
|
|
||||||
"暴击伤害",
|
|
||||||
"元素精通",
|
|
||||||
"元素充能效率"
|
|
||||||
],
|
|
||||||
"夜兰": [
|
|
||||||
"生命值百分比",
|
|
||||||
"暴击率",
|
|
||||||
"暴击伤害",
|
|
||||||
"元素精通",
|
|
||||||
"元素充能效率"
|
|
||||||
],
|
|
||||||
"久岐忍": [
|
|
||||||
"生命值百分比",
|
|
||||||
"元素精通",
|
|
||||||
"治疗加成"
|
|
||||||
],
|
|
||||||
"鹿野院平藏": [
|
|
||||||
"攻击力百分比",
|
|
||||||
"暴击率",
|
|
||||||
"暴击伤害",
|
|
||||||
"元素充能效率"
|
|
||||||
],
|
|
||||||
"提纳里": [
|
|
||||||
"攻击力百分比",
|
|
||||||
"暴击率",
|
|
||||||
"暴击伤害",
|
|
||||||
"元素精通",
|
|
||||||
"元素充能效率"
|
|
||||||
],
|
|
||||||
"柯莱": [
|
|
||||||
"攻击力百分比",
|
|
||||||
"暴击率",
|
|
||||||
"暴击伤害",
|
|
||||||
"元素精通",
|
|
||||||
"元素充能效率"
|
|
||||||
],
|
|
||||||
"赛诺": [
|
|
||||||
"攻击力百分比",
|
|
||||||
"暴击率",
|
|
||||||
"暴击伤害",
|
|
||||||
"元素精通",
|
|
||||||
"元素充能效率"
|
|
||||||
],
|
|
||||||
"妮露": [
|
|
||||||
"生命值百分比",
|
|
||||||
"暴击率",
|
|
||||||
"暴击伤害",
|
|
||||||
"元素精通",
|
|
||||||
"元素充能效率"
|
|
||||||
],
|
|
||||||
"纳西妲": [
|
|
||||||
"暴击率",
|
|
||||||
"暴击伤害",
|
|
||||||
"元素精通",
|
|
||||||
"元素充能效率",
|
|
||||||
"攻击力百分比"
|
|
||||||
],
|
|
||||||
"流浪者": [
|
|
||||||
"攻击力百分比",
|
|
||||||
"暴击率",
|
|
||||||
"暴击伤害",
|
|
||||||
"元素充能效率"
|
|
||||||
],
|
|
||||||
"珐露珊": [
|
|
||||||
"攻击力百分比",
|
|
||||||
"暴击率",
|
|
||||||
"暴击伤害",
|
|
||||||
"元素充能效率"
|
|
||||||
],
|
|
||||||
"艾尔海森": [
|
|
||||||
"攻击力百分比",
|
|
||||||
"暴击率",
|
|
||||||
"暴击伤害",
|
|
||||||
"元素精通",
|
|
||||||
"元素充能效率"
|
|
||||||
],
|
|
||||||
"瑶瑶": [
|
|
||||||
"生命值百分比",
|
|
||||||
"元素充能效率"
|
|
||||||
],
|
|
||||||
"迪希雅": [
|
|
||||||
"生命值百分比",
|
|
||||||
"攻击力百分比",
|
|
||||||
"暴击率",
|
|
||||||
"暴击伤害",
|
|
||||||
"元素精通",
|
|
||||||
"元素充能效率"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
@ -37,10 +37,10 @@ class Character(WikiModel):
|
|||||||
self.all_avatars_name[m.name] = m
|
self.all_avatars_name[m.name] = m
|
||||||
|
|
||||||
def get_by_id(self, cid: int) -> Optional[Avatar]:
|
def get_by_id(self, cid: int) -> Optional[Avatar]:
|
||||||
return self.all_avatars_map.get(cid, None)
|
return self.all_avatars_map.get(cid)
|
||||||
|
|
||||||
def get_by_name(self, name: str) -> Optional[Avatar]:
|
def get_by_name(self, name: str) -> Optional[Avatar]:
|
||||||
return self.all_avatars_name.get(name, None)
|
return self.all_avatars_name.get(name)
|
||||||
|
|
||||||
def get_name_list(self) -> List[str]:
|
def get_name_list(self) -> List[str]:
|
||||||
return list(self.all_avatars_name.keys())
|
return list(self.all_avatars_name.keys())
|
||||||
|
@ -37,10 +37,10 @@ class LightCone(WikiModel):
|
|||||||
self.all_light_cones_name[m.name] = m
|
self.all_light_cones_name[m.name] = m
|
||||||
|
|
||||||
def get_by_id(self, cid: int) -> Optional[LightConeModel]:
|
def get_by_id(self, cid: int) -> Optional[LightConeModel]:
|
||||||
return self.all_light_cones_map.get(cid, None)
|
return self.all_light_cones_map.get(cid)
|
||||||
|
|
||||||
def get_by_name(self, name: str) -> Optional[LightConeModel]:
|
def get_by_name(self, name: str) -> Optional[LightConeModel]:
|
||||||
return self.all_light_cones_name.get(name, None)
|
return self.all_light_cones_name.get(name)
|
||||||
|
|
||||||
def get_name_list(self) -> List[str]:
|
def get_name_list(self) -> List[str]:
|
||||||
return list(self.all_light_cones_name.keys())
|
return list(self.all_light_cones_name.keys())
|
||||||
|
@ -37,10 +37,10 @@ class Material(WikiModel):
|
|||||||
self.all_materials_name[m.name] = m
|
self.all_materials_name[m.name] = m
|
||||||
|
|
||||||
def get_by_id(self, cid: int) -> Optional[MaterialModel]:
|
def get_by_id(self, cid: int) -> Optional[MaterialModel]:
|
||||||
return self.all_materials_map.get(cid, None)
|
return self.all_materials_map.get(cid)
|
||||||
|
|
||||||
def get_by_name(self, name: str) -> Optional[MaterialModel]:
|
def get_by_name(self, name: str) -> Optional[MaterialModel]:
|
||||||
return self.all_materials_name.get(name, None)
|
return self.all_materials_name.get(name)
|
||||||
|
|
||||||
def get_name_list(self) -> List[str]:
|
def get_name_list(self) -> List[str]:
|
||||||
return list(self.all_materials_name.keys())
|
return list(self.all_materials_name.keys())
|
||||||
|
@ -2,7 +2,8 @@ from enum import Enum
|
|||||||
|
|
||||||
|
|
||||||
class Quality(str, Enum):
|
class Quality(str, Enum):
|
||||||
""" 星级 """
|
"""星级"""
|
||||||
|
|
||||||
Five = "五星"
|
Five = "五星"
|
||||||
Four = "四星"
|
Four = "四星"
|
||||||
Three = "三星"
|
Three = "三星"
|
||||||
@ -11,7 +12,8 @@ class Quality(str, Enum):
|
|||||||
|
|
||||||
|
|
||||||
class Destiny(str, Enum):
|
class Destiny(str, Enum):
|
||||||
""" 命途 """
|
"""命途"""
|
||||||
|
|
||||||
HuiMie = "毁灭"
|
HuiMie = "毁灭"
|
||||||
ZhiShi = "智识"
|
ZhiShi = "智识"
|
||||||
XunLie = "巡猎"
|
XunLie = "巡猎"
|
||||||
@ -22,7 +24,8 @@ class Destiny(str, Enum):
|
|||||||
|
|
||||||
|
|
||||||
class Element(str, Enum):
|
class Element(str, Enum):
|
||||||
""" 属性 """
|
"""属性"""
|
||||||
|
|
||||||
Physical = "物理"
|
Physical = "物理"
|
||||||
Pyro = "火"
|
Pyro = "火"
|
||||||
Anemo = "风"
|
Anemo = "风"
|
||||||
@ -35,7 +38,8 @@ class Element(str, Enum):
|
|||||||
|
|
||||||
|
|
||||||
class MonsterType(str, Enum):
|
class MonsterType(str, Enum):
|
||||||
""" 怪物种类 """
|
"""怪物种类"""
|
||||||
|
|
||||||
Normal = "普通"
|
Normal = "普通"
|
||||||
Elite = "精英"
|
Elite = "精英"
|
||||||
Leader = "首领"
|
Leader = "首领"
|
||||||
@ -43,7 +47,8 @@ class MonsterType(str, Enum):
|
|||||||
|
|
||||||
|
|
||||||
class Area(str, Enum):
|
class Area(str, Enum):
|
||||||
""" 地区 """
|
"""地区"""
|
||||||
|
|
||||||
Herta = "空间站「黑塔」"
|
Herta = "空间站「黑塔」"
|
||||||
YaLiLuo = "雅利洛-VI"
|
YaLiLuo = "雅利洛-VI"
|
||||||
LuoFu = "仙舟「罗浮」"
|
LuoFu = "仙舟「罗浮」"
|
||||||
@ -51,7 +56,8 @@ class Area(str, Enum):
|
|||||||
|
|
||||||
|
|
||||||
class MaterialType(str, Enum):
|
class MaterialType(str, Enum):
|
||||||
""" 材料类型 """
|
"""材料类型"""
|
||||||
|
|
||||||
AvatarUpdate = "角色晋阶材料"
|
AvatarUpdate = "角色晋阶材料"
|
||||||
XingJi = "行迹材料"
|
XingJi = "行迹材料"
|
||||||
LightConeUpdate = "光锥晋阶材料"
|
LightConeUpdate = "光锥晋阶材料"
|
||||||
@ -66,7 +72,8 @@ class MaterialType(str, Enum):
|
|||||||
|
|
||||||
|
|
||||||
class PropType(str, Enum):
|
class PropType(str, Enum):
|
||||||
""" 遗器套装效果 """
|
"""遗器套装效果"""
|
||||||
|
|
||||||
HP = "基础-生命值"
|
HP = "基础-生命值"
|
||||||
Defense = "基础-防御力"
|
Defense = "基础-防御力"
|
||||||
Attack = "基础-攻击力"
|
Attack = "基础-攻击力"
|
||||||
@ -82,3 +89,75 @@ class PropType(str, Enum):
|
|||||||
Heal = "其他-治疗加成"
|
Heal = "其他-治疗加成"
|
||||||
OtherCritical = "其他-效果命中"
|
OtherCritical = "其他-效果命中"
|
||||||
Charge = "其他-能量充能效率"
|
Charge = "其他-能量充能效率"
|
||||||
|
|
||||||
|
|
||||||
|
class RelicAffix(str, Enum):
|
||||||
|
AttackAddedRatio: str = "AttackAddedRatio"
|
||||||
|
""" 攻击力 百分比 """
|
||||||
|
AttackDelta: str = "AttackDelta"
|
||||||
|
""" 攻击力 """
|
||||||
|
BreakDamageAddedRatioBase: str = "BreakDamageAddedRatioBase"
|
||||||
|
""" 击破特攻 """
|
||||||
|
CriticalChanceBase: str = "CriticalChanceBase"
|
||||||
|
""" 暴击率 百分比 """
|
||||||
|
CriticalDamageBase: str = "CriticalDamageBase"
|
||||||
|
""" 暴击伤害 百分比 """
|
||||||
|
DefenceAddedRatio: str = "DefenceAddedRatio"
|
||||||
|
""" 防御力 百分比 """
|
||||||
|
DefenceDelta: str = "DefenceDelta"
|
||||||
|
""" 防御力 """
|
||||||
|
FireAddedRatio: str = "FireAddedRatio"
|
||||||
|
""" 火属性伤害提高 百分比 """
|
||||||
|
HPAddedRatio: str = "HPAddedRatio"
|
||||||
|
""" 生命值 百分比 """
|
||||||
|
HPDelta: str = "HPDelta"
|
||||||
|
""" 生命值 """
|
||||||
|
HealRatioBase: str = "HealRatioBase"
|
||||||
|
""" 治疗量加成 百分比"""
|
||||||
|
IceAddedRatio: str = "IceAddedRatio"
|
||||||
|
""" 冰属性伤害提高 百分比 """
|
||||||
|
ImaginaryAddedRatio: str = "ImaginaryAddedRatio"
|
||||||
|
""" 虚数属性伤害提高 百分比 """
|
||||||
|
PhysicalAddedRatio: str = "PhysicalAddedRatio"
|
||||||
|
""" 物理属性伤害提高 百分比 """
|
||||||
|
QuantumAddedRatio: str = "QuantumAddedRatio"
|
||||||
|
""" 量子属性伤害提高 百分比 """
|
||||||
|
SpeedDelta: str = "SpeedDelta"
|
||||||
|
""" 速度 """
|
||||||
|
SPRatioBase: str = "SPRatioBase"
|
||||||
|
""" 能量恢复效率 百分比 """
|
||||||
|
StatusProbabilityBase: str = "StatusProbabilityBase"
|
||||||
|
""" 效果命中 百分比 """
|
||||||
|
StatusResistanceBase: str = "StatusResistanceBase"
|
||||||
|
""" 效果抵抗 百分比 """
|
||||||
|
ThunderAddedRatio: str = "ThunderAddedRatio"
|
||||||
|
""" 雷属性伤害提高 百分比 """
|
||||||
|
WindAddedRatio: str = "WindAddedRatio"
|
||||||
|
""" 风属性伤害提高 百分比 """
|
||||||
|
|
||||||
|
|
||||||
|
class RelicPosition(str, Enum):
|
||||||
|
HEAD: str = "HEAD"
|
||||||
|
""" 头 """
|
||||||
|
HAND: str = "HAND"
|
||||||
|
""" 手 """
|
||||||
|
BODY: str = "BODY"
|
||||||
|
""" 躯干 """
|
||||||
|
FOOT: str = "FOOT"
|
||||||
|
""" 脚 """
|
||||||
|
NECK: str = "NECK"
|
||||||
|
""" 位面球 """
|
||||||
|
OBJECT: str = "OBJECT"
|
||||||
|
""" 连结绳 """
|
||||||
|
|
||||||
|
@property
|
||||||
|
def num(self):
|
||||||
|
index_map = {
|
||||||
|
RelicPosition.HEAD: 0,
|
||||||
|
RelicPosition.HAND: 1,
|
||||||
|
RelicPosition.BODY: 2,
|
||||||
|
RelicPosition.FOOT: 3,
|
||||||
|
RelicPosition.NECK: 0,
|
||||||
|
RelicPosition.OBJECT: 1,
|
||||||
|
}
|
||||||
|
return index_map.get(self)
|
||||||
|
@ -43,3 +43,7 @@ class LightCone(BaseModel):
|
|||||||
"""命途"""
|
"""命途"""
|
||||||
promote: list[LightConePromote]
|
promote: list[LightConePromote]
|
||||||
"""晋阶信息"""
|
"""晋阶信息"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def rarity(self) -> int:
|
||||||
|
return 5 - list(Quality).index(self.quality)
|
||||||
|
@ -23,4 +23,3 @@ class Monster(BaseModel):
|
|||||||
"""抗性"""
|
"""抗性"""
|
||||||
find_area: str
|
find_area: str
|
||||||
"""发现地点"""
|
"""发现地点"""
|
||||||
|
|
||||||
|
@ -1,13 +1,19 @@
|
|||||||
# 遗器套装
|
# 遗器套装
|
||||||
|
from typing import List
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
class Relic(BaseModel):
|
class Relic(BaseModel):
|
||||||
id: int
|
id: int
|
||||||
"""遗器套装ID"""
|
"""遗器套装ID"""
|
||||||
|
bbs_id: int
|
||||||
|
"""WIKI ID"""
|
||||||
name: str
|
name: str
|
||||||
"""套装名称"""
|
"""套装名称"""
|
||||||
icon: str
|
icon: str
|
||||||
"""套装图标"""
|
"""套装图标"""
|
||||||
affect: str
|
affect: str
|
||||||
"""套装效果"""
|
"""套装效果"""
|
||||||
|
image_list: List[str]
|
||||||
|
"""套装子图"""
|
||||||
|
53
modules/wiki/models/relic_affix.py
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
from decimal import Decimal
|
||||||
|
from typing import Optional, Dict
|
||||||
|
|
||||||
|
from pydantic import BaseModel, root_validator
|
||||||
|
|
||||||
|
from .enums import RelicAffix, RelicPosition
|
||||||
|
|
||||||
|
|
||||||
|
class SingleRelicAffix(BaseModel):
|
||||||
|
id: int
|
||||||
|
property: RelicAffix
|
||||||
|
base_value: float
|
||||||
|
level_value: Optional[float] = None
|
||||||
|
step_value: Optional[float] = None
|
||||||
|
is_main: bool
|
||||||
|
max_step: Optional[int] = None
|
||||||
|
|
||||||
|
def get_value(self, level_or_step: int, cnt: int = 1) -> float:
|
||||||
|
base_value = Decimal(self.base_value) * Decimal(cnt)
|
||||||
|
add_value = Decimal(self.level_value if self.is_main else self.step_value)
|
||||||
|
return float(base_value + add_value * Decimal(level_or_step))
|
||||||
|
|
||||||
|
|
||||||
|
class RelicAffixAll(BaseModel):
|
||||||
|
id: int
|
||||||
|
set_id: int
|
||||||
|
""" 套装ID """
|
||||||
|
type: RelicPosition
|
||||||
|
""" 遗器类型 """
|
||||||
|
rarity: int
|
||||||
|
""" 星级 """
|
||||||
|
main_affix_group: int
|
||||||
|
sub_affix_group: int
|
||||||
|
max_level: int
|
||||||
|
""" 最大等级 """
|
||||||
|
main_affix: Dict[str, SingleRelicAffix]
|
||||||
|
""" 主词条 """
|
||||||
|
sub_affix: Dict[str, SingleRelicAffix]
|
||||||
|
""" 副词条 """
|
||||||
|
|
||||||
|
@root_validator(pre=True)
|
||||||
|
def transform_dicts(cls, values):
|
||||||
|
for data in ["main_affix", "sub_affix"]:
|
||||||
|
affix = values.get(data)
|
||||||
|
if affix:
|
||||||
|
new_affix = {}
|
||||||
|
for key, value in affix.items():
|
||||||
|
if isinstance(value, dict):
|
||||||
|
new_affix[key] = SingleRelicAffix(**value)
|
||||||
|
else:
|
||||||
|
new_affix[key] = value
|
||||||
|
values[data] = new_affix
|
||||||
|
return values
|
@ -37,10 +37,10 @@ class Monster(WikiModel):
|
|||||||
self.all_monsters_name[m.name] = m
|
self.all_monsters_name[m.name] = m
|
||||||
|
|
||||||
def get_by_id(self, cid: int) -> Optional[MonsterModel]:
|
def get_by_id(self, cid: int) -> Optional[MonsterModel]:
|
||||||
return self.all_monsters_map.get(cid, None)
|
return self.all_monsters_map.get(cid)
|
||||||
|
|
||||||
def get_by_name(self, name: str) -> Optional[MonsterModel]:
|
def get_by_name(self, name: str) -> Optional[MonsterModel]:
|
||||||
return self.all_monsters_name.get(name, None)
|
return self.all_monsters_name.get(name)
|
||||||
|
|
||||||
def get_name_list(self) -> List[str]:
|
def get_name_list(self) -> List[str]:
|
||||||
return list(self.all_monsters_name.keys())
|
return list(self.all_monsters_name.keys())
|
||||||
|
@ -37,10 +37,10 @@ class Relic(WikiModel):
|
|||||||
self.all_relics_name[m.name] = m
|
self.all_relics_name[m.name] = m
|
||||||
|
|
||||||
def get_by_id(self, cid: int) -> Optional[RelicModel]:
|
def get_by_id(self, cid: int) -> Optional[RelicModel]:
|
||||||
return self.all_relics_map.get(cid, None)
|
return self.all_relics_map.get(cid)
|
||||||
|
|
||||||
def get_by_name(self, name: str) -> Optional[RelicModel]:
|
def get_by_name(self, name: str) -> Optional[RelicModel]:
|
||||||
return self.all_relics_name.get(name, None)
|
return self.all_relics_name.get(name)
|
||||||
|
|
||||||
def get_name_list(self) -> List[str]:
|
def get_name_list(self) -> List[str]:
|
||||||
return list(self.all_relics_name.keys())
|
return list(self.all_relics_name.keys())
|
||||||
|
@ -87,7 +87,6 @@ class BindAccountPlugin(Plugin.Conversation):
|
|||||||
elif message.text == "HoYoLab":
|
elif message.text == "HoYoLab":
|
||||||
await message.reply_text("很抱歉,暂不支持HoYoLab服务器", reply_markup=ReplyKeyboardRemove())
|
await message.reply_text("很抱歉,暂不支持HoYoLab服务器", reply_markup=ReplyKeyboardRemove())
|
||||||
return ConversationHandler.END
|
return ConversationHandler.END
|
||||||
bind_account_plugin_data.region = RegionEnum.HOYOLAB
|
|
||||||
else:
|
else:
|
||||||
await message.reply_text("选择错误,请重新选择")
|
await message.reply_text("选择错误,请重新选择")
|
||||||
return CHECK_SERVER
|
return CHECK_SERVER
|
||||||
|
@ -131,8 +131,6 @@ class AccountCookiesPlugin(Plugin.Conversation):
|
|||||||
elif message.text == "HoYoLab":
|
elif message.text == "HoYoLab":
|
||||||
await message.reply_text("很抱歉,暂不支持HoYoLab服务器", reply_markup=ReplyKeyboardRemove())
|
await message.reply_text("很抱歉,暂不支持HoYoLab服务器", reply_markup=ReplyKeyboardRemove())
|
||||||
return ConversationHandler.END
|
return ConversationHandler.END
|
||||||
bbs_name = "HoYoLab"
|
|
||||||
region = RegionEnum.HOYOLAB
|
|
||||||
else:
|
else:
|
||||||
await message.reply_text("选择错误,请重新选择")
|
await message.reply_text("选择错误,请重新选择")
|
||||||
return CHECK_SERVER
|
return CHECK_SERVER
|
||||||
|
@ -12,7 +12,7 @@ from telegram import (
|
|||||||
)
|
)
|
||||||
from telegram.constants import ParseMode
|
from telegram.constants import ParseMode
|
||||||
from telegram.error import BadRequest
|
from telegram.error import BadRequest
|
||||||
from telegram.ext import CallbackContext, InlineQueryHandler
|
from telegram.ext import CallbackContext
|
||||||
|
|
||||||
from core.plugin import Plugin, handler
|
from core.plugin import Plugin, handler
|
||||||
from core.dependence.assets import AssetsService
|
from core.dependence.assets import AssetsService
|
||||||
|
@ -14,7 +14,6 @@ from core.services.cookies import CookiesService
|
|||||||
from core.services.template.models import FileType
|
from core.services.template.models import FileType
|
||||||
from core.services.template.services import TemplateService
|
from core.services.template.services import TemplateService
|
||||||
from core.services.wiki.services import WikiService
|
from core.services.wiki.services import WikiService
|
||||||
from modules.wiki.models.enums import Quality
|
|
||||||
from plugins.tools.genshin import CookiesNotFoundError, GenshinHelper, PlayerNotFoundError
|
from plugins.tools.genshin import CookiesNotFoundError, GenshinHelper, PlayerNotFoundError
|
||||||
from utils.log import logger
|
from utils.log import logger
|
||||||
|
|
||||||
@ -106,14 +105,7 @@ class AvatarListPlugin(Plugin):
|
|||||||
|
|
||||||
def get_light_cone_star(self, name: str) -> int:
|
def get_light_cone_star(self, name: str) -> int:
|
||||||
light_cone = self.wiki_service.light_cone.get_by_name(name)
|
light_cone = self.wiki_service.light_cone.get_by_name(name)
|
||||||
star_int_map = {
|
return light_cone.rarity if light_cone else 3
|
||||||
Quality.Five: 5,
|
|
||||||
Quality.Four: 4,
|
|
||||||
Quality.Three: 3,
|
|
||||||
Quality.Two: 2,
|
|
||||||
Quality.One: 1,
|
|
||||||
}
|
|
||||||
return star_int_map[light_cone.quality] if light_cone else 3
|
|
||||||
|
|
||||||
async def get_final_data(self, characters: List[StarRailDetailCharacter]) -> List[AvatarData]:
|
async def get_final_data(self, characters: List[StarRailDetailCharacter]) -> List[AvatarData]:
|
||||||
data = []
|
data = []
|
||||||
|
@ -128,9 +128,7 @@ class ChallengePlugin(Plugin):
|
|||||||
return
|
return
|
||||||
|
|
||||||
async def reply_message_func(content: str) -> None:
|
async def reply_message_func(content: str) -> None:
|
||||||
_reply_msg = await message.reply_text(
|
_reply_msg = await message.reply_text(f"开拓者 (<code>{uid}</code>) {content}", parse_mode=ParseMode.HTML)
|
||||||
f"开拓者 (<code>{uid}</code>) {content}", parse_mode=ParseMode.HTML
|
|
||||||
)
|
|
||||||
|
|
||||||
reply_text: Optional[Message] = None
|
reply_text: Optional[Message] = None
|
||||||
|
|
||||||
@ -180,7 +178,7 @@ class ChallengePlugin(Plugin):
|
|||||||
"floor": floor_data,
|
"floor": floor_data,
|
||||||
"floor_time": floor_data.node_1.challenge_time.datetime.strftime("%Y-%m-%d %H:%M:%S"),
|
"floor_time": floor_data.node_1.challenge_time.datetime.strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
"floor_nodes": [floor_data.node_1, floor_data.node_2],
|
"floor_nodes": [floor_data.node_1, floor_data.node_2],
|
||||||
"floor_num": floor
|
"floor_num": floor,
|
||||||
}
|
}
|
||||||
return render_data
|
return render_data
|
||||||
|
|
||||||
@ -244,7 +242,7 @@ class ChallengePlugin(Plugin):
|
|||||||
8: "#1D2A5D",
|
8: "#1D2A5D",
|
||||||
9: "#292B58",
|
9: "#292B58",
|
||||||
10: "#382024",
|
10: "#382024",
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
if total:
|
if total:
|
||||||
|
|
||||||
|
@ -97,7 +97,7 @@ class LedgerPlugin(Plugin):
|
|||||||
last_month = last_month.replace(day=1) - timedelta(days=1)
|
last_month = last_month.replace(day=1) - timedelta(days=1)
|
||||||
allow_month_year[last_month.month] = last_month.year
|
allow_month_year[last_month.month] = last_month.year
|
||||||
|
|
||||||
if (month not in allow_month_year.keys()) or (not isinstance(month, int)):
|
if (month not in allow_month_year) or (not isinstance(month, int)):
|
||||||
raise IndexError
|
raise IndexError
|
||||||
year = allow_month_year[month]
|
year = allow_month_year[month]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
|
@ -35,10 +35,7 @@ class LightConePlugin(Plugin):
|
|||||||
if len(args) >= 1:
|
if len(args) >= 1:
|
||||||
light_cone_name = args[0]
|
light_cone_name = args[0]
|
||||||
else:
|
else:
|
||||||
reply_message = await message.reply_text(
|
reply_message = await message.reply_text("请回复你要查询的光锥名称", reply_markup=InlineKeyboardMarkup(self.KEYBOARD))
|
||||||
"请回复你要查询的光锥名称",
|
|
||||||
reply_markup=InlineKeyboardMarkup(self.KEYBOARD)
|
|
||||||
)
|
|
||||||
if filters.ChatType.GROUPS.filter(reply_message):
|
if filters.ChatType.GROUPS.filter(reply_message):
|
||||||
self.add_delete_message_job(message)
|
self.add_delete_message_job(message)
|
||||||
self.add_delete_message_job(reply_message)
|
self.add_delete_message_job(reply_message)
|
||||||
|
@ -36,8 +36,7 @@ class MaterialPlugin(Plugin):
|
|||||||
character_name = args[0]
|
character_name = args[0]
|
||||||
else:
|
else:
|
||||||
reply_message = await message.reply_text(
|
reply_message = await message.reply_text(
|
||||||
"请回复你要查询的角色培养素材图鉴的角色名",
|
"请回复你要查询的角色培养素材图鉴的角色名", reply_markup=InlineKeyboardMarkup(self.KEYBOARD)
|
||||||
reply_markup=InlineKeyboardMarkup(self.KEYBOARD)
|
|
||||||
)
|
)
|
||||||
if filters.ChatType.GROUPS.filter(reply_message):
|
if filters.ChatType.GROUPS.filter(reply_message):
|
||||||
self.add_delete_message_job(message)
|
self.add_delete_message_job(message)
|
||||||
|
563
plugins/starrail/player_cards.py
Normal file
@ -0,0 +1,563 @@
|
|||||||
|
import math
|
||||||
|
from typing import List, Tuple, Union, Optional, TYPE_CHECKING, Dict
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Message
|
||||||
|
from telegram.constants import ChatAction
|
||||||
|
from telegram.ext import filters
|
||||||
|
from telegram.helpers import create_deep_linked_url
|
||||||
|
|
||||||
|
from core.config import config
|
||||||
|
from core.dependence.assets import AssetsService, AssetsCouldNotFound
|
||||||
|
from core.dependence.redisdb import RedisDB
|
||||||
|
from core.plugin import Plugin, handler
|
||||||
|
from core.services.players import PlayersService
|
||||||
|
from core.services.template.services import TemplateService
|
||||||
|
from core.services.wiki.services import WikiService
|
||||||
|
from metadata.shortname import roleToName, idToRole
|
||||||
|
from modules.apihelper.client.components.player_cards import PlayerCards as PlayerCardsClient, PlayerInfo, Avatar, Relic
|
||||||
|
from modules.playercards.fight_prop import EquipmentsStats
|
||||||
|
from modules.playercards.helpers import ArtifactStatsTheory
|
||||||
|
from utils.log import logger
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from telegram.ext import ContextTypes
|
||||||
|
from telegram import Update
|
||||||
|
|
||||||
|
try:
|
||||||
|
import ujson as jsonlib
|
||||||
|
except ImportError:
|
||||||
|
import json as jsonlib
|
||||||
|
|
||||||
|
|
||||||
|
class PlayerCards(Plugin):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
player_service: PlayersService,
|
||||||
|
template_service: TemplateService,
|
||||||
|
assets_service: AssetsService,
|
||||||
|
wiki_service: WikiService,
|
||||||
|
redis: RedisDB,
|
||||||
|
):
|
||||||
|
self.player_service = player_service
|
||||||
|
self.client = PlayerCardsClient(redis)
|
||||||
|
self.cache = self.client.cache
|
||||||
|
self.assets_service = assets_service
|
||||||
|
self.template_service = template_service
|
||||||
|
self.wiki_service = wiki_service
|
||||||
|
self.kitsune: Optional[str] = None
|
||||||
|
|
||||||
|
async def initialize(self):
|
||||||
|
await self.client.async_init()
|
||||||
|
|
||||||
|
async def _load_history(self, uid) -> Optional[PlayerInfo]:
|
||||||
|
data = await self.client.player_cards_file.load_history_info(uid)
|
||||||
|
if data is None:
|
||||||
|
return None
|
||||||
|
return PlayerInfo.parse_obj(data)
|
||||||
|
|
||||||
|
async def get_uid_and_ch(
|
||||||
|
self, user_id: int, args: List[str], reply: Optional[Message]
|
||||||
|
) -> Tuple[Optional[int], Optional[str]]:
|
||||||
|
"""通过消息获取 uid,优先级:args > reply > self"""
|
||||||
|
uid, ch_name, user_id_ = None, None, user_id
|
||||||
|
if args:
|
||||||
|
for i in args:
|
||||||
|
if i is not None:
|
||||||
|
if i.isdigit() and len(i) == 9:
|
||||||
|
uid = int(i)
|
||||||
|
else:
|
||||||
|
ch_name = roleToName(i)
|
||||||
|
if reply:
|
||||||
|
try:
|
||||||
|
user_id_ = reply.from_user.id
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
if not uid:
|
||||||
|
player_info = await self.player_service.get_player(user_id_)
|
||||||
|
if player_info is not None:
|
||||||
|
uid = player_info.player_id
|
||||||
|
if (not uid) and (user_id_ != user_id):
|
||||||
|
player_info = await self.player_service.get_player(user_id)
|
||||||
|
if player_info is not None:
|
||||||
|
uid = player_info.player_id
|
||||||
|
return uid, ch_name
|
||||||
|
|
||||||
|
@handler.command(command="player_card", block=False)
|
||||||
|
@handler.message(filters=filters.Regex("^角色卡片查询(.*)"), block=False)
|
||||||
|
async def player_cards(self, update: "Update", context: "ContextTypes.DEFAULT_TYPE") -> None:
|
||||||
|
user = update.effective_user
|
||||||
|
message = update.effective_message
|
||||||
|
args = self.get_args(context)
|
||||||
|
await message.reply_chat_action(ChatAction.TYPING)
|
||||||
|
uid, ch_name = await self.get_uid_and_ch(user.id, args, message.reply_to_message)
|
||||||
|
if uid is None:
|
||||||
|
buttons = [
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(
|
||||||
|
"点我绑定账号",
|
||||||
|
url=create_deep_linked_url(context.bot.username, "set_uid"),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
if filters.ChatType.GROUPS.filter(message):
|
||||||
|
reply_message = await message.reply_text(
|
||||||
|
"未查询到您所绑定的账号信息,请先私聊派蒙绑定账号",
|
||||||
|
reply_markup=InlineKeyboardMarkup(buttons),
|
||||||
|
)
|
||||||
|
self.add_delete_message_job(reply_message, delay=30)
|
||||||
|
|
||||||
|
self.add_delete_message_job(message, delay=30)
|
||||||
|
else:
|
||||||
|
await message.reply_text("未查询到您所绑定的账号信息,请先绑定账号", reply_markup=InlineKeyboardMarkup(buttons))
|
||||||
|
return
|
||||||
|
# 暂时只支持国服
|
||||||
|
if not (100000000 < uid < 200000000):
|
||||||
|
await message.reply_text("此功能暂时只支持国服")
|
||||||
|
return
|
||||||
|
data = await self._load_history(uid)
|
||||||
|
if data is None or len(data.AvatarList) == 0:
|
||||||
|
if isinstance(self.kitsune, str):
|
||||||
|
photo = self.kitsune
|
||||||
|
else:
|
||||||
|
photo = open("resources/img/aaa.jpg", "rb")
|
||||||
|
buttons = [
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(
|
||||||
|
"更新面板",
|
||||||
|
callback_data=f"update_player_card|{user.id}|{uid}",
|
||||||
|
)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
reply_message = await message.reply_photo(
|
||||||
|
photo=photo,
|
||||||
|
caption=f"角色列表未找到,请尝试点击下方按钮更新角色列表 - UID {uid}",
|
||||||
|
reply_markup=InlineKeyboardMarkup(buttons),
|
||||||
|
)
|
||||||
|
if reply_message.photo:
|
||||||
|
self.kitsune = reply_message.photo[-1].file_id
|
||||||
|
return
|
||||||
|
if ch_name is not None:
|
||||||
|
logger.info(
|
||||||
|
"用户 %s[%s] 角色卡片查询命令请求 || character_name[%s] uid[%s]",
|
||||||
|
user.full_name,
|
||||||
|
user.id,
|
||||||
|
ch_name,
|
||||||
|
uid,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logger.info("用户 %s[%s] 角色卡片查询命令请求", user.full_name, user.id)
|
||||||
|
ttl = await self.cache.ttl(uid)
|
||||||
|
|
||||||
|
buttons = self.gen_button(data, user.id, uid, update_button=ttl < 0)
|
||||||
|
if isinstance(self.kitsune, str):
|
||||||
|
photo = self.kitsune
|
||||||
|
else:
|
||||||
|
photo = open("resources/img/aaa.jpg", "rb")
|
||||||
|
reply_message = await message.reply_photo(
|
||||||
|
photo=photo,
|
||||||
|
caption=f"请选择你要查询的角色 - UID {uid}",
|
||||||
|
reply_markup=InlineKeyboardMarkup(buttons),
|
||||||
|
)
|
||||||
|
if reply_message.photo:
|
||||||
|
self.kitsune = reply_message.photo[-1].file_id
|
||||||
|
return
|
||||||
|
for characters in data.AvatarList:
|
||||||
|
if idToRole(characters.AvatarID) == ch_name:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
await message.reply_text(f"角色展柜中未找到 {ch_name} ,请检查角色是否存在于角色展柜中,或者等待角色数据更新后重试")
|
||||||
|
return
|
||||||
|
if characters.AvatarID in {8001, 8002, 8003, 8004}:
|
||||||
|
await message.reply_text(f"暂不支持查询 {ch_name} 的角色卡片")
|
||||||
|
return
|
||||||
|
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
|
||||||
|
render_result = await RenderTemplate(
|
||||||
|
uid,
|
||||||
|
characters,
|
||||||
|
self.template_service,
|
||||||
|
self.assets_service,
|
||||||
|
self.wiki_service,
|
||||||
|
self.client,
|
||||||
|
).render() # pylint: disable=W0631
|
||||||
|
await render_result.reply_photo(
|
||||||
|
message,
|
||||||
|
filename=f"player_card_{uid}_{ch_name}.png",
|
||||||
|
)
|
||||||
|
|
||||||
|
@handler.callback_query(pattern=r"^update_player_card\|", block=False)
|
||||||
|
async def update_player_card(self, update: "Update", _: "ContextTypes.DEFAULT_TYPE") -> None:
|
||||||
|
user = update.effective_user
|
||||||
|
message = update.effective_message
|
||||||
|
callback_query = update.callback_query
|
||||||
|
|
||||||
|
async def get_player_card_callback(callback_query_data: str) -> Tuple[int, int]:
|
||||||
|
_data = callback_query_data.split("|")
|
||||||
|
_user_id = int(_data[1])
|
||||||
|
_uid = int(_data[2])
|
||||||
|
logger.debug("callback_query_data函数返回 user_id[%s] uid[%s]", _user_id, _uid)
|
||||||
|
return _user_id, _uid
|
||||||
|
|
||||||
|
user_id, uid = await get_player_card_callback(callback_query.data)
|
||||||
|
if user.id != user_id:
|
||||||
|
await callback_query.answer(text="这不是你的按钮!\n" + config.notice.user_mismatch, show_alert=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
ttl = await self.cache.ttl(uid)
|
||||||
|
|
||||||
|
if ttl > 0:
|
||||||
|
await callback_query.answer(text=f"请等待 {ttl} 秒后再更新", show_alert=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
await message.reply_chat_action(ChatAction.TYPING)
|
||||||
|
await callback_query.answer(text="正在获取角色列表 请不要重复点击按钮")
|
||||||
|
data = await self.client.update_data(str(uid))
|
||||||
|
if isinstance(data, str):
|
||||||
|
await callback_query.answer(text=data, show_alert=True)
|
||||||
|
return
|
||||||
|
if data.AvatarList is None:
|
||||||
|
await message.delete()
|
||||||
|
await callback_query.answer("请先将角色加入到角色展柜并允许查看角色详情后再使用此功能,如果已经添加了角色,请等待角色数据更新后重试", show_alert=True)
|
||||||
|
return
|
||||||
|
buttons = self.gen_button(data, user.id, uid, update_button=False)
|
||||||
|
render_data = await self.parse_holder_data(data)
|
||||||
|
holder = await self.template_service.render(
|
||||||
|
"starrail/player_card/holder.html",
|
||||||
|
render_data,
|
||||||
|
viewport={"width": 750, "height": 380},
|
||||||
|
ttl=60 * 10,
|
||||||
|
caption=f"更新角色列表成功,请选择你要查询的角色 - UID {uid}",
|
||||||
|
)
|
||||||
|
await holder.edit_media(message, reply_markup=InlineKeyboardMarkup(buttons))
|
||||||
|
|
||||||
|
@handler.callback_query(pattern=r"^get_player_card\|", block=False)
|
||||||
|
async def get_player_cards(self, update: "Update", _: "ContextTypes.DEFAULT_TYPE") -> None:
|
||||||
|
callback_query = update.callback_query
|
||||||
|
user = callback_query.from_user
|
||||||
|
message = callback_query.message
|
||||||
|
|
||||||
|
async def get_player_card_callback(
|
||||||
|
callback_query_data: str,
|
||||||
|
) -> Tuple[str, int, int]:
|
||||||
|
_data = callback_query_data.split("|")
|
||||||
|
_user_id = int(_data[1])
|
||||||
|
_uid = int(_data[2])
|
||||||
|
_result = _data[3]
|
||||||
|
logger.debug(
|
||||||
|
"callback_query_data函数返回 result[%s] user_id[%s] uid[%s]",
|
||||||
|
_result,
|
||||||
|
_user_id,
|
||||||
|
_uid,
|
||||||
|
)
|
||||||
|
return _result, _user_id, _uid
|
||||||
|
|
||||||
|
result, user_id, uid = await get_player_card_callback(callback_query.data)
|
||||||
|
if user.id != user_id:
|
||||||
|
await callback_query.answer(text="这不是你的按钮!\n" + config.notice.user_mismatch, show_alert=True)
|
||||||
|
return
|
||||||
|
if result == "empty_data":
|
||||||
|
await callback_query.answer(text="此按钮不可用", show_alert=True)
|
||||||
|
return
|
||||||
|
page = 0
|
||||||
|
if result.isdigit():
|
||||||
|
page = int(result)
|
||||||
|
logger.info(
|
||||||
|
"用户 %s[%s] 角色卡片查询命令请求 || page[%s] uid[%s]",
|
||||||
|
user.full_name,
|
||||||
|
user.id,
|
||||||
|
page,
|
||||||
|
uid,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logger.info(
|
||||||
|
"用户 %s[%s] 角色卡片查询命令请求 || character_name[%s] uid[%s]",
|
||||||
|
user.full_name,
|
||||||
|
user.id,
|
||||||
|
result,
|
||||||
|
uid,
|
||||||
|
)
|
||||||
|
data = await self._load_history(uid)
|
||||||
|
if isinstance(data, str):
|
||||||
|
await message.reply_text(data)
|
||||||
|
return
|
||||||
|
if data.AvatarList is None:
|
||||||
|
await message.delete()
|
||||||
|
await callback_query.answer("请先将角色加入到角色展柜并允许查看角色详情后再使用此功能,如果已经添加了角色,请等待角色数据更新后重试", show_alert=True)
|
||||||
|
return
|
||||||
|
if page:
|
||||||
|
buttons = self.gen_button(data, user.id, uid, page, await self.cache.ttl(uid) <= 0)
|
||||||
|
await message.edit_reply_markup(reply_markup=InlineKeyboardMarkup(buttons))
|
||||||
|
await callback_query.answer(f"已切换到第 {page} 页", show_alert=False)
|
||||||
|
return
|
||||||
|
for characters in data.AvatarList:
|
||||||
|
if idToRole(characters.AvatarID) == result:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
await message.delete()
|
||||||
|
await callback_query.answer(f"角色展柜中未找到 {result} ,请检查角色是否存在于角色展柜中,或者等待角色数据更新后重试", show_alert=True)
|
||||||
|
return
|
||||||
|
if characters.AvatarID in {8001, 8002, 8003, 8004}:
|
||||||
|
await callback_query.answer(f"暂不支持查询 {result} 的角色卡片")
|
||||||
|
return
|
||||||
|
await callback_query.answer(text="正在渲染图片中 请稍等 请不要重复点击按钮", show_alert=False)
|
||||||
|
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
|
||||||
|
render_result = await RenderTemplate(
|
||||||
|
uid, characters, self.template_service, self.assets_service, self.wiki_service, self.client
|
||||||
|
).render() # pylint: disable=W0631
|
||||||
|
render_result.filename = f"player_card_{uid}_{result}.png"
|
||||||
|
await render_result.edit_media(message)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def gen_button(
|
||||||
|
data: PlayerInfo,
|
||||||
|
user_id: Union[str, int],
|
||||||
|
uid: int,
|
||||||
|
page: int = 1,
|
||||||
|
update_button: bool = True,
|
||||||
|
) -> List[List[InlineKeyboardButton]]:
|
||||||
|
"""生成按钮"""
|
||||||
|
buttons = []
|
||||||
|
|
||||||
|
if data.AvatarList:
|
||||||
|
buttons = [
|
||||||
|
InlineKeyboardButton(
|
||||||
|
idToRole(value.AvatarID),
|
||||||
|
callback_data=f"get_player_card|{user_id}|{uid}|{idToRole(value.AvatarID)}",
|
||||||
|
)
|
||||||
|
for value in data.AvatarList
|
||||||
|
if value.AvatarID
|
||||||
|
]
|
||||||
|
all_buttons = [buttons[i : i + 4] for i in range(0, len(buttons), 4)]
|
||||||
|
send_buttons = all_buttons[(page - 1) * 3 : page * 3]
|
||||||
|
last_page = page - 1 if page > 1 else 0
|
||||||
|
all_page = math.ceil(len(all_buttons) / 3)
|
||||||
|
next_page = page + 1 if page < all_page and all_page > 1 else 0
|
||||||
|
last_button = []
|
||||||
|
if last_page:
|
||||||
|
last_button.append(
|
||||||
|
InlineKeyboardButton(
|
||||||
|
"<< 上一页",
|
||||||
|
callback_data=f"get_player_card|{user_id}|{uid}|{last_page}",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if last_page or next_page:
|
||||||
|
last_button.append(
|
||||||
|
InlineKeyboardButton(
|
||||||
|
f"{page}/{all_page}",
|
||||||
|
callback_data=f"get_player_card|{user_id}|{uid}|empty_data",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if update_button:
|
||||||
|
last_button.append(
|
||||||
|
InlineKeyboardButton(
|
||||||
|
"更新面板",
|
||||||
|
callback_data=f"update_player_card|{user_id}|{uid}",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if next_page:
|
||||||
|
last_button.append(
|
||||||
|
InlineKeyboardButton(
|
||||||
|
"下一页 >>",
|
||||||
|
callback_data=f"get_player_card|{user_id}|{uid}|{next_page}",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if last_button:
|
||||||
|
send_buttons.append(last_button)
|
||||||
|
return send_buttons
|
||||||
|
|
||||||
|
async def parse_holder_data(self, data: PlayerInfo) -> dict:
|
||||||
|
"""
|
||||||
|
生成渲染所需数据
|
||||||
|
"""
|
||||||
|
characters_data = []
|
||||||
|
for idx, character in enumerate(data.AvatarList):
|
||||||
|
cid = 8004 if character.AvatarID in {8001, 8002, 8003, 8004} else character.AvatarID
|
||||||
|
try:
|
||||||
|
characters_data.append(
|
||||||
|
{
|
||||||
|
"level": character.Level,
|
||||||
|
"constellation": character.Rank,
|
||||||
|
"icon": self.assets_service.avatar.square(cid).as_uri(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
except AssetsCouldNotFound:
|
||||||
|
logger.warning("角色 %s 的头像资源获取失败", cid)
|
||||||
|
if idx > 6:
|
||||||
|
break
|
||||||
|
return {
|
||||||
|
"uid": data.UID,
|
||||||
|
"level": data.Level or 0,
|
||||||
|
"signature": data.Signature or "",
|
||||||
|
"characters": characters_data,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Artifact(BaseModel):
|
||||||
|
equipment: Dict = {}
|
||||||
|
# 圣遗物评分
|
||||||
|
score: float = 0
|
||||||
|
# 圣遗物评级
|
||||||
|
score_label: str = "E"
|
||||||
|
# 圣遗物评级颜色
|
||||||
|
score_class: str = ""
|
||||||
|
# 圣遗物单行属性评分
|
||||||
|
substat_scores: List[float]
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
for substat_scores in self.substat_scores:
|
||||||
|
self.score += substat_scores
|
||||||
|
self.score = round(self.score, 1)
|
||||||
|
|
||||||
|
for r in (
|
||||||
|
("D", 10.0),
|
||||||
|
("C", 16.5),
|
||||||
|
("B", 23.1),
|
||||||
|
("A", 29.7),
|
||||||
|
("S", 36.3),
|
||||||
|
("SS", 42.9),
|
||||||
|
("SSS", 49.5),
|
||||||
|
("ACE", 56.1),
|
||||||
|
("ACE²", 66.0),
|
||||||
|
):
|
||||||
|
if self.score >= r[1]:
|
||||||
|
self.score_label = r[0]
|
||||||
|
self.score_class = self.get_score_class(r[0])
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_score_class(label: str) -> str:
|
||||||
|
mapping = {
|
||||||
|
"D": "text-neutral-400",
|
||||||
|
"C": "text-neutral-200",
|
||||||
|
"B": "text-violet-400",
|
||||||
|
"A": "text-violet-400",
|
||||||
|
"S": "text-yellow-400",
|
||||||
|
"SS": "text-yellow-400",
|
||||||
|
"SSS": "text-yellow-400",
|
||||||
|
"ACE": "text-red-500",
|
||||||
|
"ACE²": "text-red-500",
|
||||||
|
}
|
||||||
|
return mapping.get(label, "text-neutral-400")
|
||||||
|
|
||||||
|
|
||||||
|
class RenderTemplate:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
uid: Union[int, str],
|
||||||
|
character: Avatar,
|
||||||
|
template_service: TemplateService,
|
||||||
|
assets_service: AssetsService,
|
||||||
|
wiki_service: WikiService,
|
||||||
|
client: PlayerCardsClient,
|
||||||
|
):
|
||||||
|
self.uid = uid
|
||||||
|
self.template_service = template_service
|
||||||
|
self.character = character
|
||||||
|
self.assets_service = assets_service
|
||||||
|
self.wiki_service = wiki_service
|
||||||
|
self.client = client
|
||||||
|
|
||||||
|
async def render(self):
|
||||||
|
images = await self.cache_images()
|
||||||
|
|
||||||
|
artifacts = self.find_artifacts()
|
||||||
|
artifact_total_score: float = sum(artifact.score for artifact in artifacts)
|
||||||
|
artifact_total_score = round(artifact_total_score, 1)
|
||||||
|
|
||||||
|
artifact_total_score_label: str = "E"
|
||||||
|
for r in (
|
||||||
|
("D", 10.0),
|
||||||
|
("C", 16.5),
|
||||||
|
("B", 23.1),
|
||||||
|
("A", 29.7),
|
||||||
|
("S", 36.3),
|
||||||
|
("SS", 42.9),
|
||||||
|
("SSS", 49.5),
|
||||||
|
("ACE", 56.1),
|
||||||
|
("ACE²", 66.0),
|
||||||
|
):
|
||||||
|
if artifact_total_score / 5 >= r[1]:
|
||||||
|
artifact_total_score_label = r[0]
|
||||||
|
|
||||||
|
weapon = None
|
||||||
|
weapon_detail = None
|
||||||
|
if self.character.EquipmentID and self.character.EquipmentID.ID:
|
||||||
|
weapon = self.character.EquipmentID
|
||||||
|
weapon_detail = self.wiki_service.light_cone.get_by_id(self.character.EquipmentID.ID)
|
||||||
|
skills = [0, 0, 0, 0, 0]
|
||||||
|
for index in range(5):
|
||||||
|
skills[index] = self.character.BehaviorList[index].Level
|
||||||
|
data = {
|
||||||
|
"uid": self.uid,
|
||||||
|
"character": self.character,
|
||||||
|
"character_detail": self.wiki_service.character.get_by_id(self.character.AvatarID),
|
||||||
|
"weapon": weapon,
|
||||||
|
"weapon_detail": weapon_detail,
|
||||||
|
# 圣遗物评分
|
||||||
|
"artifact_total_score": artifact_total_score,
|
||||||
|
# 圣遗物评级
|
||||||
|
"artifact_total_score_label": artifact_total_score_label,
|
||||||
|
# 圣遗物评级颜色
|
||||||
|
"artifact_total_score_class": Artifact.get_score_class(artifact_total_score_label),
|
||||||
|
"artifacts": artifacts,
|
||||||
|
"skills": skills,
|
||||||
|
"images": images,
|
||||||
|
}
|
||||||
|
|
||||||
|
return await self.template_service.render(
|
||||||
|
"starrail/player_card/player_card.html",
|
||||||
|
data,
|
||||||
|
{"width": 1000, "height": 1200},
|
||||||
|
full_page=True,
|
||||||
|
query_selector=".text-neutral-200",
|
||||||
|
ttl=7 * 24 * 60 * 60,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def cache_images(self):
|
||||||
|
c = self.character
|
||||||
|
cid = c.AvatarID
|
||||||
|
data = {
|
||||||
|
"banner_url": self.assets_service.avatar.gacha(cid).as_uri(),
|
||||||
|
"skills": [i.as_uri() for i in self.assets_service.avatar.skills(cid)][:-1],
|
||||||
|
"constellations": [i.as_uri() for i in self.assets_service.avatar.eidolons(cid)],
|
||||||
|
"equipment": "",
|
||||||
|
}
|
||||||
|
if c.EquipmentID and c.EquipmentID.ID:
|
||||||
|
data["equipment"] = self.assets_service.light_cone.icon(c.EquipmentID.ID).as_uri()
|
||||||
|
return data
|
||||||
|
|
||||||
|
def find_artifacts(self) -> List[Artifact]:
|
||||||
|
"""在 equipments 数组中找到圣遗物,并转换成带有分数的 model。equipments 数组包含圣遗物和武器"""
|
||||||
|
|
||||||
|
stats = ArtifactStatsTheory(idToRole(self.character.AvatarID))
|
||||||
|
|
||||||
|
def substat_score(s: EquipmentsStats) -> float:
|
||||||
|
return stats.theory(s)
|
||||||
|
|
||||||
|
def fix_equipment(e: Relic) -> Dict:
|
||||||
|
rid = e.ID
|
||||||
|
affix = self.client.get_affix_by_id(rid)
|
||||||
|
relic_set = self.wiki_service.relic.get_by_id(affix.set_id)
|
||||||
|
try:
|
||||||
|
icon = relic_set.image_list[affix.type.num]
|
||||||
|
except IndexError:
|
||||||
|
icon = relic_set.icon
|
||||||
|
return {
|
||||||
|
"id": rid,
|
||||||
|
"name": relic_set.name,
|
||||||
|
"icon": icon,
|
||||||
|
"level": e.Level,
|
||||||
|
"rank": affix.rarity,
|
||||||
|
"main_sub": self.client.get_affix(e, True, False)[0],
|
||||||
|
"sub": self.client.get_affix(e, False, True),
|
||||||
|
}
|
||||||
|
|
||||||
|
relic_list = self.character.RelicList or []
|
||||||
|
return [
|
||||||
|
Artifact(
|
||||||
|
equipment=fix_equipment(e),
|
||||||
|
# 圣遗物单行属性评分
|
||||||
|
substat_scores=[substat_score(s) for s in self.client.get_affix(e, False)],
|
||||||
|
)
|
||||||
|
for e in relic_list
|
||||||
|
]
|
@ -53,13 +53,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="command-description">玩家统计查询</div>
|
<div class="command-description">玩家统计查询</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- <div class="command">-->
|
<div class="command">
|
||||||
<!-- <div class="command-name">-->
|
<div class="command-name">
|
||||||
<!-- /player_card-->
|
/player_card
|
||||||
<!-- <i class="fa fa-user-circle-o ml-2"></i>-->
|
<i class="fa fa-user-circle-o ml-2"></i>
|
||||||
<!-- </div>-->
|
</div>
|
||||||
<!-- <div class="command-description">角色卡片</div>-->
|
<div class="command-description">角色卡片</div>
|
||||||
<!-- </div>-->
|
</div>
|
||||||
<!-- 最高查询类 -->
|
<!-- 最高查询类 -->
|
||||||
<div class="command">
|
<div class="command">
|
||||||
<div class="command-name">
|
<div class="command-name">
|
||||||
@ -75,13 +75,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="command-description">查询当月开拓月历</div>
|
<div class="command-description">查询当月开拓月历</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- <div class="command">-->
|
<div class="command">
|
||||||
<!-- <div class="command-name">-->
|
<div class="command-name">
|
||||||
<!-- /abyss-->
|
/challenge
|
||||||
<!-- <i class="fa fa-id-card-o ml-2"></i>-->
|
<i class="fa fa-id-card-o ml-2"></i>
|
||||||
<!-- </div>-->
|
</div>
|
||||||
<!-- <div class="command-description">查询当期深渊战绩</div>-->
|
<div class="command-description">查询当期混沌回忆战绩</div>
|
||||||
<!-- </div>-->
|
</div>
|
||||||
<!-- <div class="command">-->
|
<!-- <div class="command">-->
|
||||||
<!-- <div class="command-name">-->
|
<!-- <div class="command-name">-->
|
||||||
<!-- /abyss_team-->
|
<!-- /abyss_team-->
|
||||||
|
BIN
resources/img/aaa.jpg
Normal file
After Width: | Height: | Size: 452 KiB |
43
resources/starrail/player_card/artifacts.html
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
{% for item in artifacts %}
|
||||||
|
<div class="bg-black bg-opacity-20 rounded-lg">
|
||||||
|
<div class="flex items-center space-x-4">
|
||||||
|
<div class="relative">
|
||||||
|
<img class="w-24 h-24" src="{{ item.equipment.icon }}" alt=""/>
|
||||||
|
<div
|
||||||
|
class="absolute bottom-3 right-3 px-1 text-base italic bg-black bg-opacity-50 rounded"
|
||||||
|
>
|
||||||
|
+{{ item.equipment.level }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="">
|
||||||
|
<div class="font-medium">{{ item.equipment.name }}</div>
|
||||||
|
<div class="flex text-sm space-x-2">
|
||||||
|
<div>{{ item.equipment.main_sub.name }}</div>
|
||||||
|
<div class="italic">
|
||||||
|
{{ item.equipment.main_sub.value }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-base {{ item.score_class }}">
|
||||||
|
<span class="italic"> {{ item.score }} </span> 分 - {{ item.score_label
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{% for stat in item.equipment.sub %}
|
||||||
|
<div class="px-4 py-1 odd:bg-white odd:bg-opacity-10 flex space-x-4 {% if item.substat_scores[loop.index0] == 0 %} text-neutral-400 {% endif %}">
|
||||||
|
<div class="flex-1 truncate">
|
||||||
|
{{ stat.name }}
|
||||||
|
</div>
|
||||||
|
<div class="min-w-30 italic text-right">
|
||||||
|
+{{ stat.value }}
|
||||||
|
</div>
|
||||||
|
<div class="min-w-30 text-right">
|
||||||
|
<span class="italic">{{ item.substat_scores[loop.index0] }}</span>
|
||||||
|
<span class="text-sm">分</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
13
resources/starrail/player_card/constellations.html
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<div class="flex-1 flex items-end justify-center">
|
||||||
|
<div class="flex pb-2">
|
||||||
|
{% for item in images.constellations %}
|
||||||
|
<div
|
||||||
|
class="w-16 h-16 flex items-center justify-center bg-contain bg-no-repeat bg-center
|
||||||
|
{%- if loop.index > character.Rank %} grayscale opacity-75 {% endif %}"
|
||||||
|
style="background-image: url('img/talent-{{ character_detail.element.name | lower }}.png')"
|
||||||
|
>
|
||||||
|
<img src="{{ item }}" alt="" class="w-8 h-8" />
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
37
resources/starrail/player_card/holder.html
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-ch">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>holder</title>
|
||||||
|
<link type="text/css" href="./style.css" rel="stylesheet"/>
|
||||||
|
<link type="text/css" href="../../styles/public.css" rel="stylesheet"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="overview">
|
||||||
|
<div class="title">角色展柜</div>
|
||||||
|
<div class="summarize">
|
||||||
|
<div>
|
||||||
|
<div>UID: {{ uid }}</div>
|
||||||
|
<div>开拓等级: {{ level }} 级</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div>签名: {{ signature }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="characters">
|
||||||
|
{% for character in characters %}
|
||||||
|
<div class="character">
|
||||||
|
{% if character.constellation > 0 %}
|
||||||
|
{% set bg = ['blue','blue', 'green','green', 'red', 'red'][character.constellation - 1] %}
|
||||||
|
<div style="background-color: var(--{{ bg }})">{{ character.constellation }} 命</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="icon" style="background-image: url('../../background/rarity/half/5.png')">
|
||||||
|
<img src="{{ character.icon }}" alt=""/>
|
||||||
|
</div>
|
||||||
|
<div class="caption">Lv.{{ character.level }}</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
58
resources/starrail/player_card/holder_example.html
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-ch">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>holder_example</title>
|
||||||
|
<link type="text/css" href="./style.css" rel="stylesheet"/>
|
||||||
|
<link type="text/css" href="../../styles/public.css" rel="stylesheet"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="overview">
|
||||||
|
<div class="title">角色展柜</div>
|
||||||
|
<div class="summarize">
|
||||||
|
<div>
|
||||||
|
<div>UID: 123456789</div>
|
||||||
|
<div>冒险等阶: 55</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div>签名: 貴方の運命は、すでに我が手中の糸が絡めとった!填充</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="characters">
|
||||||
|
<div class="character">
|
||||||
|
<div style="background-color: var(--green)">4命</div>
|
||||||
|
<div class="element" style="background-image: url('../../img/element/Cryo.png')"></div>
|
||||||
|
<div class="icon" style="background-image: url('../../background/rarity/half/5.png')">
|
||||||
|
<img src="../../assets/avatar/10000007/icon.png" alt="荧"/>
|
||||||
|
</div>
|
||||||
|
<div class="caption">Lv.90</div>
|
||||||
|
</div>
|
||||||
|
<div class="character">
|
||||||
|
<div class="icon" style="background-image: url('../../background/rarity/half/5.png')">
|
||||||
|
<img src="../../assets/avatar/10000007/icon.png" alt="荧"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="character">
|
||||||
|
<div class="icon" style="background-image: url('../../background/rarity/half/5.png')">
|
||||||
|
<img src="../../assets/avatar/10000007/icon.png" alt="荧"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="character">
|
||||||
|
<div class="icon" style="background-image: url('../../background/rarity/half/5.png')">
|
||||||
|
<img src="../../assets/avatar/10000007/icon.png" alt="荧"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="character">
|
||||||
|
<div class="icon" style="background-image: url('../../background/rarity/half/5.png')">
|
||||||
|
<img src="../../assets/avatar/10000007/icon.png" alt="荧"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="character">
|
||||||
|
<div class="icon" style="background-image: url('../../background/rarity/half/5.png')">
|
||||||
|
<img src="../../assets/avatar/10000007/icon.png" alt="荧"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
BIN
resources/starrail/player_card/img/bg-anemo.jpg
Normal file
After Width: | Height: | Size: 187 KiB |
BIN
resources/starrail/player_card/img/bg-cryo.jpg
Normal file
After Width: | Height: | Size: 173 KiB |
BIN
resources/starrail/player_card/img/bg-dendro.jpg
Normal file
After Width: | Height: | Size: 202 KiB |
BIN
resources/starrail/player_card/img/bg-electro.jpg
Normal file
After Width: | Height: | Size: 167 KiB |
BIN
resources/starrail/player_card/img/bg-nombre.jpg
Normal file
After Width: | Height: | Size: 175 KiB |
BIN
resources/starrail/player_card/img/bg-physical.jpg
Normal file
After Width: | Height: | Size: 118 KiB |
BIN
resources/starrail/player_card/img/bg-pyro.jpg
Normal file
After Width: | Height: | Size: 175 KiB |
BIN
resources/starrail/player_card/img/bg-quantum.jpg
Normal file
After Width: | Height: | Size: 182 KiB |
BIN
resources/starrail/player_card/img/holder_bg.png
Normal file
After Width: | Height: | Size: 301 KiB |
BIN
resources/starrail/player_card/img/star.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
resources/starrail/player_card/img/talent-anemo.png
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
resources/starrail/player_card/img/talent-cryo.png
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
resources/starrail/player_card/img/talent-dendro.png
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
resources/starrail/player_card/img/talent-electro.png
Normal file
After Width: | Height: | Size: 39 KiB |
BIN
resources/starrail/player_card/img/talent-nombre.png
Normal file
After Width: | Height: | Size: 39 KiB |
BIN
resources/starrail/player_card/img/talent-physical.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
resources/starrail/player_card/img/talent-pyro.png
Normal file
After Width: | Height: | Size: 39 KiB |
BIN
resources/starrail/player_card/img/talent-quantum.png
Normal file
After Width: | Height: | Size: 40 KiB |
108
resources/starrail/player_card/player_card.html
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>Title</title>
|
||||||
|
<script src="../../js/tailwindcss-3.1.8.js"></script>
|
||||||
|
<link type="text/css" href="../../styles/public.css" rel="stylesheet" />
|
||||||
|
<style>
|
||||||
|
.text-shadow {
|
||||||
|
text-shadow: 0 0.08em 0.1em #000, 0 0.1em 0.3em rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.star {
|
||||||
|
background-image: url("./img/star.png");
|
||||||
|
height: 1rem;
|
||||||
|
width: 5rem;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
.star-1 {
|
||||||
|
background-position-y: 0;
|
||||||
|
}
|
||||||
|
.star-2 {
|
||||||
|
background-position-y: -1rem;
|
||||||
|
}
|
||||||
|
.star-3 {
|
||||||
|
background-position-y: -2rem;
|
||||||
|
}
|
||||||
|
.star-4 {
|
||||||
|
background-position-y: -3rem;
|
||||||
|
}
|
||||||
|
.star-5 {
|
||||||
|
background-position-y: -4rem;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: shicon;
|
||||||
|
src: url(../../genshin/player_card/fonts/shicon.woff) format("woff");
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.enka-log {
|
||||||
|
line-height: 0;
|
||||||
|
margin-right: 0.3rem;
|
||||||
|
}
|
||||||
|
.enka-log:before {
|
||||||
|
content: "\e93a";
|
||||||
|
font-family: shicon;
|
||||||
|
font-size: 1.25em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="text-xl text-neutral-200">
|
||||||
|
<div
|
||||||
|
class="data bg-no-repeat bg-cover pb-5 min-w-[845px] overflow-hidden"
|
||||||
|
style="background-image: url('img/bg-{{ character_detail.element.name | lower }}.jpg')"
|
||||||
|
>
|
||||||
|
<div class="relative mb-4 overflow-hidden">
|
||||||
|
<!-- Character Background -->
|
||||||
|
<div
|
||||||
|
class="absolute w-full h-full -left-1/4 opacity-80 bg-no-repeat bg-center"
|
||||||
|
style="background-image: url('{{ images.banner_url }}'); background-size: auto 200%;"
|
||||||
|
></div>
|
||||||
|
<div class="relative w-full flex p-5 space-x-8">
|
||||||
|
{% include 'starrail/player_card/constellations.html' %}
|
||||||
|
|
||||||
|
<div class="flex-1 space-y-4">
|
||||||
|
<div class="text-right italic">
|
||||||
|
<div class="characters-name text-5xl font-bold text-shadow mb-2">
|
||||||
|
{{ character_detail.name }}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="flex flex-row justify-end text-2xl text-shadow space-x-6"
|
||||||
|
>
|
||||||
|
<div>UID {{ uid }}</div>
|
||||||
|
<div>Lv.{{ character.Level }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% include 'starrail/player_card/skills.html' %}
|
||||||
|
<div class="flex flex-col space-y-2">
|
||||||
|
{% if weapon != none %}
|
||||||
|
{% include 'starrail/player_card/weapon.html' %}
|
||||||
|
{% endif %}
|
||||||
|
{% include 'starrail/player_card/score.html' %}
|
||||||
|
</div>
|
||||||
|
{% include 'starrail/player_card/stats.html' %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Info -->
|
||||||
|
<div class="px-5 relative">
|
||||||
|
<div class="grid grid-cols-3 gap-4">
|
||||||
|
{% include 'starrail/player_card/artifacts.html' %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Logo -->
|
||||||
|
<div class="mt-4 relative">
|
||||||
|
<div class="text-gray-300 text-center opacity-70 text-lg">
|
||||||
|
Inspired by Miao-Plugin
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
12
resources/starrail/player_card/score.html
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<div class="flex-1 flex justify-evenly bg-black bg-opacity-20 rounded-lg">
|
||||||
|
<div class="flex flex-col items-center justify-center space-y-2">
|
||||||
|
<div class="text-5xl italic text-shadow {{ artifact_total_score_class }}">
|
||||||
|
{{ artifact_total_score_label }}
|
||||||
|
</div>
|
||||||
|
<div class="text-base text-neutral-400">遗器评级</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col items-center justify-center space-y-2">
|
||||||
|
<div class="text-5xl italic text-shadow">{{ artifact_total_score }}</div>
|
||||||
|
<div class="text-base text-neutral-400">遗器评分</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
17
resources/starrail/player_card/skills.html
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<div class="flex flex-row">
|
||||||
|
{% for item in images.skills %}
|
||||||
|
<div class="mx-auto flex flex-col items-center justify-center">
|
||||||
|
<div
|
||||||
|
class="w-32 h-32 flex items-center justify-center bg-contain bg-no-repeat bg-center"
|
||||||
|
style="background-image: url('img/talent-{{ character_detail.element.name | lower }}.png')"
|
||||||
|
>
|
||||||
|
<img src="{{ item }}" alt="" class="w-16 h-16" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="w-10 -mt-8 text-xl font-medium bg-white text-neutral-800 italic rounded-lg text-center bg-opacity-80"
|
||||||
|
>
|
||||||
|
{{ skills[loop.index0] }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
3
resources/starrail/player_card/stats.html
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<div class="rounded-lg bg-black bg-opacity-20">
|
||||||
|
|
||||||
|
</div>
|
146
resources/starrail/player_card/style.css
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
:root {
|
||||||
|
--white: rgb(246 248 249);
|
||||||
|
--bg-color: rgb(233 229 220);
|
||||||
|
--h-color: rgb(203 189 162);
|
||||||
|
--red: rgb(255 86 33/ 80%);
|
||||||
|
--blue: rgb(98 168 233/ 80%);
|
||||||
|
--green: rgb(67 185 124/ 80%);
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hr {
|
||||||
|
width: 100%;
|
||||||
|
height: 3px;
|
||||||
|
background-color: rgb(246 248 249 / 50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
width: 750px;
|
||||||
|
position: relative;
|
||||||
|
filter: drop-shadow(2px 2px 5px rgb(0 0 0 /70%));
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 27px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--h-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.caption {
|
||||||
|
margin: 10px 0;
|
||||||
|
color: var(--h-color);
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 概览 */
|
||||||
|
|
||||||
|
.overview {
|
||||||
|
height: 540px;
|
||||||
|
padding: 20px 30px;
|
||||||
|
background-size: cover;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
background-image: linear-gradient(to top, rgb(0 0 0 / 10%), rgb(0 0 0 / 10%)), url("./img/holder_bg.png");
|
||||||
|
background-attachment: local;
|
||||||
|
border-radius: 15px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summarize {
|
||||||
|
font-size: 20px;
|
||||||
|
margin: 10px;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 5px;
|
||||||
|
border: 2px solid rgb(118 121 120 / 80%);
|
||||||
|
outline: 4px solid rgb(70, 80, 100);
|
||||||
|
background-color: rgb(70 80 100 / 60%);
|
||||||
|
background-image: url("../abyss/background/banner 01.png"), url("../abyss/background/banner 02.png");
|
||||||
|
background-repeat: no-repeat, no-repeat;
|
||||||
|
background-position: right, left;
|
||||||
|
background-size: auto 100%, auto 100%;
|
||||||
|
backdrop-filter: blur(5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.summarize > div {
|
||||||
|
width: 100%;
|
||||||
|
height: 50%;
|
||||||
|
padding: 5px;
|
||||||
|
color: var(--white);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summarize > div > div {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.characters {
|
||||||
|
margin-left: 47px;
|
||||||
|
margin-top: 15px;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.character {
|
||||||
|
width: 120px;
|
||||||
|
height: 150px;
|
||||||
|
margin: 15px 12px;
|
||||||
|
background-color: rgb(233 229 220);
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 10px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.characters > .character > .element {
|
||||||
|
position: absolute;
|
||||||
|
top: 3px;
|
||||||
|
left: 3px;
|
||||||
|
width: 25px;
|
||||||
|
height: 25px;
|
||||||
|
background-size: contain;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
width: 100%;
|
||||||
|
height: 120px;
|
||||||
|
background-size: cover;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 0 0 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.character > .caption {
|
||||||
|
font-size: 16px;
|
||||||
|
margin: 4px 0 0;
|
||||||
|
padding: 0;
|
||||||
|
height: min-content;
|
||||||
|
text-align: center;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.character > div:first-child:not(.icon, .element) {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: 3px;
|
||||||
|
min-width: 27px;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 0 0 0 10px;
|
||||||
|
filter: drop-shadow(1px 1px 5px rgb(0 0 0/50%));
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon > img {
|
||||||
|
width: inherit;
|
||||||
|
height: inherit;
|
||||||
|
}
|
17
resources/starrail/player_card/weapon.html
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<div class="flex-1 flex justify-evenly bg-black bg-opacity-20 rounded-lg bg-contain bg-no-repeat">
|
||||||
|
<div class="flex flex-col items-center justify-center space-y-2">
|
||||||
|
<img class="w-24 h-24" src="{{ images.equipment }}" alt=""/>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col items-center justify-center space-y-2">
|
||||||
|
<div class="text-2xl">{{ weapon_detail.name }}</div>
|
||||||
|
<div class="star star-{{ weapon_detail.rarity }}"></div>
|
||||||
|
<div class="flex space-x-3 items-center">
|
||||||
|
<div class="italic">Lv.{{ weapon.Level }}</div>
|
||||||
|
<div
|
||||||
|
class="rounded px-2 text-base {% if weapon.Rank == 5 %} bg-red-600 {% else %} bg-gray-600 {% endif %}"
|
||||||
|
>
|
||||||
|
精{{ weapon.Rank }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|