角色练度查询

This commit is contained in:
Karako 2022-10-16 18:11:04 +08:00 committed by GitHub
parent 4eace26e77
commit b27f8af9a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 942 additions and 12 deletions

View File

@ -0,0 +1,200 @@
"""练度统计"""
from typing import Iterable, List, Optional, Sequence
from arkowrapper import ArkoWrapper
from enkanetwork import Assets as EnkaAssets, EnkaNetworkAPI
from genshin import Client
from genshin.models import CalculatorCharacterDetails, CalculatorTalent, Character
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, InputFile, Message, Update, User
from telegram.constants import ChatAction, ParseMode
from telegram.ext import CallbackContext, filters
from core.base.assets import AssetsService
from core.baseplugin import BasePlugin
from core.config import config
from core.cookies.error import CookiesNotFoundError
from core.cookies.services import CookiesService
from core.plugin import Plugin, handler
from core.template import TemplateService
from core.user.error import UserNotFoundError
from metadata.genshin import AVATAR_DATA, NAMECARD_DATA
from modules.wiki.base import Model
from utils.decorators.error import error_callable
from utils.decorators.restricts import restricts
from utils.helpers import get_genshin_client
from utils.log import logger
class AvatarListPlugin(Plugin, BasePlugin):
def __init__(
self, cookies_service: CookiesService, assets_service: AssetsService, template_service: TemplateService
) -> None:
self.cookies_service = cookies_service
self.assets_service = assets_service
self.template_service = template_service
self.enka_client = EnkaNetworkAPI(lang="chs", agent=config.enka_network_api_agent)
self.enka_assets = EnkaAssets(lang="chs")
async def get_user_client(self, user: User, message: Message, context: CallbackContext) -> Optional[Client]:
try:
return await get_genshin_client(user.id)
except UserNotFoundError: # 若未找到账号
if filters.ChatType.GROUPS.filter(message):
buttons = [[InlineKeyboardButton("点我私聊", url=f"https://t.me/{context.bot.username}?start=set_uid")]]
reply_msg = await message.reply_text(
"未查询到您所绑定的账号信息,请先私聊派蒙绑定账号", reply_markup=InlineKeyboardMarkup(buttons)
)
self._add_delete_message_job(context, reply_msg.chat_id, reply_msg.message_id, 30)
self._add_delete_message_job(context, message.chat_id, message.message_id, 30)
else:
await message.reply_text("未查询到您所绑定的账号信息,请先私聊派蒙绑定账号")
except CookiesNotFoundError:
if filters.ChatType.GROUPS.filter(message):
buttons = [[InlineKeyboardButton("点我私聊", url=f"https://t.me/{context.bot.username}?start=set_uid")]]
reply_msg = await message.reply_text(
"此功能需要绑定<code>cookie</code>后使用,请先私聊派蒙绑定账号",
reply_markup=InlineKeyboardMarkup(buttons),
parse_mode=ParseMode.HTML,
)
self._add_delete_message_job(context, reply_msg.chat_id, reply_msg.message_id, 30)
self._add_delete_message_job(context, message.chat_id, message.message_id, 30)
else:
await message.reply_text("此功能需要绑定<code>cookie</code>后使用,请先私聊派蒙进行绑定", parse_mode=ParseMode.HTML)
async def get_avatars_data(self, characters: Sequence[Character], client: Client, max_length: int = None):
avatar_datas: List[AvatarData] = []
for num, character in enumerate(characters):
if num == max_length: # 若已经有 max_length 个角色
break
detail = await client.get_character_details(character)
if character.id == 10000005: # 针对男草主
talents = []
for talent in detail.talents:
if "普通攻击" in talent.name:
talent.Config.allow_mutation = True
# noinspection Pydantic
talent.group_id = 1131
if talent.type in ["attack", "skill", "burst"]:
talents.append(talent)
else:
talents = [t for t in detail.talents if t.type in ["attack", "skill", "burst"]]
buffed_talents = []
for constellation in filter(lambda x: x.pos in [3, 5], character.constellations[: character.constellation]):
if result := list(
filter(lambda x: all([x.name in constellation.effect]), talents) # pylint: disable=W0640
):
buffed_talents.append(result[0].type)
avatar_datas.append(
AvatarData(
avatar=character,
detail=detail,
icon=(await self.assets_service.avatar(character.id).side()).as_uri(),
weapon=(
await self.assets_service.weapon(character.weapon.id).__getattr__(
"icon" if character.weapon.ascension < 2 else "awaken"
)()
).as_uri(),
skills=[
SkillData(skill=s, buffed=s.type in buffed_talents)
for s in sorted(talents, key=lambda x: ["attack", "skill", "burst"].index(x.type))
],
)
)
return avatar_datas
async def get_final_data(self, client: Client, characters: Sequence[Character], update: Update):
try:
response = await self.enka_client.fetch_user(client.uid)
namecard = (await self.assets_service.namecard(response.player.namecard.id).navbar()).as_uri()
avatar = (await self.assets_service.avatar(response.player.icon.id).icon()).as_uri()
nickname = response.player.nickname
rarity = {k: v["rank"] for k, v in AVATAR_DATA.items()}[str(response.player.icon.id)]
except Exception as e: # pylint: disable=W0703
logger.debug(f"enka 请求失败: {e}")
choices = ArkoWrapper(characters).filter(lambda x: x.friendship == 10) # 筛选出好感满了的角色
if not choices: # 若没有满好感角色、则以好感等级排序
choices = ArkoWrapper(characters).sort(lambda x: x.friendship, reverse=True)
namecard_choices = ( # 找到与角色对应的满好感名片ID
ArkoWrapper(choices)
.map(lambda x: next(filter(lambda y: y["name"].split(".")[0] == x.name, NAMECARD_DATA.values()), None))
.filter(lambda x: x)
.map(lambda x: x["id"])
)
namecard = (await self.assets_service.namecard(namecard_choices[0]).navbar()).as_uri()
avatar = (await self.assets_service.avatar(cid := choices[0].id).icon()).as_uri()
nickname = update.effective_user.full_name
rarity = {k: v["rank"] for k, v in AVATAR_DATA.items()}[str(cid)]
return namecard, avatar, nickname, rarity
@handler.command("avatars", filters.Regex(r"^/avatars\s*(?:(\d+)|(all))?$"))
@handler.message(filters.Regex(r"^(全部)?练度统计$"))
@restricts(30)
@error_callable
async def avatar_list(self, update: Update, context: CallbackContext):
user = update.effective_user
message = update.effective_message
args = context.match
all_avatars = any(["all" in args.groups(), "全部" in args.groups()]) # 是否发送全部角色
logger.info(f"用户 {user.full_name}[{user.id}] [bold]练度统计[/bold]: all={all_avatars}", extra={"markup": True})
client = await self.get_user_client(user, message, context)
if not client:
return
notice = await message.reply_text("派蒙需要收集整理数据,还请耐心等待哦~")
await message.reply_chat_action(ChatAction.TYPING)
characters = await client.get_genshin_characters(client.uid)
avatar_datas: List[AvatarData] = await self.get_avatars_data(characters, client, None if all_avatars else 20)
namecard, avatar, nickname, rarity = await self.get_final_data(client, characters, update)
render_data = {
"uid": client.uid, # 玩家uid
"nickname": nickname, # 玩家昵称
"avatar": avatar, # 玩家头像
"rarity": rarity, # 玩家头像对应的角色星级
"namecard": namecard, # 玩家名片
"avatar_datas": avatar_datas, # 角色数据
"has_more": len(characters) != len(avatar_datas), # 是否显示了全部角色
}
await message.reply_chat_action(ChatAction.UPLOAD_DOCUMENT if all_avatars else ChatAction.UPLOAD_PHOTO)
image = await self.template_service.render(
"genshin/avatar_list/main.html",
render_data,
viewport={"width": 1040, "height": 500},
full_page=True,
query_selector=".container",
)
self._add_delete_message_job(context, notice.chat_id, notice.message_id, 5)
if all_avatars and len(characters) > 20:
await message.reply_document(InputFile(image, filename="练度统计.png"))
else:
await message.reply_photo(image)
logger.info(
f"用户 {user.full_name}[{user.id}] [bold]练度统计[/bold]发送{'文件' if all_avatars else '图片'}成功",
extra={"markup": True},
)
class SkillData(Model):
"""天赋数据"""
skill: CalculatorTalent
buffed: bool = False
"""是否得到了命座加成"""
class AvatarData(Model):
avatar: Character
detail: CalculatorCharacterDetails
icon: str
weapon: str
skills: Iterable[SkillData]

24
poetry.lock generated
View File

@ -159,7 +159,7 @@ python-versions = ">=3.5"
dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"]
docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"]
tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"]
tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"]
tests_no_zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"]
[[package]]
name = "backports.zoneinfo"
@ -234,7 +234,7 @@ optional = false
python-versions = ">=3.6.0"
[package.extras]
unicode-backport = ["unicodedata2"]
unicode_backport = ["unicodedata2"]
[[package]]
name = "click"
@ -366,7 +366,7 @@ python-versions = ">=3.7"
[[package]]
name = "genshin"
version = "1.2.4"
description = ""
description = "An API wrapper for Genshin Impact."
category = "main"
optional = false
python-versions = ">=3.8"
@ -386,7 +386,7 @@ geetest = ["rsa"]
type = "git"
url = "https://github.com/thesadru/genshin.py"
reference = "HEAD"
resolved_reference = "d6aa54384cbcb260adab1b221d0f8673ac0caf4a"
resolved_reference = "7b3a4a71bfdf84d9f1bf984e91c0bcf73f9dfa7f"
[[package]]
name = "greenlet"
@ -960,19 +960,19 @@ aiomysql = ["aiomysql", "greenlet (!=0.4.17)"]
aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"]
asyncio = ["greenlet (!=0.4.17)"]
asyncmy = ["asyncmy (>=0.2.3,!=0.2.4)", "greenlet (!=0.4.17)"]
mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2)"]
mariadb_connector = ["mariadb (>=1.0.1,!=1.1.2)"]
mssql = ["pyodbc"]
mssql-pymssql = ["pymssql"]
mssql-pyodbc = ["pyodbc"]
mssql_pymssql = ["pymssql"]
mssql_pyodbc = ["pyodbc"]
mypy = ["mypy (>=0.910)", "sqlalchemy2-stubs"]
mysql = ["mysqlclient (>=1.4.0)", "mysqlclient (>=1.4.0,<2)"]
mysql-connector = ["mysql-connector-python"]
mysql_connector = ["mysql-connector-python"]
oracle = ["cx_oracle (>=7)", "cx_oracle (>=7,<8)"]
postgresql = ["psycopg2 (>=2.7)"]
postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"]
postgresql-pg8000 = ["pg8000 (>=1.16.6,!=1.29.0)"]
postgresql-psycopg2binary = ["psycopg2-binary"]
postgresql-psycopg2cffi = ["psycopg2cffi"]
postgresql_asyncpg = ["asyncpg", "greenlet (!=0.4.17)"]
postgresql_pg8000 = ["pg8000 (>=1.16.6,!=1.29.0)"]
postgresql_psycopg2binary = ["psycopg2-binary"]
postgresql_psycopg2cffi = ["psycopg2cffi"]
pymysql = ["pymysql", "pymysql (<1)"]
sqlcipher = ["sqlcipher3_binary"]

View File

@ -0,0 +1,144 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Avatar List</title>
<link type="text/css" href="./style.css" rel="stylesheet"/>
<link type="text/css" href="../../styles/public.css" rel="stylesheet"/>
<style>
.avatar > div::after {
background-position: center center;
background-size: cover;
background-image: url("../../background/rarity/half/5.png");
}
</style>
</head>
<body>
<div class="container">
<div class="head" style="background-image: url('../../assets/namecard/210081/navbar.png')">
<div class="avatar">
<div><img src="../../assets/avatar/10000002/icon.png" alt="avatar"></div>
</div>
<div class="player">
<div>
<div class="nickname">Karako</div>
<div class="uid">UID: 100206192</div>
</div>
</div>
<div class="logo"></div>
</div>
<div class="content">
<div class="row">
<div>#</div>
<div style="flex: 4">角色</div>
<div>等级</div>
<div>好感</div>
<div>命座</div>
<div class="talent">普攻</div>
<div class="talent">战技</div>
<div class="talent">爆发</div>
<div style="flex: 6">武器</div>
</div>
<div class="row">
<div style="background-color: rgb(240 226 179)">1</div>
<div class="role" style="flex: 4;background-color: rgb(240 226 179)">
<div class="role-icon" style="flex: 1.5;background-color: rgb(240 226 179)">
<img src="../../assets/avatar/10000002/side.png" alt="side icon"/>
</div>
<div class="role-name" style="background-color: rgb(240 226 179);">神里绫华</div>
</div>
<div>90</div>
<div class="full-friendship">10</div>
<div class="color red">
<div class="number">6</div>
</div>
<div class="talent red-bg talent-level-max talent-buffed">13</div>
<div class="talent talent-level-max">13</div>
<div class="talent talent-level-max">13</div>
<div class="weapon weapon-5-star" style="flex: 6">
<div>Lv.90</div>
<div class="color red">
<div class="number">5</div>
</div>
<div><img src="../../assets/weapon/11509/awaken.png" alt="weapon"></div>
<div>雾切之回光</div>
</div>
</div>
<div class="row second-row">
<div style="background-color: rgb(240 226 179)">
2
</div>
<div class="role" style="flex: 4;background-color: rgb(240 226 179)">
<div class="role-icon" style="flex: 1.5;">
<img src="../../assets/avatar/10000042/side.png" alt="side icon"/>
</div>
<div class="role-name" style="flex: 2.5">刻晴</div>
</div>
<div>90</div>
<div class="full-friendship">10</div>
<div class="color purple">
<div class="number">5</div>
</div>
<div class="talent talent-level-max talent-buffed">13</div>
<div class="talent talent-level-2 talent-buffed">13</div>
<div class="talent talent-level-4 talent-buffed">13</div>
<div class="weapon weapon-4-star" style="flex: 6">
<div>Lv.90</div>
<div class="color red">
<div class="number">5</div>
</div>
<div><img src="../../assets/weapon/11409/awaken.png" alt="weapon"></div>
<div>黑剑</div>
</div>
</div>
<div class="row">
<div style="background-color: rgb(229 171 229/70%)">
3
</div>
<div class="role" style="flex: 4;background-color: rgb(240 226 179)">
<div class="role-icon" style="flex: 1.5;">
<img src="../../assets/avatar/10000016/side.png" alt="side icon"/>
</div>
<div class="role-name" style="flex: 2.5">迪卢克</div>
</div>
<div>90</div>
<div class="full-friendship">10</div>
<div class="color purple">
<div class="number">5</div>
</div>
<div class="talent talent-level-4">9</div>
<div class="talent talent-level-4 talent-buffed">12</div>
<div class="talent talent-level-4">9</div>
<div class="weapon weapon-5-star" style="flex: 6">
<div>Lv.9&nbsp;&nbsp;</div>
<div class="color green">
<div class="number">1</div>
</div>
<div><img src="../../assets/weapon/12502/awaken.png" alt="weapon"></div>
<div>狼的末路</div>
</div>
</div>
<div class="notice">
<div>
*想查看完整数据请在指令中加上<code>all</code>或者<code>全部</code>: <code>/avatars all</code><code>全部练度查询</code>
</div>
<div>※技能列表每 <span style="font-weight: bold">6</span> 个小时更新一次</div>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,157 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Avatar List</title>
<link type="text/css" href="./style.css" rel="stylesheet"/>
<link type="text/css" href="../../styles/public.css" rel="stylesheet"/>
<style>
.avatar > div::after {
background-position: center center;
background-size: cover;
background-image: url("../../background/rarity/half/{{ rarity }}.png");
}
</style>
</head>
<body>
<div class="container">
<div class="head" style="background-image: url('{{ namecard }}')">
<div class="avatar">
<div><img src="{{ avatar }}" alt="avatar"></div>
</div>
<div class="player">
<div>
<div class="nickname">{{ nickname }}</div>
<div class="uid">UID: {{ uid }}</div>
</div>
</div>
<div class="logo"></div>
</div>
<div class="content">
<div class="row">
<div>#</div>
<div style="flex: 4">角色</div>
<div>等级</div>
<div>好感</div>
<div>命座</div>
<div class="talent">普攻</div>
<div class="talent">战技</div>
<div class="talent">爆发</div>
<div style="flex: 6">武器</div>
</div>
{% for avatar_data in avatar_datas %}
{% set avatar = avatar_data.avatar %}
{% set weapon = avatar.weapon %}
{% set skill_datas = avatar_data.skills %}
{% set is_traveler = avatar.name == '旅行者' %}
{% if avatar.rarity == 5 %}
{% set row_bg = 'rgb(240 226 179)' %}
{% else %}
{% set row_bg = 'rgb(229 171 229/70%)' %}
{% endif %}
<div
{% if loop.index is even %}
class="row second-row"
{% else %}
class="row"
{% endif %}
>
<div style="background-color: {{ row_bg }}">{{ loop.index }}</div>
<div class="role" style="flex: 4;background-color: {{ row_bg }}">
<div class="role-icon" style="flex: 1.5;">
<img src="{{ avatar_data.icon }}" alt="side icon"/>
</div>
<div class="role-name">
{% if is_traveler %}
{% if avatar.id == 10000007 %}
{% else %}
{% endif %}
{% else %}
{{ avatar.name }}
{% endif %}
</div>
</div>
<div>{{ avatar.level }}</div>
<div
{% if avatar.friendship == 10 %}
class="full-friendship"
{% endif %}
>
{% if is_traveler %}
/
{% else %}
{{ avatar.friendship }}
{% endif %}
</div>
<div
{% set constellation = avatar.constellation %}
{% if constellation != 0 %}
class="color {{ ['green', 'cyan', 'blue', 'purple', 'pink', 'red'][constellation - 1] }}"
{% endif %}
>
<div class="number">{{ constellation }}</div>
</div>
{% for skill_data in skill_datas %}
{% set skill = skill_data.skill %}
{% set talent_style = 'talent' %}
{% set skill_level = skill.level %}
{% if skill_level < 4 %}
{% set talent_style = talent_style + ' talent-level-first' %}
{% endif %}
{% if skill.max_level == skill.level %}
{% set talent_style = talent_style + ' talent-level-max' %}
{% endif %}
{% if skill_data.buffed %}
{% set talent_style = talent_style + ' talent-buffed' %}
{% set skill_level = skill_level + 3 %}
{% endif %}
{% if skill.max_level != skill.level %}
{% if skill_level < 4 %}
{% set talent_style = talent_style + ' talent-level-1' %}
{% elif skill_level < 6 %}
{% set talent_style = talent_style + ' talent-level-2' %}
{% elif skill_level < 9 %}
{% set talent_style = talent_style + ' talent-level-3' %}
{% else %}
{% set talent_style = talent_style + ' talent-level-4' %}
{% endif %}
{% endif %}
<div class="{{ talent_style }}">{{ skill_level }}</div>
{% endfor %}
<div class="weapon weapon-{{ weapon.rarity }}-star" style="flex: 6">
<div>
{% if weapon.level < 10 %}
Lv.{{ weapon.level }}&nbsp;&nbsp;
{% else %}
Lv.{{ weapon.level }}
{% endif %}
</div>
<div class="color {{ ['green', 'cyan', 'blue', 'purple', 'red'][weapon.refinement - 1] }}">
<div class="number">{{ weapon.refinement }}</div>
</div>
<div><img src="{{ avatar_data.weapon }}" alt="weapon"></div>
<div>{{ weapon.name }}</div>
</div>
</div>
{% endfor %}
<div class="notice">
{% if has_more %}
<div>
*想查看完整数据请在指令中加上<code>all</code>或者<code>全部</code>: <code>/avatars all</code><code>全部练度查询</code>
</div>
{% endif %}
<div>※技能列表每 <span style="font-weight: bold">6</span> 个小时更新一次</div>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,429 @@
:root {
--white: rgb(246 248 249);
--bg-color: rgb(233 229 220);
--h-color: rgb(203 189 162);
--red: rgb(255 86 33/ 80%);
--pink: rgb(215 57 203/80%);
--purple: rgb(159 68 211/80%);
--blue: rgb(98 168 233/ 80%);
--cyan: rgb(4 150 255/80%);
--green: rgb(67 185 124/ 80%);
--grey: rgb(189 191 190);
}
.color::before {
content: '';
width: calc(1em + 3px);
height: calc(1em + 12px);
position: absolute;
top: 50%;
left: 50%;
z-index: 1;
transform: translateX(-50%) translateY(-50%);
border-radius: 8px;
box-shadow: 1px 1px 10px rgb(0 0 0/20%);
}
.green::before {
background-image: linear-gradient(135deg, rgb(129, 251, 184) 10%, rgb(40, 199, 111) 100%);
}
.cyan::before {
background-image: linear-gradient(135deg, rgb(144, 247, 236) 10%, rgb(50, 204, 188) 100%);
}
.blue::before {
background-image: linear-gradient(135deg, rgb(171, 220, 255) 10%, rgb(3, 150, 255) 100%);
}
.purple::before {
background-image: linear-gradient(135deg, rgb(206, 159, 252) 10%, rgb(115, 103, 240) 100%);
}
.pink::before {
background-image: linear-gradient(135deg, rgb(246, 206, 236) 10%, rgb(217, 57, 205) 100%);
}
.red::before {
background-image: linear-gradient(to top left, rgb(255, 8, 68) 0%, rgb(255, 177, 153) 100%);
}
/* stylelint-disable */
body {
margin: 0;
padding: 0;
background-color: rgb(236, 236, 236);
}
.container {
width: 1000px;
display: flex;
flex-flow: column;
justify-content: center;
align-items: center;
padding: 20px;
}
.container > div {
box-shadow: 1px 1px 15px rgb(0 0 0 /60%);
}
.head {
width: 100%;
height: 150px;
margin-bottom: 40px;
background-color: rgb(236, 229, 216);
background-repeat: no-repeat;
background-size: auto calc(100% + 2px);
background-position: 0 -1px;
border-radius: 50px 100px 100px 50px;
position: relative;
display: flex;
align-items: center;
overflow: hidden;
}
.avatar {
width: 110px;
height: 110px;
margin: 0 60px 0 70px;
filter: drop-shadow(1px 1px 10px rgb(0 0 0/50%));
}
.avatar > div {
width: inherit;
height: 200%;
position: absolute;
bottom: 0;
z-index: 1;
border-radius: 0 0 200px 200px;
overflow: hidden;
}
.avatar > div::before {
content: '';
width: calc(100% - 6px);
height: calc((100% / 2 - 6px) / 2);
position: absolute;
left: 50%;
bottom: 0;
z-index: 3;
transform: translateX(-50%);
border-radius: 0 0 200px 200px;
border-bottom: 3px solid var(--white);
border-right: 3px solid var(--white);
border-left: 3px solid var(--white);
}
.avatar > div::after {
content: '';
width: calc(100% - 6px);
height: calc(100% / 2 - 6px);
position: absolute;
left: 50%;
bottom: 0;
z-index: 1;
transform: translateX(-50%);
border-radius: 50%;
border-top: 3px solid var(--white);
border-right: 3px solid var(--white);
border-left: 3px solid var(--white);
}
.avatar > div > img {
width: inherit;
position: absolute;
bottom: 0;
z-index: 2;
}
.player {
text-shadow: 1px 1px 5px rgb(0 0 0/10%);
}
.nickname {
font-size: 40px;
font-weight: bolder;
color: var(--white);
text-shadow: 1px 1px 10px rgb(0 0 0/30%);
}
.uid {
font-size: 20px;
color: var(--white);
text-shadow: 1px 1px 10px rgb(0 0 0/30%);
}
.logo {
width: 200px;
height: 100%;
margin-left: auto;
margin-right: 8%;
background-image: url("../../img/logo.png");
background-size: contain;
background-repeat: no-repeat;
background-position: center center;
filter: drop-shadow(5px 5px 10px rgb(0 0 0/50%));
}
.content {
width: 100%;
background-color: var(--white);
border-radius: 20px;
position: relative;
display: flex;
flex-flow: column;
justify-items: center;
overflow: hidden;
font-size: 21px;
}
.row {
display: flex;
align-items: center;
width: 100%;
position: relative;
z-index: 0;
}
.second-row::before {
content: '';
width: 100%;
height: 100%;
position: absolute;
z-index: 0;
background-color: rgb(0 0 0/10%);
}
.second-row > div:first-child::before,
.second-row > div:nth-child(2)::before {
content: '';
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
z-index: -1;
background-color: rgb(200 200 200 /30%);
}
.content > .row:first-child {
background-color: rgb(204, 204, 204);
font-weight: bold;
}
.row > div {
padding: 10px 0;
height: calc(1em + 4px);
flex: 1;
text-align: center;
border-style: solid;
border-width: 0 1px 1px 0;
border-color: rgb(208, 208, 208);
position: relative;
z-index: 1;
}
.row > div:last-child {
border-right-width: 0;
}
.row > div:first-child:not(.content > .row:first-child > div:first-child),
.row > div:nth-child(2):not(.content > .row:first-child > div:nth-child(2)) {
border-right-color: rgb(203, 190, 148);
border-left-color: rgba(0, 0, 0, 0);
}
.number {
position: relative;
z-index: 2;
color: rgb(102, 102, 102);
}
.color > .number {
color: var(--white);
}
.role {
display: flex;
position: relative;
}
.role-icon {
border-right-color: rgba(0, 0, 0, 0) !important;
}
.role-icon > img {
height: calc(100% + 10px);
position: absolute;
left: 15px;
bottom: 4px;
filter: drop-shadow(0 0 2px rgb(0 0 0/50%));
}
.role-name {
flex: 2.5 !important;
text-align: left !important;
}
.weapon {
position: relative;
display: inline-flex;
justify-content: center;
text-align: left !important;
}
.weapon > div {
position: relative;
z-index: 2;
}
.weapon > div:has(.number) {
margin: 0 10px !important;
}
.weapon > div:first-child {
width: 80px;
text-align: right;
}
.weapon > div:last-child {
width: 140px;
}
.weapon > div:has(img) {
filter: drop-shadow(1px 1px 2px rgb(0 0 0/80%));
}
.weapon > div > img {
height: 40px;
position: relative;
bottom: 8px;
}
.weapon-1-star {
background-color: rgb(250 250 250);
box-shadow: inset 0 0 10px 2px rgb(220 220 220);
}
.weapon-2-star {
background-color: rgb(250 250 250);
box-shadow: inset 0 0 10px 2px rgb(195, 237, 183);
}
.weapon-3-star {
background-color: rgb(228, 237, 252);
box-shadow: inset 0 0 10px 2px rgb(183, 190, 237);
}
.weapon-4-star {
background-color: rgb(250, 228, 241);
box-shadow: inset 0 0 10px 2px rgb(233, 182, 221);
}
.weapon-5-star {
background-color: rgb(255, 246, 221);
box-shadow: inset 0 0 10px 2px rgb(239, 215, 153);
}
.full-friendship {
color: var(--white);
background-size: 85%;
background-repeat: no-repeat;
background-position: center center;
background-image: url(data:image/svg+xml;base64,PHN2ZyB0PSIxNjY1NzE2OTc4NTM2IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjI3MTEiCiAgICAgd2lkdGg9IjEwMCIgaGVpZ2h0PSIxMDAiPgogICAgPHBhdGggZD0iTTUzMy41MDQgMjY4LjI4OHEzMy43OTItNDEuOTg0IDcxLjY4LTc1Ljc3NiAzMi43NjgtMjcuNjQ4IDc0LjI0LTUwLjE3NnQ4Ni41MjgtMTkuNDU2cTYzLjQ4OCA1LjEyIDEwNS45ODQgMzAuMjA4dDY3LjU4NCA2My40ODggMzQuMzA0IDg3LjA0IDYuMTQ0IDk5Ljg0LTE3LjkyIDk3Ljc5Mi0zNi44NjQgODcuMDQtNDguNjQgNzQuNzUyLTUzLjI0OCA2MS45NTJxLTQwLjk2IDQxLjk4NC04NS41MDQgNzguMzM2dC04NC45OTIgNjIuNDY0LTczLjcyOCA0MS40NzItNTEuNzEyIDE1LjM2cS0yMC40OCAxLjAyNC01Mi4yMjQtMTQuMzM2dC02OS42MzItNDEuNDcyLTc5Ljg3Mi02MS45NTItODIuOTQ0LTc1Ljc3NnEtMjYuNjI0LTI1LjYtNTcuMzQ0LTU5LjM5MnQtNTcuODU2LTc0LjI0LTQ2LjU5Mi04Ny41NTItMjEuNTA0LTEwMC4zNTIgMTEuMjY0LTk5Ljg0IDM5LjkzNi04My40NTYgNjUuNTM2LTYxLjk1MiA4OC4wNjQtMzUuMzI4cTI0LjU3Ni01LjEyIDQ5LjE1Mi0xLjUzNnQ0OC4xMjggMTIuMjg4IDQ1LjA1NiAyMi4wMTYgNDAuOTYgMjcuNjQ4cTQ1LjA1NiAzMy43OTIgODYuMDE2IDgwLjg5NnoiCiAgICAgICAgICBwLWlkPSIyNzEyIiBmaWxsPSIjZGUyOTEwIj48L3BhdGg+Cjwvc3ZnPg==);
filter: drop-shadow(1px 1px 5px rgb(0 0 0/20%));
}
.talent {
position: absolute;
background-size: contain, 1.6em;
background-repeat: no-repeat;
background-position: center center;
text-shadow: 1px 1px 2px rgb(0 0 0 /20%);
z-index: -1 !important;
border-right-width: 0 !important;
border-left-width: 0 !important;
}
.talent-buffed {
font-weight: bold;
}
.talent-level-first {
background-color: rgb(189, 191, 190) !important;
}
.talent-level-1 {
background-color: rgb(189, 191, 190);
}
.talent-level-first.talent-level-2.talent-buffed {
color: rgb(0, 108, 199);
}
.talent-level-2 {
background-color: var(--green);
}
.talent-level-3 {
background-color: var(--blue);
}
.talent-level-4 {
background-color: rgb(190, 160, 250);
}
.talent-level-max {
background-image: linear-gradient(90deg, rgba(251, 129, 124, 0.8) 0%, rgba(255, 93, 85, 0.65) 50%, rgba(251, 129, 124, 0.8) 100%), url("../../img/crown.png") !important;
}
.talent-level-1.talent-buffed {
color: rgb(0, 108, 199);
}
.talent-level-2.talent-buffed {
color: rgb(0, 88, 0);
}
.talent-level-3.talent-buffed {
color: rgb(0, 108, 199);
}
.talent-level-4.talent-buffed {
color: rgb(114, 4, 101);
}
.talent-level-max.talent-buffed {
color: rgb(183, 0, 0) !important;
text-shadow: 0 0 4px white !important;
}
.content > .row:nth-last-child(2) > div {
border-bottom-width: 0 !important;
}
.notice {
padding: 5px 0;
font-size: 14px;
font-style: italic;
background-color: rgb(204, 204, 204);
display: inline-flex;
}
.notice > div {
padding: 5px 20px;
}
.notice > div:last-child {
text-align: right;
margin-left: auto;
}
code {
padding: 5px;
background-color: rgb(0 0 0/10%);
border-radius: 5px;
}
/* stylelint-enable */

BIN
resources/img/crown.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

BIN
resources/img/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB