Skip to content
This repository has been archived by the owner on Jun 9, 2022. It is now read-only.

Commit

Permalink
ADD - Quest data transformer (#12)
Browse files Browse the repository at this point in the history
Signed-off-by: RaenonX <[email protected]>
  • Loading branch information
RaenonX committed Apr 17, 2021
1 parent 346b1d6 commit 5c8f588
Show file tree
Hide file tree
Showing 17 changed files with 210 additions and 23 deletions.
4 changes: 3 additions & 1 deletion .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@
# often used in datetime handling (dt for datetime, tz for timezone)
# - ex
# often used as the var name of exception caught by try..except
# - hp
# simply means HP (hit point)
# - fn
# often used to represent a function address

good-names=_,T,f,i,j,k,s,v,dt,ex,fn,tz
good-names=_,T,f,i,j,k,s,v,dt,ex,hp,fn,tz

[DESIGN]

Expand Down
1 change: 1 addition & 0 deletions dlparse/enums/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from .lang import Language
from .mixin import * # noqa
from .mode_change import ModeChangeType
from .quest_mode import QuestMode
from .skill_idx import SkillIndex
from .skill_num import SkillNumber
from .status import Status
Expand Down
19 changes: 19 additions & 0 deletions dlparse/enums/quest_mode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"""Enums for different mode of a quest."""
from enum import Enum

__all__ = ("QuestMode",)


class QuestMode(Enum):
"""
Enum for the quest mode.
The number corresponds to the field ``_QuestPlayModeType`` in quest data asset.
The definition can be found in ``Gluon.QuestPlayModeType`` of the application metadata.
"""

NONE = 0
NORMAL = 1
SOLO = 2
MULTI = 3
1 change: 1 addition & 0 deletions dlparse/model/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from .ex_ability import ExAbilityData
from .hit_buff import BuffingHitData
from .hit_dmg import DamagingHitData
from .quest_data import QuestData
from .skill_atk import AttackingSkillData, AttackingSkillDataEntry
from .skill_sup import SupportiveSkillData, SupportiveSkillEntry
from .unit_cancel import SkillCancelActionUnit
58 changes: 58 additions & 0 deletions dlparse/model/quest_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
"""Models for a quest data."""
from dataclasses import InitVar, dataclass, field
from typing import TYPE_CHECKING

from dlparse.enums import Element, QuestMode

if TYPE_CHECKING:
from dlparse.mono.asset import QuestDataEntry
from dlparse.mono.manager import AssetManager

__all__ = ("QuestData",)


@dataclass
class QuestData:
"""A transformed quest data."""

asset_manager: InitVar["AssetManager"]

quest_data: "QuestDataEntry"

quest_mode: QuestMode = field(init=False)
elements: list[Element] = field(init=False)
elements_limit: list[Element] = field(init=False)

max_clear_time_sec: int = field(init=False)
max_revive_allowed: int = field(init=False)
area_1_name: str = field(init=False)
spawn_enemy_param_ids: list[int] = field(init=False)

def __post_init__(self, asset_manager: "AssetManager"):
# Quest basic properties
self.quest_mode = self.quest_data.quest_mode
self.elements = [
Element(elem) for elem
in (self.quest_data.element_1, self.quest_data.element_2)
if elem.is_valid
]
self.elements_limit = [
Element(elem) for elem
in (self.quest_data.element_1_limit, self.quest_data.element_2_limit)
if elem.is_valid
]

self.max_clear_time_sec = self.quest_data.max_time_sec
self.max_revive_allowed = self.quest_data.max_revive

# Quest dungeon area
self.area_1_name = self.quest_data.area_1_name
dungeon_plan = asset_manager.asset_dungeon_planner.get_data_by_id(self.area_1_name)

# --- ``variation_idx`` is 1-based index

self.spawn_enemy_param_ids = dungeon_plan.enemy_param_ids[self.quest_data.variation_idx - 1]
if not self.spawn_enemy_param_ids:
# In Legend Ciella quest, variation type is 4 while the only enemy parameter related is located at 0
# This will attempt to take the data at variation 0
self.spawn_enemy_param_ids = dungeon_plan.enemy_param_ids[0]
17 changes: 10 additions & 7 deletions dlparse/mono/asset/base/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,24 @@ class CustomParserBase(ParserBase, ABC):
Each entries should have a field ``_Id`` to uniquely identifies the corresponding entry.
"""

key_id: str = "_Id"
@staticmethod
def get_entries_dict(file_like: TextIO, key: str = "_Id") -> dict[int, dict]:
"""
Get a dict of data entries to be further parsed.
@classmethod
def get_entries_dict(cls, file_like: TextIO) -> dict[int, dict]:
"""Get a dict of data entries to be further parsed."""
The ``key`` of the return will be the value of the data with ``key``.
This can be overridden by providing the key name.
"""
data = json.load(file_like)

ret: dict[int, dict] = {}

# Convert entries to :class:`dict`
for entry in data:
if cls.key_id not in entry:
raise AssetKeyMissingError(cls.key_id)
if key not in entry:
raise AssetKeyMissingError(key)

ret[entry[cls.key_id]] = entry
ret[entry[key]] = entry

return ret

Expand Down
13 changes: 9 additions & 4 deletions dlparse/mono/asset/base/master.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,14 @@ class MasterEntryBase(EntryBase, ABC):
class MasterParserBase(Generic[T], ParserBase, ABC):
"""Base parser class for parsing the master asset files."""

@classmethod
def get_entries_dict(cls, file_like: TextIO) -> dict[Union[int, str], dict]:
"""Get a dict of data entries to be further parsed."""
@staticmethod
def get_entries_dict(file_like: TextIO, key: str = "_Id") -> dict[Union[int, str], dict]:
"""
Get a dict of data entries to be further parsed.
The ``key`` of the return will be the value of the data with ``key``.
This can be overridden by providing the key name.
"""
data = json.load(file_like)

if "dict" not in data:
Expand All @@ -42,7 +47,7 @@ def get_entries_dict(cls, file_like: TextIO) -> dict[Union[int, str], dict]:
# ``entriesKey`` should not be used as ID because ``_Id`` offset was found in action condition asset
entry_values = data["entriesValue"][:data["count"]]

return {entry["_Id"]: entry for entry in entry_values}
return {entry[key]: entry for entry in entry_values}

@classmethod
def get_entries_list(cls, file_like: TextIO) -> list[dict]:
Expand Down
6 changes: 4 additions & 2 deletions dlparse/mono/asset/master/dungeon_planner.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ def get_enemy_param_ids(data: dict[str, Union[str, int]]) -> list[list[int]]:
Get the enemy parameter IDs.
The index of the return corresponds to ``variation_idx`` field of the quest data.
This filters unused fields.
"""
param_ids = []
param_fields = [
Expand Down Expand Up @@ -89,7 +91,7 @@ class DungeonPlannerParser(MasterParserBase[DungeonPlannerEntry]):
"""Class to parse the quest data file."""

@classmethod
def parse_file(cls, file_like: TextIO) -> dict[int, DungeonPlannerEntry]:
entries = cls.get_entries_dict(file_like)
def parse_file(cls, file_like: TextIO) -> dict[str, DungeonPlannerEntry]:
entries = cls.get_entries_dict(file_like, key="_Area")

return {key: DungeonPlannerEntry.parse_raw(value) for key, value in entries.items()}
7 changes: 5 additions & 2 deletions dlparse/mono/asset/master/quest_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from dataclasses import dataclass
from typing import Optional, TextIO, Union

from dlparse.enums import Element
from dlparse.enums import Element, QuestMode
from dlparse.mono.asset.base import MasterAssetBase, MasterEntryBase, MasterParserBase

__all__ = ("QuestDataEntry", "QuestDataAsset")
Expand All @@ -14,6 +14,8 @@ class QuestDataEntry(MasterEntryBase):

name_view_label: str

quest_mode: QuestMode

element_1: Element
element_1_limit: Element
element_2: Element
Expand All @@ -22,13 +24,14 @@ class QuestDataEntry(MasterEntryBase):
max_time_sec: int
max_revive: int

variation_idx: int
variation_idx: int # 1-based index
area_1_name: str

@staticmethod
def parse_raw(data: dict[str, Union[str, int]]) -> "QuestDataEntry":
return QuestDataEntry(
id=data["_Id"],
quest_mode=QuestMode(data["_QuestPlayModeType"]),
name_view_label=data["_QuestViewName"],
element_1=Element(data["_Elemental"]),
element_1_limit=Element(data["_LimitedElementalType"]),
Expand Down
4 changes: 1 addition & 3 deletions dlparse/mono/custom/website_text.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,8 @@ def get_all_ids(self, lang_code: str) -> list[str]:
class WebsiteTextParser(CustomParserBase):
"""Class to parse the website text asset file."""

key_id: str = "id"

@classmethod
def parse_file(cls, file_like: TextIO) -> dict[str, WebsiteTextEntry]:
entries = cls.get_entries_dict(file_like)
entries = cls.get_entries_dict(file_like, key="id")

return {key: WebsiteTextEntry.parse_raw(value) for key, value in entries.items()}
10 changes: 8 additions & 2 deletions dlparse/mono/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from dlparse.enums import Language
from dlparse.errors import ConfigError
from dlparse.transformer import AbilityTransformer, EnemyTransformer, SkillTransformer
from dlparse.transformer import AbilityTransformer, EnemyTransformer, QuestTransformer, SkillTransformer
from .asset import (
AbilityAsset, AbilityLimitGroupAsset, ActionConditionAsset, ActionGrantAsset, ActionPartsListAsset,
BuffCountAsset, CharaDataAsset, CharaModeAsset, DragonDataAsset, DungeonPlannerAsset,
Expand Down Expand Up @@ -78,8 +78,9 @@ def __init__(

# Transformers
self._transformer_ability = AbilityTransformer(self)
self._transformer_skill = SkillTransformer(self)
self._transformer_enemy = EnemyTransformer(self)
self._transformer_skill = SkillTransformer(self)
self._transformer_quest = QuestTransformer(self)

# region Master Assets

Expand Down Expand Up @@ -252,6 +253,11 @@ def transformer_enemy(self) -> EnemyTransformer:
"""Get the enemy data transformer."""
return self._transformer_enemy

@property
def transformer_quest(self) -> QuestTransformer:
"""Get the quest data transformer."""
return self._transformer_quest

# endregion

# endregion
Expand Down
1 change: 1 addition & 0 deletions dlparse/transformer/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Various transformers to "transform" the data."""
from .ability import AbilityTransformer
from .enemy import EnemyTransformer
from .quest import QuestTransformer
from .skill import SkillHitData, SkillTransformer
4 changes: 3 additions & 1 deletion dlparse/transformer/enemy.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@


class EnemyTransformer:
"""Class to transform the enemy data."""
"""Class to transform an enemy data."""

# pylint: disable=too-few-public-methods

def __init__(self, asset_manager: "AssetManager"):
self._asset_manager: "AssetManager" = asset_manager
Expand Down
24 changes: 24 additions & 0 deletions dlparse/transformer/quest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"""Quest data transformer."""
from typing import TYPE_CHECKING

from dlparse.model import QuestData

if TYPE_CHECKING:
from dlparse.mono.manager import AssetManager

__all__ = ("QuestTransformer",)


class QuestTransformer:
"""Class to transform a quest data."""

# pylint: disable=too-few-public-methods

def __init__(self, asset_manager: "AssetManager"):
self._asset_manager: "AssetManager" = asset_manager

def transform_quest_data(self, quest_id: int) -> QuestData:
"""Transform ``enemy_param_id`` to an enemy info."""
quest_data = self._asset_manager.asset_quest_data.get_data_by_id(quest_id)

return QuestData(self._asset_manager, quest_data)
8 changes: 7 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import pytest

from dlparse.mono.manager import AssetManager
from dlparse.transformer import AbilityTransformer, EnemyTransformer, SkillTransformer
from dlparse.transformer import AbilityTransformer, EnemyTransformer, QuestTransformer, SkillTransformer
from tests.static import (
PATH_LOCAL_DIR_ACTION_ASSET, PATH_LOCAL_DIR_CHARA_MOTION_ASSET, PATH_LOCAL_DIR_CUSTOM_ASSET,
PATH_LOCAL_DIR_DRAGON_MOTION_ASSET, PATH_LOCAL_DIR_MASTER_ASSET,
Expand Down Expand Up @@ -34,6 +34,12 @@ def transformer_enemy() -> EnemyTransformer:
return _asset_manager.transformer_enemy


@pytest.fixture
def transformer_quest() -> QuestTransformer:
"""Get the quest data transformer."""
return _asset_manager.transformer_quest


# endregion


Expand Down
Empty file.
56 changes: 56 additions & 0 deletions tests/test_transformer/test_quest/test_main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
from dlparse.enums import Element, QuestMode
from dlparse.transformer import QuestTransformer


def test_lilith_enchroaching_shadow_expert_solo(transformer_quest: QuestTransformer):
# Lilith's Encroaching Shadow (Expert) - Solo
# https://dl.raenonx.cc/quest/13#pos-4
quest_data = transformer_quest.transform_quest_data(228050102)

assert quest_data.quest_mode == QuestMode.MULTI
assert quest_data.elements == [Element.SHADOW]
assert quest_data.elements_limit == []
assert quest_data.max_clear_time_sec == 600
assert quest_data.max_revive_allowed == 1
assert quest_data.area_1_name == "DIABOLOS_05_0101_01"
assert quest_data.spawn_enemy_param_ids == [228050201]


def test_lilith_enchroaching_shadow_master_solo(transformer_quest: QuestTransformer):
# Lilith's Encroaching Shadow (Expert) - Solo
# https://dl.raenonx.cc/quest/13#pos-4
quest_data = transformer_quest.transform_quest_data(228051103)

assert quest_data.quest_mode == QuestMode.SOLO
assert quest_data.elements == [Element.SHADOW, Element.FLAME]
assert quest_data.elements_limit == [Element.LIGHT, Element.WATER]
assert quest_data.max_clear_time_sec == 600
assert quest_data.max_revive_allowed == 0
assert quest_data.area_1_name == "DIABOLOS_05_0111_02"
assert quest_data.spawn_enemy_param_ids == [228051301]


def test_legend_ciella_solo(transformer_quest: QuestTransformer):
# Legend Ciella - Solo
quest_data = transformer_quest.transform_quest_data(225031101)

assert quest_data.quest_mode == QuestMode.SOLO
assert quest_data.elements == [Element.WATER]
assert quest_data.elements_limit == [Element.WIND]
assert quest_data.max_clear_time_sec == 600
assert quest_data.max_revive_allowed == 0
assert quest_data.area_1_name == "AGITO_ABS_03_1102_01"
assert quest_data.spawn_enemy_param_ids == [225031401]


def test_legend_ciella_coop(transformer_quest: QuestTransformer):
# Legend Ciella - Coop
quest_data = transformer_quest.transform_quest_data(225030101)

assert quest_data.quest_mode == QuestMode.MULTI
assert quest_data.elements == [Element.WATER]
assert quest_data.elements_limit == [Element.WIND]
assert quest_data.max_clear_time_sec == 480
assert quest_data.max_revive_allowed == 0
assert quest_data.area_1_name == "AGITO_ABS_03_0102_01"
assert quest_data.spawn_enemy_param_ids == [225030401]

0 comments on commit 5c8f588

Please sign in to comment.