From 43ee000f968140da7ef64f65fd345ff8ad60670c Mon Sep 17 00:00:00 2001 From: Deviant <112615715+DEViantUA@users.noreply.github.com> Date: Thu, 23 May 2024 01:27:13 +0300 Subject: [PATCH] 2.2.0 This update takes Star Rail Card to the next level!! Added support: * EnkaNetwork Build - Allows you to get all your assemblies from the EnkaNetwork website * HoYoLab API - Gives you the opportunity to get ALL your characters * Added the ability to set a custom image for each build. * Added the ability to work with all currently available APIs * The result of the work received new arguments * Added additional main class. * Added new function for getting assemblies. * Improved work with API - Now this is not only the best module for generating cards, but also an excellent wrapper for all APIs * Added a dependency on genshin.py if you want to use the HoYoLab API At the moment the code looks a little terrible and there is also a lack of documentation, but this will be fixed soon Signed-off-by: Deviant <112615715+DEViantUA@users.noreply.github.com> --- starrailcard/__init__.py | 2 +- starrailcard/client.py | 288 ++++++- starrailcard/src/api/api.py | 11 +- starrailcard/src/api/enka.py | 79 +- starrailcard/src/api/enka_parsed.py | 37 +- starrailcard/src/api/hoyolab.py | 79 ++ starrailcard/src/api/hoyolab_parsed.py | 718 ++++++++++++++++++ starrailcard/src/generator/style_card.py | 6 +- .../src/generator/style_relict_score.py | 9 +- starrailcard/src/generator/style_ticket.py | 7 +- starrailcard/src/model/StarRailCard.py | 7 +- starrailcard/src/model/api_mihomo.py | 3 +- .../tools/calculator/src/assets/score.json | 278 ++++++- starrailcard/src/tools/calculator/stats.py | 10 +- 14 files changed, 1474 insertions(+), 60 deletions(-) create mode 100644 starrailcard/src/api/hoyolab.py create mode 100644 starrailcard/src/api/hoyolab_parsed.py diff --git a/starrailcard/__init__.py b/starrailcard/__init__.py index 91a8ec6..c78d34d 100644 --- a/starrailcard/__init__.py +++ b/starrailcard/__init__.py @@ -37,7 +37,7 @@ __title__ = 'StarRailCard.py' __author__ = 'DeviantUa' -__version__ = '2.1.9' +__version__ = '2.2.0' __license__ = 'MIT' __copyright__ = 'Copyright 2024-present DeviantUa' diff --git a/starrailcard/client.py b/starrailcard/client.py index c839caf..919a231 100644 --- a/starrailcard/client.py +++ b/starrailcard/client.py @@ -3,15 +3,193 @@ import anyio from typing import Union -#import asyncio +import asyncio from .src.tools import cache, http, ukrainization, options, translator, git from .src.generator import style_relict_score, style_ticket, style_profile_phone, style_card -from .src.api import api, enka +from .src.api import api, enka, error, hoyolab from .src.model import StarRailCard,api_mihomo from .src.tools.pill import image_control +class HoYoCard: + def __init__(self, cookie: dict, lang: str = "en", character_art = None, character_id = None, seeleland: bool = False, + user_font = None, save: bool = False, asset_save: bool = False, boost_speed: bool = False, remove_logo: bool = False, + cache = {"maxsize": 150, "ttl": 300}, proxy: str = None, color: dict = None): + + self.lang = lang + self.character_art = character_art + self.character_id = character_id + self.seeleland = seeleland + self.user_font = user_font + self.save = save + self.asset_save = asset_save + self.remove_logo = remove_logo + self.cache = cache + self.boost_speed = boost_speed + self.proxy = proxy + self.color = color + self.cookie = cookie + + async def __aenter__(self): + cache.Cache.get_cache(maxsize = self.cache.get("maxsize", 150), ttl = self.cache.get("ttl", 300)) + await http.AioSession.enter(self.proxy) + + await git.ImageCache.set_assets_download(self.asset_save) + + if self.character_id: + self.character_id = await options.get_charter_id(self.character_id) + + if self.character_art: + if not isinstance(self.character_art, dict): + raise TypeError(4,"The character_art parameter must be a dictionary, where the key is the name of the character, and the parameter is an image.\nExample: character_art = {'1235': 'img.png', '1235': ['img.png','http.../img2.png']} or {'123596': 'img.png', '123854': 'http.../img2.png', ...}") + else: + self.character_art = await options.get_character_art(self.character_art) + + if not self.lang in translator.SUPPORTED_LANGUAGES: + self.lang = "en" + + if self.lang == "ua": + await ukrainization.TranslateDataManager().check_update() + + self.translateLang = translator.Translator(self.lang) + + if self.user_font: + await git.change_font(font_path = self.user_font) + + image_control._boost_speed = self.boost_speed + + + if isinstance(self.color, dict): + self.color = await options.get_color_user(self.color) + + if self.remove_logo: + print(""" + Thank you for using our StarRailCard! + By removing the GitHub logo from the generated results, you acknowledge supporting the author through one of the following links: + - Patreon: https://www.patreon.com/deviantapi/membership + - Ko-fi: https://ko-fi.com/dezzso + + Failure to contribute to the project may lead to the author discontinuing updates in the future. + """) + return self + + async def __aexit__(self, exc_type, exc, tb): + await http.AioSession.exit(exc_type, exc, tb) + + async def set_lang(self, lang): + """Sets the language + + Args: + lang (str): lang (str): Language in which generation will take place, supported languages: cht, cn, de, en, es, fr, id, jp, kr, pt, ru, th, vi, ua . Defaults to "en". + """ + if not lang in translator.SUPPORTED_LANGUAGES: + lang = "en" + self.lang = "en" + else: + self.lang = lang + + if lang == "ua": + await ukrainization.TranslateDataManager().check_update() + + self.translateLang = translator.Translator(lang) + + async def set_user_font(self, user_font: str): + """Will install a custom font + + Args: + user_font (srt): Font path or font name. + """ + await git.change_font(font_path = user_font) + + async def create(self, uid: Union[int,str], style: int = 1, hide_uid: bool = False): + """Function for generating character cards + + Args: + uid (int): UID of the user in the game + style (int, optional): Card style. Defaults to 1. + hide_uid (bool, optional): Hide UID. Defaults to False. + + Returns: + StarRail: A class object containing profile information and a character cards + """ + try: + data = await hoyolab.HoYoLabApi(uid, lang= self.lang).get(self.cookie) + except Exception as e: + print(e) + raise error.StarRailCardError(8, "To use the HoYoLab API you need to download/update the asset") + + result = [] + style, style_settings = await options.style_setting(style, None) + + try: + player = data.player.model_dump() + except: + player = data.player + + response = { + "settings": { + "uid": int(uid), + "lang": self.lang, + "hide_uid": hide_uid, + "save": self.save, + "force_update": False, + "style": int(style) + }, + "player": player, + "card": None, + "character_name": [], + "character_id": [], + } + + async with anyio.create_task_group() as tasks: + + for key in data.characters: + async def get_result(key): + try: + response["character_id"].append(key.id) + response["character_name"].append(key.name) + + if self.character_id: + if not str(key.id) in self.character_id: + return + + if self.color: + color = self.color.get(str(key.id)) + else: + color = None + + art = None + if self.character_art: + if str(key.id) in self.character_art: + art = self.character_art[str(key.id)] + if style == 1: + result.append(await style_relict_score.Create(key,self.translateLang,art,hide_uid,uid, self.seeleland,self.remove_logo, color).start(True)) + elif style == 2: + result.append(await style_ticket.Create(key,self.translateLang,art,hide_uid,uid, self.seeleland,self.remove_logo, color).start(True)) + elif style == 3: + result.append(await style_card.Create(key,self.translateLang,art,hide_uid,uid, self.seeleland,self.remove_logo, color).start(True)) + except Exception as e: + print(f"Error in get_result for character {key.id}: {e}") + + tasks.start_soon(get_result, key) + + response["card"] = result + + if self.lang == "ua": + StarRailCard.UA_LANG = True + else: + StarRailCard.UA_LANG = False + + if self.save: + async with anyio.create_task_group() as tasks: + for key in response["card"]: + if key["animation"]: + continue + tasks.start_soon(options.save_card,uid,key["card"],key["id"]) + + return StarRailCard.StarRail(**response) + class Card: def __init__(self, lang: str = "en", character_art = None, character_id = None, seeleland: bool = False, @@ -123,7 +301,7 @@ async def set_user_font(self, user_font: str): user_font (srt): Font path or font name. """ await git.change_font(font_path = user_font) - + async def create_profile(self, uid: Union[int,str], style: bool = 1, hide_uid: bool = False, background = None, force_update: bool = False): """Function for generating a user profile card @@ -185,7 +363,7 @@ async def create_profile(self, uid: Union[int,str], style: bool = 1, hide_uid: b return StarRailCard.StarRail(**response) - async def create(self, uid: Union[int,str], style: int = 1, hide_uid: bool = False, force_update: bool = False, style_settings = None, log: bool = False): + async def create(self, uid: Union[int,str], style: int = 1, hide_uid: bool = False, force_update: bool = False, style_settings = None): """Function for generating character cards Args: @@ -235,7 +413,7 @@ async def create(self, uid: Union[int,str], style: int = 1, hide_uid: bool = Fal "character_name": [], "character_id": [], } - + async with anyio.create_task_group() as tasks: for key in data.characters: @@ -282,4 +460,104 @@ async def get_result(key): continue tasks.start_soon(options.save_card,uid,key["card"],key["id"]) + return StarRailCard.StarRail(**response) + + + async def create_build(self, uid: Union[int,str], name: str, hash: str, style: int = 1, hide_uid: bool = False): + """Function for generating character cards + + Args: + uid (int): UID of the user in the game + name (str): NickName EnkaNetwork + hash (str): hash obtained using the method: get_hash() + style (int, optional): Card style. Defaults to 1. + hide_uid (bool, optional): Hide UID. Defaults to False. + style_settings (dict, optional): not implemented yet. Defaults to None. + + Returns: + StarRail: A class object containing profile information and a character cards + """ + + if self.api_data is None: + try: + data = await enka.ApiEnkaNetwork().get_build(name, hash, uid) + except Exception as e: + print(e) + raise error.StarRailCardError(8, "To use the HoYoLab API you need to download/update the asset") + else: + data = self.api_data + + result = [] + + style, style_settings = await options.style_setting(style, None) + + try: + player = data.player.model_dump() + except: + player = data.player + + response = { + "settings": { + "uid": int(uid), + "lang": self.lang, + "hide_uid": hide_uid, + "save": self.save, + "force_update": False, + "style": int(style) + }, + "player": player, + "card": None, + "character_name": [], + "character_id": [], + } + + async with anyio.create_task_group() as tasks: + + for key in data.characters: + async def get_result(key): + try: + response["character_id"].append(key.id) + response["character_name"].append(key.name) + + if self.character_id: + if not str(key.id) in self.character_id: + return + + if self.color: + color = self.color.get(str(key.id)) + else: + color = None + + art = None + if self.character_art: + if str(key.id) in self.character_art: + art = self.character_art[str(key.id)] + if str(key.build["id"]) in self.character_art: + art = self.character_art[str(key.build["id"])] + if style == 1: + result.append(await style_relict_score.Create(key,self.translateLang,art,hide_uid,uid, self.seeleland,self.remove_logo, color).start(build= key.build)) + elif style == 2: + result.append(await style_ticket.Create(key,self.translateLang,art,hide_uid,uid, self.seeleland,self.remove_logo, color).start(build= key.build)) + elif style == 3: + result.append(await style_card.Create(key,self.translateLang,art,hide_uid,uid, self.seeleland,self.remove_logo, color).start(build= key.build)) + except Exception as e: + print(f"Error in get_result for character {key.id}: {e}") + + tasks.start_soon(get_result, key) + + response["card"] = result + + if self.lang == "ua": + StarRailCard.UA_LANG = True + else: + StarRailCard.UA_LANG = False + + if self.save: + async with anyio.create_task_group() as tasks: + for key in response["card"]: + print(key) + if key["animation"]: + continue + tasks.start_soon(options.save_card,uid,key["card"],f'{key["id"]}_{key["build"]["name_build"]}') + return StarRailCard.StarRail(**response) \ No newline at end of file diff --git a/starrailcard/src/api/api.py b/starrailcard/src/api/api.py index 50b4c37..b750ac2 100644 --- a/starrailcard/src/api/api.py +++ b/starrailcard/src/api/api.py @@ -27,12 +27,8 @@ def __init__(self, uid: str, lang: str = "en", v: int = 2, force_update: bool = api_mihomo.UA_LANG = True self.v = v - - async def recollect(self, data): - - pass - async def get(self) -> Optional[api_mihomo.MiHoMoApi]: + async def get(self, parse: bool = True) -> Optional[api_mihomo.MiHoMoApi]: """Get data from the MiHoMo API.""" try: params = { @@ -72,7 +68,10 @@ async def get(self) -> Optional[api_mihomo.MiHoMoApi]: if self.ua_lang: await ukrainization.TranslateDataManager().load_translate_data() - return api_mihomo.MiHoMoApi(player=data["player"], characters=data["characters"], dont_update_link= False) + if parse: + return api_mihomo.MiHoMoApi(player=data["player"], characters=data["characters"], dont_update_link= False) + else: + return data async def add_info(self, data): info = self.dop_info["detailInfo"]["recordInfo"] diff --git a/starrailcard/src/api/enka.py b/starrailcard/src/api/enka.py index 7a97469..6ada3fa 100644 --- a/starrailcard/src/api/enka.py +++ b/starrailcard/src/api/enka.py @@ -13,6 +13,10 @@ from ..model import api_mihomo _API_ENKA = "https://enka.network/api/hsr/uid/{uid}" +_API_ENKA_HASH = "https://enka.network/api/profile/{name}/hoyos/" +_API_ENKA_BUILD = "https://enka.network/api/profile/{name}/hoyos/{hash}/builds/" + + _INDEX_MIHOMO = "https://raw.githubusercontent.com/Mar-7th/StarRailRes/master/index_new/{lang}/{index}.json" _INDEX_NAME = [ @@ -47,12 +51,79 @@ def __init__(self, uid: Union[int, str] = 0, lang: str = "en", parsed: bool = Tr self.ua_lang = True api_mihomo.UA_LANG = True - async def get(self): - """Get data from the MiHoMo API.""" + async def get_build(self, name: str, hash: str, uid: Union[int, str] = 0): + + if uid == 0: + uid = self.uid + + if uid == 0: + raise StarRailCardError(5, "Specify UID") + try: - data = await http.AioSession.get(_API_ENKA.format(uid=self.uid)) - + data_build = await http.AioSession.get(_API_ENKA_BUILD.format(name=name, hash = hash)) + data = await self.get(uid, parsed = False) + data["detailInfo"]["avatarDetailList"] = [data_build] if self.parsed: + data = await AssetEnkaParsed(data).collect(True) + else: + return data + except aiohttp.ClientConnectionError: + raise StarRailCardError(1, "Server is not responding") + except aiohttp.ClientResponseError as e: + raise StarRailCardError(e.status, f"Server returned status code {e.status}") + + if self.ua_lang: + await ukrainization.TranslateDataManager().load_translate_data() + + + return api_mihomo.MiHoMoApi(player=data["player"], characters=data["characters"], dont_update_link = False) + + + async def get_hash(self, name: str, hash: bool = True): + """Return user's HASH + Args: + name (str): Profile nickname on the EnkaNetwork website + hash (bool, optional): Return only the user's hash + Raises: + StarRailCardError: Server is not responding + Returns: + list: List of included dictionaries with hash and profile information + """ + try: + data = await http.AioSession.get(_API_ENKA_HASH.format(name=name)) + except aiohttp.ClientConnectionError: + raise StarRailCardError(1, "Server is not responding") + except aiohttp.ClientResponseError as e: + raise StarRailCardError(e.status, f"Server returned status code {e.status}") + + if hash: + return [ + { + "enka": name, + "hash": key, + "region": data[key]["region"], + "uid": data[key]["uid"], + "icon": data[key]["player_info"]["headIcon"], + "name": data[key]["player_info"]["nickname"], + "level": data[key]["player_info"]["level"] + } for key in data if data[key]["hoyo_type"] == 1 + ] + + return data + + async def get(self, uid: Union[str,int] = 0, parsed: bool = True): + """Get data from the EnkaNetwork API.""" + + if uid == 0: + uid = self.uid + + if uid == 0: + raise StarRailCardError(5, "Specify UID") + + try: + data = await http.AioSession.get(_API_ENKA.format(uid=uid)) + + if self.parsed and parsed: data = await AssetEnkaParsed(data).collect() else: return data diff --git a/starrailcard/src/api/enka_parsed.py b/starrailcard/src/api/enka_parsed.py index fd739e4..4d4a2e1 100644 --- a/starrailcard/src/api/enka_parsed.py +++ b/starrailcard/src/api/enka_parsed.py @@ -1,4 +1,3 @@ -import json import math from pathlib import Path from copy import deepcopy @@ -42,11 +41,31 @@ async def load_assets(self): self.light_cone_promotion = await JsonManager(PathData.ENKA_INDEX.value / self.lang / "light_cone_promotions.json").read() self.avatar = await JsonManager(PathData.ENKA_INDEX.value / self.lang / "avatars.json").read() - async def collect(self): + async def collect(self, build = False): await self.load_assets() + if build: + charters = [] + for key in self.data["detailInfo"]["avatarDetailList"]: + for k in key: + if isinstance(key[k], list): + for data in key[k]: + buildInfo = { + "id": data.get("id", 0), + "name_build": data.get("name", ""), + } + charters.append(await self.get_character(data["avatar_data"], buildInfo)) + else: + buildInfo = { + "id": key[k].get("id", 0), + "name_build": key[k].get("name", ""), + } + charters.append(await self.get_character(key[k], buildInfo)) + else: + charters = [await self.get_character(key) for key in self.data["detailInfo"]["avatarDetailList"]] + data = { "player": await self.get_player(), - "characters": [await self.get_character(key) for key in self.data["detailInfo"]["avatarDetailList"]] + "characters": charters } return data @@ -180,7 +199,7 @@ async def get_light_cone_attribute_from_promotion(self, id: str, promotion: int, ) return attributes - async def get_character(self, data): + async def get_character(self, data, build = {}): id = str(data.get("avatarId")) name = self.character.get(str(id)).get("name") rarity = self.character.get(str(id)).get("rarity") @@ -194,7 +213,7 @@ async def get_character(self, data): path = await self.get_path(self.character.get(str(id)).get("path")) element = await self.get_element(self.character.get(str(id)).get("element")) skills = await self.get_skill(data, str(id), self.character.get(str(id)).get("element")) - skill_trees = await self.get_skill_trees(data, str(id)) + skill_trees = await self.get_skill_trees(data, str(id), rank) light_cone = await self.get_light_cone(data.get("equipment")) if data.get("equipment") else None attributes = await self.get_attributes(str(id), promotion, level) properties = await self.get_properties(str(id), data["skillTreeList"]) @@ -240,8 +259,7 @@ async def get_character(self, data): ] ) - additions = await self.get_additions(attributes, properties) - + additions = await self.get_additions(attributes, properties) return { "id": id, @@ -265,6 +283,7 @@ async def get_character(self, data): "attributes": attributes, "additions": additions, "properties": properties, + "build": build, "pos": [data.get("pos", 0)], } @@ -366,13 +385,13 @@ async def get_properties(self, characters_id, info): ) return properties - async def get_skill_trees(self, data, character_id): + async def get_skill_trees(self, data, character_id, rank): return [{ "id": str(key["pointId"]), "level": key["level"], "anchor": self.skill_trees_info.get(str(key["pointId"]))["anchor"], "icon": self.skill_trees_info.get(str(key["pointId"]))["icon"], - "max_level": await self.get_max_level(self.character.get(character_id)["ranks"], str(key["pointId"]),key.get("rank",0)), + "max_level": await self.get_max_level(self.character.get(character_id)["ranks"], str(key["pointId"]), rank), "parent": await self.get_parent(key["pointId"]) } for key in data["skillTreeList"]] diff --git a/starrailcard/src/api/hoyolab.py b/starrailcard/src/api/hoyolab.py new file mode 100644 index 0000000..ae73508 --- /dev/null +++ b/starrailcard/src/api/hoyolab.py @@ -0,0 +1,79 @@ +import aiohttp +from typing import Optional, Union +from .api import ApiMiHoMo +from ..model import api_mihomo +from ..tools import http, translator, options, ukrainization +from .error import StarRailCardError +from .hoyolab_parsed import AssetHoYoLabParsed + +try: + import genshin + import_genshin = True +except ImportError as e: + import_genshin = False + +LANG_MAP = { + "zh-CN": "zh-cn", + "zh-TW": "zh-tw", + "de": "de-de", + "en": "en-us", + "es": "es-es", + "fr": "fr-fr", + "id": "id-id", + "it": "it-it", + "ja": "ja-jp", + "ko": "ko-kr", + "pt": "pt-pt", + "ru": "ru-ru", + "th": "th-th", + "vi": "vi-vn", + "tr": "tr", +} + +class HoYoLabApi: + """Class for interacting with the MiHoMo API.""" + + def __init__(self, uid: str = 0, lang: str = "en"): + """Initialize the HoYoLabApi object.""" + self.uid: str = uid + self.lang: str = translator.SUPPORTED_LANGUAGES.get(lang, "en") + self.ua_lang: str = lang == "en" + api_mihomo.UA_LANG = False + if lang == "ua": + self.ua_lang = True + api_mihomo.UA_LANG = True + self.lang = "en" + + self.convert_lang = LANG_MAP.get(self.lang) + + + + async def get(self,cookie: dict, uid: Union[str,int] = 0): + if not import_genshin: + raise StarRailCardError(100, "Install the genshin.py module\n- pip install genshin") + + if uid == 0: + uid = self.uid + if uid == 0: + raise StarRailCardError(5, "Specify UID") + + player = await ApiMiHoMo(uid, self.lang).get(parse= False) + + try: + client = genshin.Client(cookie, game= genshin.Game.STARRAIL, lang= self.convert_lang) + data = await client.get_starrail_characters(uid) + except Exception as e: + print(e) + raise + data = await AssetHoYoLabParsed(data, data.property_info).collect() + + + if self.ua_lang: + await ukrainization.TranslateDataManager().load_translate_data() + + return api_mihomo.MiHoMoApi(player=player["player"], characters= data, dont_update_link = False) + + + + + \ No newline at end of file diff --git a/starrailcard/src/api/hoyolab_parsed.py b/starrailcard/src/api/hoyolab_parsed.py new file mode 100644 index 0000000..4cbb020 --- /dev/null +++ b/starrailcard/src/api/hoyolab_parsed.py @@ -0,0 +1,718 @@ +import math +from pathlib import Path +from copy import deepcopy + +from ..tools.json_data import JsonManager +from ..tools.enums import PathData + +CHARACTERS_LINK_ICON = "icon/{catalog}/{character_id}.png" +CHARACTERS_LINK_IMAGE = "image/{catalog}/{character_id}.png" +ENKA_INDEX = Path(__file__).parent / "assets" / "enka_api" / "index" +ENKA = Path(__file__).parent.parent / "assets" / "enka_api" + + +convert_id = { + "39":"IceResistanceDelta", + "60":"MaxSP", + "55":"HealRatioBase", + "27":"HPAddedRatio", + "23":"QuantumResistanceDelta", + "15":"FireResistanceDelta", + "13":"PhysicalResistanceDelta", + "58":"BreakDamageAddedRatioBase", + "29":"AttackAddedRatio", + "14":"FireAddedRatio", + "19":"ThunderResistanceDelta", + "11":"StatusResistanceBase", + "28":"BaseAttack", + "16":"IceAddedRatio", + "59":"BreakDamageAddedRatioBase", + "56":"StatusProbabilityBase", + "10":"StatusProbabilityBase", + "9":"SPRatioBase", + "35":"SpeedAddedRatio", + "1":"HPAddedRatio", + "20":"WindAddedRatio", + "57":"StatusResistanceBase", + "42":"QuantumResistanceDelta", + "31":"DefenceAddedRatio", + "12":"PhysicalAddedRatio", + "33":"AttackAddedRatio", + "43":"ImaginaryResistanceDelta", + "18":"ThunderAddedRatio", + "54":"SPRatioBase", + "4":"SpeedAddedRatio", + "34":"DefenceAddedRatio", + "21":"WindResistanceDelta", + "25":"ImaginaryResistanceDelta", + "7":"HealRatioBase", + "40":"ThunderResistanceDelta", + "32":"HPAddedRatio", + "53":"CriticalDamageBase", + "41":"WindResistanceDelta", + "30":"BaseDefence", + "17":"IceResistanceDelta", + "22":"QuantumAddedRatio", + "5":"CriticalChanceBase", + "37":"PhysicalResistanceDelta", + "3":"DefenceAddedRatio", + "8":"StanceBreakAddedRatio", + "6":"CriticalDamageBase", + "38":"FireResistanceDelta", + "51":"SpeedAddedRatio", + "52":"CriticalChanceBase", + "24":"ImaginaryAddedRatio", + "2":"AttackAddedRatio", + "26":"BaseHP", +} +class AssetHoYoLabParsed: + def __init__(self, data, propery_info) -> None: + self.data = data + self.propery_info = propery_info + self.lang = "en" + + async def load_assets(self): + self.character = await JsonManager(PathData.ENKA_INDEX.value / self.lang / "characters.json").read() + self.element = await JsonManager(PathData.ENKA_INDEX.value / self.lang / "elements.json").read() + self.path = await JsonManager(PathData.ENKA_INDEX.value / self.lang / "paths.json").read() + self.character_rank = await JsonManager(PathData.ENKA_INDEX.value / self.lang / "character_ranks.json").read() + self.character_promotions = await JsonManager(PathData.ENKA_INDEX.value / self.lang / "character_promotions.json").read() + self.skill = await JsonManager(PathData.ENKA_INDEX.value / self.lang / "character_skills.json").read() + self.skill_trees_info = await JsonManager(PathData.ENKA_INDEX.value / self.lang / "character_skill_trees.json").read() + self.light_cone = await JsonManager(PathData.ENKA_INDEX.value / self.lang / "light_cones.json").read() + self.propertie = await JsonManager(PathData.ENKA_INDEX.value / self.lang / "properties.json").read() + self.light_cone_rank = await JsonManager(PathData.ENKA_INDEX.value / self.lang / "light_cone_ranks.json").read() + self.relics = await JsonManager(PathData.ENKA_INDEX.value / self.lang / "relics.json").read() + self.relics_set = await JsonManager(PathData.ENKA_INDEX.value / self.lang / "relic_sets.json").read() + self.relic_main_affixes = await JsonManager(PathData.ENKA_INDEX.value / self.lang / "relic_main_affixes.json").read() + self.relic_sub_affixes = await JsonManager(PathData.ENKA_INDEX.value / self.lang / "relic_sub_affixes.json").read() + self.relic_set = await JsonManager(PathData.ENKA_INDEX.value / self.lang / "relic_sets.json").read() + self.light_cone_promotion = await JsonManager(PathData.ENKA_INDEX.value / self.lang / "light_cone_promotions.json").read() + self.avatar = await JsonManager(PathData.ENKA_INDEX.value / self.lang / "avatars.json").read() + + + async def get_character(self, data): + #print(data) + id = str(data.id) + name = data.name + rarity = data.rarity + rank = data.rank + level = data.level + promotion = await self.get_promotion(level) + icon = self.character.get(id).get("icon") + preview = self.character.get(id).get("preview") + portrait = self.character.get(id).get("portrait") + rank_icons = [await self.get_rank_icons(key) for key in self.character.get(id).get("ranks")] + path = await self.get_path(self.character.get(id).get("path")) + element = await self.get_element(self.character.get(id).get("element")) + skills = await self.get_skill(data, id, self.character.get(id).get("element")) + skill_trees = await self.get_skill_trees(data, id, rank) + light_cone = await self.get_light_cone(data.equip) if data.equip else None + + attributes = await self.get_attributes(str(id), promotion, level) + properties = await self.get_properties(str(id), data.skills) + + relic_infos = [await self.get_relic_info(relic) for relic in data.relics + data.ornaments] + + relics = [ + relic_info for relic_info in relic_infos if relic_info is not None + ] + + relic_sets = await self.get_relic_sets_info(relics) if relics else [] + + '''attributes = await self.merge_attribute( + [ + attributes, + light_cone["attributes"] if light_cone else [], + ] + )''' + + + relic_properties = [] + for relic in relics: + if relic["main_affix"]: + relic_properties.append(relic["main_affix"]) + relic_properties += [ + { + "type": affix["type"], + "field": affix["field"], + "name": affix["name"], + "icon": affix["icon"], + "value": affix["value"], + "display": affix["display"], + "percent": affix["percent"], + } for affix in relic["sub_affix"] + ] + + for relic_set in relic_sets: + relic_properties += relic_set["properties"] + + '''properties = await self.merge_property( + [ + properties, + light_cone["properties"] if light_cone else [], + relic_properties, + ] + )''' + + #additions = await self.get_additions(attributes, properties) + attributes = await self.get_attributes_hoyolab(data.properties) + properties = await self.get_properties_hoyolab(data.properties) + additions = await self.get_additions_hoyolab(data.properties) + + return { + "id": id, + "name": name, + "rarity": rarity, + "rank": rank, + "level": level, + "promotion": promotion, + "icon": icon, + "preview": preview, + "portrait": portrait, + "rank_icons": rank_icons, + "path": path, + "element": element, + "skills": skills, + "skill_trees": skill_trees, + "light_cone": light_cone, + + "relics": relics, + "relic_sets": relic_sets, + "attributes": attributes, + "additions": additions, + "properties": properties, + "pos": [0], + } + + async def collect(self): + await self.load_assets() + return [await self.get_character(key) for key in self.data.avatar_list] + + async def get_properties(self, characters_id, info): + skill_trees = self.character[characters_id]["skill_trees"] + properties = [] + + for skill_tree in info: + pointId = str(skill_tree.point_id) + if pointId in skill_trees and pointId in self.skill_trees_info: + try: + property_list = (self.skill_trees_info[pointId]["levels"][skill_tree.level-1]["properties"]) + except: + property_list = [] + for i in property_list: + if self.propertie.get(i.get("type")) is None or i["value"] <= 0: + continue + property = self.propertie[i["type"]] + properties.append( + { + "type": i["type"], + "field": property["field"], + "name": property["name"], + "icon": property["icon"], + "value": i["value"], + "display": await self.get_display( i["value"], property["percent"]), + "percent": property["percent"], + "type": i["type"], + } + ) + return properties + + + async def get_attributes_hoyolab(self,properties): + attributes = [] + + for key in properties: + cId = convert_id.get(str(key.property_type)) + if await self.delete_procent(key.base) != 0: + attributes.append( + { + "field": self.propertie[cId]["field"], + "name": self.propertie[cId]["name"], + "icon": self.propertie[cId]["icon"], + "value": await self.delete_procent(key.base), + "display": key.base, + "percent": True if "%" in key.base else False + } + ) + + return attributes + + async def get_properties_hoyolab(self, properties): + additions = [] + + for key in properties: + cId = convert_id.get(str(key.property_type)) + + if await self.delete_procent(key.final) != 0: + additions.append( + { + "type": self.propertie[cId]["type"], + "field": self.propertie[cId]["field"], + "name": self.propertie[cId]["name"], + "icon": self.propertie[cId]["icon"], + "value": await self.delete_procent(key.final), + "display": key.final, + "percent": True if "%" in key.final else False + } + ) + + return additions + + async def get_additions_hoyolab(self, properties): + additions = [] + + for key in properties: + cId = convert_id.get(str(key.property_type)) + if await self.delete_procent(key.add) != 0: + additions.append( + {"field": self.propertie[cId]["field"], + "name": self.propertie[cId]["name"], + "icon": self.propertie[cId]["icon"], + "value": await self.delete_procent(key.add), + "display": key.add, + "percent": True if "%" in key.add else False + } + ) + + return additions + + async def get_additions(self, attributes, properties): + attribute_dict = {} + addition_dict = {} + for attribute in attributes: + if not attribute["field"] in addition_dict: + attribute_dict[attribute["field"]] = attribute["value"] + for property in properties: + if ( + self.propertie[property["type"]]["ratio"] + and property["field"] in attribute_dict + ): + value = property["value"] * attribute_dict[property["field"]] + else: + value = property["value"] + if property["field"] not in addition_dict: + addition_dict[property["field"]] = value + else: + addition_dict[property["field"]] += value + additions = [] + for k, v in addition_dict.items(): + property = None + for i in self.propertie.values(): + if i["field"] == k: + property = i + break + + if property is None: + continue + + additions.append( + { + "field": k, + "name": property["name"], + "icon": property["icon"], + "value": v, + "display": await self.get_display(v, property["percent"]), + "percent": property["percent"], + } + ) + + return additions + + async def get_attributes(self, character_id, promotion, level): + + if character_id not in self.character_promotions: + return [] + if promotion not in range(0, 6 + 1): # 0-6 + return [] + if level not in range(1, 80 + 1): # 1-80 + return [] + attributes = [] + for k, v in self.character_promotions[character_id]["values"][promotion].items(): + property = None + for i in self.propertie.values(): + if i["field"] == k: + property = i + break + if property is None: + continue + + attributes.append( + { + "field": k, + "name": property["name"], + "icon": property["icon"], + "value": v["base"] + v["step"] * (level - 1), + "display": await self.get_display(v["base"] + v["step"] * (level - 1), property["percent"]), + "percent": property["percent"] + } + ) + return attributes + + async def get_skill_trees(self, data, character_id, rank): + return [{ + "id": str(key.point_id), + "level": key.level if key.is_activated else 0, + "anchor": self.skill_trees_info.get(str(key.point_id))["anchor"], + "icon": self.skill_trees_info.get(str(key.point_id))["icon"], + "max_level": await self.get_max_level(self.character.get(character_id)["ranks"], str(key.point_id)), + "parent": await self.get_parent(key.pre_point) + } for key in data.skills] + + async def get_skill(self, data, character_data, element): + + character_data = self.character.get(character_data) + + response= [] + + for keys in data.skills: + pointId = str(keys.point_id) + if self.skill_trees_info.get(pointId)["level_up_skills"] == []: + continue + + skills_info_id = str(self.skill_trees_info.get(pointId)["level_up_skills"][0]["id"]) + + if not str(skills_info_id) in character_data["skills"]: + continue + + info = self.skill.get(skills_info_id) + + max_level = self.skill_trees_info.get(pointId)["max_level"] + if keys.level > max_level: + max_level = info["max_level"] + + response.append( + { + "id": skills_info_id, + "name": info["name"], + "level": keys.level, + "max_level": max_level, + "element": await self.get_element(element), + "type": info["type"], + "type_text": info["type_text"], + "effect": info["effect"], + "effect_text": info["effect_text"], + "simple_desc": info["simple_desc"], + "desc": info["desc"], + "icon": info["icon"], + } + ) + + return response + + + async def get_relic_info(self, data): + if str(data.id) not in self.relics: + return None + + relics_info = self.relics.get(str(data.id)) + + info = { + "id": str(data.id), + "name": relics_info["name"], + "set_id": str(relics_info["set_id"]), + "set_name": self.relics_set[relics_info["set_id"]]["name"], + "rarity": relics_info["rarity"], + "level": data.level, + "icon": relics_info['icon'], + "main_affix": await self.get_relic_main_affix(str(data.id), data.main_property.value, str(data.main_property.property_type)), + "sub_affix": await self.get_relic_sub_affix(str(data.id), data.properties) + } + + return info + + async def get_relic_sub_affix(self, relict_id, sub_affix_info): + if relict_id not in self.relics: + return [] + + sub_affix_group = self.relics[relict_id]["sub_affix_id"] + if sub_affix_group not in self.relic_sub_affixes: + return [] + + properties = [] + for sub_affix in sub_affix_info: + cId = convert_id.get(str(sub_affix.property_type)) + + property = self.propertie[cId] + + properties.append( + { + "type": property["type"], + "field": property["field"], + "name": property["name"], + "icon": property["icon"], + "value": await self.delete_procent(sub_affix.value), + "display": sub_affix.value, + "percent": True if "%" in sub_affix.value else False, + "count": sub_affix.times, + "step": 0 + } + ) + return properties + + async def get_relic_main_affix(self, relict_id, value, main_affix_id): + if str(relict_id) not in self.relics: + return None + if not main_affix_id: + return None + + cId = convert_id.get(main_affix_id) + property = self.propertie[cId] + + return { + "type": property["type"], + "field": property["field"], + "name": self.propery_info.get(main_affix_id).name, + "icon": property["icon"], + "value": await self.delete_procent(value), + "display": value, + "percent": True if "%" in value else False + } + + + async def get_relic_sets_info(self, data): + set_num = {} + for relic in data: + if relic["set_id"] not in set_num: + set_num[relic["set_id"]] = 1 + else: + set_num[relic["set_id"]] += 1 + + relic_sets = [] + for k, v in set_num.items(): + info = self.relic_set[str(k)] + if v >= 2: + prop = [{ + "type": i["type"], + "field": self.propertie.get(i["type"])["field"], + "name": self.propertie.get(i["type"])["name"], + "icon": self.propertie.get(i["type"])["icon"], + "value": i["value"], + "display": await self.get_display(i["value"], self.propertie.get(i["type"])["percent"]), + "percent": self.propertie.get(i["type"])["percent"], + } for i in info["properties"][0]] + + relic_sets.append( + { + "id": k, + "name": info["name"], + "icon": info["icon"], + "num": 2, + "desc": info["desc"][0], + "properties": prop, + } + ) + if v >= 4: + prop = [ + { + "type": i["type"], + "field": self.propertie.get(i["type"])["field"], + "name": self.propertie.get(i["type"])["name"], + "icon": self.propertie.get(i["type"])["icon"], + "value": i["value"], + "display": await self.get_display(i["value"], self.propertie.get(i["type"])["percent"]), + "percent": self.propertie.get(i["type"])["percent"], + } for i in info["properties"][1] if len(info["properties"]) > 1 + ] + + relic_sets.append( + { + "id": k, + "name": info["name"], + "icon": info["icon"], + "num": 4, + "desc": info["desc"][1] if len(info["desc"]) > 1 else "", + "properties": prop, + } + ) + return relic_sets + + + async def get_light_cone(self, data): + info = self.light_cone.get(str(data.id)) + + id = str(data.id) + name = info.get("name") + rarity = info.get("rarity") + rank = data.rank + level = data.level + promotion = await self.get_promotion(level) + icon = info.get("icon") + preview = info.get("preview") + portrait = info.get("portrait") + path = await self.get_path(info.get("path")) + + attributes = await self.get_light_cone_attribute_from_promotion(id, promotion, level) + properties = await self.get_light_cone_property_from_rank(id, rank) + + return { + "id": id, + "name": name, + "rarity": rarity, + "rank": rank, + "level": level, + "promotion": promotion, + "icon": icon, + "preview": preview, + "portrait": portrait, + "path": path, + "attributes": attributes, + "properties": properties, + } + + async def get_light_cone_property_from_rank(self, id: str, rank: int): + if id not in self.light_cone_rank: + return [] + if rank not in range(1, 5 + 1): + return [] + properties = [] + + for i in self.light_cone_rank[id]["properties"][rank - 1]: + if i["type"] not in self.propertie: + continue + property = self.propertie[i["type"]] + properties.append( + { + "type": i["type"], + "field": property["field"], + "name": property["name"], + "icon": property["icon"], + "value": i["value"], + "display": await self.get_display(i["value"], property["percent"]), + "percent": property["percent"], + } + ) + return properties + + async def get_light_cone_attribute_from_promotion(self, id: str, promotion: int, level: int): + if id not in self.light_cone_promotion: + return [] + if promotion not in range(0, 6 + 1): # 0-6 + return [] + if level not in range(1, 80 + 1): # 1-80 + return [] + attributes = [] + for k, v in self.light_cone_promotion[id]["values"][promotion].items(): + property = None + for i in self.propertie.values(): + if i["field"] == k: + property = i + break + if property is None: + continue + attributes.append( + { + "field": k, + "name": property["name"], + "icon": property["icon"], + "value": v["base"] + v["step"] * (level - 1), + "display": await self.get_display(v["base"] + v["step"] * (level - 1), property["percent"]), + "percent": property["percent"], + } + ) + return attributes + + async def merge_property(self, properties): + property_dict = {} + for property_list in properties: + for property in property_list: + if isinstance(property, list): + if len(property) == 0: + continue + property = property[0] + + if property["type"] not in property_dict: + property_dict[property["type"]] = {} + property_dict[property["type"]]["value"] = property["value"] + property_dict[property["type"]]["origin"] = deepcopy(property) + else: + property_dict[property["type"]]["value"] += property["value"] + property_res = [] + + for v in property_dict.values(): + + info = v["origin"] + info["value"] = v["value"] + info["display"] = await self.get_display(v["value"], info["percent"]) + + property_res.append(info) + return property_res + + async def merge_attribute(self, attributes): + attribute_dict = {} + for attribute_list in attributes: + for attribute in attribute_list: + if attribute["field"] not in attribute_dict: + attribute_dict[attribute["field"]] = {} + attribute_dict[attribute["field"]]["value"] = attribute["value"] + attribute_dict[attribute["field"]]["origin"] = deepcopy(attribute) + else: + attribute_dict[attribute["field"]]["value"] += attribute["value"] + attribute_res = [] + for v in attribute_dict.values(): + attribute_info = v["origin"] + attribute_info["value"] = v["value"] + attribute_info["display"] = await self.get_display( + v["value"], attribute_info["percent"] + ) + attribute_res.append(attribute_info) + + return attribute_res + + async def get_rank_icons(self, rank_id): + return self.character_rank.get(rank_id).get("icon") + + async def get_parent(self, skill_id): + if skill_id == "0": + return None + + parent = self.skill_trees_info.get(str(skill_id)).get("pre_points") + if parent == []: + return None + else: + return parent[0] + + async def get_max_level(self, ranks_list,skill_id, rank = 0): + + skill_trees_info = self.skill_trees_info.get(skill_id) + + max_level = skill_trees_info["max_level"] + if skill_trees_info.get("level_up_skills", []) != []: + if int(skill_trees_info.get("level_up_skills")[0]["id"]) in ranks_list[:rank]: + max_level = self.skill[skill_trees_info.get("level_up_skills")[0]["id"]]["max_level"] + + return max_level + + async def get_path(self, path): + return {"id": self.path.get(path)["id"], "name": self.path.get(path)["name"], "icon": self.path.get(path)["icon"]} + + async def get_element(self, element): + return {"id": self.element.get(element)["id"], "name": self.element.get(element)["name"], "color": self.element.get(element)["color"], "icon": self.element.get(element)["icon"]} + + async def get_promotion(self, level): + if 0 < level <= 20: + return 0 + elif 20 < level <= 30: + return 1 + elif 30 < level <= 40: + return 2 + elif 40 < level <= 50: + return 3 + elif 50 < level <= 60: + return 4 + elif 60 < level <= 70: + return 5 + elif 70 < level <= 80: + return 6 + + async def get_display(self, value, percent): + if percent: + return format(math.floor(value * 1000) / 10.0, ".1f") + "%" + else: + return f"{math.floor(value)}" + + async def delete_procent(self, value): + if "%" in value: + value_in_percent = float(value.strip('%')) + return value_in_percent / 100 + return int(value) \ No newline at end of file diff --git a/starrailcard/src/generator/style_card.py b/starrailcard/src/generator/style_card.py index f31d319..0c3c62f 100644 --- a/starrailcard/src/generator/style_card.py +++ b/starrailcard/src/generator/style_card.py @@ -191,7 +191,7 @@ async def create_stats(self): position_line += x async def get_score(self): - self.score_info = await stats.Calculator(self.data).start() + self.score_info = await stats.Calculator(self.data).start(self.hoyo) async def create_relict(self,relict): background_main = Image.new(Card.RGBA,Card.relict_size, (0,0,0,0)) @@ -468,8 +468,9 @@ async def create_name(self): starts = await options.get_stars(self.data.rarity) self.background_name.alpha_composite(starts, Card.position_name_star) - async def start(self): + async def start(self, build = None, hoyo = False ): _of.set_mapping(4) + self.hoyo = hoyo if self.art: if "gif" in self.art: @@ -536,6 +537,7 @@ async def process(func, i): "card": self.background, "size": Card.background_size, "color": self.element_color, + "build": build } return data \ No newline at end of file diff --git a/starrailcard/src/generator/style_relict_score.py b/starrailcard/src/generator/style_relict_score.py index 3c50569..3a9263e 100644 --- a/starrailcard/src/generator/style_relict_score.py +++ b/starrailcard/src/generator/style_relict_score.py @@ -334,7 +334,7 @@ async def get_path(self): self.background_path = await pill.create_path(self.data) async def get_score(self): - self.score_info = await stats.Calculator(self.data).start() + self.score_info = await stats.Calculator(self.data).start(self.hoyo) async def build_relict(self): self.background_relict = Image.new(RelictScore.RGBA, (1131,297), (0,0,0,0)) @@ -438,9 +438,9 @@ async def build(self): else: self.background.alpha_composite(bg) - async def start(self): + async def start(self, build = None, hoyo = False): _of.set_mapping(1) - + self.hoyo = hoyo if self.art: if "gif" in self.art: self.gif = True @@ -508,7 +508,8 @@ async def process(func, i): "rarity": self.data.rarity, "card": self.background, "size": RelictScore.background_size, - "color": self.element_color + "color": self.element_color, + "build": build } return data \ No newline at end of file diff --git a/starrailcard/src/generator/style_ticket.py b/starrailcard/src/generator/style_ticket.py index 2f00983..2820124 100644 --- a/starrailcard/src/generator/style_ticket.py +++ b/starrailcard/src/generator/style_ticket.py @@ -318,7 +318,7 @@ async def create_relict_sets(self): self.background_sets.alpha_composite(key["line"],(0,45)) async def get_score(self): - self.score_info = await stats.Calculator(self.data).start() + self.score_info = await stats.Calculator(self.data).start(self.hoyo) async def create_stats(self): self.background_stats = Image.new(Ticket.RGBA, (434, 692), (0, 0, 0, 0)) @@ -601,9 +601,9 @@ async def build(self): else: self.background.alpha_composite(bg) - async def start(self): + async def start(self, build = None, hoyo = False): _of.set_mapping(2) - + self.hoyo = hoyo if self.art: if "gif" in self.art: self.gif = True @@ -674,6 +674,7 @@ async def process(func, i): "card": self.background, "size": Ticket.background_size, "color": self.element_color, + "build": build } return data \ No newline at end of file diff --git a/starrailcard/src/model/StarRailCard.py b/starrailcard/src/model/StarRailCard.py index 1166bd7..8e0fcaf 100644 --- a/starrailcard/src/model/StarRailCard.py +++ b/starrailcard/src/model/StarRailCard.py @@ -34,6 +34,10 @@ def __init__(self, *args, **kwargs): if UA_LANG: self.name = TranslateDataManager._data.avatar.get(self.id, self.name) +class BuildSetting(BaseModel): + id: Optional[Union[str,int]] + name_build: Optional[str] + class Card(BaseModel): id: int name: Optional[str] @@ -42,6 +46,7 @@ class Card(BaseModel): animation: bool size: Optional[tuple] color: Optional[tuple] + build: Optional[BuildSetting] class Config: arbitrary_types_allowed = True @@ -89,7 +94,7 @@ class Setting(BaseModel): lang: Optional[str] hide_uid: bool save: bool - force_update: bool + force_update: Optional[bool] style: int class StarRail(BaseModel): diff --git a/starrailcard/src/model/api_mihomo.py b/starrailcard/src/model/api_mihomo.py index 643e632..309dda2 100644 --- a/starrailcard/src/model/api_mihomo.py +++ b/starrailcard/src/model/api_mihomo.py @@ -276,7 +276,7 @@ def __init__(self, *args, **kwargs): if UA_LANG: self.name = TranslateDataManager._data.stats.get(self.field, self.name) - + class Character(BaseModel): id: Optional[str] name: Optional[str] @@ -298,6 +298,7 @@ class Character(BaseModel): attributes: List[CharacterAttributes] additions: List[CharacterAttributes] properties: Optional[List[CharacterProperties]] + build: Optional[dict] = Field(None) pos: list def __init__(self, *args, **kwargs): diff --git a/starrailcard/src/tools/calculator/src/assets/score.json b/starrailcard/src/tools/calculator/src/assets/score.json index d8d5a03..c63d45f 100644 --- a/starrailcard/src/tools/calculator/src/assets/score.json +++ b/starrailcard/src/tools/calculator/src/assets/score.json @@ -2368,22 +2368,22 @@ "AttackDelta": 1 }, "3": { - "HPAddedRatio": 1, + "HPAddedRatio": 0, "AttackAddedRatio": 0, "DefenceAddedRatio": 1, - "CriticalChanceBase": 0, - "CriticalDamageBase": 0, + "CriticalChanceBase": 0.8, + "CriticalDamageBase": 1, "HealRatioBase": 0, "StatusProbabilityBase": 0 }, "4": { - "HPAddedRatio": 0.8, + "HPAddedRatio": 0, "AttackAddedRatio": 0, - "DefenceAddedRatio": 0.8, - "SpeedDelta": 1 + "DefenceAddedRatio": 1, + "SpeedDelta": 0.8 }, "5": { - "HPAddedRatio": 1, + "HPAddedRatio": 0, "AttackAddedRatio": 0, "DefenceAddedRatio": 1, "PhysicalAddedRatio": 0, @@ -2392,29 +2392,29 @@ "ThunderAddedRatio": 0, "WindAddedRatio": 0, "QuantumAddedRatio": 0, - "ImaginaryAddedRatio": 0 + "ImaginaryAddedRatio": 0.8 }, "6": { - "BreakDamageAddedRatioBase": 1, - "SPRatioBase": 1, - "HPAddedRatio": 0.8, + "BreakDamageAddedRatioBase": 0, + "SPRatioBase": 0, + "HPAddedRatio": 0, "AttackAddedRatio": 0, - "DefenceAddedRatio": 0.8 + "DefenceAddedRatio": 1 } }, "weight": { - "HPDelta": 0.3, + "HPDelta": 0, "AttackDelta": 0, - "DefenceDelta": 0.3, - "HPAddedRatio": 0.8, + "DefenceDelta": 0.5, + "HPAddedRatio": 0, "AttackAddedRatio": 0, - "DefenceAddedRatio": 0.8, - "SpeedDelta": 1, - "CriticalChanceBase": 0, - "CriticalDamageBase": 0, + "DefenceAddedRatio": 1, + "SpeedDelta": 0.8, + "CriticalChanceBase": 0.8, + "CriticalDamageBase": 1, "StatusProbabilityBase": 0, - "StatusResistanceBase": 0.5, - "BreakDamageAddedRatioBase": 1 + "StatusResistanceBase": 0, + "BreakDamageAddedRatioBase": 0 }, "max": 10.0 }, @@ -2654,6 +2654,65 @@ }, "max": 0.0 }, + "1309": { + "main": { + "1": { + "HPDelta": 1 + }, + "2": { + "AttackDelta": 1 + }, + "3": { + "HPAddedRatio": 0.8, + "AttackAddedRatio": 1, + "DefenceAddedRatio": 0.9, + "CriticalChanceBase": 0, + "CriticalDamageBase": 0, + "HealRatioBase": 0, + "StatusProbabilityBase": 0 + }, + "4": { + "HPAddedRatio": 0.8, + "AttackAddedRatio": 1, + "DefenceAddedRatio": 0.9, + "SpeedDelta": 0.9 + }, + "5": { + "HPAddedRatio": 0.8, + "AttackAddedRatio": 1, + "DefenceAddedRatio": 0.9, + "PhysicalAddedRatio": 1, + "FireAddedRatio": 0, + "IceAddedRatio": 0, + "ThunderAddedRatio": 0, + "WindAddedRatio": 0, + "QuantumAddedRatio": 0, + "ImaginaryAddedRatio": 0 + }, + "6": { + "BreakDamageAddedRatioBase": 0, + "SPRatioBase": 1, + "HPAddedRatio": 0.8, + "AttackAddedRatio": 1, + "DefenceAddedRatio": 0.9 + } + }, + "weight": { + "HPDelta": 0.4, + "AttackDelta": 0.6, + "DefenceDelta": 0.5, + "HPAddedRatio": 0.8, + "AttackAddedRatio": 1, + "DefenceAddedRatio": 0.9, + "SpeedDelta": 0.9, + "CriticalChanceBase": 0, + "CriticalDamageBase": 0, + "StatusProbabilityBase": 0, + "StatusResistanceBase": 0.8, + "BreakDamageAddedRatioBase": 0 + }, + "max": 0.0 + }, "1312": { "main": { "1": { @@ -2713,6 +2772,65 @@ }, "max": 0.0 }, + "1315": { + "main": { + "1": { + "HPDelta": 1 + }, + "2": { + "AttackDelta": 1 + }, + "3": { + "HPAddedRatio": 0, + "AttackAddedRatio": 0.8, + "DefenceAddedRatio": 0, + "CriticalChanceBase": 1, + "CriticalDamageBase": 1, + "HealRatioBase": 0, + "StatusProbabilityBase": 0.6 + }, + "4": { + "HPAddedRatio": 0, + "AttackAddedRatio": 1, + "DefenceAddedRatio": 0, + "SpeedDelta": 1 + }, + "5": { + "HPAddedRatio": 0, + "AttackAddedRatio": 1, + "DefenceAddedRatio": 0, + "PhysicalAddedRatio": 0, + "FireAddedRatio": 0, + "IceAddedRatio": 1, + "ThunderAddedRatio": 0, + "WindAddedRatio": 0, + "QuantumAddedRatio": 0, + "ImaginaryAddedRatio": 0 + }, + "6": { + "BreakDamageAddedRatioBase": 0, + "SPRatioBase": 0, + "HPAddedRatio": 0, + "AttackAddedRatio": 1, + "DefenceAddedRatio": 0 + } + }, + "weight": { + "HPDelta": 0, + "AttackDelta": 0.3, + "DefenceDelta": 0, + "HPAddedRatio": 0, + "AttackAddedRatio": 0.8, + "DefenceAddedRatio": 0, + "SpeedDelta": 0.8, + "CriticalChanceBase": 1, + "CriticalDamageBase": 1, + "StatusProbabilityBase": 0.6, + "StatusResistanceBase": 0, + "BreakDamageAddedRatioBase": 0 + }, + "max": 0.0 + }, "8001": { "main": { "1": { @@ -2948,5 +3066,123 @@ "BreakDamageAddedRatioBase": 0 }, "max": 9.8 + }, + "8005": { + "main": { + "1": { + "HPDelta": 1 + }, + "2": { + "AttackDelta": 1 + }, + "3": { + "HPAddedRatio": 1, + "AttackAddedRatio": 0.8, + "DefenceAddedRatio": 1, + "CriticalChanceBase": 0, + "CriticalDamageBase": 0, + "HealRatioBase": 0, + "StatusProbabilityBase": 0 + }, + "4": { + "HPAddedRatio": 0, + "AttackAddedRatio": 0.8, + "DefenceAddedRatio": 0, + "SpeedDelta": 1 + }, + "5": { + "HPAddedRatio": 1, + "AttackAddedRatio": 0.8, + "DefenceAddedRatio": 1, + "PhysicalAddedRatio": 0, + "FireAddedRatio": 0, + "IceAddedRatio": 0, + "ThunderAddedRatio": 0, + "WindAddedRatio": 0, + "QuantumAddedRatio": 0, + "ImaginaryAddedRatio": 0.8 + }, + "6": { + "BreakDamageAddedRatioBase": 1, + "SPRatioBase": 1, + "HPAddedRatio": 0, + "AttackAddedRatio": 0.8, + "DefenceAddedRatio": 0 + } + }, + "weight": { + "HPDelta": 0.3, + "AttackDelta": 0.4, + "DefenceDelta": 0.3, + "HPAddedRatio": 0.7, + "AttackAddedRatio": 0.8, + "DefenceAddedRatio": 0.7, + "SpeedDelta": 1, + "CriticalChanceBase": 0.7, + "CriticalDamageBase": 0.7, + "StatusProbabilityBase": 0.7, + "StatusResistanceBase": 0.7, + "BreakDamageAddedRatioBase": 0.9 + }, + "max": 9.8 + }, + "8006": { + "main": { + "1": { + "HPDelta": 1 + }, + "2": { + "AttackDelta": 1 + }, + "3": { + "HPAddedRatio": 1, + "AttackAddedRatio": 0.8, + "DefenceAddedRatio": 1, + "CriticalChanceBase": 0, + "CriticalDamageBase": 0, + "HealRatioBase": 0, + "StatusProbabilityBase": 0 + }, + "4": { + "HPAddedRatio": 0, + "AttackAddedRatio": 0.8, + "DefenceAddedRatio": 0, + "SpeedDelta": 1 + }, + "5": { + "HPAddedRatio": 1, + "AttackAddedRatio": 0.8, + "DefenceAddedRatio": 1, + "PhysicalAddedRatio": 0, + "FireAddedRatio": 0, + "IceAddedRatio": 0, + "ThunderAddedRatio": 0, + "WindAddedRatio": 0, + "QuantumAddedRatio": 0, + "ImaginaryAddedRatio": 0.8 + }, + "6": { + "BreakDamageAddedRatioBase": 1, + "SPRatioBase": 1, + "HPAddedRatio": 0, + "AttackAddedRatio": 0.8, + "DefenceAddedRatio": 0 + } + }, + "weight": { + "HPDelta": 0.3, + "AttackDelta": 0.4, + "DefenceDelta": 0.3, + "HPAddedRatio": 0.7, + "AttackAddedRatio": 0.8, + "DefenceAddedRatio": 0.7, + "SpeedDelta": 1, + "CriticalChanceBase": 0.7, + "CriticalDamageBase": 0.7, + "StatusProbabilityBase": 0.7, + "StatusResistanceBase": 0.7, + "BreakDamageAddedRatioBase": 0.9 + }, + "max": 9.8 } } \ No newline at end of file diff --git a/starrailcard/src/tools/calculator/stats.py b/starrailcard/src/tools/calculator/stats.py index 911ccc2..c29c957 100644 --- a/starrailcard/src/tools/calculator/stats.py +++ b/starrailcard/src/tools/calculator/stats.py @@ -83,8 +83,10 @@ def get_rolls(self,rarity, stats): async def get_relic_score(self, chara_id, relic_json): result_json = {} - - main_weight = self.score[chara_id]["main"][self.relict_id.get(relic_json.id, relic_json.id)[-1]][relic_json.main_affix.type] + try: + main_weight = self.score[chara_id]["main"][self.relict_id.get(relic_json.id, relic_json.id)[-1]][relic_json.main_affix.type] + except: + main_weight = 0 main_affix_score = (relic_json.level + 1) / 16 * main_weight result_json["main_formula"] = f'{round((relic_json.level + 1) / 16 * 100, 1)}×{main_weight}={main_affix_score * 100}' @@ -101,7 +103,9 @@ async def get_relic_score(self, chara_id, relic_json): return result_json,dont_sub - async def start(self): + async def start(self, hoyo): + if hoyo: + return self.result if not self.data.id in self.score: await self.update_score(self.data.id)