From 8259775837ff753d848ea0156dd5579b871a108b Mon Sep 17 00:00:00 2001 From: Karako Date: Tue, 2 May 2023 18:20:27 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=A7=20build=20skill=20parser?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model/avatar/_avatar.py | 56 +++++++++++---- model/avatar/_talente.py | 23 +++++- model/other.py | 8 +++ scripts/avatar.py | 146 ++++++++++++++++++++++++++++++++++++++- 4 files changed, 216 insertions(+), 17 deletions(-) create mode 100644 model/other.py diff --git a/model/avatar/_avatar.py b/model/avatar/_avatar.py index afa7d59..1197d2c 100644 --- a/model/avatar/_avatar.py +++ b/model/avatar/_avatar.py @@ -1,10 +1,10 @@ from typing import TYPE_CHECKING from model.enums import Association, AvatarQuality, Element, WeaponType +from model.other import ItemCount from utils.model import BaseModel if TYPE_CHECKING: - from model.item import Item from model.avatar._talente import AvatarTalents __all__ = ( @@ -16,7 +16,7 @@ __all__ = ( "AvatarStories", "Story", "Seuyu", - "ItemCount", + "AvatarAttribute", ) @@ -102,16 +102,7 @@ class AvatarInfo(BaseModel): """故事""" -class ItemCount(BaseModel): - item: "Item" - """物品""" - count: int - """数量""" - - class AvatarPromote(BaseModel): - required_level: int = 0 - """突破所需等级""" promote_level: int = 0 """突破等级""" max_level: int @@ -119,7 +110,7 @@ class AvatarPromote(BaseModel): coin: int = 0 """摩拉""" - items: list[ItemCount] = [] + cost_items: list[ItemCount] = [] """突破所需材料""" @@ -134,6 +125,41 @@ class AvatarConstellation(BaseModel): """命座图标""" +class AvatarAttribute(BaseModel): + """角色属性""" + + HP: float + """生命值""" + Attack: float + """攻击力""" + Defense: float + """防御力""" + Critical: float + """暴击率""" + CriticalDamage: float + """暴击伤害""" + ChargeEfficiency: float + """元素充能效率""" + ElementalMastery: float = 0 + """元素精通""" + PhysicalAddHurt: float = 0 + """物理伤害加成""" + PyroAddHurt: float = 0 + """火元素伤害加成""" + HydroAddHurt: float = 0 + """水元素伤害加成""" + AnemoAddHurt: float = 0 + """风元素伤害加成""" + ElectroAddHurt: float = 0 + """雷元素伤害加成""" + DendroAddHurt: float = 0 + """草元素伤害加成""" + CryoAddHurt: float = 0 + """冰元素伤害加成""" + GeoAddHurt: float = 0 + """岩元素伤害加成""" + + class Avatar(BaseModel): """角色""" @@ -149,9 +175,11 @@ class Avatar(BaseModel): """武器类型""" information: AvatarInfo """角色信息""" - promote: AvatarPromote - """角色突破数据""" + attributes: AvatarAttribute + """角色基础属性""" talents: "AvatarTalents" """角色天赋信息""" + promotes: list[AvatarPromote] + """角色突破数据""" constellations: list[AvatarConstellation] """角色命座信息""" diff --git a/model/avatar/_talente.py b/model/avatar/_talente.py index a5b89df..d1153d1 100644 --- a/model/avatar/_talente.py +++ b/model/avatar/_talente.py @@ -1,6 +1,8 @@ -from utils.model import BaseModel from typing import ClassVar +from model.other import ItemCount +from utils.model import BaseModel + __all__ = ( "Talent", "CombatTalent", @@ -15,9 +17,26 @@ __all__ = ( "UtilityPassive", "MiscellaneousPassive", "AvatarTalents", + "TalentAttribute", ) +class TalentAttribute(BaseModel): + level: int + """等级""" + param_descriptions: list[str] + """参数描述""" + param_list: list[float] + """参数列表""" + + break_level: int = 0 + """所需突破等级""" + coin: int = 0 + """摩拉""" + cost_items: list[ItemCount] = [] + """消耗物品""" + + class Talent(BaseModel): name: str """天赋名称""" @@ -28,7 +47,7 @@ class Talent(BaseModel): level: int = 0 """解锁等级""" - params: list[float] = [] + attributes: list[TalentAttribute] """数值参数列表""" diff --git a/model/other.py b/model/other.py new file mode 100644 index 0000000..ef2d8e6 --- /dev/null +++ b/model/other.py @@ -0,0 +1,8 @@ +from utils.model import BaseModel + + +class ItemCount(BaseModel): + item_id: int + """物品ID""" + count: int + """数量""" diff --git a/scripts/avatar.py b/scripts/avatar.py index e921d5d..546b5cc 100644 --- a/scripts/avatar.py +++ b/scripts/avatar.py @@ -1,11 +1,34 @@ from itertools import chain +from typing import TypeVar -from model.avatar import AvatarBirth, AvatarInfo, AvatarStories, Seuyu, Story +from model.avatar import ( + AlternateSprint, + AvatarAttribute, + AvatarBirth, + AvatarInfo, + AvatarPromote, + AvatarStories, + ElementalBurst, + ElementalSkill, + NormalAttack, + Seuyu, + Story, + Talent, + TalentAttribute, +) from model.enums import Association, AvatarQuality, Element, WeaponType +from model.other import ItemCount from utils.const import PROJECT_ROOT from utils.manager import ResourceManager from utils.typedefs import Lang +try: + import regex as re +except ImportError: + import re + +T = TypeVar("T", bound=Talent) + OUT_DIR = PROJECT_ROOT.joinpath("out") elements = { @@ -29,6 +52,11 @@ async def parse_avatar_data(lang: Lang): avatar_json_data = manager.fetch("AvatarExcelConfigData") fetter_info_json_data = manager.fetch("FetterInfoExcelConfigData") story_json_data = manager.fetch("FetterStoryExcelConfigData") + promote_json_data = manager.fetch("AvatarPromoteExcelConfigData") + + skill_depot_json_data = manager.fetch("AvatarSkillDepotExcelConfigData") + skill_json_data = manager.fetch("AvatarSkillExcelConfigData") + proud_skill_json_data = manager.fetch("ProudSkillExcelConfigData") avatar_list = [] for data in avatar_json_data: @@ -51,6 +79,7 @@ async def parse_avatar_data(lang: Lang): filter(lambda x: x in data["weaponType"], WeaponType.__members__.values()) ) + # 角色信息 title = manager.get_text(info_data["avatarTitleTextMapHash"]) birth = AvatarBirth( month=info_data["infoBirthMonth"], day=info_data["infoBirthDay"] @@ -113,4 +142,119 @@ async def parse_avatar_data(lang: Lang): stories=stories, ) + # 角色基础属性 + attribute = AvatarAttribute( + HP=data["hpBase"], + Attack=data["attackBase"], + Defense=data["defenseBase"], + Critical=data["critical"], + CriticalDamage=data["criticalHurt"], + ChargeEfficiency=data["chargeEfficiency"], + ) + + def get_skill_attributes(proud_skill_group_id: int) -> list[TalentAttribute]: + proud_skill_datas = sorted( + filter( + lambda x: x["proudSkillGroupId"] == proud_skill_group_id, + proud_skill_json_data, + ), + key=lambda x: x["level"], + ) + result: list[TalentAttribute] = [] + for proud_skill_data in proud_skill_datas: + param_descriptions = list( + filter( + lambda x: x is not None, + map( + lambda x: manager.get_text(x), + proud_skill_data["paramDescList"], + ), + ) + ) + param_num = len(param_descriptions) + for param_description in param_descriptions: + param_num = max( + param_num, + *map(int, re.findall(r"param(\d*)\:", param_description)), + ) + result.append( + TalentAttribute( + level=proud_skill_data["level"], + param_descriptions=param_descriptions, + param_list=proud_skill_data["paramList"][:param_num], + break_level=proud_skill_data.get("breakLevel", 0), + coin=proud_skill_data.get("coinCost", 0), + cost_items=list( + map( + lambda x: ItemCount( + item_id=x["id"], + count=x["count"], + ), + filter(lambda x: x, proud_skill_data["costItems"]), + ) + ), + ) + ) + return result + + def parse_skill(skill_id: int, skill_cls: type[T]) -> T: + skill_data = next(filter(lambda x: x["id"] == skill_id, skill_json_data)) + _name = manager.get_text(skill_data["nameTextMapHash"]) + _description = manager.get_text(skill_data["descTextMapHash"]) + icon = skill_data["skillIcon"] + cooldown = ( + skill_data.get("cdTime", 0) + if "cooldown" in skill_cls.__fields__.keys() + else None + ) + attributes = get_skill_attributes(skill_data["proudSkillGroupId"]) + return skill_cls( + **{ + i[0]: i[1] + for i in zip( + ["name", "description", "icon", "cooldown", "attributes"], + [_name, _description, icon, cooldown, attributes], + ) + if i is not None + } + ) + + # 天赋 + skill_depot_data = next( + filter(lambda x: x["id"] == data["skillDepotId"], skill_depot_json_data) + ) + skill_ids = list(filter(lambda x: x != 0, skill_depot_data["skills"])) + # 普通攻击 + normal_attack = parse_skill(skill_ids[0], NormalAttack) + # 元素战技 + elemental_skill = parse_skill(skill_ids[1], ElementalSkill) + # 冲刺技能 + if len(skill_ids) == 3: + alternate_sprint = parse_skill(skill_ids[2], AlternateSprint) + # 元素爆发 + burst_skill = parse_skill(skill_depot_data["energySkill"], ElementalBurst) + breakpoint() + + # 角色突破数据 + promote_id = data["avatarPromoteId"] + promote_datas = sorted( + filter(lambda x: x["avatarPromoteId"] == promote_id, promote_json_data), + key=lambda x: x.get("promoteLevel", 0), + ) + promotes = [] + for promote_data in promote_datas: + items = [] + for item_data in promote_data["costItems"]: + if item_data: + items.append( + ItemCount(item_id=item_data["id"], count=item_data["count"]) + ) + promotes.append( + AvatarPromote( + promote_level=promote_data.get("promoteLevel", 0), + max_level=promote_data["unlockMaxLevel"], + coin=promote_data.get("scoinCost", 0), + cost_items=items, + ) + ) breakpoint()