🎉 GenshinUID 3.1 beta
2
.gitignore
vendored
@ -1,2 +0,0 @@
|
||||
/.idea/
|
||||
mihoyo_libs/mihoyo_bbs/bg/**
|
16
.pre-commit-config.yaml
Normal file
@ -0,0 +1,16 @@
|
||||
ci:
|
||||
autofix_commit_msg: ":rotating_light: auto fix by pre-commit-ci"
|
||||
autofix_prs: true
|
||||
autoupdate_branch: master
|
||||
autoupdate_schedule: monthly
|
||||
autoupdate_commit_msg: ":arrow_up: auto update by pre-commit-ci"
|
||||
repos:
|
||||
- repo: https://github.com/pycqa/isort
|
||||
rev: 5.10.1
|
||||
hooks:
|
||||
- id: isort
|
||||
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 22.6.0
|
||||
hooks:
|
||||
- id: black
|
3
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["ms-python.python", "ms-python.vscode-pylance"]
|
||||
}
|
4
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"python.languageServer": "Pylance",
|
||||
"python.analysis.typeCheckingMode": "basic"
|
||||
}
|
5
__init__.py
Normal file
@ -0,0 +1,5 @@
|
||||
import os.path
|
||||
|
||||
from nonebot import load_plugins
|
||||
|
||||
load_plugins(os.path.dirname(__file__))
|
34
all_import.py
Normal file
@ -0,0 +1,34 @@
|
||||
import re
|
||||
import base64
|
||||
import asyncio
|
||||
import traceback
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Tuple, Union, Optional
|
||||
|
||||
import httpx
|
||||
from nonebot.log import logger
|
||||
from aiohttp import ClientConnectorError
|
||||
from aiocqhttp.exceptions import ActionFailed
|
||||
from nonebot import MessageSegment, get_bot # type: ignore
|
||||
|
||||
import hoshino
|
||||
from hoshino import Service
|
||||
from hoshino.typing import CQEvent, HoshinoBot, CommandSession # type: ignore
|
||||
from hoshino.util import (
|
||||
FreqLimiter,
|
||||
pic2b64,
|
||||
silence,
|
||||
concat_pic,
|
||||
filt_message,
|
||||
)
|
||||
|
||||
from .utils.db_operation.db_operation import select_db
|
||||
from .utils.message.get_image_and_at import ImageAndAt
|
||||
from .utils.message.error_reply import * # noqa: F403,F401
|
||||
from .utils.alias.alias_to_char_name import alias_to_char_name
|
||||
from .utils.exception.handle_exception import handle_exception
|
||||
from .utils.draw_image_tools.send_image_tool import convert_img
|
||||
from .utils.genshin_fonts.genshin_fonts import genshin_font_origin
|
||||
|
||||
sv = Service('genshinuid')
|
||||
hoshino_bot = get_bot()
|
239679
enkaToData/data/textMap.json
@ -1,154 +0,0 @@
|
||||
import asyncio
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import httpx
|
||||
|
||||
R_PATH = Path(__file__).parents[0]
|
||||
MAP_PATH = R_PATH / 'map'
|
||||
DATA_PATH = R_PATH / 'data'
|
||||
|
||||
version = '2.8.0'
|
||||
|
||||
avatarName2Element_fileName = f'avatarName2Element_mapping_{version}.json'
|
||||
weaponHash2Name_fileName = f'weaponHash2Name_mapping_{version}.json'
|
||||
weaponHash2Type_fileName = f'weaponHash2Type_mapping_{version}.json'
|
||||
skillId2Name_fileName = f'skillId2Name_mapping_{version}.json'
|
||||
talentId2Name_fileName = f'talentId2Name_mapping_{version}.json'
|
||||
avatarId2Name_fileName = f'avatarId2Name_mapping_{version}.json'
|
||||
|
||||
artifact2attr_fileName = f'artifact2attr_mapping_{version}.json'
|
||||
icon2Name_fileName = f'icon2Name_mapping_{version}.json'
|
||||
|
||||
with open(DATA_PATH / 'textMap.json', "r", encoding='UTF-8') as f:
|
||||
raw_data = json.load(f)
|
||||
|
||||
|
||||
async def avatarId2NameJson() -> None:
|
||||
with open(DATA_PATH / 'AvatarExcelConfigData.json', "r", encoding='UTF-8') as f:
|
||||
avatar_data = json.load(f)
|
||||
|
||||
temp = {}
|
||||
for i in avatar_data:
|
||||
temp[str(i['id'])] = raw_data[str(i['nameTextMapHash'])]
|
||||
|
||||
with open(MAP_PATH / avatarId2Name_fileName, 'w', encoding='UTF-8') as file:
|
||||
json.dump(temp, file, ensure_ascii=False)
|
||||
|
||||
|
||||
async def avatarName2ElementJson() -> None:
|
||||
with open(MAP_PATH / avatarId2Name_fileName, "r", encoding='UTF-8') as f:
|
||||
avatarId2Name = json.load(f)
|
||||
|
||||
temp = {}
|
||||
elementMap = {'风': 'Anemo', '岩': 'Geo', '草': 'Dendro', '火': 'Pyro', '水': 'Hydro', '冰': 'Cryo', '雷': 'Electro'}
|
||||
for i in list(avatarId2Name.values()):
|
||||
data = json.loads(httpx.get('https://info.minigg.cn/characters?query={}'.format(i)).text)
|
||||
if 'errcode' in data:
|
||||
pass
|
||||
else:
|
||||
temp[i] = elementMap[data['element']]
|
||||
|
||||
with open(MAP_PATH / avatarName2Element_fileName, 'w', encoding='UTF-8') as file:
|
||||
json.dump(temp, file, ensure_ascii=False)
|
||||
|
||||
|
||||
async def weaponHash2NameJson() -> None:
|
||||
with open(DATA_PATH / 'WeaponExcelConfigData.json', "r", encoding='UTF-8') as f:
|
||||
weapon_data = json.load(f)
|
||||
|
||||
temp = {}
|
||||
for i in weapon_data:
|
||||
temp[str(i['nameTextMapHash'])] = raw_data[str(i['nameTextMapHash'])]
|
||||
|
||||
with open(MAP_PATH / weaponHash2Name_fileName, 'w', encoding='UTF-8') as file:
|
||||
json.dump(temp, file, ensure_ascii=False)
|
||||
|
||||
|
||||
async def weaponHash2TypeJson() -> None:
|
||||
with open(DATA_PATH / 'WeaponExcelConfigData.json', "r", encoding='UTF-8') as f:
|
||||
weapon_data = json.load(f)
|
||||
|
||||
temp = {}
|
||||
for i in weapon_data:
|
||||
if i['weaponType'] == 'WEAPON_POLE':
|
||||
weaponType = "长柄武器"
|
||||
elif i['weaponType'] == 'WEAPON_BOW':
|
||||
weaponType = "弓"
|
||||
elif i['weaponType'] == 'WEAPON_SWORD_ONE_HAND':
|
||||
weaponType = "单手剑"
|
||||
elif i['weaponType'] == 'WEAPON_CLAYMORE':
|
||||
weaponType = "双手剑"
|
||||
elif i['weaponType'] == 'WEAPON_CATALYST':
|
||||
weaponType = "法器"
|
||||
else:
|
||||
weaponType = ""
|
||||
temp[str(i['nameTextMapHash'])] = weaponType
|
||||
|
||||
with open(MAP_PATH / weaponHash2Type_fileName, 'w', encoding='UTF-8') as file:
|
||||
json.dump(temp, file, ensure_ascii=False)
|
||||
|
||||
|
||||
async def skillId2NameJson() -> None:
|
||||
with open(DATA_PATH / 'AvatarSkillExcelConfigData.json', "r", encoding='UTF-8') as f:
|
||||
skill_data = json.load(f)
|
||||
|
||||
temp = {'Name': {}, 'Icon': {}}
|
||||
for i in skill_data:
|
||||
temp['Name'][str(i['id'])] = raw_data[str(i['nameTextMapHash'])]
|
||||
temp['Icon'][str(i['id'])] = i['skillIcon']
|
||||
|
||||
with open(MAP_PATH / skillId2Name_fileName, 'w', encoding='UTF-8') as file:
|
||||
json.dump(temp, file, ensure_ascii=False)
|
||||
|
||||
|
||||
async def talentId2NameJson() -> None:
|
||||
with open(DATA_PATH / 'AvatarTalentExcelConfigData.json', "r", encoding='UTF-8') as f:
|
||||
talent_data = json.load(f)
|
||||
|
||||
temp = {'Name': {}, 'Icon': {}}
|
||||
for i in talent_data:
|
||||
temp['Name'][str(i['talentId'])] = raw_data[str(i['nameTextMapHash'])]
|
||||
temp['Icon'][str(i['talentId'])] = i['icon']
|
||||
|
||||
with open(MAP_PATH / talentId2Name_fileName, 'w', encoding='UTF-8') as file:
|
||||
json.dump(temp, file, ensure_ascii=False)
|
||||
|
||||
async def artifact2attrJson() -> None:
|
||||
with open(DATA_PATH / 'ReliquaryExcelConfigData.json', "r", encoding='UTF-8') as f:
|
||||
reliquary_data = json.load(f)
|
||||
|
||||
with open(DATA_PATH / 'DisplayItemExcelConfigData.json', "r", encoding='UTF-8') as f:
|
||||
Display_data = json.load(f)
|
||||
|
||||
temp = {}
|
||||
for i in reliquary_data:
|
||||
temp[str(i['icon'])] = raw_data[str(i['nameTextMapHash'])]
|
||||
|
||||
with open(MAP_PATH / icon2Name_fileName, 'w', encoding='UTF-8') as file:
|
||||
json.dump(temp, file, ensure_ascii=False)
|
||||
|
||||
temp2 = {}
|
||||
for i in Display_data:
|
||||
if i['icon'].startswith('UI_RelicIcon'):
|
||||
temp2[raw_data[str(i['nameTextMapHash'])]] = '_'.join(i['icon'].split('_')[:-1])
|
||||
|
||||
temp3 = {}
|
||||
for i in temp:
|
||||
for k in temp2:
|
||||
if i.startswith(temp2[k]):
|
||||
temp3[temp[i]] = k
|
||||
|
||||
with open(MAP_PATH / artifact2attr_fileName, 'w', encoding='UTF-8') as file:
|
||||
json.dump(temp3, file, ensure_ascii=False)
|
||||
|
||||
async def main():
|
||||
await avatarId2NameJson()
|
||||
await avatarName2ElementJson()
|
||||
await weaponHash2NameJson()
|
||||
await skillId2NameJson()
|
||||
await talentId2NameJson()
|
||||
await weaponHash2TypeJson()
|
||||
await artifact2attrJson()
|
||||
|
||||
asyncio.run(main())
|
@ -1,421 +0,0 @@
|
||||
from pathlib import Path
|
||||
from typing import List, Optional, Tuple
|
||||
import json
|
||||
from PIL import Image, ImageDraw, ImageFont, ImageChops
|
||||
|
||||
DMG_PATH = Path(__file__).parents[0]
|
||||
DMG_TEXT_PATH = DMG_PATH / 'texture2d'
|
||||
|
||||
version = '2.8.0'
|
||||
avatarName2SkillAdd_fileName = f'avatarName2SkillAdd_mapping_{version}.json'
|
||||
|
||||
with open(DMG_PATH / avatarName2SkillAdd_fileName, "r", encoding='UTF-8') as f:
|
||||
avatarName2SkillAdd = json.load(f)
|
||||
|
||||
with open(DMG_PATH / 'char_action.json', "r", encoding='UTF-8') as f:
|
||||
char_action = json.load(f)
|
||||
|
||||
with open(DMG_PATH / 'artifacts_effect.json', "r", encoding='UTF-8') as f:
|
||||
artifacts_effect_map = json.load(f)
|
||||
|
||||
with open(DMG_PATH / 'weapons_effect.json', "r", encoding='UTF-8') as f:
|
||||
weapons_effect_map = json.load(f)
|
||||
|
||||
with open(DMG_PATH / 'char_talent_effect.json', "r", encoding='UTF-8') as f:
|
||||
char_talent_effect_map = json.load(f)
|
||||
|
||||
with open(DMG_PATH / 'char_skill_effect.json', "r", encoding='UTF-8') as f:
|
||||
char_skill_effect_map = json.load(f)
|
||||
|
||||
dmgBar_1 = Image.open(DMG_TEXT_PATH / 'dmgBar_1.png')
|
||||
dmgBar_2 = Image.open(DMG_TEXT_PATH / 'dmgBar_2.png')
|
||||
|
||||
|
||||
def genshin_font_origin(size: int) -> ImageFont:
|
||||
return ImageFont.truetype(str(DMG_TEXT_PATH / 'yuanshen_origin.ttf'), size=size)
|
||||
|
||||
|
||||
async def draw_dmgCacl_img(raw_data: dict) -> Image:
|
||||
char_name = raw_data['avatarName']
|
||||
char_level = int(raw_data['avatarLevel'])
|
||||
weaponName = raw_data['weaponInfo']['weaponName']
|
||||
weaponType = raw_data['weaponInfo']['weaponType']
|
||||
weaponAffix = raw_data['weaponInfo']['weaponAffix']
|
||||
|
||||
skillList = raw_data['avatarSkill']
|
||||
a_skill_name = skillList[0]['skillName'].replace('普通攻击·', '')
|
||||
prop = {}
|
||||
prop['A_skill_level'] = skillList[0]['skillLevel']
|
||||
e_skill_name = skillList[1]['skillName']
|
||||
prop['E_skill_level'] = skillList[1]['skillLevel']
|
||||
q_skill_name = skillList[-1]['skillName']
|
||||
prop['Q_skill_level'] = skillList[-1]['skillLevel']
|
||||
|
||||
enemy_level = char_level
|
||||
skill_add = avatarName2SkillAdd[char_name]
|
||||
for skillAdd_index in range(0, 2):
|
||||
if len(raw_data['talentList']) >= 3 + skillAdd_index * 2:
|
||||
if skill_add[skillAdd_index] == 'E':
|
||||
prop['E_skill_level'] += 3
|
||||
elif skill_add[skillAdd_index] == 'Q':
|
||||
prop['Q_skill_level'] += 3
|
||||
|
||||
fight_prop = raw_data['avatarFightProp']
|
||||
prop['basehp'] = fight_prop['baseHp']
|
||||
prop['baseattack'] = fight_prop['baseAtk']
|
||||
prop['basedefense'] = fight_prop['baseDef']
|
||||
|
||||
prop['hp'] = fight_prop['hp']
|
||||
prop['attack'] = fight_prop['atk']
|
||||
prop['defense'] = fight_prop['def']
|
||||
prop['em'] = fight_prop['elementalMastery']
|
||||
prop['critrate'] = fight_prop['critRate']
|
||||
prop['critdmg'] = fight_prop['critDmg']
|
||||
prop['ce'] = fight_prop['energyRecharge']
|
||||
prop['physicalDmgBonus'] = physicalDmgBonus = fight_prop['physicalDmgBonus']
|
||||
prop['dmgBonus'] = dmgBonus = fight_prop['dmgBonus']
|
||||
prop['healBouns'] = fight_prop['healBonus']
|
||||
prop['shieldBouns'] = 0
|
||||
|
||||
if char_name not in char_action:
|
||||
faild_img = Image.new('RGBA', (950, 1))
|
||||
return faild_img, 0
|
||||
power_list = char_action[char_name]
|
||||
|
||||
for prop_attr in [
|
||||
'dmgBonus',
|
||||
'critrate',
|
||||
'critdmg',
|
||||
'addDmg',
|
||||
'd',
|
||||
'r',
|
||||
'ignoreDef',
|
||||
]:
|
||||
if prop_attr in ['addDmg', 'd', 'r', 'ignoreDef']:
|
||||
prop['{}'.format(prop_attr)] = 0
|
||||
for prop_limit in ['A', 'B', 'C', 'E', 'Q']:
|
||||
prop['{}_{}'.format(prop_limit, prop_attr)] = 0
|
||||
|
||||
for prop_limit in ['A', 'B', 'C', 'E', 'Q']:
|
||||
if weaponType == '法器' or char_name in ['荒泷一斗', '刻晴', '诺艾尔']:
|
||||
prop['{}_dmgBonus'.format(prop_limit)] = dmgBonus
|
||||
elif weaponType == '弓':
|
||||
if prop_limit in ['A', 'C']:
|
||||
prop['{}_dmgBonus'.format(prop_limit)] = physicalDmgBonus
|
||||
elif prop_limit in ['B', 'E', 'Q']:
|
||||
prop['{}_dmgBonus'.format(prop_limit)] = dmgBonus
|
||||
else:
|
||||
if prop_limit in ['A', 'B', 'C']:
|
||||
prop['{}_dmgBonus'.format(prop_limit)] = physicalDmgBonus
|
||||
elif prop_limit in ['E', 'Q']:
|
||||
prop['{}_dmgBonus'.format(prop_limit)] = dmgBonus
|
||||
|
||||
prop['hp_green'] = fight_prop['addHp']
|
||||
prop['attack_green'] = fight_prop['addAtk']
|
||||
prop['defense_green'] = fight_prop['addDef']
|
||||
|
||||
prop['r'] = 0.1
|
||||
prop['a'] = 0
|
||||
prop['g'] = 0
|
||||
prop['k'] = 1
|
||||
|
||||
# 计算圣遗物套装
|
||||
if 'equipSets' in raw_data:
|
||||
equipSets = raw_data['equipSets']
|
||||
else:
|
||||
artifact_set_list = []
|
||||
for i in raw_data['equipList']:
|
||||
artifact_set_list.append(i['aritifactSetsName'])
|
||||
equipSetList = set(artifact_set_list)
|
||||
equipSets = {'type': '', 'set': ''}
|
||||
for equip in equipSetList:
|
||||
if artifact_set_list.count(equip) >= 4:
|
||||
equipSets['type'] = '4'
|
||||
equipSets['set'] = equip
|
||||
break
|
||||
elif artifact_set_list.count(equip) == 1:
|
||||
pass
|
||||
elif artifact_set_list.count(equip) >= 2:
|
||||
equipSets['type'] += '2'
|
||||
equipSets['set'] += '|' + equip
|
||||
|
||||
if equipSets['set'].startswith('|'):
|
||||
equipSets['set'] = equipSets['set'][1:]
|
||||
all_effect = []
|
||||
|
||||
# 计算圣遗物buff
|
||||
if equipSets['type'] == '4':
|
||||
all_effect.append(artifacts_effect_map[equipSets['set']]['4'])
|
||||
elif equipSets['type'] == '2':
|
||||
all_effect.append(artifacts_effect_map[equipSets['set']]['2'])
|
||||
elif equipSets['type'] == '22':
|
||||
if equipSets['set'][-2] == '之':
|
||||
first_artifact = equipSets['set'][-3:]
|
||||
else:
|
||||
first_artifact = equipSets['set'][-2:]
|
||||
second_artifact = equipSets['set'][:2]
|
||||
temp = []
|
||||
temp_number = 0
|
||||
for artifacts_single_effect in artifacts_effect_map:
|
||||
if first_artifact in artifacts_single_effect:
|
||||
temp_number += 1
|
||||
temp.append(artifacts_effect_map[artifacts_single_effect]['2'])
|
||||
if temp_number >= 2:
|
||||
break
|
||||
all_effect.extend(temp)
|
||||
|
||||
# 计算武器buff
|
||||
weapon_effet = weapons_effect_map[weaponName][str(weaponAffix)]
|
||||
all_effect.append(weapon_effet)
|
||||
|
||||
# 计算技能buff
|
||||
for talent in char_talent_effect_map[char_name]:
|
||||
if int(talent) <= len(raw_data['talentList']):
|
||||
all_effect.append(char_talent_effect_map[char_name][talent])
|
||||
else:
|
||||
break
|
||||
|
||||
# 计算角色buff
|
||||
for skill in char_skill_effect_map[char_name]:
|
||||
if int(skill) <= char_level:
|
||||
all_effect.append(char_skill_effect_map[char_name][skill])
|
||||
else:
|
||||
break
|
||||
|
||||
power_effect = ''
|
||||
if 'effect' in power_list:
|
||||
for skill_effect_single in power_list['effect']:
|
||||
skill_effect_name = skill_effect_single['name']
|
||||
skill_effect_value = skill_effect_single['value']
|
||||
skill_effect = skill_effect_single['effect']
|
||||
skill_effect_level = prop['{}_skill_level'.format(skill_effect_name)]
|
||||
skill_effect_value_detail = skill_effect_value[skill_effect_level]
|
||||
if skill_effect[-1] == '}':
|
||||
skill_effect_value_detail = skill_effect_value_detail.replace('%', '')
|
||||
add_limit = skill_effect.split(':')
|
||||
if len(add_limit) == 1:
|
||||
for i in power_list:
|
||||
if i == 'effect':
|
||||
pass
|
||||
else:
|
||||
power_list[i]['power_name'] = (
|
||||
'开{}后 '.format(skill_effect_name)
|
||||
+ power_list[i]['power_name']
|
||||
)
|
||||
else:
|
||||
for i in power_list:
|
||||
if i == 'effect':
|
||||
pass
|
||||
else:
|
||||
add_type = i[0]
|
||||
if '重击' in i or '蓄力' in i:
|
||||
add_type = 'B'
|
||||
elif '冲击伤害' in i:
|
||||
add_type = 'C'
|
||||
if add_type in add_limit[0]:
|
||||
power_list[i]['power_name'] = (
|
||||
'开{}后 '.format(skill_effect_name)
|
||||
+ power_list[i]['power_name']
|
||||
)
|
||||
power_effect = skill_effect.format(skill_effect_value_detail)
|
||||
all_effect.append(power_effect)
|
||||
del power_list['effect']
|
||||
|
||||
sp = []
|
||||
# 计算全部的buff,添加入属性
|
||||
if all_effect:
|
||||
all_effect = ';'.join(all_effect)
|
||||
all_effect_list = all_effect.split(';')
|
||||
for effect in all_effect_list:
|
||||
if effect == '':
|
||||
continue
|
||||
|
||||
effect_limit = ''
|
||||
if ':' in effect:
|
||||
pass
|
||||
else:
|
||||
effect = ':' + effect
|
||||
|
||||
effect_limit = effect.split(':')[0]
|
||||
effect_attr = effect.split(':')[1].split('+')[0]
|
||||
effect_value = effect.split(':')[1].split('+')[-1]
|
||||
|
||||
base_check = True
|
||||
if '%' in effect_value:
|
||||
effect_value_base_on_attr = effect_value.split('%')[-1]
|
||||
effect_value_base_on_value = '%'.join(effect_value.split('%')[:-1])
|
||||
if '%' in effect_value_base_on_value:
|
||||
effect_value_base_on_max_value = effect_value_base_on_value.split(
|
||||
'%'
|
||||
)[0]
|
||||
effect_value_base_on_value = effect_value_base_on_value.split('%')[
|
||||
-1
|
||||
]
|
||||
effect_now_value = (
|
||||
float(effect_value_base_on_value)
|
||||
* prop[effect_value_base_on_attr]
|
||||
)
|
||||
effect_value = (
|
||||
float(effect_value_base_on_max_value)
|
||||
if effect_now_value >= float(effect_value_base_on_max_value)
|
||||
else effect_now_value
|
||||
)
|
||||
else:
|
||||
effect_value = (
|
||||
float(effect_value_base_on_value)
|
||||
* prop[effect_value_base_on_attr]
|
||||
)
|
||||
base_check = False
|
||||
|
||||
if effect_attr != 'em':
|
||||
effect_value = float(effect_value) / 100
|
||||
if effect_attr in ['hp', 'attack', 'defense'] and base_check:
|
||||
effect_value += effect_value * prop['base{}'.format(effect_attr)]
|
||||
else:
|
||||
effect_value = float(effect_value)
|
||||
|
||||
if effect_limit:
|
||||
if '\u4e00' <= effect_limit[0] <= '\u9fff':
|
||||
sp.append(
|
||||
{
|
||||
'effect_name': effect_limit,
|
||||
'effect_attr': effect_attr,
|
||||
'effect_value': effect_value,
|
||||
}
|
||||
)
|
||||
else:
|
||||
for limit in effect_limit:
|
||||
prop['{}_{}'.format(limit, effect_attr)] += effect_value
|
||||
else:
|
||||
prop['{}'.format(effect_attr)] += effect_value
|
||||
|
||||
w = 950
|
||||
h = 40 * (len(power_list) + 1)
|
||||
result_img = Image.new('RGBA', (w, h), (0, 0, 0, 0))
|
||||
for i in range(0, len(power_list) + 1):
|
||||
if i % 2 == 0:
|
||||
result_img.paste(dmgBar_1, (0, i * 40))
|
||||
else:
|
||||
result_img.paste(dmgBar_2, (0, i * 40))
|
||||
|
||||
result_draw = ImageDraw.Draw(result_img)
|
||||
|
||||
text_color = (255, 255, 255)
|
||||
title_color = (255, 255, 100)
|
||||
text_size = genshin_font_origin(28)
|
||||
result_draw.text((45, 22), '角色动作', title_color, text_size, anchor='lm')
|
||||
result_draw.text((460, 22), '暴击伤害', title_color, text_size, anchor='lm')
|
||||
result_draw.text((695, 22), '期望伤害', title_color, text_size, anchor='lm')
|
||||
|
||||
for index, power_name in enumerate(power_list):
|
||||
attack_type = power_name[0]
|
||||
if '重击' in power_name or '瞄准射击' in power_name:
|
||||
attack_type = 'B'
|
||||
elif '冲击伤害' in power_name:
|
||||
attack_type = 'C'
|
||||
elif '段' in power_name and '伤害' in power_name:
|
||||
attack_type = 'A'
|
||||
|
||||
sp_dmgBonus = 0
|
||||
sp_addDmg = 0
|
||||
|
||||
if sp:
|
||||
for sp_single in sp:
|
||||
if sp_single['effect_name'] == power_name[1:]:
|
||||
if sp_single['effect_attr'] == 'dmgBouns':
|
||||
sp_dmgBonus += sp_single['effect_value']
|
||||
elif sp_single['effect_attr'] == 'addDmg':
|
||||
sp_addDmg += sp_single['effect_value']
|
||||
|
||||
if '攻击' in power_list[power_name]['type']:
|
||||
effect_prop = prop['attack']
|
||||
elif '生命值' in power_list[power_name]['type']:
|
||||
effect_prop = prop['hp']
|
||||
elif '防御' in power_list[power_name]['type']:
|
||||
effect_prop = prop['defense']
|
||||
power = power_list[power_name]['value'][
|
||||
prop['{}_skill_level'.format(power_name[0])]
|
||||
]
|
||||
power_plus = power_list[power_name]['plus']
|
||||
|
||||
power_percent, power_value = await power_to_value(power, power_plus)
|
||||
|
||||
dmgBonus_cal = prop['{}_dmgBonus'.format(attack_type)] + sp_dmgBonus
|
||||
critdmg_cal = prop['critdmg'] + prop['{}_critdmg'.format(attack_type)]
|
||||
critrate_cal = prop['critrate'] + prop['{}_critrate'.format(attack_type)]
|
||||
d_cal = (char_level + 100) / (
|
||||
(char_level + 100)
|
||||
+ (1 - prop['{}_d'.format(attack_type)])
|
||||
* (1 - prop['{}_ignoreDef'.format(attack_type)])
|
||||
* (enemy_level + 100)
|
||||
)
|
||||
r = 1 - prop['r']
|
||||
e_dmg = prop['k'] * (1 + (2.78 * prop['em']) / (prop['em'] + 1400) + prop['a'])
|
||||
add_dmg = prop['{}_addDmg'.format(attack_type)] + sp_addDmg
|
||||
|
||||
if '治疗' in power_name:
|
||||
crit_dmg = avg_dmg = (effect_prop * power_percent + power_value) * (
|
||||
1 + prop['healBouns']
|
||||
)
|
||||
elif '护盾' in power_name:
|
||||
crit_dmg = avg_dmg = (effect_prop * power_percent + power_value) * (
|
||||
1 + prop['shieldBouns']
|
||||
)
|
||||
elif '提升' in power_name or '提高' in power_name:
|
||||
continue
|
||||
else:
|
||||
crit_dmg = (effect_prop * power_percent + power_value) * (
|
||||
1 + critdmg_cal
|
||||
) * (1 + dmgBonus_cal) * d_cal * r + add_dmg
|
||||
avg_dmg = (
|
||||
(crit_dmg - add_dmg) * critrate_cal
|
||||
+ (1 - critrate_cal)
|
||||
* (effect_prop * power_percent + power_value)
|
||||
* (1 + dmgBonus_cal)
|
||||
* d_cal
|
||||
* r
|
||||
+ add_dmg
|
||||
)
|
||||
|
||||
result_draw.text(
|
||||
(45, 22 + (index + 1) * 40),
|
||||
power_list[power_name]['power_name'],
|
||||
text_color,
|
||||
text_size,
|
||||
anchor='lm',
|
||||
)
|
||||
result_draw.text(
|
||||
(460, 22 + (index + 1) * 40),
|
||||
str(round(crit_dmg)),
|
||||
text_color,
|
||||
text_size,
|
||||
anchor='lm',
|
||||
)
|
||||
result_draw.text(
|
||||
(695, 22 + (index + 1) * 40),
|
||||
str(round(avg_dmg)),
|
||||
text_color,
|
||||
text_size,
|
||||
anchor='lm',
|
||||
)
|
||||
|
||||
return result_img, len(power_list) + 2
|
||||
|
||||
|
||||
async def power_to_value(power: str, power_plus: int) -> List:
|
||||
"""
|
||||
将power转换为value
|
||||
"""
|
||||
if '+' in power:
|
||||
power_percent = (float(power.split('+')[0].replace('%', '')) / 100) * power_plus
|
||||
power_value = power.split('+')[1]
|
||||
if '%' in power_value:
|
||||
power_percent += float(power_value.replace('%', '')) / 100 * power_plus
|
||||
power_value = 0
|
||||
else:
|
||||
power_value = float(power_value)
|
||||
else:
|
||||
power_percent = float(power.replace('%', '')) / 100 * power_plus
|
||||
power_value = 0
|
||||
|
||||
return power_percent, power_value
|
@ -1,762 +0,0 @@
|
||||
import math
|
||||
import json,asyncio
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
from typing import List, Optional, Tuple
|
||||
from base64 import b64encode
|
||||
|
||||
from PIL import Image, ImageDraw, ImageFont, ImageChops
|
||||
from httpx import get
|
||||
from nonebot import logger
|
||||
|
||||
from .dmgCalc.dmgCalc import *
|
||||
|
||||
R_PATH = Path(__file__).parents[0]
|
||||
TEXT_PATH = R_PATH / 'texture2D'
|
||||
ICON_PATH = R_PATH / 'icon'
|
||||
GACHA_PATH = R_PATH / 'gachaImg'
|
||||
PLAYER_PATH = R_PATH / 'player'
|
||||
RELIC_PATH = R_PATH / 'relicIcon'
|
||||
MAP_PATH = R_PATH / 'map'
|
||||
ETC_PATH = R_PATH / 'etc'
|
||||
|
||||
COLOR_MAP = {'Anemo' : (43, 170, 163), 'Cryo': (97, 168, 202), 'Dendro': (84, 169, 62),
|
||||
'Electro': (150, 62, 169), 'Geo': (169, 143, 62), 'Hydro': (66, 98, 182), 'Pyro': (169, 62, 67)}
|
||||
|
||||
SCORE_MAP = {'暴击率': 2, '暴击伤害': 1, '元素精通': 0.25, '元素充能效率': 0.65, '百分比血量': 0.86,
|
||||
'百分比攻击力': 1, '百分比防御力': 0.7, '血量': 0.014, '攻击力': 0.12, '防御力': 0.18}
|
||||
|
||||
VALUE_MAP = {'攻击力': 4.975, '血量': 4.975, '防御力': 6.2, '元素精通': 19.75,
|
||||
'元素充能效率': 5.5, '暴击率': 3.3, '暴击伤害': 6.6}
|
||||
|
||||
# 引入ValueMap
|
||||
with open(ETC_PATH / 'ValueAttrMap.json', 'r', encoding='UTF-8') as f:
|
||||
ATTR_MAP = json.load(f)
|
||||
|
||||
# 引入dmgMap
|
||||
with open(ETC_PATH / 'dmgMap.json', 'r', encoding='UTF-8') as f:
|
||||
dmgMap = json.load(f)
|
||||
|
||||
# 引入offset
|
||||
with open(ETC_PATH / 'avatarOffsetMap.json', 'r', encoding='UTF-8') as f:
|
||||
avatarOffsetMap = json.load(f)
|
||||
|
||||
# 引入offset2
|
||||
with open(ETC_PATH / 'avatarCardOffsetMap.json', 'r', encoding='UTF-8') as f:
|
||||
avatarCardOffsetMap = json.load(f)
|
||||
|
||||
def genshin_font_origin(size: int) -> ImageFont:
|
||||
return ImageFont.truetype(str(TEXT_PATH / 'yuanshen_origin.ttf'), size=size)
|
||||
|
||||
|
||||
def get_star_png(star: int) -> Image:
|
||||
png = Image.open(TEXT_PATH / 's-{}.png'.format(str(star)))
|
||||
return png
|
||||
|
||||
|
||||
def strLenth(r: str, size: int, limit: int = 540) -> str:
|
||||
result = ''
|
||||
temp = 0
|
||||
for i in r:
|
||||
if temp >= limit:
|
||||
result += '\n' + i
|
||||
temp = 0
|
||||
else:
|
||||
result += i
|
||||
|
||||
if i.isdigit():
|
||||
temp += round(size / 10 * 6)
|
||||
elif i == '/':
|
||||
temp += round(size / 10 * 2.2)
|
||||
elif i == '.':
|
||||
temp += round(size / 10 * 3)
|
||||
elif i == '%':
|
||||
temp += round(size / 10 * 9.4)
|
||||
else:
|
||||
temp += size
|
||||
return result
|
||||
|
||||
|
||||
async def get_artifacts_score(subName: str, subValue: int) -> int:
|
||||
score = subValue * SCORE_MAP[subName]
|
||||
return score
|
||||
|
||||
async def get_artifacts_value(subName: str, subValue: int, baseAtk: int,
|
||||
baseHp: int, baseDef: int, charName: str) -> int:
|
||||
if charName not in ATTR_MAP:
|
||||
ATTR_MAP[charName] = ['攻击力', '暴击率', '暴击伤害']
|
||||
if subName in ATTR_MAP[charName] and subName in ['血量', '防御力', '攻击力']:
|
||||
if subName == '血量':
|
||||
base = (subValue / baseHp) * 100
|
||||
elif subName == '防御力':
|
||||
base = (subValue / baseDef) * 100
|
||||
elif subName == '攻击力':
|
||||
base = (subValue / baseAtk) * 100
|
||||
value = float('{:.2f}'.format(base / VALUE_MAP[subName]))
|
||||
elif subName in ['百分比血量', '百分比防御力', '百分比攻击力']:
|
||||
subName = subName.replace('百分比', '')
|
||||
if subName in ATTR_MAP[charName]:
|
||||
value = float('{:.2f}'.format(subValue / VALUE_MAP[subName]))
|
||||
else:
|
||||
return 0
|
||||
else:
|
||||
if subName in ATTR_MAP[charName]:
|
||||
value = float('{:.2f}'.format(subValue / VALUE_MAP[subName]))
|
||||
else:
|
||||
value = 0
|
||||
|
||||
if charName == '胡桃' and subName == '攻击力':
|
||||
value = value * 0.4
|
||||
return value
|
||||
|
||||
async def get_all_artifacts_value(raw_data: dict, baseHp: int, baseAtk: int, baseDef: int, char_name: str) -> int:
|
||||
artifactsValue = 0
|
||||
for aritifact in raw_data:
|
||||
for i in aritifact['reliquarySubstats']:
|
||||
subName = i['statName']
|
||||
subValue = i['statValue']
|
||||
value_temp = await get_artifacts_value(subName, subValue, baseAtk, baseHp, baseDef, char_name)
|
||||
artifactsValue += value_temp
|
||||
return artifactsValue
|
||||
|
||||
async def get_first_main(mainName: str) -> str:
|
||||
if '伤害加成' in mainName:
|
||||
equipMain = mainName[0]
|
||||
elif '元素' in mainName:
|
||||
equipMain = mainName[2]
|
||||
elif '百分比' in mainName:
|
||||
if '血量' in mainName:
|
||||
equipMain = '生'
|
||||
else:
|
||||
equipMain = mainName[3]
|
||||
else:
|
||||
equipMain = mainName[0]
|
||||
return equipMain
|
||||
|
||||
|
||||
async def get_char_percent(raw_data: dict) -> str:
|
||||
char_name = raw_data['avatarName']
|
||||
weaponName = raw_data['weaponInfo']['weaponName']
|
||||
weaponType = raw_data['weaponInfo']['weaponType']
|
||||
|
||||
fight_prop = raw_data['avatarFightProp']
|
||||
hp = fight_prop['hp']
|
||||
attack = fight_prop['atk']
|
||||
defense = fight_prop['def']
|
||||
em = fight_prop['elementalMastery']
|
||||
critrate = fight_prop['critRate']
|
||||
critdmg = fight_prop['critDmg']
|
||||
ce = fight_prop['energyRecharge']
|
||||
dmgBonus = fight_prop['dmgBonus'] if fight_prop['physicalDmgBonus'] <= fight_prop['dmgBonus'] else fight_prop['physicalDmgBonus']
|
||||
healBouns = fight_prop['healBonus']
|
||||
|
||||
hp_green = fight_prop['addHp']
|
||||
attack_green = fight_prop['addAtk']
|
||||
defense_green = fight_prop['addDef']
|
||||
|
||||
r = 0.9
|
||||
equipMain = ''
|
||||
for aritifact in raw_data['equipList']:
|
||||
mainName = aritifact['reliquaryMainstat']['statName']
|
||||
artifactsPos = aritifact['aritifactPieceName']
|
||||
if artifactsPos == '时之沙':
|
||||
equipMain += await get_first_main(mainName)
|
||||
elif artifactsPos == '空之杯':
|
||||
equipMain += await get_first_main(mainName)
|
||||
elif artifactsPos == '理之冠':
|
||||
equipMain += await get_first_main(mainName)
|
||||
|
||||
if 'equipSets' in raw_data:
|
||||
equipSets = raw_data['equipSets']
|
||||
else:
|
||||
artifact_set_list = []
|
||||
for i in raw_data['equipList']:
|
||||
artifact_set_list.append(i['aritifactSetsName'])
|
||||
equipSetList = set(artifact_set_list)
|
||||
equipSets = {'type':'','set':''}
|
||||
for equip in equipSetList:
|
||||
if artifact_set_list.count(equip) >= 4:
|
||||
equipSets['type'] = '4'
|
||||
equipSets['set'] = equip
|
||||
break
|
||||
elif artifact_set_list.count(equip) == 1:
|
||||
pass
|
||||
elif artifact_set_list.count(equip) >= 2:
|
||||
equipSets['type'] += '2'
|
||||
equipSets['set'] += equip
|
||||
|
||||
if equipSets['type'] in ['2','']:
|
||||
seq = ''
|
||||
else:
|
||||
seq = '{}|{}|{}'.format(weaponName.replace('「','').replace('」',''),equipSets['set'],equipMain)
|
||||
|
||||
if char_name in dmgMap:
|
||||
for action in dmgMap[char_name]:
|
||||
if action['seq'] == seq:
|
||||
cal = action
|
||||
break
|
||||
else:
|
||||
if '钟离' in char_name:
|
||||
cal = dmgMap[char_name][-1]
|
||||
else:
|
||||
cal = dmgMap[char_name][0]
|
||||
|
||||
print(seq)
|
||||
print(cal)
|
||||
if cal['action'] == 'E刹那之花':
|
||||
effect_prop = defense
|
||||
elif cal['key'] == '攻击力':
|
||||
effect_prop = attack
|
||||
elif cal['key'] == '防御力':
|
||||
effect_prop = defense
|
||||
elif cal['key'] == '血量':
|
||||
effect_prop = hp
|
||||
else:
|
||||
effect_prop = attack
|
||||
|
||||
dmgBonus_value_cal = 0
|
||||
dmgBonus_cal = dmgBonus
|
||||
em_cal = em
|
||||
|
||||
if '夜兰' in char_name:
|
||||
effect_prop = hp
|
||||
elif '胡桃' in char_name:
|
||||
effect_prop += 0.4 * hp if 0.4 * hp <= fight_prop['baseAtk'] * 4 else fight_prop['baseAtk'] * 4
|
||||
elif '一斗' in char_name:
|
||||
effect_prop += 0.9792 * defense
|
||||
dmgBonus_value_cal += 0.35 * defense
|
||||
elif '诺艾尔' in char_name:
|
||||
effect_prop = attack
|
||||
effect_prop += 1.3 * defense
|
||||
elif '烟绯' in char_name:
|
||||
dmgBonus_value_cal += 0.6 + 0.2
|
||||
elif '优菈' in char_name:
|
||||
r = 1.065
|
||||
elif '钟离' in char_name:
|
||||
r = 1.05
|
||||
elif '辛焱' in char_name:
|
||||
r = 1.025
|
||||
|
||||
if '踩班' in cal['action']:
|
||||
effect_prop += 1202
|
||||
effect_prop += fight_prop['baseAtk'] * 0.25
|
||||
|
||||
if '雾切' in weaponName:
|
||||
dmgBonus_cal += 0.28
|
||||
elif '弓藏' in weaponName and ('首' in cal['action'] or '击' in cal['action'] or '两段' in cal['action']):
|
||||
dmgBonus_cal += 0.8
|
||||
elif '飞雷' in weaponName and ('首' in cal['action'] or '击' in cal['action'] or '两段' in cal['action']):
|
||||
dmgBonus_cal += 0.4
|
||||
elif '阿莫斯' in weaponName:
|
||||
dmgBonus_cal += 0.52
|
||||
elif '破魔' in weaponName:
|
||||
dmgBonus_cal += 0.18*2
|
||||
elif '赤角石溃杵' in weaponName and ('首' in cal['action'] or '击' in cal['action'] or '两段' in cal['action']):
|
||||
dmgBonus_value_cal += 0.4 * defense
|
||||
elif '螭骨剑' in weaponName:
|
||||
dmgBonus_cal += 0.4
|
||||
elif '松籁响起之时' in weaponName:
|
||||
effect_prop += fight_prop['baseAtk'] * 0.2
|
||||
elif '试作澹月' in weaponName:
|
||||
effect_prop += fight_prop['baseAtk'] * 0.72
|
||||
elif '流浪乐章' in weaponName and '烟绯' in char_name:
|
||||
em_cal += 480
|
||||
elif '冬极' in weaponName:
|
||||
effect_prop += fight_prop['baseAtk'] * 0.48
|
||||
dmgBonus_cal += 0.12
|
||||
|
||||
if '蒸发' in cal['action'] or '融化' in cal['action']:
|
||||
if '蒸发' in cal['action']:
|
||||
if raw_data['avatarElement'] == 'Pyro':
|
||||
k = 1.5
|
||||
else:
|
||||
k = 2
|
||||
elif '融化' in cal['action']:
|
||||
if raw_data['avatarElement'] == 'Pyro':
|
||||
k = 2
|
||||
else:
|
||||
k = 1.5
|
||||
|
||||
if equipSets['type'] in ['2','']:
|
||||
a = 0
|
||||
else:
|
||||
if '魔女' in equipSets['set']:
|
||||
a = 0.15
|
||||
else:
|
||||
a = 0
|
||||
add_dmg = k*(1+(2.78*em_cal)/(em_cal+1400)+a)
|
||||
else:
|
||||
add_dmg = 1
|
||||
|
||||
if equipSets['type'] in ['2','','22']:
|
||||
pass
|
||||
else:
|
||||
if '追忆' in equipSets['set']:
|
||||
dmgBonus_cal += 0.5
|
||||
elif '绝缘' in equipSets['set']:
|
||||
Bouns = ce * 0.25 if ce * 0.25 <= 0.75 else 0.75
|
||||
dmgBonus_cal += Bouns
|
||||
elif '乐团' in equipSets['set']:
|
||||
if weaponType in ['法器', '弓']:
|
||||
dmgBonus_cal += 0.35
|
||||
elif '华馆' in equipSets['set']:
|
||||
if raw_data['avatarElement'] == 'Geo':
|
||||
effect_prop += 0.24 * defense
|
||||
dmgBonus_cal += 0.24
|
||||
|
||||
critdmg_cal = critdmg
|
||||
healBouns_cal = healBouns
|
||||
|
||||
if '魈' in char_name:
|
||||
dmgBonus_cal += 0.906
|
||||
elif '绫华' in char_name:
|
||||
dmgBonus_cal += 0.18
|
||||
elif '宵宫' in char_name:
|
||||
dmgBonus_cal += 0.5
|
||||
elif '九条' in char_name:
|
||||
effect_prop += 0.9129 * fight_prop['baseAtk']
|
||||
critdmg_cal += 0.6
|
||||
|
||||
if '治疗' in cal['action']:
|
||||
if equipSets['type'] in ['2','']:
|
||||
healBouns_cal += 0
|
||||
else:
|
||||
if '少女' in equipSets['set']:
|
||||
healBouns_cal += 0.2
|
||||
|
||||
if cal['action'] == '扩散':
|
||||
dmg = 868 * 1.15 * (1+0.6+(16*em_cal)/(em_cal+2000))
|
||||
elif '霄宫' in char_name:
|
||||
dmg = effect_prop * cal['power'] * (1 + critdmg_cal) * (1 + dmgBonus_cal) * 0.5 * r * add_dmg * 1.5879
|
||||
elif '班尼特' in char_name and 'Q治疗' in cal['action']:
|
||||
power = cal['power'].split('+')
|
||||
dmg = (effect_prop * float(power[0]) / 100 + float(power[1])) * (1 + healBouns_cal)
|
||||
elif '心海' in char_name and cal['action'] == '开Q普攻':
|
||||
dmg = (attack * cal['power'] + hp*(0.0971 + 0.15 * healBouns_cal)) * (1 + dmgBonus_cal) * 0.5 * r * add_dmg
|
||||
elif '心海' in char_name and cal['action'] == '水母回血':
|
||||
dmg = (862 + 0.0748 * hp) * (1 + healBouns_cal)
|
||||
elif char_name in ['芭芭拉', '早柚', '琴', '七七']:
|
||||
power = cal['power'].split('+')
|
||||
dmg = (effect_prop * float(power[0]) / 100 + float(power[1])) * (1 + healBouns_cal)
|
||||
elif '绫人' in char_name:
|
||||
dmg = (effect_prop * cal['power'] + 0.0222 * hp) * (1 + critdmg_cal) * (1 + dmgBonus_cal) * 0.5 * r * add_dmg * 1.5879
|
||||
elif char_name in ['荒泷一斗', '诺艾尔']:
|
||||
dmg = (effect_prop * cal['power'] + dmgBonus_value_cal) * (1 + critdmg_cal) * (1 + dmgBonus_cal) * 0.5 * r
|
||||
elif '迪奥娜' in char_name:
|
||||
dmg = (effect_prop * cal['power'] + 1905) * 1.9
|
||||
elif '钟离' in char_name and 'E实际盾值' in cal['action']:
|
||||
dmg = (2506 + hp * cal['power']) * 1.5 * 1.3
|
||||
elif cal['action'] == 'Q开盾天星':
|
||||
effect_prop = attack
|
||||
dmg = (effect_prop * cal['power'] + 0.33 * hp) * (1 + critdmg_cal) * (1 + dmgBonus_cal) * 0.5 * r * add_dmg
|
||||
elif '凝光' in char_name:
|
||||
dmg = effect_prop * cal['power'] * (1 + critdmg_cal * critrate) * (1 + dmgBonus_cal) * 0.5 * r * add_dmg
|
||||
elif isinstance(cal['power'], str):
|
||||
if cal['power'] == '攻击力':
|
||||
dmg = attack
|
||||
elif cal['power'] == '防御力':
|
||||
dmg = defense
|
||||
else:
|
||||
power = cal['power'].split('+')
|
||||
dmg = effect_prop * float(power[0]) / 100 + float(power[1])
|
||||
elif cal['val'] != 'any':
|
||||
dmg = effect_prop * cal['power'] * (1 + critdmg_cal) * (1 + dmgBonus_cal) * 0.5 * r * add_dmg
|
||||
else:
|
||||
dmg = attack
|
||||
print(dmg)
|
||||
|
||||
if cal['val'] != 'any':
|
||||
percent = '{:.2f}'.format(dmg / cal['val'] * 100)
|
||||
elif cal['power'] == '攻击力':
|
||||
percent = '{:.2f}'.format(dmg / cal['atk'] * 100)
|
||||
elif '云堇' in char_name:
|
||||
percent = '{:.2f}'.format(dmg / cal['other2'] * 100)
|
||||
elif cal['power'] == '防御力':
|
||||
percent = '{:.2f}'.format(dmg / cal['other'] * 100)
|
||||
else:
|
||||
percent = 0.00
|
||||
return percent
|
||||
|
||||
|
||||
async def draw_char_card(raw_data: dict, charUrl: str = None) -> bytes:
|
||||
char_name = raw_data['avatarName']
|
||||
char_level = raw_data['avatarLevel']
|
||||
char_fetter = raw_data['avatarFetter']
|
||||
|
||||
based_w, based_h = 600, 1200
|
||||
if charUrl:
|
||||
offset_x, offset_y = 200, 0
|
||||
char_img = Image.open(BytesIO(get(charUrl).content)).convert('RGBA')
|
||||
else:
|
||||
if char_name in avatarOffsetMap:
|
||||
offset_x, offset_y = avatarOffsetMap[char_name][0], avatarOffsetMap[char_name][1]
|
||||
else:
|
||||
offset_x, offset_y = 200, 0
|
||||
char_img = Image.open(GACHA_PATH / 'UI_Gacha_AvatarImg_{}.png'.format(raw_data['avatarEnName'])) #角色图像
|
||||
|
||||
# 确定图片的长宽
|
||||
w, h = char_img.size
|
||||
if (w, h) != (based_w, based_h):
|
||||
#offset_all = offset_x if offset_x >= offset_y else offset_y
|
||||
based_new_w, based_new_h = based_w + offset_x, based_h + offset_y
|
||||
based_scale = '%.3f' % (based_new_w / based_new_h)
|
||||
scale_f = '%.3f' % (w / h)
|
||||
new_w = math.ceil(based_new_h * float(scale_f))
|
||||
new_h = math.ceil(based_new_w / float(scale_f))
|
||||
if scale_f > based_scale:
|
||||
bg_img2 = char_img.resize((new_w, based_new_h), Image.Resampling.LANCZOS)
|
||||
x1 = new_w/2 - based_new_w /2 + offset_x
|
||||
y1 = 0 + offset_y / 2
|
||||
x2 = new_w/2 + based_new_w /2
|
||||
y2 = based_new_h - offset_y / 2
|
||||
else:
|
||||
bg_img2 = char_img.resize((based_new_w , new_h), Image.Resampling.LANCZOS)
|
||||
x1 = 0 + offset_x
|
||||
y1 = new_h/2 - based_new_h/2 + offset_y / 2
|
||||
x2 = based_new_w
|
||||
y2 = new_h/2 + based_new_h/2 - offset_y / 2
|
||||
char_img = bg_img2.crop((x1, y1, x2, y2))
|
||||
|
||||
dmg_img, dmg_len = await draw_dmgCacl_img(raw_data)
|
||||
img_w, img_h = 950, 1850 + dmg_len * 40
|
||||
overlay = Image.open(TEXT_PATH / 'overlay.png')
|
||||
overlay_w, overlay_h = overlay.size
|
||||
if overlay_h < img_h:
|
||||
new_overlay_h = img_h
|
||||
new_overlay_w = math.ceil(new_overlay_h * overlay_w / overlay_h)
|
||||
overlay = overlay.resize((new_overlay_w, new_overlay_h), Image.Resampling.LANCZOS)
|
||||
overlay = overlay.crop((0, 0, img_w, img_h))
|
||||
color_img = Image.new('RGBA', overlay.size, COLOR_MAP[raw_data['avatarElement']])
|
||||
img = ImageChops.overlay(color_img, overlay)
|
||||
char_info_1 = Image.open(TEXT_PATH / 'char_info_1.png')
|
||||
char_info_mask = Image.open(TEXT_PATH / 'char_info_mask.png')
|
||||
|
||||
img_temp = Image.new('RGBA', (based_w, based_h), (0,0,0,0))
|
||||
img_temp.paste(char_img,(0,0),char_info_mask)
|
||||
img.paste(img_temp, (0, 0), img_temp)
|
||||
img.paste(char_info_1, (0, 0), char_info_1)
|
||||
img.paste(dmg_img,(0,1850),dmg_img)
|
||||
|
||||
lock_img = Image.open(TEXT_PATH / 'icon_lock.png')
|
||||
|
||||
# 命座处理
|
||||
for talent_num in range(0, 6):
|
||||
if talent_num + 1 <= len(raw_data['talentList']):
|
||||
talent = raw_data['talentList'][talent_num]
|
||||
# img.paste(color_holo_img, (13,270 + talent_num * 66), holo_img)
|
||||
talent_img = Image.open(ICON_PATH / '{}.png'.format(talent['talentIcon']))
|
||||
talent_img_new = talent_img.resize((50, 50), Image.Resampling.LANCZOS).convert("RGBA")
|
||||
img.paste(talent_img_new, (850, 375 + talent_num * 81), talent_img_new)
|
||||
else:
|
||||
img.paste(lock_img, (850, 375 + talent_num * 81), lock_img)
|
||||
|
||||
# 天赋处理
|
||||
skillList = raw_data['avatarSkill']
|
||||
a_skill_name = skillList[0]['skillName'].replace('普通攻击·', '')
|
||||
a_skill_level = skillList[0]['skillLevel']
|
||||
e_skill_name = skillList[1]['skillName']
|
||||
e_skill_level = skillList[1]['skillLevel']
|
||||
q_skill_name = skillList[-1]['skillName']
|
||||
q_skill_level = skillList[-1]['skillLevel']
|
||||
|
||||
skill_add = avatarName2SkillAdd[char_name]
|
||||
for skillAdd_index in range(0, 2):
|
||||
if len(raw_data['talentList']) >= 3 + skillAdd_index * 2:
|
||||
if skill_add[skillAdd_index] == 'E':
|
||||
e_skill_level += 3
|
||||
elif skill_add[skillAdd_index] == 'Q':
|
||||
q_skill_level += 3
|
||||
|
||||
for skill_num, skill in enumerate(skillList[0:2] + [skillList[-1]]):
|
||||
skill_img = Image.open(ICON_PATH / '{}.png'.format(skill['skillIcon']))
|
||||
skill_img_new = skill_img.resize((50, 50), Image.Resampling.LANCZOS).convert("RGBA")
|
||||
img.paste(skill_img_new, (78, 756 + 101 * skill_num), skill_img_new)
|
||||
|
||||
# 武器部分
|
||||
weapon_img = Image.open(TEXT_PATH / 'char_info_weapon.png')
|
||||
weapon_star_img = get_star_png(raw_data['weaponInfo']['weaponStar'])
|
||||
weaponName = raw_data['weaponInfo']['weaponName']
|
||||
|
||||
weaponAtk = raw_data['weaponInfo']['weaponStats'][0]['statValue']
|
||||
weaponLevel = raw_data['weaponInfo']['weaponLevel']
|
||||
weaponAffix = raw_data['weaponInfo']['weaponAffix']
|
||||
weaponEffect = raw_data['weaponInfo']['weaponEffect']
|
||||
weapon_type = raw_data['weaponInfo']['weaponType']
|
||||
|
||||
weapon_img.paste(weapon_star_img, (25, 235), weapon_star_img)
|
||||
weapon_text = ImageDraw.Draw(weapon_img)
|
||||
weapon_text.text((35, 80), weaponName, (255, 255, 255), genshin_font_origin(50), anchor='lm')
|
||||
weapon_text.text((35, 120), weapon_type, (255, 255, 255), genshin_font_origin(20), anchor='lm')
|
||||
weapon_text.text((35, 160), '基础攻击力', (255, 255, 255), genshin_font_origin(32), anchor='lm')
|
||||
weapon_text.text((368, 160), str(weaponAtk), (255, 255, 255), genshin_font_origin(32), anchor='rm')
|
||||
if len(raw_data['weaponInfo']['weaponStats']) == 2:
|
||||
weapon_sub_info = raw_data['weaponInfo']['weaponStats'][1]['statName']
|
||||
weapon_sub_value = raw_data['weaponInfo']['weaponStats'][1]['statValue']
|
||||
weapon_text.text((35, 211), weapon_sub_info, (255, 255, 255), genshin_font_origin(32), anchor='lm')
|
||||
weapon_text.text((368, 211), str(weapon_sub_value), (255, 255, 255), genshin_font_origin(32), anchor='rm')
|
||||
else:
|
||||
weapon_text.text((35, 211), '该武器无副词条', (255, 255, 255), genshin_font_origin(32), anchor='lm')
|
||||
weapon_text.text((73, 303), f'Lv.{weaponLevel}', (255, 255, 255), genshin_font_origin(28), anchor='mm')
|
||||
weapon_text.text((130, 305), f'精炼{str(weaponAffix)}阶', (255, 239, 173), genshin_font_origin(28), anchor='lm')
|
||||
|
||||
weaponEffect = strLenth(weaponEffect, 25, 455)
|
||||
weapon_text.text((25, 335), weaponEffect, (255, 255, 255), genshin_font_origin(25))
|
||||
img.paste(weapon_img, (387, 590), weapon_img)
|
||||
|
||||
fight_prop = raw_data['avatarFightProp']
|
||||
hp = fight_prop['hp']
|
||||
baseHp = fight_prop['baseHp']
|
||||
attack = fight_prop['atk']
|
||||
baseAtk = fight_prop['baseAtk']
|
||||
defense = fight_prop['def']
|
||||
baseDef = fight_prop['baseDef']
|
||||
em = fight_prop['elementalMastery']
|
||||
critrate = fight_prop['critRate']
|
||||
critdmg = fight_prop['critDmg']
|
||||
ce = fight_prop['energyRecharge']
|
||||
dmgBonus = fight_prop['dmgBonus'] if fight_prop['physicalDmgBonus'] <= fight_prop['dmgBonus'] else fight_prop['physicalDmgBonus']
|
||||
|
||||
hp_green = fight_prop['addHp']
|
||||
attack_green = fight_prop['addAtk']
|
||||
defense_green = fight_prop['addDef']
|
||||
|
||||
# 圣遗物部分
|
||||
artifactsAllScore = 0
|
||||
for aritifact in raw_data['equipList']:
|
||||
artifacts_img = Image.open(TEXT_PATH / 'char_info_artifacts.png')
|
||||
artifacts_piece_img = Image.open(RELIC_PATH / '{}.png'.format(aritifact['icon']))
|
||||
artifacts_piece_new_img = artifacts_piece_img.resize((75, 75), Image.Resampling.LANCZOS).convert("RGBA")
|
||||
#artifacts_piece_new_img.putalpha(
|
||||
# artifacts_piece_new_img.getchannel('A').point(lambda x: round(x * 0.5) if x > 0 else 0))
|
||||
|
||||
artifacts_img.paste(artifacts_piece_new_img, (195, 35), artifacts_piece_new_img)
|
||||
aritifactStar_img = get_star_png(aritifact['aritifactStar'])
|
||||
artifactsPos = aritifact['aritifactPieceName']
|
||||
|
||||
artifacts_img.paste(aritifactStar_img, (20, 165), aritifactStar_img)
|
||||
artifacts_text = ImageDraw.Draw(artifacts_img)
|
||||
artifacts_text.text((30, 66), aritifact['aritifactName'][:4], (255, 255, 255), genshin_font_origin(34), anchor='lm')
|
||||
artifacts_text.text((30, 102), artifactsPos, (255, 255, 255), genshin_font_origin(20), anchor='lm')
|
||||
|
||||
mainValue = aritifact['reliquaryMainstat']['statValue']
|
||||
mainName = aritifact['reliquaryMainstat']['statName']
|
||||
mainLevel = aritifact['aritifactLevel']
|
||||
|
||||
if mainName in ['攻击力', '血量', '防御力', '元素精通']:
|
||||
mainValueStr = str(mainValue)
|
||||
else:
|
||||
mainValueStr = str(mainValue) + '%'
|
||||
|
||||
mainNameNew = mainName.replace('百分比', '').replace('伤害加成', '伤加成').replace('元素', '').replace('理', '')
|
||||
|
||||
artifacts_text.text((30, 141), mainNameNew, (255, 255, 255), genshin_font_origin(28), anchor='lm')
|
||||
artifacts_text.text((263, 141), mainValueStr, (255, 255, 255), genshin_font_origin(28), anchor='rm')
|
||||
artifacts_text.text((55, 219), '+{}'.format(str(mainLevel)), (255, 255, 255), genshin_font_origin(24),
|
||||
anchor='mm')
|
||||
|
||||
artifactsScore = 0
|
||||
for index, i in enumerate(aritifact['reliquarySubstats']):
|
||||
subName = i['statName']
|
||||
subValue = i['statValue']
|
||||
if subName in ['攻击力', '血量', '防御力', '元素精通']:
|
||||
subValueStr = str(subValue)
|
||||
else:
|
||||
subValueStr = str(subValue) + '%'
|
||||
#artifactsScore += await get_artifacts_score(subName, subValue)
|
||||
value_temp = await get_artifacts_value(subName, subValue, baseAtk, baseHp, baseDef, char_name)
|
||||
artifactsScore += value_temp
|
||||
subNameStr = subName.replace('百分比', '').replace('元素', '')
|
||||
if value_temp == 0:
|
||||
artifacts_color = (160, 160, 160)
|
||||
elif value_temp >= 4.5:
|
||||
artifacts_color = (247, 50, 50)
|
||||
else:
|
||||
artifacts_color = (255, 255, 100)
|
||||
artifacts_text.text((20, 256 + index * 33), '·{}'.format(subNameStr), artifacts_color,
|
||||
genshin_font_origin(25), anchor='lm')
|
||||
artifacts_text.text((268, 256 + index * 33), '{}'.format(subValueStr), artifacts_color,
|
||||
genshin_font_origin(25), anchor='rm')
|
||||
if artifactsScore >= 6:
|
||||
artifactsScore_color = (247, 26, 26)
|
||||
else:
|
||||
artifactsScore_color = (255, 255, 255)
|
||||
artifactsAllScore += artifactsScore
|
||||
artifacts_text.text((268, 190), '{:.2f}'.format(artifactsScore) + '条', artifactsScore_color, genshin_font_origin(23),
|
||||
anchor='rm')
|
||||
|
||||
if artifactsPos == '生之花':
|
||||
img.paste(artifacts_img, (18, 1075), artifacts_img)
|
||||
elif artifactsPos == '死之羽':
|
||||
img.paste(artifacts_img, (318, 1075), artifacts_img)
|
||||
elif artifactsPos == '时之沙':
|
||||
img.paste(artifacts_img, (618, 1075), artifacts_img)
|
||||
elif artifactsPos == '空之杯':
|
||||
img.paste(artifacts_img, (18, 1447), artifacts_img)
|
||||
elif artifactsPos == '理之冠':
|
||||
img.paste(artifacts_img, (318, 1447), artifacts_img)
|
||||
|
||||
# 角色基本信息
|
||||
img_text = ImageDraw.Draw(img)
|
||||
img_text.text((411, 72), char_name, (255, 255, 255), genshin_font_origin(55), anchor='lm')
|
||||
img_text.text((411, 122), '等级{}'.format(char_level), (255, 255, 255), genshin_font_origin(40), anchor='lm')
|
||||
img_text.text((747, 126), str(char_fetter), (255, 255, 255), genshin_font_origin(28), anchor='lm')
|
||||
|
||||
# aeq
|
||||
# img_text.text((110, 771), a_skill_name, (255, 255, 255), genshin_font_origin(26), anchor='lm')
|
||||
img_text.text((103, 812), f'{str(a_skill_level)}', (255, 255, 255), genshin_font_origin(30), anchor='mm')
|
||||
|
||||
# img_text.text((110, 872), e_skill_name, (255, 255, 255), genshin_font_origin(26), anchor='lm')
|
||||
img_text.text((103, 915), f'{str(e_skill_level)}', (255, 255, 255), genshin_font_origin(30), anchor='mm')
|
||||
|
||||
# img_text.text((110, 973), q_skill_name, (255, 255, 255), genshin_font_origin(26), anchor='lm')
|
||||
img_text.text((103, 1016), f'{str(q_skill_level)}', (255, 255, 255), genshin_font_origin(30), anchor='mm')
|
||||
|
||||
# 属性
|
||||
img_text.text((785, 174), str(round(hp)), (255, 255, 255), genshin_font_origin(28), anchor='rm')
|
||||
img_text.text((785, 227), str(round(attack)), (255, 255, 255), genshin_font_origin(28), anchor='rm')
|
||||
img_text.text((785, 280), str(round(defense)), (255, 255, 255), genshin_font_origin(28), anchor='rm')
|
||||
img_text.text((785, 333), str(round(em)), (255, 255, 255), genshin_font_origin(28), anchor='rm')
|
||||
img_text.text((785, 386), f'{str(round(critrate * 100, 2))}%', (255, 255, 255), genshin_font_origin(28),
|
||||
anchor='rm')
|
||||
img_text.text((785, 439), f'{str(round(critdmg * 100, 2))}%', (255, 255, 255), genshin_font_origin(28), anchor='rm')
|
||||
img_text.text((785, 492), f'{str(round(ce * 100, 1))}%', (255, 255, 255), genshin_font_origin(28), anchor='rm')
|
||||
img_text.text((785, 545), f'{str(round(dmgBonus * 100, 1))}%', (255, 255, 255), genshin_font_origin(28),
|
||||
anchor='rm')
|
||||
|
||||
img_text.text((805, 174), f'(+{str(round(hp_green))})', (95, 251, 80), genshin_font_origin(28), anchor='lm')
|
||||
img_text.text((805, 227), f'(+{str(round(attack_green))})', (95, 251, 80), genshin_font_origin(28), anchor='lm')
|
||||
img_text.text((805, 280), f'(+{str(round(defense_green))})', (95, 251, 80), genshin_font_origin(28), anchor='lm')
|
||||
|
||||
uid = raw_data['playerUid']
|
||||
data_time = raw_data['dataTime']
|
||||
# uid
|
||||
img_text.text((350, 1035), f'UID{uid}', (255, 255, 255), genshin_font_origin(24), anchor='rm')
|
||||
|
||||
# 数据最后更新时间
|
||||
img_text.text((780, 600), f'数据最后更新于{data_time}', (255, 255, 255), genshin_font_origin(22), anchor='rm')
|
||||
|
||||
# 角色评分
|
||||
img_text.text((768, 1557), f'{round(artifactsAllScore, 1)}', (255, 255, 255), genshin_font_origin(50), anchor='mm')
|
||||
percent = await get_char_percent(raw_data)
|
||||
img_text.text((768, 1690), f'{str(percent)+"%"}', (255, 255, 255), genshin_font_origin(50), anchor='mm')
|
||||
|
||||
img = img.convert('RGB')
|
||||
result_buffer = BytesIO()
|
||||
img.save(result_buffer, format='JPEG', subsampling=0, quality=90)
|
||||
imgmes = 'base64://' + b64encode(result_buffer.getvalue()).decode()
|
||||
res = imgmes
|
||||
return res
|
||||
|
||||
async def draw_single_card(img: Image, char: dict, index: int, color: Tuple[int, int, int],
|
||||
x_limit: int, char_card_mask: Image, char_card_1: Image, img_card: Image):
|
||||
|
||||
size_36 = genshin_font_origin(36)
|
||||
size_46 = genshin_font_origin(46)
|
||||
|
||||
overlay = Image.open(TEXT_PATH / 'overlay.png')
|
||||
color_img = Image.new('RGBA', overlay.size, COLOR_MAP[char['avatarElement']])
|
||||
img_base = ImageChops.overlay(color_img, overlay)
|
||||
if char['char_name'] in avatarCardOffsetMap:
|
||||
offset_x, offset_y = avatarCardOffsetMap[char['char_name']][0], avatarCardOffsetMap[char['char_name']][1]
|
||||
else:
|
||||
offset_x, offset_y = 200, 0
|
||||
char_img = Image.open(GACHA_PATH / 'UI_Gacha_AvatarImg_{}.png'.format(char['avatarEnName']))
|
||||
|
||||
img_base.paste(char_img, (-439 + offset_x, 130 + offset_y), char_img)
|
||||
img_card.paste(img_base, (-25, -260), char_card_mask)
|
||||
img_card = Image.alpha_composite(img_card, char_card_1)
|
||||
#img_card.paste(img_card, (0, 0), img_card)
|
||||
|
||||
char_card_text = ImageDraw.Draw(img_card)
|
||||
|
||||
char_card_text.text((448, 59.2), f'{str(round(char["critrate"] * 100, 2))}%', color, size_36, anchor='lm')
|
||||
char_card_text.text((448, 122.2), f'{str(round(char["critdmg"] * 100, 2))}%', color, size_36, anchor='lm')
|
||||
|
||||
char_card_text.text((410.9, 252.6), str(char['a_skill_level']), color, size_36, anchor='mm')
|
||||
char_card_text.text((485, 252.6), str(char['e_skill_level']), color, size_36, anchor='mm')
|
||||
char_card_text.text((558, 252.6), str(char['q_skill_level']), color, size_36, anchor='mm')
|
||||
|
||||
if float(char['percent']) >= 100:
|
||||
percent_color = (204, 57, 78)
|
||||
else:
|
||||
percent_color = color
|
||||
|
||||
if char['value'] >= 28.5:
|
||||
value_color = (204, 57, 78)
|
||||
else:
|
||||
value_color = color
|
||||
|
||||
char_card_text.text((742, 253.1), str(char['percent']) + '%', percent_color, size_46, anchor='mm')
|
||||
char_card_text.text((742, 113.1), str(char['value']) + '条', value_color, size_46, anchor='mm')
|
||||
|
||||
char_card_text.text((21.2, 70.5), f'{str(char["talent_num"])}命', color, size_36, anchor='lm')
|
||||
char_card_text.text((21.2, 129.8), f'{str(char["weapon_affix"])}精', color, size_36, anchor='lm')
|
||||
|
||||
img.paste(img_card, ((index % x_limit) * 900, (index // x_limit) * 300), img_card)
|
||||
|
||||
async def draw_cahrcard_list(uid: str,limit :int = 24) -> str:
|
||||
uid_fold = PLAYER_PATH / str(uid)
|
||||
char_file_list = uid_fold.glob('*')
|
||||
char_list = []
|
||||
for i in char_file_list:
|
||||
file_name = i.name
|
||||
if '\u4e00' <= file_name[0] <= '\u9fff':
|
||||
char_list.append(file_name.split('.')[0])
|
||||
if not char_list:
|
||||
return '你还没有已缓存的角色!\n请先使用【强制刷新】进行刷新!'
|
||||
|
||||
char_done_list = []
|
||||
for char_name in char_list:
|
||||
temp = {}
|
||||
with open(uid_fold / f'{char_name}.json', 'r', encoding='UTF-8') as f:
|
||||
raw_data = json.load(f)
|
||||
|
||||
fight_prop = raw_data['avatarFightProp']
|
||||
skillList = raw_data['avatarSkill']
|
||||
|
||||
temp['char_name'] = char_name
|
||||
temp['avatarEnName'] = raw_data['avatarEnName']
|
||||
temp['avatarElement'] = raw_data['avatarElement']
|
||||
temp['percent'] = await get_char_percent(raw_data)
|
||||
temp['critrate'] = fight_prop['critRate']
|
||||
temp['critdmg'] = fight_prop['critDmg']
|
||||
baseHp = fight_prop['baseHp']
|
||||
baseAtk = fight_prop['baseAtk']
|
||||
baseDef = fight_prop['baseDef']
|
||||
temp['value'] = await get_all_artifacts_value(raw_data['equipList'], baseHp, baseAtk, baseDef, char_name)
|
||||
temp['value'] = float('{:.2f}'.format(temp['value']))
|
||||
temp['avatarElement'] = raw_data['avatarElement']
|
||||
temp['a_skill_level'] = skillList[0]['skillLevel']
|
||||
temp['e_skill_level'] = skillList[1]['skillLevel']
|
||||
temp['q_skill_level'] = skillList[-1]['skillLevel']
|
||||
temp['talent_num'] = len(raw_data['talentList'])
|
||||
temp['weapon_affix'] = raw_data['weaponInfo']['weaponAffix']
|
||||
char_done_list.append(temp)
|
||||
|
||||
# 排序
|
||||
char_done_list.sort(key=lambda x: (-float(x['percent'])))
|
||||
char_done_list = char_done_list[:limit]
|
||||
|
||||
char_card_1 = Image.open(TEXT_PATH / 'charcard_1.png')
|
||||
char_card_mask = Image.open(TEXT_PATH / 'charcard_mask.png')
|
||||
|
||||
x_limit = 2
|
||||
color = (255, 255, 255)
|
||||
x_tile = (len(char_done_list) + x_limit - 1) // x_limit
|
||||
y_tile = math.ceil(len(char_done_list) / x_tile)
|
||||
x_tile, y_tile = x_tile if x_tile <= y_tile else y_tile, y_tile if y_tile >= x_tile else x_tile
|
||||
|
||||
img = Image.new('RGBA', (900 * x_tile, 300 * y_tile), (0, 0, 0))
|
||||
img_card = Image.new('RGBA', (900, 300))
|
||||
|
||||
tasks = []
|
||||
for index, char in enumerate(char_done_list):
|
||||
tasks.append(draw_single_card(img, char, index, color, x_limit, char_card_mask, char_card_1, img_card))
|
||||
await asyncio.wait(tasks)
|
||||
|
||||
img = img.convert('RGB')
|
||||
result_buffer = BytesIO()
|
||||
img.save(result_buffer, format='JPEG', subsampling=0, quality=90)
|
||||
imgmes = 'base64://' + b64encode(result_buffer.getvalue()).decode()
|
||||
res = imgmes
|
||||
return res
|
@ -1,297 +0,0 @@
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
import httpx
|
||||
import json
|
||||
import time
|
||||
|
||||
R_PATH = Path(__file__).parents[0]
|
||||
MAP_PATH = R_PATH / 'map'
|
||||
ICON_PATH = R_PATH / 'icon'
|
||||
DATA_PATH = R_PATH / 'data'
|
||||
PLAYER_PATH = R_PATH / 'player'
|
||||
|
||||
version = '2.8.0'
|
||||
|
||||
avatarName2Element_fileName = f'avatarName2Element_mapping_{version}.json'
|
||||
weaponHash2Name_fileName = f'weaponHash2Name_mapping_{version}.json'
|
||||
weaponHash2Type_fileName = f'weaponHash2Type_mapping_{version}.json'
|
||||
skillId2Name_fileName = f'skillId2Name_mapping_{version}.json'
|
||||
talentId2Name_fileName = f'talentId2Name_mapping_{version}.json'
|
||||
avatarId2Name_fileName = f'avatarId2Name_mapping_{version}.json'
|
||||
|
||||
artifact2attr_fileName = f'artifact2attr_mapping_{version}.json'
|
||||
icon2Name_fileName = f'icon2Name_mapping_{version}.json'
|
||||
|
||||
with open(MAP_PATH / avatarId2Name_fileName, "r", encoding='UTF-8') as f:
|
||||
avatarId2Name = json.load(f)
|
||||
|
||||
with open(MAP_PATH / icon2Name_fileName, "r", encoding='UTF-8') as f:
|
||||
icon2Name = json.load(f)
|
||||
|
||||
with open(MAP_PATH / artifact2attr_fileName, "r", encoding='UTF-8') as f:
|
||||
artifact2attr = json.load(f)
|
||||
|
||||
with open(MAP_PATH / 'propId2Name_mapping.json', "r", encoding='UTF-8') as f:
|
||||
propId2Name = json.load(f)
|
||||
|
||||
with open(MAP_PATH / weaponHash2Name_fileName, "r", encoding='UTF-8') as f:
|
||||
weaponHash2Name = json.load(f)
|
||||
|
||||
with open(MAP_PATH / weaponHash2Type_fileName, "r", encoding='UTF-8') as f:
|
||||
weaponHash2Type = json.load(f)
|
||||
|
||||
with open(MAP_PATH / 'artifactId2Piece_mapping.json', "r", encoding='UTF-8') as f:
|
||||
artifactId2Piece = json.load(f)
|
||||
|
||||
with open(MAP_PATH / skillId2Name_fileName, "r", encoding='UTF-8') as f:
|
||||
skillId2Name = json.load(f)
|
||||
|
||||
with open(MAP_PATH / talentId2Name_fileName, "r", encoding='UTF-8') as f:
|
||||
talentId2Name = json.load(f)
|
||||
|
||||
with open(MAP_PATH / avatarName2Element_fileName, 'r', encoding='UTF-8') as f:
|
||||
avatarName2Element = json.load(f)
|
||||
|
||||
|
||||
async def enkaToData(uid: str, enka_data: Optional[dict] = None) -> dict:
|
||||
if enka_data:
|
||||
pass
|
||||
else:
|
||||
enka_data = json.loads(
|
||||
httpx.get(
|
||||
url=f'https://enka.minigg.cn/u/{str(uid)}/__data.json',
|
||||
headers={'User-Agent': 'GenshinUID/2.1'},
|
||||
).text
|
||||
)
|
||||
if enka_data == {}:
|
||||
return enka_data
|
||||
now = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time()))
|
||||
playerInfo = enka_data['playerInfo']
|
||||
path = PLAYER_PATH / str(uid)
|
||||
path.mkdir(parents=True, exist_ok=True)
|
||||
with open(path / '{}.json'.format(str(uid)), 'w', encoding='UTF-8') as file:
|
||||
json.dump(playerInfo, file, ensure_ascii=False)
|
||||
with open(path / 'rawData.json', 'w', encoding='UTF-8') as file:
|
||||
json.dump(enka_data, file, ensure_ascii=False)
|
||||
|
||||
if 'avatarInfoList' not in enka_data:
|
||||
return f'UID{uid}刷新失败!未打开角色展柜!'
|
||||
|
||||
char_name_list = []
|
||||
for char in enka_data['avatarInfoList']:
|
||||
# 处理基本信息
|
||||
char_data = {}
|
||||
avatarId = char['avatarId']
|
||||
char_data['playerUid'] = str(uid)
|
||||
char_data['playerName'] = enka_data['playerInfo']['nickname']
|
||||
char_data['avatarId'] = avatarId
|
||||
avatarName = avatarId2Name[str(char['avatarId'])]
|
||||
char_data['avatarName'] = avatarId2Name[str(char['avatarId'])]
|
||||
char_name_list.append(char_data['avatarName'])
|
||||
char_data['avatarFetter'] = char['fetterInfo']['expLevel']
|
||||
char_data['avatarLevel'] = char['propMap']['4001']['val']
|
||||
|
||||
try:
|
||||
char_data['avatarElement'] = avatarName2Element[char_data['avatarName']]
|
||||
except KeyError:
|
||||
check = skillId2Name['Name'][str(list(char['skillLevelMap'].keys())[2])]
|
||||
if '风' in check:
|
||||
char_data['avatarElement'] = 'Anemo'
|
||||
elif '雷' in check:
|
||||
char_data['avatarElement'] = 'Electro'
|
||||
elif '岩' in check:
|
||||
char_data['avatarElement'] = 'Geo'
|
||||
elif '草' in check:
|
||||
char_data['avatarElement'] = 'Dendro'
|
||||
elif '冰' in check:
|
||||
char_data['avatarElement'] = 'Cryo'
|
||||
elif '水' in check:
|
||||
char_data['avatarElement'] = 'Hydro'
|
||||
else:
|
||||
char_data['avatarElement'] = 'Pyro'
|
||||
|
||||
char_data['dataTime'] = now
|
||||
|
||||
char_data['avatarSkill'] = []
|
||||
# 处理天赋
|
||||
for skill in char['skillLevelMap']:
|
||||
skill_temp = {}
|
||||
skill_temp['skillId'] = skill
|
||||
skill_temp['skillName'] = skillId2Name['Name'][skill_temp['skillId']]
|
||||
skill_temp['skillLevel'] = char['skillLevelMap'][skill]
|
||||
skill_temp['skillIcon'] = skillId2Name['Icon'][skill_temp['skillId']]
|
||||
char_data['avatarSkill'].append(skill_temp)
|
||||
|
||||
if char_data['avatarName'] in ['神里绫华', '安柏']:
|
||||
char_data['avatarSkill'][0], char_data['avatarSkill'][-1] = (
|
||||
char_data['avatarSkill'][-1],
|
||||
char_data['avatarSkill'][0],
|
||||
)
|
||||
char_data['avatarSkill'][2], char_data['avatarSkill'][-1] = (
|
||||
char_data['avatarSkill'][-1],
|
||||
char_data['avatarSkill'][2],
|
||||
)
|
||||
char_data['avatarEnName'] = char_data['avatarSkill'][1]['skillIcon'].split(
|
||||
'_'
|
||||
)[-2]
|
||||
elif char_data['avatarName'] in ['旅行者']:
|
||||
char_data['avatarSkill'][0], char_data['avatarSkill'][-1] = (
|
||||
char_data['avatarSkill'][-1],
|
||||
char_data['avatarSkill'][0],
|
||||
)
|
||||
char_data['avatarSkill'][1], char_data['avatarSkill'][-1] = (
|
||||
char_data['avatarSkill'][-1],
|
||||
char_data['avatarSkill'][1],
|
||||
)
|
||||
char_data['avatarEnName'] = str(avatarId)
|
||||
else:
|
||||
char_data['avatarEnName'] = char_data['avatarSkill'][-1]['skillIcon'].split(
|
||||
'_'
|
||||
)[-2]
|
||||
|
||||
# 处理命座
|
||||
talent_temp = []
|
||||
if 'talentIdList' in char:
|
||||
for index, talent in enumerate(char['talentIdList']):
|
||||
talentTemp = {}
|
||||
talentTemp['talentId'] = char['talentIdList'][index]
|
||||
talentTemp['talentName'] = talentId2Name['Name'][str(talent)]
|
||||
talentTemp['talentIcon'] = talentId2Name['Icon'][str(talent)]
|
||||
talent_temp.append(talentTemp)
|
||||
char_data['talentList'] = talent_temp
|
||||
|
||||
# 处理属性
|
||||
fight_prop = {}
|
||||
# 血量
|
||||
fight_prop['hp'] = char["fightPropMap"]["2000"]
|
||||
fight_prop['baseHp'] = char["fightPropMap"]["1"]
|
||||
fight_prop['addHp'] = char["fightPropMap"]["2000"] - char["fightPropMap"]["1"]
|
||||
# 攻击力
|
||||
fight_prop['atk'] = char["fightPropMap"]["2001"]
|
||||
fight_prop['baseAtk'] = char["fightPropMap"]["4"]
|
||||
fight_prop['addAtk'] = char["fightPropMap"]["2001"] - char["fightPropMap"]["4"]
|
||||
# 防御力
|
||||
fight_prop['def'] = char["fightPropMap"]["2002"]
|
||||
fight_prop['baseDef'] = char["fightPropMap"]["7"]
|
||||
fight_prop['addDef'] = char["fightPropMap"]["2002"] - char["fightPropMap"]["7"]
|
||||
# 元素精通
|
||||
fight_prop['elementalMastery'] = char["fightPropMap"]["28"]
|
||||
# 暴击率
|
||||
fight_prop['critRate'] = char["fightPropMap"]["20"]
|
||||
# 暴击伤害
|
||||
fight_prop['critDmg'] = char["fightPropMap"]["22"]
|
||||
# 充能效率
|
||||
fight_prop['energyRecharge'] = char["fightPropMap"]["23"]
|
||||
# 治疗&受治疗
|
||||
fight_prop['healBonus'] = char["fightPropMap"]["26"]
|
||||
fight_prop['healedBonus'] = char["fightPropMap"]["27"]
|
||||
# 物理伤害加成 & 抗性
|
||||
fight_prop['physicalDmgSub'] = char["fightPropMap"]["29"]
|
||||
fight_prop['physicalDmgBonus'] = char["fightPropMap"]["30"]
|
||||
# 伤害加成
|
||||
for i in range(40, 47):
|
||||
if char["fightPropMap"][str(i)] > 0:
|
||||
fight_prop['dmgBonus'] = char["fightPropMap"][str(i)]
|
||||
break
|
||||
else:
|
||||
fight_prop['dmgBonus'] = 0
|
||||
|
||||
char_data['avatarFightProp'] = fight_prop
|
||||
|
||||
# 处理武器
|
||||
weapon_info = {}
|
||||
weapon_data = char['equipList'][-1]
|
||||
weapon_info['itemId'] = weapon_data['itemId']
|
||||
weapon_info['nameTextMapHash'] = weapon_data['flat']['nameTextMapHash']
|
||||
weapon_info['weaponIcon'] = weapon_data['flat']['icon']
|
||||
weapon_info['weaponType'] = weaponHash2Type[weapon_info['nameTextMapHash']]
|
||||
weapon_info['weaponName'] = weaponHash2Name[weapon_info['nameTextMapHash']]
|
||||
weapon_info['weaponStar'] = weapon_data['flat']['rankLevel']
|
||||
# 防止未精炼
|
||||
if 'promoteLevel' in weapon_data['weapon']:
|
||||
weapon_info['promoteLevel'] = weapon_data['weapon']['promoteLevel']
|
||||
else:
|
||||
weapon_info['promoteLevel'] = 0
|
||||
weapon_info['weaponLevel'] = weapon_data['weapon']['level']
|
||||
if 'affixMap' in weapon_data['weapon']:
|
||||
weapon_info['weaponAffix'] = (
|
||||
list(weapon_data['weapon']['affixMap'].values())[0] + 1
|
||||
)
|
||||
else:
|
||||
weapon_info['weaponAffix'] = 1
|
||||
weapon_info['weaponStats'] = []
|
||||
for k in weapon_data['flat']['weaponStats']:
|
||||
weapon_prop_temp = {}
|
||||
weapon_prop_temp['appendPropId'] = k['appendPropId']
|
||||
weapon_prop_temp['statName'] = propId2Name[k['appendPropId']]
|
||||
weapon_prop_temp['statValue'] = k['statValue']
|
||||
weapon_info['weaponStats'].append(weapon_prop_temp)
|
||||
# 武器特效,须请求API
|
||||
effect_raw = json.loads(
|
||||
httpx.get(
|
||||
'https://info.minigg.cn/weapons?query={}'.format(
|
||||
weapon_info['weaponName']
|
||||
)
|
||||
).text
|
||||
)
|
||||
if 'effect' in effect_raw:
|
||||
effect = effect_raw['effect'].format(
|
||||
*effect_raw['r{}'.format(str(weapon_info['weaponAffix']))]
|
||||
)
|
||||
else:
|
||||
effect = '无特效。'
|
||||
weapon_info['weaponEffect'] = effect
|
||||
char_data['weaponInfo'] = weapon_info
|
||||
|
||||
# 处理圣遗物
|
||||
artifacts_info = []
|
||||
artifacts_data = char['equipList'][:-1]
|
||||
artifact_set_list = []
|
||||
for artifact in artifacts_data:
|
||||
artifact_temp = {}
|
||||
artifact_temp['itemId'] = artifact['itemId']
|
||||
artifact_temp['nameTextMapHash'] = artifact['flat']['nameTextMapHash']
|
||||
artifact_temp['icon'] = artifact['flat']['icon']
|
||||
artifact_temp['aritifactName'] = icon2Name[artifact['flat']['icon']]
|
||||
artifact_temp['aritifactSetsName'] = artifact2attr[
|
||||
artifact_temp['aritifactName']
|
||||
]
|
||||
artifact_set_list.append(artifact_temp['aritifactSetsName'])
|
||||
artifact_temp['aritifactSetPiece'] = artifactId2Piece[
|
||||
artifact_temp['icon'].split('_')[-1]
|
||||
][0]
|
||||
artifact_temp['aritifactPieceName'] = artifactId2Piece[
|
||||
artifact_temp['icon'].split('_')[-1]
|
||||
][1]
|
||||
|
||||
artifact_temp['aritifactStar'] = artifact['flat']['rankLevel']
|
||||
artifact_temp['aritifactLevel'] = artifact['reliquary']['level'] - 1
|
||||
|
||||
artifact_temp['reliquaryMainstat'] = artifact['flat']['reliquaryMainstat']
|
||||
artifact_temp['reliquaryMainstat']['statName'] = propId2Name[
|
||||
artifact_temp['reliquaryMainstat']['mainPropId']
|
||||
]
|
||||
|
||||
artifact_temp['reliquarySubstats'] = artifact['flat']['reliquarySubstats']
|
||||
for sub in artifact_temp['reliquarySubstats']:
|
||||
sub['statName'] = propId2Name[sub['appendPropId']]
|
||||
artifacts_info.append(artifact_temp)
|
||||
|
||||
equipSetList = set(artifact_set_list)
|
||||
char_data['equipSets'] = {'type': '', 'set': ''}
|
||||
char_data['equipList'] = artifacts_info
|
||||
for equip in equipSetList:
|
||||
if artifact_set_list.count(equip) >= 4:
|
||||
char_data['equipSets']['type'] = '4'
|
||||
char_data['equipSets']['set'] = equip
|
||||
break
|
||||
elif artifact_set_list.count(equip) == 1:
|
||||
pass
|
||||
elif artifact_set_list.count(equip) >= 2:
|
||||
char_data['equipSets']['type'] += '2'
|
||||
char_data['equipSets']['set'] += equip
|
||||
|
||||
with open(path / '{}.json'.format(avatarName), 'w', encoding='UTF-8') as file:
|
||||
json.dump(char_data, file, ensure_ascii=False)
|
||||
char_name_list_str = ','.join(char_name_list)
|
||||
return f'UID{uid}刷新完成!\n本次缓存:{char_name_list_str}'
|
@ -1 +0,0 @@
|
||||
{"凯特": [200, 0], "神里绫华": [-110, 10], "琴": [100, 30], "旅行者": [200, 0], "丽莎": [260, 110], "芭芭拉": [200, 70], "凯亚": [100, 130], "迪卢克": [-350, 0], "雷泽": [60, -40], "安柏": [220, -100], "温迪": [100, 0], "香菱": [150, 170], "北斗": [0, 0], "行秋": [250, 100], "魈": [40, -135], "凝光": [-100, 20], "可莉": [0, 0], "钟离": [200, 0], "菲谢尔": [150, 0], "班尼特": [30, -70], "达达利亚": [0, -40], "诺艾尔": [150, 110], "七七": [120, -100], "重云": [120, -10], "甘雨": [100, 70], "阿贝多": [-50, -15], "迪奥娜": [350, 50], "莫娜": [50, 0], "刻晴": [20, -15], "砂糖": [130, 30], "辛焱": [-40, -40], "罗莎莉亚": [160, 30], "胡桃": [-245, -25], "枫原万叶": [45, 160], "烟绯": [345, 130], "宵宫": [70, 0], "托马": [160, 20], "优菈": [110, 30], "雷电将军": [150, 0], "早柚": [-50, 0], "珊瑚宫心海": [100, 30], "五郎": [260, 0], "九条裟罗": [180, 110], "荒泷一斗": [100, 0], "八重神子": [80, 30], "夜兰": [140, 10], "埃洛伊": [280, 0], "申鹤": [-10, 30], "云堇": [350, 100], "久岐忍": [200, 0], "神里绫人": [150, 100], "队伍测试4号": [200, 0], "场景测试": [200, 0], "裸模1号": [200, 0], "裸男": [200, 0], "联机测试": [200, 0], "成男体型测试": [200, 0], "成女体型测试": [200, 0], "少女体型测试": [200, 0], "阿葵丽雅": [200, 0], "瑶瑶": [200, 0], "少女体型测试-二号机": [200, 0], "白盒少女": [200, 0], "大剑少女": [200, 0], "后武器测试A": [200, 0], "后武器测试B": [200, 0], "后武器测试C": [200, 0], "后武器测试D": [200, 0], "长枪成女": [200, 0], "单手剑成女测试": [200, 0], "Rx白盒": [200, 0], "少男体型测试": [200, 0], "女主新普攻": [200, 0], "男主新普攻": [200, 0], "重云(测试)": [200, 0], "测试角色": [200, 0], "七七(测试)": [200, 0], "迪奥娜(测试)": [200, 0]}
|
@ -1 +0,0 @@
|
||||
{"凯特": [200, 0], "神里绫华": [0, 0], "琴": [100, 0], "旅行者": [0, -160], "丽莎": [200, -100], "芭芭拉": [200, 0], "凯亚": [150, -150], "迪卢克": [300, 0], "雷泽": [400, 0], "安柏": [0, 200], "温迪": [100, 0], "香菱": [-100, -250], "北斗": [200, 0], "行秋": [250, -100], "魈": [200, 200], "凝光": [500, 0], "可莉": [0, 0], "钟离": [100, 0], "菲谢尔": [300, 0], "班尼特": [150, 150], "达达利亚": [0, 0], "诺艾尔": [300, -80], "七七": [200, 100], "重云": [200, 0], "甘雨": [100, 0], "阿贝多": [200, 0], "迪奥娜": [-300, 0], "莫娜": [200, 0], "刻晴": [400, 250], "砂糖": [50, 0], "辛焱": [250, 0], "罗莎莉亚": [200, 0], "胡桃": [560, 0], "枫原万叶": [150, -200], "烟绯": [-400, 0], "宵宫": [250, 0], "托马": [200, 0], "优菈": [-200, 0], "雷电将军": [100, 0], "早柚": [500, 0], "珊瑚宫心海": [200, 0], "五郎": [60, 0], "九条裟罗": [100, 0], "荒泷一斗": [100, 0], "八重神子": [300, 0], "夜兰": [80, 0], "埃洛伊": [60, 0], "申鹤": [360, 0], "云堇": [100, 0], "久岐忍": [200, 0], "神里绫人": [50, 0], "队伍测试4号": [200, 0], "场景测试": [200, 0], "裸模1号": [200, 0], "裸男": [200, 0], "联机测试": [200, 0], "成男体型测试": [200, 0], "成女体型测试": [200, 0], "少女体型测试": [200, 0], "阿葵丽雅": [200, 0], "瑶瑶": [200, 0], "少女体型测试-二号机": [200, 0], "白盒少女": [200, 0], "大剑少女": [200, 0], "后武器测试A": [200, 0], "后武器测试B": [200, 0], "后武器测试C": [200, 0], "后武器测试D": [200, 0], "长枪成女": [200, 0], "单手剑成女测试": [200, 0], "Rx白盒": [200, 0], "少男体型测试": [200, 0], "女主新普攻": [200, 0], "男主新普攻": [200, 0], "重云(测试)": [200, 0], "测试角色": [200, 0], "七七(测试)": [200, 0], "迪奥娜(测试)": [200, 0]}
|
Before Width: | Height: | Size: 800 KiB |
Before Width: | Height: | Size: 722 KiB |
Before Width: | Height: | Size: 1.4 MiB |
Before Width: | Height: | Size: 772 KiB |
Before Width: | Height: | Size: 595 KiB |
Before Width: | Height: | Size: 609 KiB |
Before Width: | Height: | Size: 1.7 MiB |
Before Width: | Height: | Size: 1.1 MiB |
Before Width: | Height: | Size: 635 KiB |
Before Width: | Height: | Size: 1.1 MiB |
Before Width: | Height: | Size: 892 KiB |
Before Width: | Height: | Size: 935 KiB |
Before Width: | Height: | Size: 1.5 MiB |
Before Width: | Height: | Size: 440 KiB |
Before Width: | Height: | Size: 1.1 MiB |
Before Width: | Height: | Size: 858 KiB |
Before Width: | Height: | Size: 694 KiB |
Before Width: | Height: | Size: 1.0 MiB |
Before Width: | Height: | Size: 642 KiB |
Before Width: | Height: | Size: 237 KiB |
Before Width: | Height: | Size: 1.2 MiB |
Before Width: | Height: | Size: 1.1 MiB |
Before Width: | Height: | Size: 590 KiB |
Before Width: | Height: | Size: 759 KiB |
Before Width: | Height: | Size: 1.0 MiB |
Before Width: | Height: | Size: 486 KiB |
Before Width: | Height: | Size: 964 KiB |
Before Width: | Height: | Size: 495 KiB |
Before Width: | Height: | Size: 1.8 MiB |
Before Width: | Height: | Size: 1.8 MiB |
Before Width: | Height: | Size: 902 KiB |
Before Width: | Height: | Size: 636 KiB |
Before Width: | Height: | Size: 974 KiB |
Before Width: | Height: | Size: 979 KiB |
Before Width: | Height: | Size: 1.1 MiB |
Before Width: | Height: | Size: 949 KiB |
Before Width: | Height: | Size: 749 KiB |
Before Width: | Height: | Size: 795 KiB |
Before Width: | Height: | Size: 807 KiB |
Before Width: | Height: | Size: 574 KiB |
Before Width: | Height: | Size: 613 KiB |
Before Width: | Height: | Size: 1.1 MiB |
Before Width: | Height: | Size: 462 KiB |
Before Width: | Height: | Size: 1.2 MiB |
Before Width: | Height: | Size: 693 KiB |
Before Width: | Height: | Size: 1.5 MiB |
Before Width: | Height: | Size: 792 KiB |
Before Width: | Height: | Size: 996 KiB |
Before Width: | Height: | Size: 579 KiB |
Before Width: | Height: | Size: 1.1 MiB |
Before Width: | Height: | Size: 542 KiB |
Before Width: | Height: | Size: 1.4 MiB |
Before Width: | Height: | Size: 1.2 MiB |
Before Width: | Height: | Size: 1.2 MiB |
Before Width: | Height: | Size: 1.0 MiB |
Before Width: | Height: | Size: 474 KiB |
Before Width: | Height: | Size: 1.2 MiB |
Before Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 6.0 KiB |
Before Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 5.8 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 6.7 KiB |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 6.2 KiB |
Before Width: | Height: | Size: 18 KiB |