添加角色卡片插件

Co-authored-by: Li Chuangbo <im@chuangbo.li>
Co-authored-by: xtaodada <xtao@xtaolink.cn>
Co-authored-by: luoshuijs <luoshuijs@outlook.com>
This commit is contained in:
洛水居室 2022-09-09 22:50:17 +08:00 committed by GitHub
parent b5fad5cf66
commit a046b22f3a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 1076 additions and 123 deletions

View File

@ -66,3 +66,5 @@ python ./run.py
| [西风驿站 猫冬](https://bbs.mihoyo.com/ys/accountCenter/postList?id=74019947) | 本项目攻略图图源 |
| [Yunzai-Bot](https://github.com/Le-niao/Yunzai-Bot) | 本项使用的抽卡图片和前端资源来源 |
| [Crawler-ghhw](https://github.com/DGP-Studio/Crawler-ghhw) | 本项目参考的爬虫代码 |
| [Enka.Network](https://enka.network) | 角色卡片的数据来源 |
| [miao-plugin](https://github.com/yoimiya-kokomi/miao-plugin) | 角色卡片的参考项目 |

View File

@ -0,0 +1,68 @@
import enum
class FightProp(enum.Enum):
BASE_HP = "基础血量"
FIGHT_PROP_BASE_ATTACK = "基础攻击力"
FIGHT_PROP_BASE_DEFENSE = "基础防御力"
FIGHT_PROP_BASE_HP = "基础血量"
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):
_value_: float
value: float
FIGHT_PROP_BASE_ATTACK = 1
FIGHT_PROP_BASE_DEFENSE = 1
FIGHT_PROP_BASE_HP = 1
FIGHT_PROP_ATTACK = 662 / 3110 # 攻击力
FIGHT_PROP_ATTACK_PERCENT = 4 / 3 # 攻击力百分比
FIGHT_PROP_HP = 662 / 47800 # 生命
FIGHT_PROP_HP_PERCENT = 4 / 3 # 生命百分比
FIGHT_PROP_DEFENSE = 662 / 3890 # 防御力
FIGHT_PROP_DEFENSE_PERCENT = 662 / 583 # 防御力百分比
FIGHT_PROP_ELEMENT_MASTERY = 1 / 3 # 元素精通
FIGHT_PROP_CRITICAL = 2 # 暴击率
FIGHT_PROP_CRITICAL_HURT = 1 # 暴击伤害
FIGHT_PROP_CHARGE_EFFICIENCY = 662 / 518 # 元素充能效率
FIGHT_PROP_FIRE_SUB_HURT = 1
FIGHT_PROP_ELEC_SUB_HURT = 1
FIGHT_PROP_ICE_SUB_HURT = 1
FIGHT_PROP_WATER_SUB_HURT = 1
FIGHT_PROP_WIND_SUB_HURT = 1
FIGHT_PROP_ROCK_SUB_HURT = 1
FIGHT_PROP_GRASS_SUB_HURT = 1
FIGHT_PROP_FIRE_ADD_HURT = 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

View File

@ -0,0 +1,42 @@
import os
import ujson as json
from enkanetwork import EquipmentsStats
from modules.playercards.fight_prop import FightProp, FightPropScore
_project_path = os.path.dirname(__file__)
_fight_prop_rule_file = os.path.join(_project_path, "metadata", "FightPropRule.json")
with open(_fight_prop_rule_file, "r", encoding="utf-8") as f:
fight_prop_rule_data: dict = json.load(f)
class ArtifactStatsTheory:
def __init__(self, character_name: str):
self.character_name = 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]
if not self.main_prop:
self.main_prop = [FightProp.FIGHT_PROP_CRITICAL, FightProp.FIGHT_PROP_CRITICAL_HURT,
FightProp.FIGHT_PROP_ATTACK_PERCENT]
# 修正要评分的数值词条
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:
"""圣遗物副词条评分
Args:
sub_stats: 圣遗物对象
Returns:
返回得分
"""
score: float = 0
if sub_stats.prop_id in map(lambda x: x.name, self.main_prop):
score = float(FightPropScore[sub_stats.prop_id].value) * sub_stats.value
return round(score, 1)

View File

@ -0,0 +1,380 @@
{
"旅行者": [
"攻击力百分比",
"暴击率",
"暴击伤害",
"元素充能效率"
],
"安柏": [
"攻击力百分比",
"暴击率",
"暴击伤害",
"元素精通",
"物理伤害加成"
],
"凯亚": [
"攻击力百分比",
"暴击率",
"暴击伤害",
"物理伤害加成",
"元素充能效率"
],
"丽莎": [
"攻击力百分比",
"暴击率",
"暴击伤害",
"元素精通"
],
"芭芭拉": [
"生命值百分比",
"攻击力百分比",
"暴击率",
"暴击伤害",
"元素充能效率",
"治疗加成"
],
"芭芭拉-核爆": [
"生命值百分比",
"攻击力百分比",
"暴击率",
"暴击伤害",
"元素精通",
"元素充能效率",
"治疗加成"
],
"雷泽": [
"攻击力百分比",
"暴击率",
"暴击伤害",
"物理伤害加成"
],
"香菱": [
"攻击力百分比",
"暴击率",
"暴击伤害",
"元素精通",
"元素充能效率"
],
"北斗": [
"攻击力百分比",
"暴击率",
"暴击伤害",
"元素精通",
"元素充能效率"
],
"行秋": [
"攻击力百分比",
"暴击率",
"暴击伤害",
"元素充能效率"
],
"凝光": [
"攻击力百分比",
"暴击率",
"暴击伤害",
"元素充能效率"
],
"菲谢尔": [
"攻击力百分比",
"暴击率",
"暴击伤害",
"元素精通",
"物理伤害加成"
],
"班尼特": [
"生命值百分比",
"攻击力百分比",
"暴击率",
"暴击伤害",
"元素充能效率",
"治疗加成"
],
"诺艾尔": [
"攻击力百分比",
"暴击率",
"暴击伤害",
"元素充能效率"
],
"重云": [
"攻击力百分比",
"暴击率",
"暴击伤害",
"元素精通",
"元素充能效率"
],
"砂糖": [
"攻击力百分比",
"暴击率",
"暴击伤害",
"元素精通",
"元素充能效率"
],
"琴": [
"攻击力百分比",
"暴击率",
"暴击伤害",
"物理伤害加成",
"元素充能效率",
"治疗加成"
],
"迪卢克": [
"攻击力百分比",
"暴击率",
"暴击伤害",
"元素精通"
],
"七七": [
"攻击力百分比",
"暴击率",
"暴击伤害",
"物理伤害加成",
"元素充能效率",
"治疗加成"
],
"莫娜": [
"攻击力百分比",
"暴击率",
"暴击伤害",
"元素精通",
"元素充能效率"
],
"刻晴": [
"攻击力百分比",
"暴击率",
"暴击伤害",
"元素精通",
"物理伤害加成"
],
"温迪": [
"攻击力百分比",
"暴击率",
"暴击伤害",
"元素精通",
"元素充能效率"
],
"可莉": [
"攻击力百分比",
"暴击率",
"暴击伤害",
"元素精通",
"元素充能效率"
],
"迪奥娜": [
"生命值百分比",
"攻击力百分比",
"暴击率",
"暴击伤害",
"元素充能效率",
"治疗加成"
],
"达达利亚": [
"攻击力百分比",
"暴击率",
"暴击伤害",
"元素精通",
"元素充能效率"
],
"辛焱": [
"攻击力百分比",
"暴击率",
"暴击伤害",
"物理伤害加成"
],
"钟离": [
"生命值百分比",
"攻击力百分比",
"暴击率",
"暴击伤害",
"物理伤害加成",
"元素充能效率"
],
"钟离-安如磐石": [
"生命值百分比",
"攻击力百分比",
"暴击率",
"暴击伤害",
"元素充能效率"
],
"阿贝多": [
"攻击力百分比",
"暴击率",
"暴击伤害"
],
"甘雨": [
"攻击力百分比",
"暴击率",
"暴击伤害",
"元素精通"
],
"甘雨-永冻": [
"攻击力百分比",
"暴击率",
"暴击伤害",
"元素充能效率"
],
"魈": [
"攻击力百分比",
"暴击率",
"暴击伤害",
"元素充能效率"
],
"胡桃": [
"生命值百分比",
"攻击力百分比",
"暴击率",
"暴击伤害",
"元素精通"
],
"罗莎莉亚": [
"攻击力百分比",
"暴击率",
"暴击伤害",
"物理伤害加成",
"元素充能效率"
],
"烟绯": [
"攻击力百分比",
"暴击率",
"暴击伤害",
"元素精通",
"元素充能效率"
],
"优菈": [
"攻击力百分比",
"暴击率",
"暴击伤害",
"物理伤害加成",
"元素充能效率"
],
"枫原万叶": [
"攻击力百分比",
"暴击率",
"暴击伤害",
"元素精通",
"元素充能效率"
],
"神里绫华": [
"攻击力百分比",
"暴击率",
"暴击伤害",
"元素充能效率"
],
"早柚": [
"攻击力百分比",
"暴击率",
"暴击伤害",
"元素精通",
"元素充能效率",
"治疗加成"
],
"宵宫": [
"攻击力百分比",
"暴击率",
"暴击伤害",
"元素精通"
],
"埃洛伊": [
"攻击力百分比",
"暴击率",
"暴击伤害"
],
"九条裟罗": [
"攻击力百分比",
"暴击率",
"暴击伤害",
"元素充能效率"
],
"雷电将军": [
"攻击力百分比",
"暴击率",
"暴击伤害",
"元素充能效率"
],
"珊瑚宫心海": [
"生命值百分比",
"攻击力百分比",
"元素充能效率",
"治疗加成"
],
"托马": [
"生命值百分比",
"攻击力百分比",
"暴击率",
"暴击伤害",
"元素充能效率"
],
"五郎": [
"攻击力百分比",
"暴击率",
"暴击伤害",
"元素充能效率"
],
"荒泷一斗": [
"攻击力百分比",
"暴击率",
"暴击伤害",
"元素充能效率"
],
"云堇": [
"攻击力百分比",
"暴击率",
"暴击伤害",
"元素充能效率"
],
"申鹤": [
"攻击力百分比",
"暴击率",
"暴击伤害",
"元素充能效率"
],
"八重神子": [
"攻击力百分比",
"暴击率",
"暴击伤害",
"元素精通",
"元素充能效率"
],
"神里绫人": [
"生命值百分比",
"攻击力百分比",
"暴击率",
"暴击伤害",
"元素充能效率"
],
"夜兰": [
"生命值百分比",
"暴击率",
"暴击伤害",
"元素充能效率"
],
"久岐忍": [
"生命值百分比",
"攻击力百分比",
"暴击率",
"暴击伤害",
"元素精通",
"元素充能效率",
"治疗加成"
],
"鹿野院平藏": [
"攻击力百分比",
"暴击率",
"暴击伤害",
"元素精通",
"元素充能效率"
],
"提纳里": [
"攻击力百分比",
"暴击率",
"暴击伤害",
"元素精通",
"元素充能效率"
],
"柯莱": [
"攻击力百分比",
"暴击率",
"暴击伤害",
"元素精通",
"元素充能效率"
]
}

View File

@ -0,0 +1,308 @@
from typing import Union, List, Any, Tuple
from enkanetwork import (
EnkaNetworkAPI,
Equipments,
EquipmentsType,
EquipmentsStats,
Stats,
CharacterInfo,
Assets,
DigitType, EnkaServerError, Forbidden, UIDNotFounded, VaildateUIDError, HTTPException, StatsPercentage, )
from pydantic import BaseModel
from telegram import Update
from telegram.constants import ChatAction
from telegram.ext import CommandHandler, filters, CallbackContext, MessageHandler
from core.baseplugin import BasePlugin
from core.plugin import Plugin, handler
from core.template import TemplateService
from core.user import UserService
from core.user.error import UserNotFoundError
from modules.playercards.helpers import ArtifactStatsTheory
from utils.bot import get_all_args
from utils.decorators.error import error_callable
from utils.decorators.restricts import restricts
from utils.helpers import url_to_file
from utils.log import logger
from utils.models.base import RegionEnum
assets = Assets(lang="chs")
class PlayerCards(Plugin, BasePlugin):
def __init__(self, user_service: UserService = None, template_service: TemplateService = None):
self.user_service = user_service
self.client = EnkaNetworkAPI(lang="chs")
self.template_service = template_service
@handler(CommandHandler, command="player_card", block=False)
@handler(MessageHandler, filters=filters.Regex("^角色卡片查询(.*)"), block=False)
@restricts()
@error_callable
async def player_cards(self, update: Update, context: CallbackContext) -> None:
user = update.effective_user
message = update.effective_message
args = get_all_args(context)
await message.reply_chat_action(ChatAction.TYPING)
try:
user_info = await self.user_service.get_user_by_id(user.id)
if user_info.region == RegionEnum.HYPERION:
uid = user_info.yuanshen_uid
else:
uid = user_info.genshin_uid
except UserNotFoundError:
reply_message = await message.reply_text("未查询到账号信息,请先私聊派蒙绑定账号")
if filters.ChatType.GROUPS.filter(message):
self._add_delete_message_job(
context, reply_message.chat_id, reply_message.message_id, 30
)
self._add_delete_message_job(
context, message.chat_id, message.message_id, 30
)
return
if len(args) == 1:
character_name = args[0]
else:
reply_message = await message.reply_text("请回复角色名参数")
if filters.ChatType.GROUPS.filter(reply_message):
self._add_delete_message_job(context, message.chat_id, message.message_id)
self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id)
return
logger.info(f"用户 {user.full_name}[{user.id}] 角色卡片查询命令请求 || character_name[{character_name}] uid[{uid}]")
try:
data = await self.client.fetch_user(uid)
except EnkaServerError:
await message.reply_text("Enka.Network 服务请求错误,请稍后重试")
return
except Forbidden:
await message.reply_text("Enka.Network 服务请求被拒绝,请稍后重试")
return
except HTTPException:
await message.reply_text("Enka.Network HTTP 服务请求错误,请稍后重试")
return
except UIDNotFounded:
await message.reply_text("UID 未找到")
return
except VaildateUIDError:
await message.reply_text("UID 错误或者非法")
return
if len(data.characters) == 0:
await message.reply_text("请先将角色加入到角色展柜并允许查看角色详情")
return
for characters in data.characters:
if characters.name == character_name:
break
else:
await message.reply_text(f"角色展柜中未找到 {character_name}")
return
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
pnd_data = await RenderTemplate(uid, characters, self.template_service).render()
await message.reply_photo(pnd_data, filename=f"player_card_{uid}_{character_name}.png")
class Artifact(BaseModel):
"""在 enka Equipments model 基础上扩展了圣遗物评分数据"""
equipment: Equipments
# 圣遗物评分
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),
("C", 16.5),
("B", 23.1),
("A", 29.7),
("S", 36.3),
("SS", 42.9),
("SSS", 49.5),
("ACE", 56.1),
("ACE²", 66)):
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-neutral-200",
"A": "text-violet-400",
"S": "text-violet-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: CharacterInfo, template_service: TemplateService = None):
self.uid = uid
self.template_service = template_service
# 因为需要替换线上 enka 图片地址为本地地址,先克隆数据,避免修改原数据
self.character = character.copy(deep=True)
async def render(self):
# 缓存所有图片到本地
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),
("C", 16.5),
("B", 23.1),
("A", 29.7),
("S", 36.3),
("SS", 42.9),
("SSS", 49.5),
("ACE", 56.1),
("ACE²", 66)):
if artifact_total_score / 5 >= r[1]:
artifact_total_score_label = r[0]
data = {
"uid": self.uid,
"character": self.character,
"stats": await self.de_stats(),
"weapon": self.find_weapon(),
# 圣遗物评分
"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,
# 需要在模板中使用的 enum 类型
"DigitType": DigitType,
}
# html = await self.template_service.render_async(
# "genshin/player_card", "player_card.html", data
# )
# logger.debug(html)
return await self.template_service.render(
"genshin/player_card",
"player_card.html",
data,
{"width": 950, "height": 1080},
full_page=True,
)
async def de_stats(self) -> List[Tuple[str, Any]]:
stats = self.character.stats
items: List[Tuple[str, Any]] = []
logger.debug(self.character.stats)
# items.append(("基础生命值", stats.BASE_HP.to_rounded()))
items.append(("生命值", stats.FIGHT_PROP_MAX_HP.to_rounded()))
# items.append(("基础攻击力", stats.FIGHT_PROP_BASE_ATTACK.to_rounded()))
items.append(("攻击力", stats.FIGHT_PROP_CUR_ATTACK.to_rounded()))
# items.append(("基础防御力", stats.FIGHT_PROP_BASE_DEFENSE.to_rounded()))
items.append(("防御力", stats.FIGHT_PROP_CUR_DEFENSE.to_rounded()))
items.append(("暴击率", stats.FIGHT_PROP_CRITICAL.to_percentage_symbol()))
items.append(
(
"暴击伤害",
stats.FIGHT_PROP_CRITICAL_HURT.to_percentage_symbol(),
)
)
items.append(
(
"元素充能效率",
stats.FIGHT_PROP_CHARGE_EFFICIENCY.to_percentage_symbol(),
)
)
items.append(("元素精通", stats.FIGHT_PROP_ELEMENT_MASTERY.to_rounded()))
# 查找元素伤害加成和治疗加成
max_stat = StatsPercentage() # 用于记录最高元素伤害加成 避免武器特效影响
for stat in stats:
if 40 <= stat[1].id <= 46: # 元素伤害加成
if max_stat.value <= stat[1].value:
max_stat = stat[1]
elif stat[1].id == 29: # 物理伤害加成
pass
elif stat[1].id != 26: # 治疗加成
continue
value = (
stat[1].to_rounded()
if isinstance(stat[1], Stats)
else stat[1].to_percentage_symbol()
)
if value in ("0%", 0):
continue
name = assets.get_hash_map(stat[0])
if name is None:
continue
items.append((name, value))
if max_stat.id != 0:
for item in items:
if "元素伤害加成" in item[0]:
if max_stat.to_percentage_symbol() != item[1]:
items.remove(item)
return items
async def cache_images(self) -> None:
"""缓存所有图片到本地"""
# TODO: 并发下载所有资源
c = self.character
# 角色
c.image.banner.url = await url_to_file(c.image.banner.url)
# 技能
for item in c.skills:
item.icon.url = await url_to_file(item.icon.url)
# 命座
for item in c.constellations:
item.icon.url = await url_to_file(item.icon.url)
# 装备,包括圣遗物和武器
for item in c.equipments:
item.detail.icon.url = await url_to_file(item.detail.icon.url)
def find_weapon(self) -> Union[Equipments, None]:
"""在 equipments 数组中找到武器equipments 数组包含圣遗物和武器"""
for item in self.character.equipments:
if item.type == EquipmentsType.WEAPON:
return item
def find_artifacts(self) -> List[Artifact]:
"""在 equipments 数组中找到圣遗物,并转换成带有分数的 model。equipments 数组包含圣遗物和武器"""
stats = ArtifactStatsTheory(self.character.name)
def substat_score(s: EquipmentsStats) -> float:
return stats.theory(s)
return [
Artifact(
equipment=e,
# 圣遗物单行属性评分
substat_scores=[substat_score(s) for s in e.detail.substats],
)
for e in self.character.equipments
if e.type == EquipmentsType.ARTIFACT
]

View File

@ -0,0 +1,62 @@
{% 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.detail.icon.url }}"
alt="{{ item.equipment.detail.name }}"
/>
<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.detail.artifact_name_set }}</div>
<div class="flex text-sm space-x-2">
<div>{{ item.equipment.detail.mainstats.name }}</div>
<div class="italic">
{{ item.equipment.detail.mainstats.value }}
{%- if item.equipment.detail.mainstats.type == DigitType.PERCENT -%} % {%- endif %}
</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.detail.substats %}
<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">
{%- if stat.name == '元素充能效率' -%}
充能效率
{%- else -%}
{{ stat.name }}
{%- endif -%}
</div>
<div class="min-w-30 italic text-right">
+{{ stat.value }}
{%- if stat.type == DigitType.PERCENT -%} % {%- endif %}
</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>
<!-- 计算圣遗物分数就行了 原神又不是PVP 也卷不动隔壁 -->
<!--
<div
class="px-4 py-1 flex justify-between border-t border-neutral-200 border-dashed"
>
<div>备用</div>
<div class="italic">x 100.0%</div>
</div>
-->
</div>
{% endfor %}

View File

@ -0,0 +1,13 @@
<div class="flex-1 flex items-end justify-center">
<div class="flex pb-2">
{% for item in character.constellations %}
<div
class="w-16 h-16 flex items-center justify-center bg-contain bg-no-repeat bg-center
{%- if not item.unlocked %} grayscale opacity-75 {% endif %}"
style="background-image: url('img/talent-{{ character.element.name | lower }}.png')"
>
<img src="{{ item.icon.url }}" alt="" class="w-8 h-8" />
</div>
{% endfor %}
</div>
</div>

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View File

@ -1,112 +0,0 @@
body {
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: 10px;
}
.container {
background-color: #f24e4c;
width: 1600px;
height: 700px;;
display: flex;
flex-wrap: wrap;
color: #fff;
}
.left {
left: 0;
flex: 0 0 33.4%;
max-width: 33.4%;
height: 100%;
display: flex;
/* background-color: rgb(255,100,126); */
}
.middle {
left: 30%;
flex: 0 0 33.3%;
max-width: 34%;
width: 30%;
height: 100%;
display: flex;
background-color: rgb(246, 52, 64);
}
.right {
right: 0;
flex: 0 0 33.3%;
max-width: 33.3%;
height: 100%;
display: flex;
background-color: rgb(208, 52, 58);
}
.characters {
}
.gacha-splash {
z-index: 1;
}
.characters-info {
flex-direction: column;
position: absolute;
z-index: 10;
}
.characters-name {
}
.passive-talents {
left: 0;
width: 50%;
flex: 0 0 50%;
}
.passive-talents-icon {
width: 64px;
height: 64px;
}
.passive-talents-icon img {
width: 100%;
height: 100%;
}
.attack-talents {
right: 0;
width: 50%;
flex: 0 0 50%;
}
.attack-talents-icon {
width: 64px;
height: 64px;
}
.attack-talents-icon img {
width: 100%;
height: 100%;
}
.text {
text-shadow: 0 .08em .1em #000, 0 .1em .3em rgba(0, 0, 0, .4);
}
.hp-name {
}
.hp-value {
right: 0;
}
.numerical-panel {
width: 100%;
}

View File

@ -1,15 +1,86 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<title>Title</title>
<link href="./player_card.html.css" rel="stylesheet">
<link href="../styles/tailwind.min.css" rel="stylesheet">
</head>
<div class="container">
<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);
}
</div>
<body>
.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;
}
</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.element.name | lower }}.jpg')"
>
<div class="relative mb-4 overflow-hidden">
<!-- Character Background -->
<div
class="absolute w-full h-full -left-1/4 top-8 opacity-80 bg-cover bg-no-repeat bg-center"
style="background-image: url('{{ character.image.banner.url }}');"
></div>
<div class="relative w-full flex p-5 space-x-8">
{% include 'constellations.html' %}
</body>
</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.name }}
</div>
<div
class="flex flex-row justify-end text-2xl text-shadow space-x-2"
>
<div class="characters-level pr-4">UID {{ uid }}</div>
<div class="characters-level">Lv.{{ character.level }}</div>
<div class="characters-level bg-red-600 rounded-lg px-2">
❤ {{ character.friendship_level }}
</div>
</div>
</div>
{% include 'skills.html' %} {% include 'stats.html' %}
</div>
</div>
</div>
<!-- Info -->
<div class="px-5 relative">
<div class="grid grid-cols-3 gap-4">
<div class="flex flex-col space-y-2">
{% include 'weapon.html' %}
{% include 'score.html' %}
</div>
{% include 'artifacts.html' %}
</div>
</div>
</div>
</body>
</html>

View 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 {{ 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">{{ artifact_total_score }}</div>
<div class="text-base text-neutral-400">圣遗物评分</div>
</div>
</div>

View File

@ -0,0 +1,17 @@
<div class="flex flex-row">
{% for item in character.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.element.name | lower }}.png')"
>
<img src="{{ item.icon.url }}" 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"
>
{{ item.level }}
</div>
</div>
{% endfor %}
</div>

View File

@ -0,0 +1,10 @@
<div class="rounded-lg overflow-hidden bg-black bg-opacity-20">
{% for (name, value) in stats %}
<div
class="flex justify-between px-10 py-1.5 even:bg-black even:bg-opacity-30"
>
<div>{{ name }}</div>
<div class="italic">{{ value }}</div>
</div>
{% endfor %}
</div>

View File

@ -0,0 +1,17 @@
<div
class="flex-1 flex items-center bg-black bg-opacity-20 rounded-lg bg-contain bg-no-repeat"
style="background-image: url('{{ weapon.detail.icon.url }}')"
>
<div
class="flex-1 p-4 flex flex-col items-end justify-center h-full space-y-1"
>
<div class="text-2xl text-shadow">{{ weapon.detail.name }}</div>
<div class="star star-{{ weapon.detail.rarity }}"></div>
<div class="flex space-x-3 items-center">
<div class="italic text-shadow">Lv.{{ weapon.level }}</div>
<div class="bg-gray-600 rounded px-2 text-base">
精{{ weapon.refinement }}
</div>
</div>
</div>
</div>

File diff suppressed because one or more lines are too long