From fb77ca0569496c58f4c68b89ffaf6c42566ddd75 Mon Sep 17 00:00:00 2001 From: Karako Date: Sat, 29 Apr 2023 22:59:16 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=A7=20building...?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model/_base.py | 80 +++++++++++++++++-- model/avatar/__init__.py | 2 + model/{avatar.py => avatar/_avatar.py} | 47 +++-------- model/avatar/_talente.py | 103 +++++++++++++++++++++++++ model/item.py | 4 +- run.py | 89 ++------------------- scripts/__init__.py | 0 scripts/_base.py | 44 +++++++++++ scripts/avatar.py | 7 ++ scripts/item.py | 19 +++++ 10 files changed, 269 insertions(+), 126 deletions(-) create mode 100644 model/avatar/__init__.py rename model/{avatar.py => avatar/_avatar.py} (70%) create mode 100644 model/avatar/_talente.py create mode 100644 scripts/__init__.py create mode 100644 scripts/_base.py create mode 100644 scripts/avatar.py create mode 100644 scripts/item.py diff --git a/model/_base.py b/model/_base.py index 1a43e21..7d3cdc4 100644 --- a/model/_base.py +++ b/model/_base.py @@ -1,8 +1,78 @@ -from humps import camelize +from datetime import datetime -from utils.model import BaseConfig, BaseModel +# noinspection PyPep8Naming +from enum import Enum as E +from typing import Any, TYPE_CHECKING, Type + +from databases import Database + +# noinspection PyPep8Naming +from ormar import ( + BaseField, + Enum as ENUM, + Model as _Model, + ModelMeta as _ModelMeta, + String as STRING, +) +from sqlalchemy import MetaData + +from utils import json + +try: + import regex as re +except ImportError: + import re + +if TYPE_CHECKING: + pass + +__all__ = ("Model", "String", "Enum") + +DATABASE_URL = "sqlite:///data/db.sqlite" +database = Database(DATABASE_URL) +metadata = MetaData() -class DataModel(BaseModel): - class Config(BaseConfig): - alias_generator = lambda x: camelize(x.removesuffix("TextHashMap")) +class ModelMeta(_ModelMeta): + database = database + metadata = metadata + + +class Model(_Model): + class Config(_Model.Config): + json_dumps = json.dumps + json_loads = json.loads + json_encoders = {datetime: lambda v: v.timestamp()} + + +class String(STRING): + def __new__( + cls, + *, + max_length: int = 255, + min_length: int = None, + regex: str = None, + **kwargs: Any, + ) -> BaseField: + return super().__new__( + cls=STRING, + max_length=max_length, + min_length=min_length, + regex=regex, + **kwargs, + ) + + +class Enum(ENUM): + def __new__(cls, enum_class: Type[E], **kwargs: Any) -> BaseField: + return super().__new__( + cls, + **{ + **kwargs, + **{ + k: v + for k, v in locals().items() + if k not in ["cls", "__class__", "kwargs"] + }, + }, + ) diff --git a/model/avatar/__init__.py b/model/avatar/__init__.py new file mode 100644 index 0000000..7dff576 --- /dev/null +++ b/model/avatar/__init__.py @@ -0,0 +1,2 @@ +from model.avatar._avatar import * +from model.avatar._talente import * diff --git a/model/avatar.py b/model/avatar/_avatar.py similarity index 70% rename from model/avatar.py rename to model/avatar/_avatar.py index 0132255..5eb175e 100644 --- a/model/avatar.py +++ b/model/avatar/_avatar.py @@ -6,6 +6,16 @@ from utils.model import BaseModel if TYPE_CHECKING: from model.item import Item +__all__ = ( + "Avatar", + "AvatarBirth", + "AvatarInfo", + "AvatarConstellation", + "AvatarPromote", + "Seuyu", + "ItemCount", +) + class AvatarBirth(BaseModel): """角色生日""" @@ -46,7 +56,7 @@ class AvatarInfo(BaseModel): """声优""" -class AvatarItem(BaseModel): +class ItemCount(BaseModel): item: "Item" """物品""" count: int @@ -63,43 +73,10 @@ class AvatarPromote(BaseModel): coin: int = 0 """摩拉""" - items: list[AvatarItem] + items: list[ItemCount] = [] """突破所需材料""" -class AvatarSkill(BaseModel): - name: str - """技能名称""" - description: str - """技能描述""" - promote_level: int = 0 - """所需突破等级""" - icon: str - """图标""" - - -class AvatarActiveSkill(AvatarSkill): - """角色主动技能""" - - CD: float - """冷却时间""" - max_charge_num: int - """技能最大储存次数""" - - -class AvatarPassiveSkill(AvatarSkill): - """角色被动技能""" - - -class AvatarSkills(BaseModel): - attack_skill: AvatarActiveSkill - """普通攻击""" - energy_skill: AvatarActiveSkill - """元素爆发""" - proud_skills: list[AvatarSkill] - """被动技能""" - - class AvatarConstellation(BaseModel): """角色命座""" diff --git a/model/avatar/_talente.py b/model/avatar/_talente.py new file mode 100644 index 0000000..a5b89df --- /dev/null +++ b/model/avatar/_talente.py @@ -0,0 +1,103 @@ +from utils.model import BaseModel +from typing import ClassVar + +__all__ = ( + "Talent", + "CombatTalent", + "NormalAttack", + "ElementalBurst", + "ElementalSkill", + "AlternateSprint", + "PassiveTalent", + "AscensionPassive", + "FirstAscensionPassive", + "FourthAscensionPassive", + "UtilityPassive", + "MiscellaneousPassive", + "AvatarTalents", +) + + +class Talent(BaseModel): + name: str + """天赋名称""" + description: str + """天赋描述""" + icon: str + """图标""" + level: int = 0 + """解锁等级""" + + params: list[float] = [] + """数值参数列表""" + + +class CombatTalent(Talent): + """战斗天赋""" + + cooldown: float = 0 + """冷却时间""" + + +class NormalAttack(CombatTalent): + """普通攻击""" + + +class ElementalSkill(CombatTalent): + """元素战技""" + + +class ElementalBurst(CombatTalent): + """元素爆发""" + + +class AlternateSprint(CombatTalent): + """冲刺技能""" + + +class PassiveTalent(Talent): + """被动天赋""" + + +class AscensionPassive(PassiveTalent): + """突破被动天赋""" + + ascension: ClassVar[int] + + +class FirstAscensionPassive(AscensionPassive): + """第一次突破被动天赋""" + + ascension = 1 + + +class FourthAscensionPassive(AscensionPassive): + """第四次突破被动天赋""" + + ascension = 4 + + +class UtilityPassive(PassiveTalent): + """实用被动天赋""" + + +class MiscellaneousPassive(PassiveTalent): + """杂项被动天赋""" + + +class AvatarTalents(BaseModel): + """角色天赋""" + + normal_attack: NormalAttack + """普通攻击""" + elemental_skill: ElementalSkill + """元素战技""" + elemental_burst: ElementalBurst + """元素爆发""" + alternate_sprint: AlternateSprint | None = None + """冲刺技能""" + + first_ascension_passive: FirstAscensionPassive + fourth_ascension_passive: FourthAscensionPassive + utility_passive: UtilityPassive | None = None + miscellaneous_passive: MiscellaneousPassive | None = None diff --git a/model/item.py b/model/item.py index 074137d..0eb545b 100644 --- a/model/item.py +++ b/model/item.py @@ -1,7 +1,7 @@ from model.enums import ItemType, MaterialType from model.enums import FoodQuality -from utils.model import BaseModel +from model._base import Model as BaseModel class Item(BaseModel): @@ -9,8 +9,6 @@ class Item(BaseModel): """ID""" name: str """名称""" - family: str - """种类""" type: str | None """类型""" icon: str diff --git a/run.py b/run.py index 30f0fd0..08b7141 100644 --- a/run.py +++ b/run.py @@ -1,98 +1,21 @@ -import asyncio - -from model.enums import ItemType, MaterialType -from model.item import Material, Namecard -from utils.const import PROJECT_ROOT -from utils.context import ContextManager -from utils.manager import ResourceManager -from utils.text import Text +from scripts.avatar import parse_avatar_data +from scripts.item import parse_item_data from utils.typedefs import Lang -import ujson as json - -OUTPUT_DIR = PROJECT_ROOT / "data" -OUTPUT_DIR.mkdir(parents=True, exist_ok=True) -# noinspection PyShadowingBuiltins -async def parse_item_data(resource: ResourceManager): - from model.item import Food, FoodQuality, Item - - json_data = resource.fetch("MaterialExcelConfigData") - data_list = [] - - for item_data in json_data: - id = item_data["id"] - name = Text(item_data["nameTextMapHash"]) - family = item_data.get("materialType", "") - rarity = item_data.get("rankLevel") - type = Text(item_data["typeDescTextMapHash"]) - icon = item_data["icon"] - description = Text(item_data["descTextMapHash"]) - special = Text(item_data["specialDescTextMapHash"]) or None - item_type = ItemType(item_data["itemType"].removeprefix("ITEM_")) - - base_kwargs = { - "id": id, - "name": name, - "family": family, - "rarity": rarity, - "type": type, - "icon": icon, - "description": description, - "item_type": item_type, - } - if special is not None: - base_kwargs["special_description"] = special - - if pics := list(filter(lambda x: x, item_data["picPath"])): - item = Namecard(pictures=pics, **base_kwargs) - elif "materialType" in item_data: # 材料 - material_type = MaterialType( - item_data["materialType"].removeprefix("MATERIAL_") - ) - item = Material(material_type=material_type, **base_kwargs) - elif "foodQuality" in item_data: # 食物 - quality = FoodQuality( - item_data["foodQuality"].removeprefix("FOOD_QUALITY_").title() - ) - effect = Text(item_data["effectDescTextMapHash"]) - item = Food(quality=quality, effect=effect, **base_kwargs) - else: - item = Item(**base_kwargs) - data_list.append(item.dict(exclude_none=True)) - - item_data_file = OUTPUT_DIR / "item.json" - with open(item_data_file, "w", encoding="utf-8") as f: - f.write(json.dumps(data_list, ensure_ascii=False)) - breakpoint() - - -async def fetch_parse_data(lang: Lang): - with ContextManager().with_context( - "resource_manager", ResourceManager(lang=lang) - ) as resource_manager: - try: - await parse_item_data(resource_manager) - except Exception as e: - breakpoint() +async def parse(lang: Lang): + await parse_item_data(lang) + await parse_avatar_data(lang) async def main(): - task_list = [] for lang in Lang.__args__: - task = asyncio.create_task(fetch_parse_data(lang=lang)) - task_list.append(task) - await asyncio.gather(*task_list) + await parse(lang) def __main__(): import asyncio - import sys - - if (3, 10) >= sys.version_info >= (3, 8) and sys.platform.startswith("win"): - asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) - loop = asyncio.new_event_loop() loop.run_until_complete(main()) diff --git a/scripts/__init__.py b/scripts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/scripts/_base.py b/scripts/_base.py new file mode 100644 index 0000000..7e70b9d --- /dev/null +++ b/scripts/_base.py @@ -0,0 +1,44 @@ +import asyncio +from abc import ABC, abstractmethod +from asyncio import Task + +targets: list["Target"] = [] + + +class Target(ABC): + _task: Task | None = None + + @property + def task(self) -> Task: + return self._task + + def __init__(self): + targets.append(self) + + async def __async_init__(self) -> None: + pass + + async def __async_del__(self) -> None: + pass + + @abstractmethod + async def run(self) -> None: + pass + + async def _run(self) -> None: + await self.__async_init__() + await self.run() + await self.__async_del__() + + async def run_in_background(self) -> Task: + self._task = asyncio.create_task(self._run()) + return self._task + + +async def waiting_for_done(): + task_list: list[Task] = [] + for target in targets: + if target.task is not None and not target.task.done(): + task_list.append(target.task) + + await asyncio.gather(*task_list) diff --git a/scripts/avatar.py b/scripts/avatar.py new file mode 100644 index 0000000..db7e3b0 --- /dev/null +++ b/scripts/avatar.py @@ -0,0 +1,7 @@ +from utils.typedefs import Lang + +from utils.manager import ResourceManager + + +async def parse_avatar_data(lang: Lang): + ... diff --git a/scripts/item.py b/scripts/item.py new file mode 100644 index 0000000..c9f6bf8 --- /dev/null +++ b/scripts/item.py @@ -0,0 +1,19 @@ +from model.enums import ItemType +from utils.manager import ResourceManager +from utils.typedefs import Lang + + +# noinspection PyShadowingBuiltins +async def parse_item_data(lang: Lang): + manager = ResourceManager(lang=lang) + json_data = manager.fetch("MaterialExcelConfigData") + for data in json_data: + id = data["id"] + name = manager.get_text(data["nameTextMapHash"]) + type = manager.get_text(data["typeDescTextMapHash"]) + icon = data["icon"] + rarity = data["rankLevel"] + description = manager.get_text(data["descTextMapHash"]) + special_description = manager.get_text(data["specialDescTextMapHash"]) + item_type = ItemType(data["itemType"].removeprefix('ITEM_')) + breakpoint()