From f05148fcf874cbb3e6584eb7f34fcf6019d744aa Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Sat, 11 May 2024 18:21:28 +0800 Subject: [PATCH 01/37] Dev: Extract item keywords --- dev_tools/keyword_extract.py | 2 + dev_tools/keywords/base.py | 18 + dev_tools/keywords/dungeon_list.py | 7 +- dev_tools/keywords/item.py | 109 +++++ tasks/dungeon/keywords/classes.py | 16 +- tasks/dungeon/keywords/dungeon.py | 67 +++ tasks/planner/keywords/__init__.py | 9 + tasks/planner/keywords/classes.py | 59 +++ tasks/planner/keywords/item_ascension.py | 252 +++++++++++ tasks/planner/keywords/item_calyx.py | 317 ++++++++++++++ tasks/planner/keywords/item_currency.py | 226 ++++++++++ tasks/planner/keywords/item_exp.py | 135 ++++++ tasks/planner/keywords/item_trace.py | 525 +++++++++++++++++++++++ tasks/planner/keywords/item_weekly.py | 83 ++++ 14 files changed, 1822 insertions(+), 3 deletions(-) create mode 100644 dev_tools/keywords/item.py create mode 100644 tasks/planner/keywords/__init__.py create mode 100644 tasks/planner/keywords/classes.py create mode 100644 tasks/planner/keywords/item_ascension.py create mode 100644 tasks/planner/keywords/item_calyx.py create mode 100644 tasks/planner/keywords/item_currency.py create mode 100644 tasks/planner/keywords/item_exp.py create mode 100644 tasks/planner/keywords/item_trace.py create mode 100644 tasks/planner/keywords/item_weekly.py diff --git a/dev_tools/keyword_extract.py b/dev_tools/keyword_extract.py index d98d6a865..106e395f8 100644 --- a/dev_tools/keyword_extract.py +++ b/dev_tools/keyword_extract.py @@ -576,6 +576,8 @@ class KeywordExtract: self.load_keywords(['养成材料', '光锥', '遗器', '其他材料', '消耗品', '任务', '贵重物']) self.write_keywords(keyword_class='ItemTab', text_convert=lambda name: name.replace(' ', ''), output_file='./tasks/item/keywords/tab.py') + from dev_tools.keywords.item import generate_items + generate_items() self.generate_rogue_buff() self.load_keywords(['已强化']) self.write_keywords(keyword_class='RogueEnhancement', output_file='./tasks/rogue/keywords/enhancement.py') diff --git a/dev_tools/keywords/base.py b/dev_tools/keywords/base.py index f9c147b3f..d3549c606 100644 --- a/dev_tools/keywords/base.py +++ b/dev_tools/keywords/base.py @@ -159,6 +159,7 @@ class GenerateKeyword: base = self.keyword_format.copy() text_id = keyword.pop('text_id') if text_id is None: + logger.warning(f'Empty text_id in {keyword}') return # id self.keyword_index += 1 @@ -191,3 +192,20 @@ class GenerateKeyword: def __call__(self, *args, **kwargs): self.generate() + + +class ShareData(GenerateKeyword): + @cached_property + def GameplayGuideData(self): + return self.read_file('./ExcelOutput/GameplayGuideData.json') + + @cached_property + def MappingInfo(self): + return self.read_file('./ExcelOutput/MappingInfo.json') + + @cached_property + def ItemConfig(self): + return self.read_file('./ExcelOutput/ItemConfig.json') + + +SHARE_DATA = ShareData() diff --git a/dev_tools/keywords/dungeon_list.py b/dev_tools/keywords/dungeon_list.py index 35221f9da..10f362a87 100644 --- a/dev_tools/keywords/dungeon_list.py +++ b/dev_tools/keywords/dungeon_list.py @@ -1,7 +1,7 @@ import re import typing as t -from dev_tools.keywords.base import GenerateKeyword, text_to_variable +from dev_tools.keywords.base import GenerateKeyword, SHARE_DATA, text_to_variable from module.base.decorator import cached_property from module.config.utils import deep_get @@ -26,13 +26,14 @@ class GenerateDungeonList(GenerateKeyword): @cached_property def data(self): - return self.read_file('./ExcelOutput/GameplayGuideData.json') + return SHARE_DATA.GameplayGuideData def iter_keywords(self) -> t.Iterable[dict]: for keyword in self.iter_dungeon(): if isinstance(keyword, str): yield dict( text_id=self.find_keyword(keyword, lang='cn')[0], + dungeon_id=-1, plane_id=-1, ) else: @@ -41,6 +42,7 @@ class GenerateDungeonList(GenerateKeyword): def iter_dungeon(self): temp_save = "" for data in self.data.values(): + dungeon_id = data.get('ID', 0) text_id = deep_get(data, keys='Name.Hash') plane_id = deep_get(data, 'MapEntranceID', 0) _, name = self.find_keyword(text_id, lang='cn') @@ -51,6 +53,7 @@ class GenerateDungeonList(GenerateKeyword): continue yield dict( text_id=text_id, + dungeon_id=dungeon_id, plane_id=plane_id, ) if temp_save: diff --git a/dev_tools/keywords/item.py b/dev_tools/keywords/item.py new file mode 100644 index 000000000..e6b26be7e --- /dev/null +++ b/dev_tools/keywords/item.py @@ -0,0 +1,109 @@ +import typing as t + +from dev_tools.keywords.base import GenerateKeyword, SHARE_DATA +from module.base.decorator import cached_property +from module.config.utils import deep_get + + +class GenerateItemBase(GenerateKeyword): + purpose_type = [] + + def iter_items(self) -> t.Iterable[dict]: + for data in SHARE_DATA.ItemConfig.values(): + item_id = data.get('ID', 0) + text_id = deep_get(data, keys='ItemName.Hash') + subtype = data.get('ItemSubType', 0) + rarity = data.get('Rarity', 0) + purpose = data.get('PurposeType', 0) + item_group = data.get('ItemGroup', 0) + yield dict( + text_id=text_id, + rarity=rarity, + item_id=item_id, + item_group=item_group, + dungeon_id=-1, + subtype=subtype, + purpose=purpose, + ) + + def iter_keywords(self) -> t.Iterable[dict]: + for data in self.iter_items(): + if data['subtype'] == 'Material' and data['purpose'] in self.purpose_type: + data['dungeon_id'] = self.dict_itemid_to_dungeonid.get(data['item_id'], -1) + yield data + + def iter_rows(self) -> t.Iterable[dict]: + for data in super().iter_rows(): + data.pop('subtype') + data.pop('purpose') + yield data + + @cached_property + def dict_itemid_to_dungeonid(self): + """ + MappingInfo is like + dungeon_id: + dungeon_level: + data + """ + dic = {} + for level_data in SHARE_DATA.MappingInfo.values(): + # Use the highest level + # And must contain: + # "Type": "FARM_ENTRANCE", + # "FarmType": "COCOON", + for dungeon_data in level_data.values(): + if dungeon_data.get('Type') != 'FARM_ENTRANCE': + continue + # parse + dungeon_id = dungeon_data.get('ID', 0) + for item_data in dungeon_data.get('DisplayItemList', []): + item_id = item_data.get('ItemID', 0) + if item_id < 100: + continue + dic.setdefault(item_id, dungeon_id) + + return dic + + +class GenerateItemCurrency(GenerateItemBase): + output_file = './tasks/planner/keywords/item_currency.py' + + def iter_keywords(self) -> t.Iterable[dict]: + for data in self.iter_items(): + if data['subtype'] == 'Virtual' and data['item_id'] < 100: + yield data + + +class GenerateItemExp(GenerateItemBase): + output_file = './tasks/planner/keywords/item_exp.py' + purpose_type = [1, 5, 6] + + +class GenerateItemAscension(GenerateItemBase): + output_file = './tasks/planner/keywords/item_ascension.py' + purpose_type = [2] + + +class GenerateItemTrace(GenerateItemBase): + output_file = './tasks/planner/keywords/item_trace.py' + purpose_type = [3] + + +class GenerateItemWeekly(GenerateItemBase): + output_file = './tasks/planner/keywords/item_weekly.py' + purpose_type = [4] + + +class GenerateItemCalyx(GenerateItemBase): + output_file = './tasks/planner/keywords/item_calyx.py' + purpose_type = [7] + + +def generate_items(): + GenerateItemCurrency()() + GenerateItemExp()() + GenerateItemAscension()() + GenerateItemTrace()() + GenerateItemWeekly()() + GenerateItemCalyx()() diff --git a/tasks/dungeon/keywords/classes.py b/tasks/dungeon/keywords/classes.py index 5b9da5a36..d9c504aa2 100644 --- a/tasks/dungeon/keywords/classes.py +++ b/tasks/dungeon/keywords/classes.py @@ -20,6 +20,7 @@ class DungeonTab(Keyword): class DungeonList(Keyword): instances: ClassVar = {} + dungeon_id: int plane_id: int @cached_property @@ -183,6 +184,20 @@ class DungeonList(Keyword): else: return '' + @classmethod + def find_dungeon_id(cls, dungeon_id): + """ + Args: + dungeon_id: + + Returns: + DungeonList: DungeonList object or None + """ + for instance in cls.instances.values(): + if instance.dungeon_id == dungeon_id: + return instance + return None + @classmethod def find_dungeon_by_string(cls, cn='', en='', **kwargs): """ @@ -225,7 +240,6 @@ class DungeonList(Keyword): return None - @dataclass(repr=False) class DungeonEntrance(Keyword): instances: ClassVar = {} diff --git a/tasks/dungeon/keywords/dungeon.py b/tasks/dungeon/keywords/dungeon.py index c4301cba0..52bf89da9 100644 --- a/tasks/dungeon/keywords/dungeon.py +++ b/tasks/dungeon/keywords/dungeon.py @@ -11,6 +11,7 @@ Calyx_Golden_Memories_Jarilo_VI = DungeonList( en='Bud of Memories (Jarilo-Ⅵ)', jp='回憶の蕾・ヤリーロ-Ⅵ', es='Flor de los recuerdos (Jarilo-Ⅵ)', + dungeon_id=1001, plane_id=2010101, ) Calyx_Golden_Aether_Jarilo_VI = DungeonList( @@ -21,6 +22,7 @@ Calyx_Golden_Aether_Jarilo_VI = DungeonList( en='Bud of Aether (Jarilo-Ⅵ)', jp='エーテルの蕾・ヤリーロ-Ⅵ', es='Flor de éter (Jarilo-Ⅵ)', + dungeon_id=1002, plane_id=2011101, ) Calyx_Golden_Treasures_Jarilo_VI = DungeonList( @@ -31,6 +33,7 @@ Calyx_Golden_Treasures_Jarilo_VI = DungeonList( en='Bud of Treasures (Jarilo-Ⅵ)', jp='秘蔵の蕾・ヤリーロ-Ⅵ', es='Flor de tesoros (Jarilo-Ⅵ)', + dungeon_id=1003, plane_id=2012101, ) Calyx_Golden_Memories_The_Xianzhou_Luofu = DungeonList( @@ -41,6 +44,7 @@ Calyx_Golden_Memories_The_Xianzhou_Luofu = DungeonList( en='Bud of Memories (The Xianzhou Luofu)', jp='回憶の蕾・仙舟「羅浮」', es='Flor de los recuerdos (El Luofu de Xianzhou)', + dungeon_id=1011, plane_id=2021101, ) Calyx_Golden_Aether_The_Xianzhou_Luofu = DungeonList( @@ -51,6 +55,7 @@ Calyx_Golden_Aether_The_Xianzhou_Luofu = DungeonList( en='Bud of Aether (The Xianzhou Luofu)', jp='エーテルの蕾・仙舟「羅浮」', es='Flor de éter (El Luofu de Xianzhou)', + dungeon_id=1012, plane_id=2022101, ) Calyx_Golden_Treasures_The_Xianzhou_Luofu = DungeonList( @@ -61,6 +66,7 @@ Calyx_Golden_Treasures_The_Xianzhou_Luofu = DungeonList( en='Bud of Treasures (The Xianzhou Luofu)', jp='秘蔵の蕾・仙舟「羅浮」', es='Flor de tesoros (El Luofu de Xianzhou)', + dungeon_id=1013, plane_id=2022201, ) Calyx_Golden_Memories_Penacony = DungeonList( @@ -71,6 +77,7 @@ Calyx_Golden_Memories_Penacony = DungeonList( en='Bud of Memories (Penacony)', jp='回憶の蕾・ピノコニー', es='Flor de los recuerdos (Colonipenal)', + dungeon_id=1014, plane_id=2031301, ) Calyx_Golden_Aether_Penacony = DungeonList( @@ -81,6 +88,7 @@ Calyx_Golden_Aether_Penacony = DungeonList( en='Bud of Aether (Penacony)', jp='エーテルの蕾・ピノコニー', es='Flor de éter (Colonipenal)', + dungeon_id=1015, plane_id=2031201, ) Calyx_Golden_Treasures_Penacony = DungeonList( @@ -91,6 +99,7 @@ Calyx_Golden_Treasures_Penacony = DungeonList( en='Bud of Treasures (Penacony)', jp='秘蔵の蕾・ピノコニー', es='Flor de tesoros (Colonipenal)', + dungeon_id=1016, plane_id=2031101, ) Calyx_Crimson_Destruction_Herta_StorageZone = DungeonList( @@ -101,6 +110,7 @@ Calyx_Crimson_Destruction_Herta_StorageZone = DungeonList( en='Calyx (Crimson): Bud of Destruction', jp='疑似花萼(赤)・壊滅の蕾', es='Flor de la Destrucción', + dungeon_id=1004, plane_id=2000201, ) Calyx_Crimson_Destruction_Luofu_ScalegorgeWaterscape = DungeonList( @@ -111,6 +121,7 @@ Calyx_Crimson_Destruction_Luofu_ScalegorgeWaterscape = DungeonList( en='Calyx (Crimson): Bud of Destruction', jp='疑似花萼(赤)・壊滅の蕾', es='Flor de la Destrucción', + dungeon_id=1018, plane_id=2023201, ) Calyx_Crimson_Preservation_Herta_SupplyZone = DungeonList( @@ -121,6 +132,7 @@ Calyx_Crimson_Preservation_Herta_SupplyZone = DungeonList( en='Calyx (Crimson): Bud of Preservation', jp='疑似花萼(赤)・存護の蕾', es='Flor de la Conservación', + dungeon_id=1005, plane_id=2000301, ) Calyx_Crimson_Preservation_Penacony_ClockStudiosThemePark = DungeonList( @@ -131,6 +143,7 @@ Calyx_Crimson_Preservation_Penacony_ClockStudiosThemePark = DungeonList( en='Calyx (Crimson): Bud of Preservation', jp='疑似花萼(赤)・存護の蕾', es='Flor de la Conservación', + dungeon_id=1020, plane_id=2032101, ) Calyx_Crimson_The_Hunt_Jarilo_OutlyingSnowPlains = DungeonList( @@ -141,6 +154,7 @@ Calyx_Crimson_The_Hunt_Jarilo_OutlyingSnowPlains = DungeonList( en='Calyx (Crimson): Bud of The Hunt', jp='疑似花萼(赤)・巡狩の蕾', es='Flor de la Cacería', + dungeon_id=1006, plane_id=2010101, ) Calyx_Crimson_The_Hunt_Penacony_SoulGladScorchsandAuditionVenue = DungeonList( @@ -151,6 +165,7 @@ Calyx_Crimson_The_Hunt_Penacony_SoulGladScorchsandAuditionVenue = DungeonList( en='Calyx (Crimson): Bud of The Hunt', jp='疑似花萼(赤)・巡狩の蕾', es='Cáliz (carmesí): Flor de la Cacería', + dungeon_id=1022, plane_id=2033101, ) Calyx_Crimson_Abundance_Jarilo_BackwaterPass = DungeonList( @@ -161,6 +176,7 @@ Calyx_Crimson_Abundance_Jarilo_BackwaterPass = DungeonList( en='Calyx (Crimson): Bud of Abundance', jp='疑似花萼(赤)・豊穣の蕾', es='Flor de la Abundancia', + dungeon_id=1007, plane_id=2011101, ) Calyx_Crimson_Abundance_Luofu_FyxestrollGarden = DungeonList( @@ -171,6 +187,7 @@ Calyx_Crimson_Abundance_Luofu_FyxestrollGarden = DungeonList( en='Calyx (Crimson): Bud of Abundance', jp='疑似花萼(赤)・豊穣の蕾', es='Flor de la Abundancia', + dungeon_id=1021, plane_id=2022301, ) Calyx_Crimson_Erudition_Jarilo_RivetTown = DungeonList( @@ -181,6 +198,7 @@ Calyx_Crimson_Erudition_Jarilo_RivetTown = DungeonList( en='Calyx (Crimson): Bud of Erudition', jp='疑似花萼(赤)・知恵の蕾', es='Flor de la Erudición', + dungeon_id=1008, plane_id=2012201, ) Calyx_Crimson_Harmony_Jarilo_RobotSettlement = DungeonList( @@ -191,6 +209,7 @@ Calyx_Crimson_Harmony_Jarilo_RobotSettlement = DungeonList( en='Calyx (Crimson): Bud of Harmony', jp='疑似花萼(赤)・調和の蕾', es='Flor de la Armonía', + dungeon_id=1009, plane_id=2012301, ) Calyx_Crimson_Harmony_Penacony_TheReverieDreamscape = DungeonList( @@ -201,6 +220,7 @@ Calyx_Crimson_Harmony_Penacony_TheReverieDreamscape = DungeonList( en='Calyx (Crimson): Bud of Harmony', jp='疑似花萼(赤)・調和の蕾', es='Flor de la Armonía', + dungeon_id=1019, plane_id=2031101, ) Calyx_Crimson_Nihility_Jarilo_GreatMine = DungeonList( @@ -211,6 +231,7 @@ Calyx_Crimson_Nihility_Jarilo_GreatMine = DungeonList( en='Calyx (Crimson): Bud of Nihility', jp='疑似花萼(赤)・虚無の蕾', es='Flor de la Nihilidad', + dungeon_id=1010, plane_id=2012101, ) Calyx_Crimson_Nihility_Luofu_AlchemyCommission = DungeonList( @@ -221,6 +242,7 @@ Calyx_Crimson_Nihility_Luofu_AlchemyCommission = DungeonList( en='Calyx (Crimson): Bud of Nihility', jp='疑似花萼(赤)・虚無の蕾', es='Flor de la Nihilidad', + dungeon_id=1017, plane_id=2023101, ) Stagnant_Shadow_Quanta = DungeonList( @@ -231,6 +253,7 @@ Stagnant_Shadow_Quanta = DungeonList( en='Stagnant Shadow: Shape of Quanta', jp='凝結虚影・虚海の形', es='Forma del cuanto', + dungeon_id=1101, plane_id=2000101, ) Stagnant_Shadow_Gust = DungeonList( @@ -241,6 +264,7 @@ Stagnant_Shadow_Gust = DungeonList( en='Stagnant Shadow: Shape of Gust', jp='凝結虚影・薫風の形', es='Forma del aire', + dungeon_id=1102, plane_id=2012201, ) Stagnant_Shadow_Fulmination = DungeonList( @@ -251,6 +275,7 @@ Stagnant_Shadow_Fulmination = DungeonList( en='Stagnant Shadow: Shape of Fulmination', jp='凝結虚影・鳴雷の形', es='Forma del trueno', + dungeon_id=1103, plane_id=2013201, ) Stagnant_Shadow_Blaze = DungeonList( @@ -261,6 +286,7 @@ Stagnant_Shadow_Blaze = DungeonList( en='Stagnant Shadow: Shape of Blaze', jp='凝結虚影・炎華の形', es='Forma de las llamas', + dungeon_id=1104, plane_id=2013101, ) Stagnant_Shadow_Spike = DungeonList( @@ -271,6 +297,7 @@ Stagnant_Shadow_Spike = DungeonList( en='Stagnant Shadow: Shape of Spike', jp='凝結虚影・切先の形', es='Forma afilada', + dungeon_id=1105, plane_id=2012101, ) Stagnant_Shadow_Rime = DungeonList( @@ -281,6 +308,7 @@ Stagnant_Shadow_Rime = DungeonList( en='Stagnant Shadow: Shape of Rime', jp='凝結虚影・霜晶の形', es='Forma de la escarcha', + dungeon_id=1106, plane_id=2013201, ) Stagnant_Shadow_Mirage = DungeonList( @@ -291,6 +319,7 @@ Stagnant_Shadow_Mirage = DungeonList( en='Stagnant Shadow: Shape of Mirage', jp='凝結虚影・幻光の形', es='Forma del espejismo', + dungeon_id=1107, plane_id=2011101, ) Stagnant_Shadow_Icicle = DungeonList( @@ -301,6 +330,7 @@ Stagnant_Shadow_Icicle = DungeonList( en='Stagnant Shadow: Shape of Icicle', jp='凝結虚影・氷柱の形', es='Forma del témpano', + dungeon_id=1108, plane_id=2021101, ) Stagnant_Shadow_Doom = DungeonList( @@ -311,6 +341,7 @@ Stagnant_Shadow_Doom = DungeonList( en='Stagnant Shadow: Shape of Doom', jp='凝結虚影・震厄の形', es='Forma de la perdición', + dungeon_id=1109, plane_id=2021201, ) Stagnant_Shadow_Puppetry = DungeonList( @@ -321,6 +352,7 @@ Stagnant_Shadow_Puppetry = DungeonList( en='Stagnant Shadow: Shape of Puppetry', jp='凝結虚影・傀儡の形', es='Forma de las marionetas', + dungeon_id=1110, plane_id=2022201, ) Stagnant_Shadow_Abomination = DungeonList( @@ -331,6 +363,7 @@ Stagnant_Shadow_Abomination = DungeonList( en='Stagnant Shadow: Shape of Abomination', jp='凝結虚影・厄獣の形', es='Forma de la abominación', + dungeon_id=1111, plane_id=2023201, ) Stagnant_Shadow_Scorch = DungeonList( @@ -341,6 +374,7 @@ Stagnant_Shadow_Scorch = DungeonList( en='Stagnant Shadow: Shape of Scorch', jp='凝結虚影・燔灼の形', es='Forma abrasada', + dungeon_id=1112, plane_id=2012101, ) Stagnant_Shadow_Celestial = DungeonList( @@ -351,6 +385,7 @@ Stagnant_Shadow_Celestial = DungeonList( en='Stagnant Shadow: Shape of Celestial', jp='凝結虚影・天人の形', es='Forma de lo celestial', + dungeon_id=1113, plane_id=2023101, ) Stagnant_Shadow_Perdition = DungeonList( @@ -361,6 +396,7 @@ Stagnant_Shadow_Perdition = DungeonList( en='Stagnant Shadow: Shape of Perdition', jp='凝結虚影・幽府の形', es='Forma del aislamiento', + dungeon_id=1114, plane_id=2022301, ) Stagnant_Shadow_Nectar = DungeonList( @@ -371,6 +407,7 @@ Stagnant_Shadow_Nectar = DungeonList( en='Stagnant Shadow: Shape of Nectar', jp='凝結虚影・氷醸の形', es='Forma del néctar', + dungeon_id=1115, plane_id=2031101, ) Stagnant_Shadow_Roast = DungeonList( @@ -381,6 +418,7 @@ Stagnant_Shadow_Roast = DungeonList( en='Stagnant Shadow: Shape of Roast', jp='凝結虚影・焦灼の形', es='Forma del agostamiento', + dungeon_id=1116, plane_id=2031301, ) Stagnant_Shadow_Ire = DungeonList( @@ -391,6 +429,7 @@ Stagnant_Shadow_Ire = DungeonList( en='Stagnant Shadow: Shape of Ire', jp='凝結虚影・憤怒の形', es='Forma de la ira', + dungeon_id=1117, plane_id=2032201, ) Stagnant_Shadow_Duty = DungeonList( @@ -401,6 +440,7 @@ Stagnant_Shadow_Duty = DungeonList( en='Stagnant Shadow: Shape of Duty', jp='凝結虚影・職掌の形', es='Sombra paralizada: Forma del deber', + dungeon_id=1118, plane_id=2032101, ) Cavern_of_Corrosion_Path_of_Gelid_Wind = DungeonList( @@ -411,6 +451,7 @@ Cavern_of_Corrosion_Path_of_Gelid_Wind = DungeonList( en='Cavern of Corrosion: Path of Gelid Wind', jp='侵蝕トンネル・霜風の路', es='Senda del viento gélido', + dungeon_id=1201, plane_id=2000201, ) Cavern_of_Corrosion_Path_of_Jabbing_Punch = DungeonList( @@ -421,6 +462,7 @@ Cavern_of_Corrosion_Path_of_Jabbing_Punch = DungeonList( en='Cavern of Corrosion: Path of Jabbing Punch', jp='侵蝕トンネル・迅拳の路', es='Senda de los puños rápidos', + dungeon_id=1202, plane_id=2013101, ) Cavern_of_Corrosion_Path_of_Drifting = DungeonList( @@ -431,6 +473,7 @@ Cavern_of_Corrosion_Path_of_Drifting = DungeonList( en='Cavern of Corrosion: Path of Drifting', jp='侵蝕トンネル・漂泊の路', es='Senda de la deriva', + dungeon_id=1203, plane_id=2013201, ) Cavern_of_Corrosion_Path_of_Providence = DungeonList( @@ -441,6 +484,7 @@ Cavern_of_Corrosion_Path_of_Providence = DungeonList( en='Cavern of Corrosion: Path of Providence', jp='侵蝕トンネル・睿治の路', es='Senda de la providencia', + dungeon_id=1204, plane_id=2013401, ) Cavern_of_Corrosion_Path_of_Holy_Hymn = DungeonList( @@ -451,6 +495,7 @@ Cavern_of_Corrosion_Path_of_Holy_Hymn = DungeonList( en='Cavern of Corrosion: Path of Holy Hymn', jp='侵蝕トンネル・聖頌の路', es='Senda del himno sagrado', + dungeon_id=1205, plane_id=2021101, ) Cavern_of_Corrosion_Path_of_Conflagration = DungeonList( @@ -461,6 +506,7 @@ Cavern_of_Corrosion_Path_of_Conflagration = DungeonList( en='Cavern of Corrosion: Path of Conflagration', jp='侵蝕トンネル・野焔の路', es='Senda de la conflagración', + dungeon_id=1206, plane_id=2021201, ) Cavern_of_Corrosion_Path_of_Elixir_Seekers = DungeonList( @@ -471,6 +517,7 @@ Cavern_of_Corrosion_Path_of_Elixir_Seekers = DungeonList( en='Cavern of Corrosion: Path of Elixir Seekers', jp='侵蝕トンネル・薬使の路', es='Senda de los elixires', + dungeon_id=1207, plane_id=2023101, ) Cavern_of_Corrosion_Path_of_Darkness = DungeonList( @@ -481,6 +528,7 @@ Cavern_of_Corrosion_Path_of_Darkness = DungeonList( en='Cavern of Corrosion: Path of Darkness', jp='侵蝕トンネル・幽冥の路', es='Senda de la oscuridad', + dungeon_id=1208, plane_id=2022301, ) Cavern_of_Corrosion_Path_of_Dreamdive = DungeonList( @@ -491,6 +539,7 @@ Cavern_of_Corrosion_Path_of_Dreamdive = DungeonList( en='Cavern of Corrosion: Path of Dreamdive', jp='侵蝕トンネル・夢潜の路', es='Senda de los sueños', + dungeon_id=1209, plane_id=2031101, ) Echo_of_War_Destruction_Beginning = DungeonList( @@ -501,6 +550,7 @@ Echo_of_War_Destruction_Beginning = DungeonList( en="Echo of War: Destruction's Beginning", jp='歴戦余韻・壊滅の始まり', es='El principio de la Destrucción', + dungeon_id=1301, plane_id=2000301, ) Echo_of_War_End_of_the_Eternal_Freeze = DungeonList( @@ -511,6 +561,7 @@ Echo_of_War_End_of_the_Eternal_Freeze = DungeonList( en='Echo of War: End of the Eternal Freeze', jp='歴戦余韻・寒波の幕切れ', es='El fin del Hielo Eterno', + dungeon_id=1302, plane_id=2013401, ) Echo_of_War_Divine_Seed = DungeonList( @@ -521,6 +572,7 @@ Echo_of_War_Divine_Seed = DungeonList( en='Echo of War: Divine Seed', jp='歴戦余韻・不死の神実', es='Semilla divina', + dungeon_id=1303, plane_id=2023201, ) Echo_of_War_Borehole_Planet_Old_Crater = DungeonList( @@ -531,6 +583,7 @@ Echo_of_War_Borehole_Planet_Old_Crater = DungeonList( en="Echo of War: Borehole Planet's Old Crater", jp='歴戦余韻・星を蝕む往日の面影', es='Cráter del planeta devorado', + dungeon_id=1304, plane_id=2000401, ) Echo_of_War_Salutations_of_Ashen_Dreams = DungeonList( @@ -541,6 +594,7 @@ Echo_of_War_Salutations_of_Ashen_Dreams = DungeonList( en='Echo of War: Salutations of Ashen Dreams', jp='歴戦余韻・現世の夢の礼賛', es='Ecos de la guerra: Tributo del sueño ceniciento', + dungeon_id=1305, plane_id=2033201, ) Simulated_Universe_World_1 = DungeonList( @@ -551,6 +605,7 @@ Simulated_Universe_World_1 = DungeonList( en='Simulated Universe: World 1', jp='第一世界・模擬宇宙', es='Mundo 1', + dungeon_id=110, plane_id=100000104, ) Simulated_Universe_World_3 = DungeonList( @@ -561,6 +616,7 @@ Simulated_Universe_World_3 = DungeonList( en='Simulated Universe: World 3', jp='第三世界・模擬宇宙', es='Mundo 3', + dungeon_id=130, plane_id=100000104, ) Simulated_Universe_World_4 = DungeonList( @@ -571,6 +627,7 @@ Simulated_Universe_World_4 = DungeonList( en='Simulated Universe: World 4', jp='第四世界・模擬宇宙', es='Mundo 4', + dungeon_id=140, plane_id=100000104, ) Simulated_Universe_World_5 = DungeonList( @@ -581,6 +638,7 @@ Simulated_Universe_World_5 = DungeonList( en='Simulated Universe: World 5', jp='第五世界・模擬宇宙', es='Mundo 5', + dungeon_id=150, plane_id=100000104, ) Simulated_Universe_World_6 = DungeonList( @@ -591,6 +649,7 @@ Simulated_Universe_World_6 = DungeonList( en='Simulated Universe: World 6', jp='第六世界・模擬宇宙', es='Mundo 6', + dungeon_id=160, plane_id=100000104, ) Simulated_Universe_World_7 = DungeonList( @@ -601,6 +660,7 @@ Simulated_Universe_World_7 = DungeonList( en='Simulated Universe: World 7', jp='第七世界・模擬宇宙', es='Mundo 7', + dungeon_id=170, plane_id=100000104, ) Simulated_Universe_World_8 = DungeonList( @@ -611,6 +671,7 @@ Simulated_Universe_World_8 = DungeonList( en='Simulated Universe: World 8', jp='第八世界・模擬宇宙', es='Mundo 8', + dungeon_id=180, plane_id=100000104, ) Simulated_Universe_World_9 = DungeonList( @@ -621,6 +682,7 @@ Simulated_Universe_World_9 = DungeonList( en='Simulated Universe: World 9', jp='第九世界・模擬宇宙', es='Mundo 9', + dungeon_id=190, plane_id=100000104, ) Simulated_Universe_The_Swarm_Disaster = DungeonList( @@ -631,6 +693,7 @@ Simulated_Universe_The_Swarm_Disaster = DungeonList( en='The Swarm Disaster', jp='宇宙の蝗害', es='La Plaga', + dungeon_id=-1, plane_id=-1, ) Simulated_Universe_Gold_and_Gears = DungeonList( @@ -641,6 +704,7 @@ Simulated_Universe_Gold_and_Gears = DungeonList( en='Gold and Gears', jp='黄金と機械', es='Oro y maquinaria', + dungeon_id=-1, plane_id=-1, ) Memory_of_Chaos = DungeonList( @@ -651,6 +715,7 @@ Memory_of_Chaos = DungeonList( en='Memory of Chaos', jp='混沌の記憶', es='Evocación caótica', + dungeon_id=-1, plane_id=-1, ) The_Voyage_of_Navis_Astriger = DungeonList( @@ -661,6 +726,7 @@ The_Voyage_of_Navis_Astriger = DungeonList( en='The Voyage of Navis Astriger', jp='天艟求仙放浪記', es='El viaje de las naves astriger', + dungeon_id=-1, plane_id=-1, ) The_Last_Vestiges_of_Towering_Citadel = DungeonList( @@ -671,5 +737,6 @@ The_Last_Vestiges_of_Towering_Citadel = DungeonList( en='The Last Vestiges of Towering Citadel', jp='永屹の城の秘密', es='Herencia de la Ciudadela Imponente', + dungeon_id=-1, plane_id=-1, ) diff --git a/tasks/planner/keywords/__init__.py b/tasks/planner/keywords/__init__.py new file mode 100644 index 000000000..81a8d2fc4 --- /dev/null +++ b/tasks/planner/keywords/__init__.py @@ -0,0 +1,9 @@ +import tasks.planner.keywords.item_ascension as KEYWORDS_ITEM_ASCENSION +import tasks.planner.keywords.item_calyx as KEYWORDS_ITEM_CALYX +import tasks.planner.keywords.item_currency as KEYWORDS_ITEM_CURRENCY +import tasks.planner.keywords.item_exp as KEYWORDS_ITEM_EXP +import tasks.planner.keywords.item_trace as KEYWORDS_ITEM_TRACE +import tasks.planner.keywords.item_weekly as KEYWORDS_ITEM_WEEKLY +from tasks.planner.keywords.classes import ItemAscension, ItemCalyx, ItemCurrency, ItemExp, ItemTrace, ItemWeekly + +ITEM_CLASSES = [ItemAscension, ItemCalyx, ItemCurrency, ItemExp, ItemTrace, ItemWeekly] diff --git a/tasks/planner/keywords/classes.py b/tasks/planner/keywords/classes.py new file mode 100644 index 000000000..2066209ed --- /dev/null +++ b/tasks/planner/keywords/classes.py @@ -0,0 +1,59 @@ +from dataclasses import dataclass +from functools import cached_property +from typing import ClassVar + +from module.ocr.keyword import Keyword + + +@dataclass(repr=False) +class ItemBase(Keyword): + instances: ClassVar = {} + + rarity: str + item_id: int + item_group: int + dungeon_id: int + + @cached_property + def dungeon(self): + """ + Dungeon that drops this item + + Returns: + DungeonList: DungeonList object or None + """ + if self.dungeon_id > 0: + from tasks.dungeon.keywords.classes import DungeonList + return DungeonList.find_dungeon_id(self.dungeon_id) + else: + return None + + +@dataclass(repr=False) +class ItemAscension(ItemBase): + instances: ClassVar = {} + + +@dataclass(repr=False) +class ItemCalyx(ItemBase): + instances: ClassVar = {} + + +@dataclass(repr=False) +class ItemCurrency(ItemBase): + instances: ClassVar = {} + + +@dataclass(repr=False) +class ItemExp(ItemBase): + instances: ClassVar = {} + + +@dataclass(repr=False) +class ItemTrace(ItemBase): + instances: ClassVar = {} + + +@dataclass(repr=False) +class ItemWeekly(ItemBase): + instances: ClassVar = {} diff --git a/tasks/planner/keywords/item_ascension.py b/tasks/planner/keywords/item_ascension.py new file mode 100644 index 000000000..1da25b870 --- /dev/null +++ b/tasks/planner/keywords/item_ascension.py @@ -0,0 +1,252 @@ +from .classes import ItemAscension + +# This file was auto-generated, do not modify it manually. To generate: +# ``` python -m dev_tools.keyword_extract ``` + +Enigmatic_Ectostella = ItemAscension( + id=1, + name='Enigmatic_Ectostella', + cn='深邃的星外质', + cht='深邃的星外質', + en='Enigmatic Ectostella', + jp='深邃な星外物質', + es='Ectoestela profunda', + rarity='VeryRare', + item_id=110400, + item_group=1100, + dungeon_id=-1, +) +Broken_Teeth_of_Iron_Wolf = ItemAscension( + id=2, + name='Broken_Teeth_of_Iron_Wolf', + cn='铁狼碎齿', + cht='鐵狼碎齒', + en='Broken Teeth of Iron Wolf', + jp='鉄狼の砕けた刃', + es='Dientes rotos del huargo férreo', + rarity='VeryRare', + item_id=110401, + item_group=1100, + dungeon_id=1105, +) +Endotherm_Chitin = ItemAscension( + id=3, + name='Endotherm_Chitin', + cn='恒温晶壳', + cht='恆溫晶殼', + en='Endotherm Chitin', + jp='恒温晶殻', + es='Quitina endoterma', + rarity='VeryRare', + item_id=110402, + item_group=1100, + dungeon_id=1104, +) +Horn_of_Snow = ItemAscension( + id=4, + name='Horn_of_Snow', + cn='风雪之角', + cht='風雪之角', + en='Horn of Snow', + jp='吹雪の角', + es='Cuerno de nieve', + rarity='VeryRare', + item_id=110403, + item_group=1100, + dungeon_id=1106, +) +Lightning_Crown_of_the_Past_Shadow = ItemAscension( + id=5, + name='Lightning_Crown_of_the_Past_Shadow', + cn='往日之影的雷冠', + cht='往日之影的雷冠', + en='Lightning Crown of the Past Shadow', + jp='過去の影の雷冠', + es='Corona de rayos de la sombra del pasado', + rarity='VeryRare', + item_id=110404, + item_group=1100, + dungeon_id=1103, +) +Storm_Eye = ItemAscension( + id=6, + name='Storm_Eye', + cn='暴风之眼', + cht='暴風之眼', + en='Storm Eye', + jp='暴風の眼', + es='Ojo del vendaval', + rarity='VeryRare', + item_id=110405, + item_group=1100, + dungeon_id=1102, +) +Void_Cast_Iron = ItemAscension( + id=7, + name='Void_Cast_Iron', + cn='虚幻铸铁', + cht='虛幻鑄鐵', + en='Void Cast Iron', + jp='虚幻鋳鉄', + es='Hierro forjado del Vacío', + rarity='VeryRare', + item_id=110406, + item_group=1100, + dungeon_id=1101, +) +Golden_Crown_of_the_Past_Shadow = ItemAscension( + id=8, + name='Golden_Crown_of_the_Past_Shadow', + cn='往日之影的金饰', + cht='往日之影的金飾', + en='Golden Crown of the Past Shadow', + jp='過去の影の金装飾', + es='Corona dorada de la sombra del pasado', + rarity='VeryRare', + item_id=110407, + item_group=1100, + dungeon_id=1107, +) +Netherworld_Token = ItemAscension( + id=9, + name='Netherworld_Token', + cn='幽府通令', + cht='幽府通令', + en='Netherworld Token', + jp='幽府通令', + es='Pase del inframundo', + rarity='VeryRare', + item_id=110411, + item_group=1100, + dungeon_id=1114, +) +Searing_Steel_Blade = ItemAscension( + id=10, + name='Searing_Steel_Blade', + cn='过热钢刃', + cht='過熱鋼刃', + en='Searing Steel Blade', + jp='灼熱の鋼刃', + es='Hoja de acero sobrecalentado', + rarity='VeryRare', + item_id=110412, + item_group=1100, + dungeon_id=1112, +) +Gelid_Chitin = ItemAscension( + id=11, + name='Gelid_Chitin', + cn='苦寒晶壳', + cht='苦寒晶殼', + en='Gelid Chitin', + jp='苦寒晶殻', + es='Quitina gélida', + rarity='VeryRare', + item_id=110413, + item_group=1100, + dungeon_id=1108, +) +Shape_Shifter_Lightning_Staff = ItemAscension( + id=12, + name='Shape_Shifter_Lightning_Staff', + cn='炼形者雷枝', + cht='煉形者雷枝', + en="Shape Shifter's Lightning Staff", + jp='鍛錬者の雷枝', + es='Báculo del Cambiaformas', + rarity='VeryRare', + item_id=110414, + item_group=1100, + dungeon_id=1109, +) +Ascendant_Debris = ItemAscension( + id=13, + name='Ascendant_Debris', + cn='天人遗垢', + cht='天人遺垢', + en='Ascendant Debris', + jp='天人の残穢', + es='Vestigios elevados', + rarity='VeryRare', + item_id=110415, + item_group=1100, + dungeon_id=1113, +) +Nail_of_the_Ape = ItemAscension( + id=14, + name='Nail_of_the_Ape', + cn='苍猿之钉', + cht='蒼猿之釘', + en='Nail of the Ape', + jp='蒼猿の釘', + es='Clavo del simio', + rarity='VeryRare', + item_id=110416, + item_group=1100, + dungeon_id=1111, +) +Suppressing_Edict = ItemAscension( + id=15, + name='Suppressing_Edict', + cn='镇灵敕符', + cht='鎮靈敕符', + en='Suppressing Edict', + jp='霊鎮めの勅符', + es='Edicto de la contención', + rarity='VeryRare', + item_id=110417, + item_group=1100, + dungeon_id=1110, +) +IPC_Work_Permit = ItemAscension( + id=16, + name='IPC_Work_Permit', + cn='星际和平工作证', + cht='星際和平工作證', + en='IPC Work Permit', + jp='スターピース社員証', + es='Permiso de trabajo de la Corporación', + rarity='VeryRare', + item_id=110421, + item_group=1100, + dungeon_id=1118, +) +Raging_Heart = ItemAscension( + id=17, + name='Raging_Heart', + cn='忿火之心', + cht='忿火之心', + en='Raging Heart', + jp='憤怒の心', + es='Corazón ardiente', + rarity='VeryRare', + item_id=110422, + item_group=1100, + dungeon_id=1117, +) +Dream_Fridge = ItemAscension( + id=18, + name='Dream_Fridge', + cn='冷藏梦箱', + cht='冷藏夢箱', + en='Dream Fridge', + jp='夢の冷蔵庫', + es='Nevera de sueños', + rarity='VeryRare', + item_id=110423, + item_group=1100, + dungeon_id=1115, +) +Dream_Flamer = ItemAscension( + id=19, + name='Dream_Flamer', + cn='炙梦喷枪', + cht='炙夢噴槍', + en='Dream Flamer', + jp='夢を炙るトーチバーナー', + es='Soplete de ensueño', + rarity='VeryRare', + item_id=110426, + item_group=1100, + dungeon_id=1116, +) diff --git a/tasks/planner/keywords/item_calyx.py b/tasks/planner/keywords/item_calyx.py new file mode 100644 index 000000000..d46e7be26 --- /dev/null +++ b/tasks/planner/keywords/item_calyx.py @@ -0,0 +1,317 @@ +from .classes import ItemCalyx + +# This file was auto-generated, do not modify it manually. To generate: +# ``` python -m dev_tools.keyword_extract ``` + +Extinguished_Core = ItemCalyx( + id=1, + name='Extinguished_Core', + cn='熄灭原核', + cht='熄滅原核', + en='Extinguished Core', + jp='消滅した原核', + es='Núcleo apagado', + rarity='NotNormal', + item_id=111001, + item_group=1401, + dungeon_id=1011, +) +Glimmering_Core = ItemCalyx( + id=2, + name='Glimmering_Core', + cn='微光原核', + cht='微光原核', + en='Glimmering Core', + jp='微かに光る原核', + es='Núcleo reluciente', + rarity='Rare', + item_id=111002, + item_group=1401, + dungeon_id=-1, +) +Squirming_Core = ItemCalyx( + id=3, + name='Squirming_Core', + cn='蠢动原核', + cht='蠢動原核', + en='Squirming Core', + jp='脈動する原核', + es='Núcleo serpenteante', + rarity='VeryRare', + item_id=111003, + item_group=1401, + dungeon_id=-1, +) +Thief_Instinct = ItemCalyx( + id=4, + name='Thief_Instinct', + cn='掠夺的本能', + cht='掠奪的本能', + en="Thief's Instinct", + jp='略奪の本能', + es='Instinto del ladrón', + rarity='NotNormal', + item_id=111011, + item_group=1402, + dungeon_id=1001, +) +Usurper_Scheme = ItemCalyx( + id=5, + name='Usurper_Scheme', + cn='篡改的野心', + cht='篡改的野心', + en="Usurper's Scheme", + jp='改ざんの野心', + es='Ambición distorsionada', + rarity='Rare', + item_id=111012, + item_group=1402, + dungeon_id=-1, +) +Conqueror_Will = ItemCalyx( + id=6, + name='Conqueror_Will', + cn='践踏的意志', + cht='踐踏的意志', + en="Conqueror's Will", + jp='踏みにじる意志', + es='Voluntad de conquista', + rarity='VeryRare', + item_id=111013, + item_group=1402, + dungeon_id=-1, +) +Silvermane_Badge = ItemCalyx( + id=7, + name='Silvermane_Badge', + cn='铁卫扣饰', + cht='鐵衛扣飾', + en='Silvermane Badge', + jp='シルバーメインの釦', + es='Pin del guardia', + rarity='NotNormal', + item_id=112001, + item_group=1403, + dungeon_id=1001, +) +Silvermane_Insignia = ItemCalyx( + id=8, + name='Silvermane_Insignia', + cn='铁卫军徽', + cht='鐵衛軍徽', + en='Silvermane Insignia', + jp='シルバーメインの記章', + es='Insignia del guardia', + rarity='Rare', + item_id=112002, + item_group=1403, + dungeon_id=-1, +) +Silvermane_Medal = ItemCalyx( + id=9, + name='Silvermane_Medal', + cn='铁卫勋章', + cht='鐵衛勳章', + en='Silvermane Medal', + jp='シルバーメインの勲章', + es='Medalla del guardia', + rarity='VeryRare', + item_id=112003, + item_group=1403, + dungeon_id=-1, +) +Ancient_Part = ItemCalyx( + id=10, + name='Ancient_Part', + cn='古代零件', + cht='古代零件', + en='Ancient Part', + jp='古代パーツ', + es='Componente antiguo', + rarity='NotNormal', + item_id=112011, + item_group=1404, + dungeon_id=1001, +) +Ancient_Spindle = ItemCalyx( + id=11, + name='Ancient_Spindle', + cn='古代转轴', + cht='古代轉軸', + en='Ancient Spindle', + jp='古代シャフト', + es='Eje antiguo', + rarity='Rare', + item_id=112012, + item_group=1404, + dungeon_id=-1, +) +Ancient_Engine = ItemCalyx( + id=12, + name='Ancient_Engine', + cn='古代引擎', + cht='古代引擎', + en='Ancient Engine', + jp='古代エンジン', + es='Motor antiguo', + rarity='VeryRare', + item_id=112013, + item_group=1404, + dungeon_id=-1, +) +Immortal_Scionette = ItemCalyx( + id=13, + name='Immortal_Scionette', + cn='永寿幼芽', + cht='永壽幼芽', + en='Immortal Scionette', + jp='永寿の萌芽', + es='Brote verde inmortal', + rarity='NotNormal', + item_id=113001, + item_group=1405, + dungeon_id=1011, +) +Immortal_Aeroblossom = ItemCalyx( + id=14, + name='Immortal_Aeroblossom', + cn='永寿天华', + cht='永壽天華', + en='Immortal Aeroblossom', + jp='永寿の天華', + es='Flor etérea inmortal', + rarity='Rare', + item_id=113002, + item_group=1405, + dungeon_id=-1, +) +Immortal_Lumintwig = ItemCalyx( + id=15, + name='Immortal_Lumintwig', + cn='永寿荣枝', + cht='永壽榮枝', + en='Immortal Lumintwig', + jp='永寿の栄枝', + es='Rama gloriosa inmortal', + rarity='VeryRare', + item_id=113003, + item_group=1405, + dungeon_id=-1, +) +Artifex_Module = ItemCalyx( + id=16, + name='Artifex_Module', + cn='工造机杼', + cht='工造機杼', + en="Artifex's Module", + jp='工造機関', + es='Componente artificial mecánico', + rarity='NotNormal', + item_id=113011, + item_group=1406, + dungeon_id=1011, +) +Artifex_Cogwheel = ItemCalyx( + id=17, + name='Artifex_Cogwheel', + cn='工造迴轮', + cht='工造迴輪', + en="Artifex's Cogwheel", + jp='工造迴輪', + es='Engranaje cilíndrico mecánico', + rarity='Rare', + item_id=113012, + item_group=1406, + dungeon_id=-1, +) +Artifex_Gyreheart = ItemCalyx( + id=18, + name='Artifex_Gyreheart', + cn='工造浑心', + cht='工造渾心', + en="Artifex's Gyreheart", + jp='工造渾心', + es='Corazón armonioso mecánico', + rarity='VeryRare', + item_id=113013, + item_group=1406, + dungeon_id=-1, +) +Dream_Collection_Component = ItemCalyx( + id=19, + name='Dream_Collection_Component', + cn='蓄梦元件', + cht='蓄夢元件', + en='Dream Collection Component', + jp='ドリームコレクションパーツ', + es='Componente del acumulador de sueños', + rarity='NotNormal', + item_id=114001, + item_group=1407, + dungeon_id=1014, +) +Dream_Flow_Valve = ItemCalyx( + id=20, + name='Dream_Flow_Valve', + cn='流梦阀门', + cht='流夢閥門', + en='Dream Flow Valve', + jp='ドリームフローバルブ', + es='Válvula del flujo de sueños', + rarity='Rare', + item_id=114002, + item_group=1407, + dungeon_id=-1, +) +Dream_Making_Engine = ItemCalyx( + id=21, + name='Dream_Making_Engine', + cn='造梦马达', + cht='造夢馬達', + en='Dream Making Engine', + jp='ドリームメイキングモーター', + es='Motor creasueños', + rarity='VeryRare', + item_id=114003, + item_group=1407, + dungeon_id=-1, +) +Tatters_of_Thought = ItemCalyx( + id=22, + name='Tatters_of_Thought', + cn='思绪末屑', + cht='思緒末屑', + en='Tatters of Thought', + jp='思考の粉末', + es='Jirones de pensamientos', + rarity='NotNormal', + item_id=114011, + item_group=1408, + dungeon_id=1014, +) +Fragments_of_Impression = ItemCalyx( + id=23, + name='Fragments_of_Impression', + cn='印象残晶', + cht='印象殘晶', + en='Fragments of Impression', + jp='印象の残晶', + es='Fragmento de impresiones', + rarity='Rare', + item_id=114012, + item_group=1408, + dungeon_id=-1, +) +Shards_of_Desires = ItemCalyx( + id=24, + name='Shards_of_Desires', + cn='欲念碎镜', + cht='欲念碎鏡', + en='Shards of Desires', + jp='砕けた欲望の鏡', + es='Fragmento de deseos', + rarity='VeryRare', + item_id=114013, + item_group=1408, + dungeon_id=-1, +) diff --git a/tasks/planner/keywords/item_currency.py b/tasks/planner/keywords/item_currency.py new file mode 100644 index 000000000..90514f8ed --- /dev/null +++ b/tasks/planner/keywords/item_currency.py @@ -0,0 +1,226 @@ +from .classes import ItemCurrency + +# This file was auto-generated, do not modify it manually. To generate: +# ``` python -m dev_tools.keyword_extract ``` + +Stellar_Jade = ItemCurrency( + id=1, + name='Stellar_Jade', + cn='星琼', + cht='星瓊', + en='Stellar Jade', + jp='星玉', + es='Jade estelar', + rarity='SuperRare', + item_id=1, + item_group=0, + dungeon_id=-1, +) +Credit = ItemCurrency( + id=2, + name='Credit', + cn='信用点', + cht='信用點', + en='Credit', + jp='信用ポイント', + es='Crédito', + rarity='Rare', + item_id=2, + item_group=0, + dungeon_id=-1, +) +Oneiric_Shard = ItemCurrency( + id=3, + name='Oneiric_Shard', + cn='古老梦华', + cht='古老夢華', + en='Oneiric Shard', + jp='往日の夢華', + es='Esquirla onírica', + rarity='SuperRare', + item_id=3, + item_group=0, + dungeon_id=-1, +) +Trailblaze_Power = ItemCurrency( + id=4, + name='Trailblaze_Power', + cn='开拓力', + cht='開拓力', + en='Trailblaze Power', + jp='開拓力', + es='Poder trazacaminos', + rarity='VeryRare', + item_id=11, + item_group=0, + dungeon_id=-1, +) +Reserved_Trailblaze_Power = ItemCurrency( + id=5, + name='Reserved_Trailblaze_Power', + cn='后备开拓力', + cht='後備開拓力', + en='Reserved Trailblaze Power', + jp='予備開拓力', + es='Poder trazacaminos de reserva', + rarity='VeryRare', + item_id=12, + item_group=0, + dungeon_id=-1, +) +EXP = ItemCurrency( + id=6, + name='EXP', + cn='经验', + cht='經驗', + en='EXP', + jp='経験', + es='EXP', + rarity='Rare', + item_id=21, + item_group=0, + dungeon_id=-1, +) +Trailblaze_EXP = ItemCurrency( + id=7, + name='Trailblaze_EXP', + cn='里程', + cht='里程', + en='Trailblaze EXP', + jp='マイレージ', + es='EXP trazacaminos', + rarity='Rare', + item_id=22, + item_group=0, + dungeon_id=-1, +) +Activity = ItemCurrency( + id=8, + name='Activity', + cn='活跃度', + cht='活躍度', + en='Activity', + jp='アクティブ度', + es='Actividad', + rarity='Rare', + item_id=23, + item_group=0, + dungeon_id=-1, +) +Trailblaze_Timer = ItemCurrency( + id=9, + name='Trailblaze_Timer', + cn='开拓进行时', + cht='開拓進行時', + en='Trailblaze Timer', + jp='開拓進行計', + es='Trazascopio', + rarity='SuperRare', + item_id=24, + item_group=0, + dungeon_id=-1, +) +The_Returning_Trail = ItemCurrency( + id=10, + name='The_Returning_Trail', + cn='归程轨迹', + cht='歸程軌跡', + en='The Returning Trail', + jp='帰還の軌跡', + es='Trayectoria de regreso', + rarity='Rare', + item_id=25, + item_group=0, + dungeon_id=-1, +) +Cosmic_Fragment = ItemCurrency( + id=11, + name='Cosmic_Fragment', + cn='宇宙碎片', + cht='宇宙碎片', + en='Cosmic Fragment', + jp='宇宙の欠片', + es='Fragmentos cósmicos', + rarity='Rare', + item_id=31, + item_group=0, + dungeon_id=-1, +) +Ability_Point = ItemCurrency( + id=12, + name='Ability_Point', + cn='技能点', + cht='技能點', + en='Ability Point', + jp='アビリティポイント', + es='Punto de habilidad', + rarity='Rare', + item_id=32, + item_group=0, + dungeon_id=-1, +) +Immersifier = ItemCurrency( + id=13, + name='Immersifier', + cn='沉浸器', + cht='沉浸器', + en='Immersifier', + jp='没入器', + es='Inmersor', + rarity='VeryRare', + item_id=33, + item_group=0, + dungeon_id=-1, +) +Achievement_Points = ItemCurrency( + id=14, + name='Achievement_Points', + cn='成就点数', + cht='成就點數', + en='Achievement Points', + jp='アチーブメントポイント', + es='Puntos de logro', + rarity='Rare', + item_id=41, + item_group=0, + dungeon_id=-1, +) +The_Nameless_EXP = ItemCurrency( + id=15, + name='The_Nameless_EXP', + cn='无名客的经验', + cht='無名客的經驗', + en='The Nameless EXP', + jp='ナナシビトの経験', + es='EXP anónima', + rarity='Rare', + item_id=51, + item_group=0, + dungeon_id=-1, +) +The_Nameless_EXP = ItemCurrency( + id=16, + name='The_Nameless_EXP', + cn='无名客的经验', + cht='無名客的經驗', + en='The Nameless EXP', + jp='ナナシビトの経験', + es='EXP anónima', + rarity='Rare', + item_id=52, + item_group=0, + dungeon_id=-1, +) +Development_Fund = ItemCurrency( + id=17, + name='Development_Fund', + cn='发展资金', + cht='發展資金', + en='Development Fund', + jp='発展資金', + es='Fondos de desarrollo', + rarity='Rare', + item_id=53, + item_group=0, + dungeon_id=-1, +) diff --git a/tasks/planner/keywords/item_exp.py b/tasks/planner/keywords/item_exp.py new file mode 100644 index 000000000..b1d81fc29 --- /dev/null +++ b/tasks/planner/keywords/item_exp.py @@ -0,0 +1,135 @@ +from .classes import ItemExp + +# This file was auto-generated, do not modify it manually. To generate: +# ``` python -m dev_tools.keyword_extract ``` + +Travel_Encounters = ItemExp( + id=1, + name='Travel_Encounters', + cn='旅情见闻', + cht='旅情見聞', + en='Travel Encounters', + jp='旅の見聞', + es='Noticias del viaje', + rarity='NotNormal', + item_id=211, + item_group=1010, + dungeon_id=1001, +) +Adventure_Log = ItemExp( + id=2, + name='Adventure_Log', + cn='冒险记录', + cht='冒險紀錄', + en='Adventure Log', + jp='冒険記録', + es='Registro de aventuras', + rarity='Rare', + item_id=212, + item_group=1010, + dungeon_id=1001, +) +Traveler_Guide = ItemExp( + id=3, + name='Traveler_Guide', + cn='漫游指南', + cht='漫遊指南', + en="Traveler's Guide", + jp='漫遊指南', + es='Guía del espíritu viajero', + rarity='VeryRare', + item_id=213, + item_group=1010, + dungeon_id=1001, +) +Sparse_Aether = ItemExp( + id=4, + name='Sparse_Aether', + cn='稀薄以太', + cht='稀薄乙太', + en='Sparse Aether', + jp='希薄なエーテル', + es='Éter disperso', + rarity='NotNormal', + item_id=221, + item_group=1020, + dungeon_id=1002, +) +Condensed_Aether = ItemExp( + id=5, + name='Condensed_Aether', + cn='凝缩以太', + cht='凝縮乙太', + en='Condensed Aether', + jp='濃縮エーテル', + es='Éter condensado', + rarity='Rare', + item_id=222, + item_group=1020, + dungeon_id=1002, +) +Refined_Aether = ItemExp( + id=6, + name='Refined_Aether', + cn='提纯以太', + cht='精煉乙太', + en='Refined Aether', + jp='精製エーテル', + es='Éter refinado', + rarity='VeryRare', + item_id=223, + item_group=1020, + dungeon_id=1002, +) +Lost_Lightdust = ItemExp( + id=7, + name='Lost_Lightdust', + cn='遗失光尘', + cht='遺失光塵', + en='Lost Lightdust', + jp='遺失光塵', + es='Polvo luminoso perdido', + rarity='NotNormal', + item_id=231, + item_group=1030, + dungeon_id=-1, +) +Lost_Gold_Fragment = ItemExp( + id=8, + name='Lost_Gold_Fragment', + cn='遗失碎金', + cht='遺失碎金', + en='Lost Gold Fragment', + jp='遺失砕金', + es='Fragmento dorado perdido', + rarity='Rare', + item_id=232, + item_group=1030, + dungeon_id=-1, +) +Lost_Crystal = ItemExp( + id=9, + name='Lost_Crystal', + cn='遗失晶块', + cht='遺失晶塊', + en='Lost Crystal', + jp='遺失晶塊', + es='Cristal perdido', + rarity='VeryRare', + item_id=233, + item_group=1030, + dungeon_id=-1, +) +Lost_Essence = ItemExp( + id=10, + name='Lost_Essence', + cn='遗失精粹', + cht='遺失精粹', + en='Lost Essence', + jp='遺失精華', + es='Esencia perdida', + rarity='SuperRare', + item_id=234, + item_group=1030, + dungeon_id=-1, +) diff --git a/tasks/planner/keywords/item_trace.py b/tasks/planner/keywords/item_trace.py new file mode 100644 index 000000000..a8ee7bc4e --- /dev/null +++ b/tasks/planner/keywords/item_trace.py @@ -0,0 +1,525 @@ +from .classes import ItemTrace + +# This file was auto-generated, do not modify it manually. To generate: +# ``` python -m dev_tools.keyword_extract ``` + +Tears_of_Dreams = ItemTrace( + id=1, + name='Tears_of_Dreams', + cn='梦之珠泪', + cht='夢之珠淚', + en='Tears of Dreams', + jp='夢の涙', + es='Lágrima de los sueños', + rarity='VeryRare', + item_id=110101, + item_group=1200, + dungeon_id=-1, +) +Shattered_Blade = ItemTrace( + id=2, + name='Shattered_Blade', + cn='破碎残刃', + cht='破碎殘刃', + en='Shattered Blade', + jp='破砕の刃切', + es='Espada rota', + rarity='NotNormal', + item_id=110111, + item_group=1201, + dungeon_id=1004, +) +Lifeless_Blade = ItemTrace( + id=3, + name='Lifeless_Blade', + cn='无生残刃', + cht='無生殘刃', + en='Lifeless Blade', + jp='無生の刃切', + es='Espada inerte', + rarity='Rare', + item_id=110112, + item_group=1201, + dungeon_id=1004, +) +Worldbreaker_Blade = ItemTrace( + id=4, + name='Worldbreaker_Blade', + cn='净世残刃', + cht='淨世殘刃', + en='Worldbreaker Blade', + jp='浄世の刃切', + es='Espada rompemundos', + rarity='VeryRare', + item_id=110113, + item_group=1201, + dungeon_id=1004, +) +Arrow_of_the_Beast_Hunter = ItemTrace( + id=5, + name='Arrow_of_the_Beast_Hunter', + cn='猎兽之矢', + cht='獵獸之矢', + en='Arrow of the Beast Hunter', + jp='狩獣の矢', + es='Flecha del cazabestias', + rarity='NotNormal', + item_id=110121, + item_group=1202, + dungeon_id=1006, +) +Arrow_of_the_Demon_Slayer = ItemTrace( + id=6, + name='Arrow_of_the_Demon_Slayer', + cn='屠魔之矢', + cht='屠魔之矢', + en='Arrow of the Demon Slayer', + jp='屠魔の矢', + es='Flecha del matademonios', + rarity='Rare', + item_id=110122, + item_group=1202, + dungeon_id=1006, +) +Arrow_of_the_Starchaser = ItemTrace( + id=7, + name='Arrow_of_the_Starchaser', + cn='逐星之矢', + cht='逐星之矢', + en='Arrow of the Starchaser', + jp='逐星の矢', + es='Flecha del persigueestrellas', + rarity='VeryRare', + item_id=110123, + item_group=1202, + dungeon_id=1006, +) +Key_of_Inspiration = ItemTrace( + id=8, + name='Key_of_Inspiration', + cn='灵感之钥', + cht='靈感之鑰', + en='Key of Inspiration', + jp='着想のカギ', + es='Llave de la inspiración', + rarity='NotNormal', + item_id=110131, + item_group=1203, + dungeon_id=1008, +) +Key_of_Knowledge = ItemTrace( + id=9, + name='Key_of_Knowledge', + cn='启迪之钥', + cht='啟迪之鑰', + en='Key of Knowledge', + jp='啓発のカギ', + es='Llave del conocimiento', + rarity='Rare', + item_id=110132, + item_group=1203, + dungeon_id=1008, +) +Key_of_Wisdom = ItemTrace( + id=10, + name='Key_of_Wisdom', + cn='智识之钥', + cht='智識之鑰', + en='Key of Wisdom', + jp='叡智のカギ', + es='Llave de la sabiduría', + rarity='VeryRare', + item_id=110133, + item_group=1203, + dungeon_id=1008, +) +Endurance_of_Bronze = ItemTrace( + id=11, + name='Endurance_of_Bronze', + cn='青铜的执着', + cht='青銅的執著', + en='Endurance of Bronze', + jp='青銅の執着', + es='Persistencia del bronce', + rarity='NotNormal', + item_id=110141, + item_group=1204, + dungeon_id=1005, +) +Oath_of_Steel = ItemTrace( + id=12, + name='Oath_of_Steel', + cn='寒铁的誓言', + cht='寒鐵的誓言', + en='Oath of Steel', + jp='寒鉄の誓い', + es='Juramento de acero', + rarity='Rare', + item_id=110142, + item_group=1204, + dungeon_id=1005, +) +Safeguard_of_Amber = ItemTrace( + id=13, + name='Safeguard_of_Amber', + cn='琥珀的坚守', + cht='琥珀的堅守', + en='Safeguard of Amber', + jp='琥珀の堅守', + es='Custodia de ámbar', + rarity='VeryRare', + item_id=110143, + item_group=1204, + dungeon_id=1005, +) +Obsidian_of_Dread = ItemTrace( + id=14, + name='Obsidian_of_Dread', + cn='黯淡黑曜', + cht='黯淡黑曜', + en='Obsidian of Dread', + jp='黯淡な黒曜', + es='Obsidiana lúgubre', + rarity='NotNormal', + item_id=110151, + item_group=1205, + dungeon_id=1010, +) +Obsidian_of_Desolation = ItemTrace( + id=15, + name='Obsidian_of_Desolation', + cn='虚空黑曜', + cht='虛空黑曜', + en='Obsidian of Desolation', + jp='虚空の黒曜', + es='Obsidiana del vacío', + rarity='Rare', + item_id=110152, + item_group=1205, + dungeon_id=1010, +) +Obsidian_of_Obsession = ItemTrace( + id=16, + name='Obsidian_of_Obsession', + cn='沉沦黑曜', + cht='沉淪黑曜', + en='Obsidian of Obsession', + jp='沈淪せし黒曜', + es='Obsidiana de la obsesión', + rarity='VeryRare', + item_id=110153, + item_group=1205, + dungeon_id=1010, +) +Harmonic_Tune = ItemTrace( + id=17, + name='Harmonic_Tune', + cn='谐乐小调', + cht='諧樂小調', + en='Harmonic Tune', + jp='調和のハーモニー', + es='Melodía armónica', + rarity='NotNormal', + item_id=110161, + item_group=1206, + dungeon_id=1009, +) +Ancestral_Hymn = ItemTrace( + id=18, + name='Ancestral_Hymn', + cn='家族颂歌', + cht='家族頌歌', + en='Ancestral Hymn', + jp='ファミリー賛歌', + es='Himno del acervo', + rarity='Rare', + item_id=110162, + item_group=1206, + dungeon_id=1009, +) +Stellaris_Symphony = ItemTrace( + id=19, + name='Stellaris_Symphony', + cn='群星乐章', + cht='群星樂章', + en='Stellaris Symphony', + jp='群星の楽章', + es='Opus Stellaris', + rarity='VeryRare', + item_id=110163, + item_group=1206, + dungeon_id=1009, +) +Seed_of_Abundance = ItemTrace( + id=20, + name='Seed_of_Abundance', + cn='丰饶之种', + cht='豐饒之種', + en='Seed of Abundance', + jp='豊穣の種', + es='Semilla de la abundancia', + rarity='NotNormal', + item_id=110171, + item_group=1207, + dungeon_id=1007, +) +Sprout_of_Life = ItemTrace( + id=21, + name='Sprout_of_Life', + cn='生命之芽', + cht='生命之芽', + en='Sprout of Life', + jp='生命の芽', + es='Brote de la vida', + rarity='Rare', + item_id=110172, + item_group=1207, + dungeon_id=1007, +) +Flower_of_Eternity = ItemTrace( + id=22, + name='Flower_of_Eternity', + cn='永恒之花', + cht='永恆之花', + en='Flower of Eternity', + jp='永久の花', + es='Flor de la eternidad', + rarity='VeryRare', + item_id=110173, + item_group=1207, + dungeon_id=1007, +) +Borisin_Teeth = ItemTrace( + id=23, + name='Borisin_Teeth', + cn='步离犬牙', + cht='步離犬牙', + en='Borisin Teeth', + jp='歩離の犬牙', + es='Canino de borisin', + rarity='NotNormal', + item_id=110181, + item_group=1211, + dungeon_id=1018, +) +Lupitoxin_Sawteeth = ItemTrace( + id=24, + name='Lupitoxin_Sawteeth', + cn='狼毒锯牙', + cht='狼毒鋸牙', + en='Lupitoxin Sawteeth', + jp='狼毒の牙', + es='Diente serrado con lupitoxina', + rarity='Rare', + item_id=110182, + item_group=1211, + dungeon_id=1018, +) +Moon_Madness_Fang = ItemTrace( + id=25, + name='Moon_Madness_Fang', + cn='月狂獠牙', + cht='月狂獠牙', + en='Moon Madness Fang', + jp='月狂いの凶牙', + es='Colmillo de la locura lunar', + rarity='VeryRare', + item_id=110183, + item_group=1211, + dungeon_id=1018, +) +Meteoric_Bullet = ItemTrace( + id=26, + name='Meteoric_Bullet', + cn='陨铁弹丸', + cht='隕鐵彈丸', + en='Meteoric Bullet', + jp='隕鉄の弾丸', + es='Perdigón meteórico', + rarity='NotNormal', + item_id=110191, + item_group=1212, + dungeon_id=1022, +) +Destined_Expiration = ItemTrace( + id=27, + name='Destined_Expiration', + cn='命定死因', + cht='命定死因', + en='Destined Expiration', + jp='定められた死因', + es='Deceso predestinado', + rarity='Rare', + item_id=110192, + item_group=1212, + dungeon_id=1022, +) +Countertemporal_Shot = ItemTrace( + id=28, + name='Countertemporal_Shot', + cn='逆时一击', + cht='逆時一擊', + en='Countertemporal Shot', + jp='時に抗う一撃', + es='Disparo antitemporal', + rarity='VeryRare', + item_id=110193, + item_group=1212, + dungeon_id=1022, +) +Scattered_Stardust = ItemTrace( + id=29, + name='Scattered_Stardust', + cn='散逸星砂', + cht='散逸星砂', + en='Scattered Stardust', + jp='散らばった星の砂', + es='Polvo estelar disperso', + rarity='NotNormal', + item_id=110211, + item_group=1214, + dungeon_id=1020, +) +Crystal_Meteorites = ItemTrace( + id=30, + name='Crystal_Meteorites', + cn='流星棱晶', + cht='流星稜晶', + en='Crystal Meteorites', + jp='流星プリズム', + es='Meteoritos de cristal', + rarity='Rare', + item_id=110212, + item_group=1214, + dungeon_id=1020, +) +Divine_Amber = ItemTrace( + id=31, + name='Divine_Amber', + cn='神体琥珀', + cht='神體琥珀', + en='Divine Amber', + jp='聖なる琥珀', + es='Ámbar divino', + rarity='VeryRare', + item_id=110213, + item_group=1214, + dungeon_id=1020, +) +Fiery_Spirit = ItemTrace( + id=32, + name='Fiery_Spirit', + cn='炽情之灵', + cht='熾情之靈', + en='Fiery Spirit', + jp='熾烈の霊', + es='Espíritu ardiente', + rarity='NotNormal', + item_id=110221, + item_group=1215, + dungeon_id=1017, +) +Starfire_Essence = ItemTrace( + id=33, + name='Starfire_Essence', + cn='星火之精', + cht='星火之精', + en='Starfire Essence', + jp='星火の精', + es='Esencia de fuego estelar', + rarity='Rare', + item_id=110222, + item_group=1215, + dungeon_id=1017, +) +Heaven_Incinerator = ItemTrace( + id=34, + name='Heaven_Incinerator', + cn='焚天之魔', + cht='焚天之魔', + en='Heaven Incinerator', + jp='焼天の魔', + es='Incinerador divino', + rarity='VeryRare', + item_id=110223, + item_group=1215, + dungeon_id=1017, +) +Firmament_Note = ItemTrace( + id=35, + name='Firmament_Note', + cn='云际音符', + cht='雲際音符', + en='Firmament Note', + jp='雲端の音符', + es='Nota del firmamento', + rarity='NotNormal', + item_id=110231, + item_group=1216, + dungeon_id=1019, +) +Celestial_Section = ItemTrace( + id=36, + name='Celestial_Section', + cn='空际小节', + cht='空際小節', + en='Celestial Section', + jp='空際の小節', + es='Compás celestial', + rarity='Rare', + item_id=110232, + item_group=1216, + dungeon_id=1019, +) +Heavenly_Melody = ItemTrace( + id=37, + name='Heavenly_Melody', + cn='天外乐章', + cht='天外樂章', + en='Heavenly Melody', + jp='天外の楽章', + es='Movimiento celestial', + rarity='VeryRare', + item_id=110233, + item_group=1216, + dungeon_id=1019, +) +Alien_Tree_Seed = ItemTrace( + id=38, + name='Alien_Tree_Seed', + cn='异木种籽', + cht='異木種籽', + en='Alien Tree Seed', + jp='珍木の種', + es='Semilla del árbol extraño', + rarity='NotNormal', + item_id=110241, + item_group=1217, + dungeon_id=1021, +) +Nourishing_Honey = ItemTrace( + id=39, + name='Nourishing_Honey', + cn='滋长花蜜', + cht='滋長花蜜', + en='Nourishing Honey', + jp='育みの蜜', + es='Miel nutricia', + rarity='Rare', + item_id=110242, + item_group=1217, + dungeon_id=1021, +) +Myriad_Fruit = ItemTrace( + id=40, + name='Myriad_Fruit', + cn='万相果实', + cht='萬相果實', + en='Myriad Fruit', + jp='森羅の果実', + es='Fruta del sinfín', + rarity='VeryRare', + item_id=110243, + item_group=1217, + dungeon_id=1021, +) diff --git a/tasks/planner/keywords/item_weekly.py b/tasks/planner/keywords/item_weekly.py new file mode 100644 index 000000000..7e8c35f5c --- /dev/null +++ b/tasks/planner/keywords/item_weekly.py @@ -0,0 +1,83 @@ +from .classes import ItemWeekly + +# This file was auto-generated, do not modify it manually. To generate: +# ``` python -m dev_tools.keyword_extract ``` + +Tracks_of_Destiny = ItemWeekly( + id=1, + name='Tracks_of_Destiny', + cn='命运的足迹', + cht='命運的足跡', + en='Tracks of Destiny', + jp='運命の足跡', + es='Huellas del destino', + rarity='SuperRare', + item_id=241, + item_group=1300, + dungeon_id=-1, +) +Destroyer_Final_Road = ItemWeekly( + id=2, + name='Destroyer_Final_Road', + cn='毁灭者的末路', + cht='毀滅者的末路', + en="Destroyer's Final Road", + jp='壊滅者の末路', + es='Senda final del destructor', + rarity='VeryRare', + item_id=110501, + item_group=1310, + dungeon_id=1301, +) +Guardian_Lament = ItemWeekly( + id=3, + name='Guardian_Lament', + cn='守护者的悲愿', + cht='守護者的悲願', + en="Guardian's Lament", + jp='守護者の悲願', + es='Lamento de la Guardiana', + rarity='VeryRare', + item_id=110502, + item_group=1310, + dungeon_id=1302, +) +Regret_of_Infinite_Ochema = ItemWeekly( + id=4, + name='Regret_of_Infinite_Ochema', + cn='无穷假身的遗恨', + cht='無窮假身的遺恨', + en='Regret of Infinite Ochema', + jp='無窮なる仮身の遺恨', + es='Arrepentimiento del eterno vehículo del alma', + rarity='VeryRare', + item_id=110503, + item_group=1310, + dungeon_id=1303, +) +Past_Evils_of_the_Borehole_Planet_Disaster = ItemWeekly( + id=5, + name='Past_Evils_of_the_Borehole_Planet_Disaster', + cn='蛀星孕灾的旧恶', + cht='蛀星孕災的舊惡', + en='Past Evils of the Borehole Planet Disaster', + jp='星を蝕む古の悪', + es='Agravios pasados de la catástrofe devoraplanetas', + rarity='VeryRare', + item_id=110504, + item_group=1310, + dungeon_id=1304, +) +Lost_Echo_of_the_Shared_Wish = ItemWeekly( + id=6, + name='Lost_Echo_of_the_Shared_Wish', + cn='同愿的遗音', + cht='同願的遺音', + en='Lost Echo of the Shared Wish', + jp='同願の遺音', + es='Eco perdido del deseo compartido', + rarity='VeryRare', + item_id=110505, + item_group=1310, + dungeon_id=1305, +) From 8bbc9bb27760a82257328b92ed932a3628394c54 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Sat, 11 May 2024 18:22:54 +0800 Subject: [PATCH 02/37] Add: Parse planner results --- .../share/planner/result/CALCULATE_TITLE.png | Bin 0 -> 7881 bytes assets/share/planner/result/DETAIL_TITLE.png | Bin 0 -> 8749 bytes .../share/planner/result/MATERIAL_TITLE.png | Bin 0 -> 8411 bytes assets/share/planner/result/OCR_RESULT.png | Bin 0 -> 7235 bytes .../planner/result/RESULT_CHECK.SEARCH.png | Bin 0 -> 6996 bytes assets/share/planner/result/RESULT_CHECK.png | Bin 0 -> 7623 bytes assets/share/planner/result/RESULT_SCROLL.png | Bin 0 -> 8164 bytes tasks/planner/assets/assets_planner_result.py | 65 ++++++ tasks/planner/result.py | 202 ++++++++++++++++++ 9 files changed, 267 insertions(+) create mode 100644 assets/share/planner/result/CALCULATE_TITLE.png create mode 100644 assets/share/planner/result/DETAIL_TITLE.png create mode 100644 assets/share/planner/result/MATERIAL_TITLE.png create mode 100644 assets/share/planner/result/OCR_RESULT.png create mode 100644 assets/share/planner/result/RESULT_CHECK.SEARCH.png create mode 100644 assets/share/planner/result/RESULT_CHECK.png create mode 100644 assets/share/planner/result/RESULT_SCROLL.png create mode 100644 tasks/planner/assets/assets_planner_result.py create mode 100644 tasks/planner/result.py diff --git a/assets/share/planner/result/CALCULATE_TITLE.png b/assets/share/planner/result/CALCULATE_TITLE.png new file mode 100644 index 0000000000000000000000000000000000000000..5850f57a11dbd050569c5fcc35c64f8c568f677a GIT binary patch literal 7881 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 literal 0 HcmV?d00001 diff --git a/assets/share/planner/result/DETAIL_TITLE.png b/assets/share/planner/result/DETAIL_TITLE.png new file mode 100644 index 0000000000000000000000000000000000000000..f4386bf44fef7b98e7471f670728ff2b84b43ee1 GIT binary patch literal 8749 zcmeI0`8U*W`2SzhCP@e(^d6z?OG;!ZM6xeq3)!=bu``yTRFrH9F}4uNmYuPTvNx9O z3`W+$3}YW=2A_GqzvufOe17_z?sM*Qz0SGMxzGK2+|TQ}pXYjOpr^@nf%^gg08Edy z)Qte(4DIvO@AIc@TjXB;0NA~{%&OY) zI{E_m3Oh@NmTCLT(Fs=nai?)n0eJa1fKV4W87ED}Ur;JmYa2YsiKWu^@dL156YpO>+Vhxz~!$1F>{K+D*+Hr&Jq(XQnTV#7?#T1aeALK@z81%jhC3YGpJ} zMKqrQ0^-aQm{p$v5or&_dV%ZmrvNoRwm$mnI`lbGL#RJRwqCF6wdjCWbaG$nlOfqF z^~t}v1GhK-%>Oh0)1%^{9p|mf&J2s7j8udGn--2kSYwwo6N6^j=+b-~CbUMOFX?3_PDbw3eM>$g#Ze zJNPgLCQnu6%ld54eV_N*1xChoLHp0mQVTK4C$e+UKQZuq+UoIC@Fw7$u9{ZbniqNG zZRZ(KW)z1*pMEIqNx}&ncfAfjin}#9f4kzT82h^|^SAW|Al)ulgHfXTKRwCwtgGQN zTl^_U3`v1ZK*<~ZR=wJDHE54k_y*mnm`mnE0e~ueNBliEV!+2Fu zI-oB#e*plRR<1t%`jJzo^E?2k=e!X4{@~ojA6H6RFJ=7TZEEE_Wp`Fx_2P}z`f+H zsqX7C@2KyZG^Ibjs5GP>Ipwd~@RgtYU1yH(pxxh(G_d)JzKBb7-+`_>pEB<~! z*tvajECSqTYpn1QTQ)Gd_0Ln$i-_H_lfcy6l>vBZN%@hh&(EO;tu=-xy7bO__^rz+nZCOsfnQiWo*XibsohO&g2ul=T}08pRt77~1Bw*dDzk zJ1|wgdHpEojaHtCLtQ6V>^~fxGEQ6O5667K^msA%PrCM2m4^hKfHM@H0Lc&?V>RDz z&Lz%?&Y8>JeRP*PEBJq3sY$)5pv2Wg0WjhylySW_N$z{6G<_I~YMTAdlx+=J}q?QyOV$B2Xc zccpHhzmve_AuaE)*_+_7;{M6CesJ^E+*cO#b7W%s^Sr^wdBXKV8&NI`azUKR&Lz-m z_9eHmPSfu9?d)A;rt;hKI~H4R^?h_tcSa6Y5Bv4@c8g7yOqBEx;_i)RxM$h5Og2qX zV_$#K$mr0Hxf9csf=LNQpGnb8A(earD}!O+J@9zR>mfI!!N zEk%^J2OuD`SLUz8-x|8LXY7fQCQu0%2-YBU_67GL`vbha*I0SS6#W!Kef<1; z13bK%@cDDM+ltzY+E&e6cw9dFX70#w7CcEnv!({5avDPl-xYdh2cwghxHphPHN$&( zh(^S8=_F)irdO#WteB!Jf#B(}Zc`#Y*xe+V&wKYSWELU{f9@pUBy9q0ENm#veEL-x zLRn){knX8=t3S=?kY@_YN>1edReI%4?@rHuI_>nAb8=^5&orKuyr|6n@FD|~1;0-cw3Tz4 z=g>iR+C?mVkp0*BOm<5ivQ)2-4&N9{+?#>(vzOt}f6R!>-k-|(l`&p zuG|ul`6jVuSZ(a*?MD(DD_OLlxHQLY#TUJAP@HnB^$Bn(aq}?pa3CP+BkI4^zX*yt z(AY|4^<%riu>N+jeWG3I?Sk(qoJYEB#$vijil0N-(sXVE=kjVlIxFzLg`7n~sQl|s ztf`eMOOe+f8c1H%Un=y}-3#PMU$8oVat$T zJV{Z%NQ(l8zB^Ow9wLU+D8TrVBeXf; zyFvZ;s)_m_i>*H(ltv?mK#VbGpoFxw5z;Zpb#o{T{TyAHa%3=XZe+Uc2AkfFGm0Gg z@4IpjzU#3K#cCh@@Xk}Eg>SH7{q#d zV2zih$fchTmVOA&7KDZ@7_5BmZ<}uGn@tO^+dGfff{6|(p!`e3^B_4&pPPv*@AEQw zZFn_X>Q3N9=>-S>F1dKKteni7N?V7EFOwDu9SgHE^;DEb(xzb~9cr4pwV`#hi`qbz z3a8(OPMs~OX9tUmx87{>+69s9rv&Sslq!`eX~MmZ=CsPKpm-+|dfn`PncuE1UU85( z98Pv?9rYbQ7?-flhBXsA2|RV4LNnLu1(ACP0sHHmffqJ(rmchHgUYEbn4`6GWahQ- z>f;KhR>+@&XRXf$+ANrfml&e5jz69ZN6igs_G*p_JQ9eF&*bOlVd8GycI;1@E37PJ z8~6nWpTO|G#kkp{dCHRkbLzw<<6+DIsti`N<3lViDp%+d=;Ckchbe6pP)qx#!ie=m zaoGjAlad-@V7om85ep?l_b5|p$HCT%hKHDbq6gYT>uCc1b~Q00609M z#Z>_CmjZybX8@q^2>>{uA8b1w(W2#J^#>+F7{ZKMhUsYYxy_R_ZQ{kB7a`9s=ZoYf zf>O*4Dl`VaN_S$dnpizqW8c1g+Zo-ipSx_CYu_eubHczdZ=&qF#=Glm!CY4hq-9owyRzWXjJROR9fnjiT){u200;4gu{1pW^Zh#(?fl>^*d zwI@ZZkp;TG!nzp-2ESdm=GjwiVa9C=NkV!zMb^Mz@K!ONc;GQtNj&iJUmH0-4hiRE zfi~}KiI%qnt`7`gOpM6C+lH}03rFQ(<>i&ZE#4>UvGU@uBf#~_g=BFWmRPfzfSje+?4;XiP7*nQQc z=?v0r*u?NT;7^EHn;BqRSm`r`!XDiKZSU;7*_cALgpPdutf4UjvOlH{poESdsjn4l zEU_oAM)tn%&Be}CR#jct-s!aU9b-?;&PvMUEqZ=1Ug@+`X07PIj=6RBt_!YnNDh>! zb%kS@lVdq>ZKUkjavWNLN1n)U!LY3L<&uICRrX)S3rEX7aMcm?IM&QO< zUQFF%vyJHnwE;fs?QP5#qauvs5F72N6mhf}5bH<8F6~cs7RJlU$bdkK!uatM7(DEC zZ*TAJ4=iOjY`JJkAQxE>Fnvp9*7Pja^;&(U{ZyPY8?(}F|NdzWPTL@Dl4s^roqLkb zSlE*~?z<@%r&6{!mBsIBB>fw#Od%m7i2^>pz8sPk-Xot3yJC66+U?p-;JA-wHX(I$ zvHt%4d`h&YmK4E;^{u+6r(W9!LP?2ri>R#`{K%hkT`8YaC+f zL#k>~C>nJo9D!}DY)0N6gQ#)|PuKo}48%X>j~F(jF2r*_YIm2NwK|x@v2|uo6-%MM zRbr~n0;<598B3Y8>1tPWmA<~VW9U6>`h@g;@a`u=%~-C<@5k@h-6fg{q(+amnzSmO zWWrq!)fkSx+7#2KlOKh)4A^1;`^C-XsEy%ryF+H?!w2dmR<@PFd*mOH)*du4>pC+65$5x+e~4Q%Z-)*n{Dy%mU6?MbvEJMTWerrV_&p5 zNmCsTw{dbBEzte4$L{9lRtqVPizX0Cc2BSVmrKaCwR8-|k!)nCrx$=Uqe($du12n= zhJ__D*ngSu;e$8~Hr4gcHux}efI~c6CEQg#LuMm7)#ljj@#EFJc-?{S?+o|<70+dF zf_Lf{W*J#oXtsQdncaAF{i;5xH+^p|5Sp)nUS`r475#R{bNZUbjAfl$T2hj=wY6_g zhYH{A?<=#Sp3F>+8}qa4Hq;|eqCxNlgJDFW!?S1 z-bFX;q)_dDw)-72p&A@FTXQEWjLnL{eUc3;AEV`$zZxd-!{$n4sz0-fMA~)Ar2e+e!$z#=Bs$GF+#y$&zNXUe#G&Qs2xevZuLuQ6K${eoyHAx|LMC| zBb6!vQV84#>Yo%|4(y4EHWBg1SsO3+T7N9_YM*P1SJ_C2o-nCgdT}oMgiy7_VmSKD z-!n<5fyL0wj5Ll?Voh8bY({%KFu#jlgN)J=c-TL9Pvq)xxL315-St0P{?IqFS@W2_RrM!3!b@j&DQpx6nuC8lJNZOl_K8nX%X$>fpefySm zJa08@n^EI2`O6QctGh0oEv7>R`){t;iCf-|iN5O`W^QOW`JKKnGr%MO3>K+5!i5W~ z$dp#PS;<(`dAFrF_oW|GzsXwFJ_pmV#D`>K;A(D#$E1zXuyXk8y}~2%UK9D7EQ(63 z{dhE25K`-LYYLvFRSg{{=Q4KbUJZ}aWCmJE2$pBh38)R^XN#)L0tKR?UwI@mwj9pB zA%53(xIzuF8~5A7B2ANDZf&#e(!t8Y8PYV#Z|mJC%V3Fxr@9GbDy3{Tzh{4PkUiOS zX(?Isq`6EpUSf5((R*Q;a&qXQE)t-l$tVAPr8QAaz#dB-IJZk=XAw0kMQkskli5x2 z<51+>T~5-*j2y@&V{5;W*ne0V!z8m=(G}O<+j~>A*kdr))zs7<#FD4UBraj2csS>J z@7`n>WnXMWVpc7c|Nhp8p7BZ8Oh zGe7L$HMrFBDGARQbrCBIN)Z!pp==2IaC-!pmTSgSX$e(eRK%>3ewW-DRtG7D@07-O z@`e3rp%CPJ+Oda+ygZQ16Z(-3b`I5}w{3!&h; zN}Q;G#Bk`UWY+B>d5&vgJK+C+HzyJ{6pG4prn2&%hzBf*I%DFq6>)Wmm8DC@S)Zhr z-g4~;hA^)pl9Rbu+{Zl$;NP(9F!C34s<;(9%R;%5%S6l3u;994HBBNqLP>pyyZFK8 zons0!l7uas&Db*>$U2Ny*&nL+jvBACu(Zs|%w%UlXlp~8RyUT=W?|O2sagmxOL@z_ z4g7fjOs~})Xfrw8>K9scHVmp45HPmAR7`U$70(B^9cAuqNiI;X_!pOa6S;M(hMD6( zx6*RFkeo4fWosu14AwO|1cdg zJB-T;v)@_P4G&%WWT$rSw)@zPv=WMAe{S?mn+jx5M|yef{c!A%XgS$f{UY}?E+KNi zSXT&f7n3+a49yuw+2JO-zU+fjyHM)E&U{o4vPio^{5^$N|X=&!WG=hwA( zFI$WpN8*zT)uQY}K=vEc9jo5s>yYIdJWVj_CDx5w1qk9H&CLtx`VOezG$$=AXyxpE z^clv~FM$bqxMAo)^!&VB9`khF<0MH=K+>i%_qg7}{g5!34Szz8HLuIG#awB|mvLmMDV1v}MMW(=bF~^JVEA*8p+HKohvRKdIf~9U2weaxa znN{11@a0uA{0DzwzVPRWJK|;~<~wzY{FFF;!^$GRo}qboSjgDONSQ^|Tx9&R(q^mg zNpZMT@;gTvr~ZGNWq-$C0)GkoCGfvVfErH8rUME!%H>^kQ{Vl&^J5J?^|FW0Uj7e~ Cn~YQd literal 0 HcmV?d00001 diff --git a/assets/share/planner/result/MATERIAL_TITLE.png b/assets/share/planner/result/MATERIAL_TITLE.png new file mode 100644 index 0000000000000000000000000000000000000000..a60a04717b1a76f423e5ec01a5a8bb6c4f81ead3 GIT binary patch literal 8411 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 literal 0 HcmV?d00001 diff --git a/assets/share/planner/result/OCR_RESULT.png b/assets/share/planner/result/OCR_RESULT.png new file mode 100644 index 0000000000000000000000000000000000000000..14f8d7fe356bf1165235d0cc6f45bbea4f3b1693 GIT binary patch literal 7235 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 literal 0 HcmV?d00001 diff --git a/assets/share/planner/result/RESULT_CHECK.png b/assets/share/planner/result/RESULT_CHECK.png new file mode 100644 index 0000000000000000000000000000000000000000..6b183abbd2fae376e535d6cd11545ffcaa180822 GIT binary patch literal 7623 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 Byt)7Y literal 0 HcmV?d00001 diff --git a/tasks/planner/assets/assets_planner_result.py b/tasks/planner/assets/assets_planner_result.py new file mode 100644 index 000000000..46dc97ab0 --- /dev/null +++ b/tasks/planner/assets/assets_planner_result.py @@ -0,0 +1,65 @@ +from module.base.button import Button, ButtonWrapper + +# This file was auto-generated, do not modify it manually. To generate: +# ``` python -m dev_tools.button_extract ``` + +CALCULATE_TITLE = ButtonWrapper( + name='CALCULATE_TITLE', + share=Button( + file='./assets/share/planner/result/CALCULATE_TITLE.png', + area=(264, 187, 346, 207), + search=(244, 167, 366, 227), + color=(141, 143, 161), + button=(264, 187, 346, 207), + ), +) +DETAIL_TITLE = ButtonWrapper( + name='DETAIL_TITLE', + share=Button( + file='./assets/share/planner/result/DETAIL_TITLE.png', + area=(263, 402, 346, 422), + search=(243, 382, 366, 442), + color=(144, 146, 164), + button=(263, 402, 346, 422), + ), +) +MATERIAL_TITLE = ButtonWrapper( + name='MATERIAL_TITLE', + share=Button( + file='./assets/share/planner/result/MATERIAL_TITLE.png', + area=(263, 194, 346, 214), + search=(243, 174, 366, 234), + color=(135, 136, 156), + button=(263, 194, 346, 214), + ), +) +OCR_RESULT = ButtonWrapper( + name='OCR_RESULT', + share=Button( + file='./assets/share/planner/result/OCR_RESULT.png', + area=(331, 104, 1201, 592), + search=(311, 84, 1221, 612), + color=(254, 254, 254), + button=(331, 104, 1201, 592), + ), +) +RESULT_CHECK = ButtonWrapper( + name='RESULT_CHECK', + share=Button( + file='./assets/share/planner/result/RESULT_CHECK.png', + area=(263, 128, 324, 148), + search=(255, 103, 355, 593), + color=(132, 119, 92), + button=(263, 128, 324, 148), + ), +) +RESULT_SCROLL = ButtonWrapper( + name='RESULT_SCROLL', + share=Button( + file='./assets/share/planner/result/RESULT_SCROLL.png', + area=(1238, 104, 1245, 592), + search=(1218, 84, 1265, 612), + color=(33, 44, 62), + button=(1238, 104, 1245, 592), + ), +) diff --git a/tasks/planner/result.py b/tasks/planner/result.py new file mode 100644 index 000000000..e4725e3aa --- /dev/null +++ b/tasks/planner/result.py @@ -0,0 +1,202 @@ +import cv2 +from pponnxcr.predict_system import BoxedResult +from pydantic import BaseModel + +from module.base.utils import area_center, area_in_area +from module.logger import logger +from module.ocr.ocr import OcrWhiteLetterOnComplexBackground +from module.ui.scroll import AdaptiveScroll +from tasks.daily.synthesize import SynthesizeUI +from tasks.planner.assets.assets_planner_result import * +from tasks.planner.keywords import ITEM_CLASSES +from tasks.planner.keywords.classes import ItemBase, ItemCurrency + +CALCULATE_TITLE.load_search(RESULT_CHECK.search) +MATERIAL_TITLE.load_search(RESULT_CHECK.search) +DETAIL_TITLE.load_search(RESULT_CHECK.search) + + +class PlannerResultRow(BaseModel): + item: ItemBase + total: int + synthesize: int + demand: int + + def __eq__(self, other): + return self.item == other.item + + +class OcrPlannerResult(OcrWhiteLetterOnComplexBackground): + def __init__(self): + # Planner currently CN only + super().__init__(OCR_RESULT, lang='cn') + self.limited_area = OCR_RESULT.area + self.limit_y = 720 + + def filter_detected(self, result: BoxedResult) -> bool: + if not area_in_area(result.box, self.limited_area, threshold=0): + return False + if area_center(result.box)[1] > self.limit_y: + return False + return True + + def detect_and_ocr(self, image, *args, **kwargs): + # Remove rows below DETAIL_TITLE + if DETAIL_TITLE.match_template(image): + self.limit_y = DETAIL_TITLE.button[3] + else: + self.limit_y = 720 + return super().detect_and_ocr(image, *args, **kwargs) + + def pre_process(self, image): + r, g, b = cv2.split(image) + cv2.max(r, g, dst=r) + cv2.max(r, b, dst=r) + image = cv2.merge([r, r, r]) + return image + + +class PlannerResult(SynthesizeUI): + def is_in_planner_result(self): + if self.appear(RESULT_CHECK): + return True + if self.appear(CALCULATE_TITLE): + return True + if self.appear(MATERIAL_TITLE): + return True + if self.appear(DETAIL_TITLE): + return True + return False + + def parse_planner_result_page(self) -> list[PlannerResultRow]: + """ + Pages: + in: planner result + """ + ocr = OcrPlannerResult() + results = ocr.detect_and_ocr(self.device.image) + + x_total = 842 + x_synthesize = 965 + x_demand = 1129 + + def x_match(result: BoxedResult, x): + rx = area_center(result.box)[0] + return x - 50 <= rx <= x + 50 + + def y_match(result: BoxedResult, y): + rx = area_center(result.box)[1] + return y - 15 <= rx <= y + 15 + + # Split columns + list_item = [r for r in results + if not r.ocr_text.isdigit() and ocr._match_result(r.ocr_text, keyword_classes=ITEM_CLASSES)] + list_number = [r for r in results if r.ocr_text.isdigit()] + list_total = [r for r in list_number if x_match(r, x_total)] + list_synthesize = [r for r in list_number if x_match(r, x_synthesize)] + list_demand = [r for r in list_number if x_match(r, x_demand)] + + # Structure + out: list[PlannerResultRow] = [] + for item in list_item: + y_item = area_center(item.box)[1] + total = -1 + for number in list_total: + if y_match(number, y_item): + total = int(number.ocr_text) + break + synthesize = -1 + for number in list_synthesize: + if y_match(number, y_item): + synthesize = int(number.ocr_text) + break + demand = -1 + for number in list_demand: + if y_match(number, y_item): + demand = int(number.ocr_text) + break + item = ocr._match_result(item.ocr_text, keyword_classes=ITEM_CLASSES) + row = PlannerResultRow( + item=item, + total=total, + synthesize=synthesize, + demand=demand + ) + # Validate item + # print(row) + if row.total <= 0: + logger.warning(f'Planner row with total <= 0, {row}') + continue + if row.synthesize < 0: + # Credits always have synthesize="-" + if row.item.__class__ != ItemCurrency: + logger.warning(f'Planner row with synthesize < 0, {row}') + continue + if row.demand < 0: + logger.warning(f'Planner row with demand < 0, {row}') + continue + # Add + out.append(row) + + logger.info(f'parse_planner_result_page: {out}') + return out + + def parse_planner_result(self, skip_first_screenshot=True) -> list[PlannerResultRow]: + """ + Pages: + in: planner result + """ + logger.hr('Parse planner result') + scroll = AdaptiveScroll(RESULT_SCROLL.button, name=RESULT_SCROLL.name) + scroll.drag_threshold = 0.1 + scroll.edge_threshold = 0.1 + scroll.parameters = { + 'height': 50, + 'prominence': 15, + 'width': 5, + } + if not skip_first_screenshot: + self.device.screenshot() + skip_first_screenshot = False + if not scroll.at_top(main=self): + scroll.set_top(main=self) + + out = [] + while 1: + if skip_first_screenshot: + skip_first_screenshot = False + else: + self.device.screenshot() + + # Skip first page + if self.appear(CALCULATE_TITLE): + scroll.next_page(main=self, page=0.75) + continue + + # Parse + rows = self.parse_planner_result_page() + for row in rows: + if row not in out: + out.append(row) + logger.attr('PlannerResult', len(rows)) + + # Scroll + if scroll.at_bottom(main=self): + logger.info('Reached scroll end, stop') + break + elif self.appear(DETAIL_TITLE): + logger.info('Reached DETAIL_TITLE, stop') + break + else: + scroll.next_page(main=self, page=0.8) + + logger.hr('Planner Result') + for row in out: + logger.info(f'Item: {row.item.name}, {row.total}, {row.synthesize}, {row.demand}') + return out + + +if __name__ == '__main__': + self = PlannerResult('src') + self.device.screenshot() + self.parse_planner_result() From 71d23545a0c350f987dae76992fe8ee4639da29b Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Sun, 12 May 2024 02:30:30 +0800 Subject: [PATCH 03/37] Refactor: Use cooresponding model in Digit and DigitCounter --- module/ocr/ocr.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/module/ocr/ocr.py b/module/ocr/ocr.py index 82fb32d39..f22d2ff72 100644 --- a/module/ocr/ocr.py +++ b/module/ocr/ocr.py @@ -325,7 +325,7 @@ class Ocr: class Digit(Ocr): - def __init__(self, button: ButtonWrapper, lang='en', name=None): + def __init__(self, button: ButtonWrapper, lang=None, name=None): super().__init__(button, lang=lang, name=name) def format_result(self, result) -> int: @@ -345,7 +345,7 @@ class Digit(Ocr): class DigitCounter(Ocr): - def __init__(self, button: ButtonWrapper, lang='en', name=None): + def __init__(self, button: ButtonWrapper, lang=None, name=None): super().__init__(button, lang=lang, name=name) @classmethod From 711242b1f485833cc5cb1695e72f5e1ac1ad2416 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Sun, 12 May 2024 02:31:42 +0800 Subject: [PATCH 04/37] Add: Get item amount in COMBAT_PREPARE --- assets/share/dungeon/obtain/ITEM_AMOUNT.png | Bin 0 -> 12950 bytes assets/share/dungeon/obtain/ITEM_CLOSE.png | Bin 0 -> 6880 bytes assets/share/dungeon/obtain/ITEM_NAME.png | Bin 0 -> 12071 bytes assets/share/dungeon/obtain/OBTAIN_1.png | Bin 0 -> 16545 bytes assets/share/dungeon/obtain/OBTAIN_2.png | Bin 0 -> 15976 bytes assets/share/dungeon/obtain/OBTAIN_3.png | Bin 0 -> 15084 bytes tasks/dungeon/assets/assets_dungeon_obtain.py | 65 +++++ tasks/dungeon/obtain.py | 222 ++++++++++++++++++ tasks/planner/keywords/classes.py | 16 ++ tasks/planner/result.py | 17 +- 10 files changed, 317 insertions(+), 3 deletions(-) create mode 100644 assets/share/dungeon/obtain/ITEM_AMOUNT.png create mode 100644 assets/share/dungeon/obtain/ITEM_CLOSE.png create mode 100644 assets/share/dungeon/obtain/ITEM_NAME.png create mode 100644 assets/share/dungeon/obtain/OBTAIN_1.png create mode 100644 assets/share/dungeon/obtain/OBTAIN_2.png create mode 100644 assets/share/dungeon/obtain/OBTAIN_3.png create mode 100644 tasks/dungeon/assets/assets_dungeon_obtain.py create mode 100644 tasks/dungeon/obtain.py diff --git a/assets/share/dungeon/obtain/ITEM_AMOUNT.png b/assets/share/dungeon/obtain/ITEM_AMOUNT.png new file mode 100644 index 0000000000000000000000000000000000000000..85b737523b4b3d6087324a0c1901f7f292f23ddc GIT binary patch literal 12950 zcmeHt_gfR`A8nM~6+y}hf-3?dt0+}Pic~l33ep#lUIY}R1f&EAkf1986)7%Fx-0^Q zUJQW%iP9nTYG|Q_P(w{ZLP+lH_x=(0{;==!2c^(|-<>UVp1d5o>vktdUWr*pK7*B<4!QXdlGyPwJuF6*M_6R%tBm6hW zJy!Vq{Z8a-K3mZXKj#`f>iBDd&nWNp(#IQ(-oL$l?U?Y$S(7R~TZ)E9?rd)#PaU^V zaw+UV*pSeRpU+=^zxt0a=$;i)N9((EvV~DcZ|jTG7mOBuxFWntX!HKD69n4jfkhxf zYxLQtssll%LC+_Ye?8y5$bE&iylw}&@*cEs^*n|DREia-N7<%F6x4GORAXKLgb$Pk z0{O$D!B;_L0-&wKhk8GPzN1oS#X;Zyxs2ikC7uFh-7x(1^!;Yg<3}&86iL z(6lY+8HwqlI&=1;Bj0c&GXbW_zAu$uXxXD9d6|!&fBTw8Ve75s1U;_mV&Fmof)2dP znTgy51!dpQs{C5?>LkcLAgszNi9+}Rd^>zwFV@{H8jx61+bymFacF+Dzqvchz zZdjAm``=}H)0fV#$K2VI&Nw-n8ZH2;h_m==-f#v@@c)XSo;sZ%Xg?YTx{o+edUuKc zJxJL5?j0Bd1WLs-lkY2>0u5w-S_Xky)-F1KP7^on;st?z|M*hA=C?EFzKK?R70mr6 z+45EVwEIuL-#_>3R~=#SPb)7j^A%pq`0W>)uUg-KK-chG`uKD9qVSh*w>1SQ3+J6* zZax?P>G8$!sXMgyuhgZa1M(g#@9!cM?4)Tg;CH-*K-RSP$FFsvae=Yjw)y|&a zPp45}-}f#?vinNGqW2P=@3T^7dam4g%YNHpcsuQ7-Jd+;r=j~i1b>_4`C{a@pw~O1yYsvFKOVoazjVD@%#&=dW}B?$ zDQT|}c0uB6zWWaEucs4Uh-~q?#tOIj|CN$rXm4t7@cG)7?3eRcUc0leVsE#}T}}OL z@#R|Nd1gn1hgNNh{ez0@R2eCm{A;dqf+B9IM!yR7{=H((Cvz?3&O`o(envA!N}sGh z>5U5eHRIBs#lIhuhezp3PcHUU+sXqCK z;(|NitdC`X4xsYKdCOjF6*xYGd=E)l6=&RQm-1c3b#tO9QxC(2Rv>4E{Sas~5SR{4^I<%oT z=D7)5KTGSQ39iqL7}6kzMKtk^oynaMX^mTVdDT;X@xQIv>fKUiPTCJO6anPsiQE=g!Qhj!&H{xSIoMe%W5w zkuPJR{rx?PvlZVfdbg79P2~FJyW@y0GweiU@gKRJ#tG^P-5Jvv(S#o}%rY1iA8hq( zVYW2esfve_&YOyxkV!j#O9{K2b2(Z$goe+RIh7q@IS`WQl4$bH(VH~ufa%*?>@B{n z?5&@+@6p}q*XR)XkYxYm^OBR=q1w^Gq0s&?|K}~6pBC@7qdHLS>vrA}-i0A~2WsLa zCMksTnPHjY){xS-r2z$zgmm&HDs~h-Mk~r`&Y7(wLdNI%bvxnZ$7aeo61^_%x;wue z?lSC`g8G;9N^?rv4pJy8u3@eYuE(uX7WKK;^CxdXdS*JD7Fr2WkG1r4pRxwmdGxyU zP8UQL#N$eES4p}!S;QnF3vq^>f2_?SoFpIN7h(Hid*(krKK1Ps`Bdx6OE2qr{CK2) z$oir9jM|ThKQ{lQa!&68_}p0m2kGF{z^~%_5=>9kS#QPckqdpic^4cdShxDGn@Uao zoD?_2OA)Gu$D;?iEbl)t8oTm6^ ztLCm`+hl}#R*`4_X%b&uA0*_5>o}-6q(o~XKb+63(y8iXmbU zQR!YU=iF9n5Effd*)q)#F>zK^jY%M3Y~x!{j99Vz-^Rb?qC zW~%a8A;RgRQ$(XttC8clD#?qp!Ib6yODwuIrN*+crv7_lgu~v#Rs0{#VzLR=;^E4- zU97NAB7Am7;e=;|x{MRcC3~w!`@XQ*{Uobbqs7Gr3V+$%O-H5m5(26Y9h*EaxTimP zwBwsO;ba>lFP`pJ@x0=5)ltoZ#?>V|bGM0T8VoW$J#UQZ+uk(V{InTR!EnG=8@KxY zHZdHxD^Mx1mq?aygBZ_;7DoEgk_NsE^bPQ+xvOnE^*2<^>=&vR(IfL>TnM%&mE{;5 z@%r>6el9%fK=7611?~Brw5=AjLWG@P>mP7hz$o>vx#UY85?K5yVy@9J+@do-IjT77 z4E>LtP2bQ!|JiChzV0lyBs5!WgFPEI*e&U0S^jhVu2rL#d`1H%9NlJSEv>x8?caRffmR zHH=B6Zk4Vf;`zy=cAg=xNRI_o@CmUy2LK`t(IJHl|LKco5e|7Sr;tvTj!lN_+242h znF&MqDj4b@c&8jyt<^2tE!{E*)7>j!R}Rj=cN%xFr0!PftQ+k69Ls|dmS=bAumwYp z63t<&Fk%mmuZo}S)U8$83jOFa>}4uOYNc7H$Z-M`&%!=&WKn@=6br;Scup*DkTT4$v} ze_i>5n6`qOEwH%J0k*ZpMORD7ZzM!#sr&$Z)&ItS2>ge@e+c}Cz<&t*hrs`H1YY31 zM`RWbm`7X4WBt@oc!AHls!krYyfxHLddz#nuqB!Fw<009Y1a1yg+ej+*;wedni^Um zu~^iA{Y1M|pR=~q$v>pWh(=W*(7XLv$wPVCjJ3t~R$YmmGJN9~O`Yg^bTPWHFbKk& zot+&YxAF7yb8(p~mHbFfOioPXu%-+#{zRI%7zhZaw~1ySInj2dkCmYwjW7a5&0_wIU`b zcik0ECRaz3%cJO9PEPYv;k308H{FxNdM8Xk^X9b)Uvk)?#UcQbMu0P^F~~aOkv{CJ zCvTH8CpAh(OG`^ljW`O3_esvZq$tyyitmtp&ZZ9i^0hjbJo=4YPzZyb3*fTV>*n2W z>I<+S) z9`yAKAdv72CJ*83`XHsWv~>Px&rf7|xx9FX&Dw4^l$ybC@3XWw#sW{6xM?Gqu3P&0 z8h7qkDT+;1lWLq1tp0U=ippIzwZVabcS%WlT(-s~iJSoZ>SRfkYwP~E;~h0Mjh)%r zdR$?lvvPrMNF>VYtc;?PlCrY$3C)cg2!SZHcNP`NPF1^U2d~t+G)whRl;ecob}sP< z>Db>8-FfF$Xyt&t7ahXf>*$Ecc+}nXfWI^S^JHyRQG;cLtwLZ6XL=g`i-d%Lj59WL zk45tt7nM?I5uUEN5}?U=-R@1cC%`alwnE}IOex7!T}v`TcFuaq1IkJ(vwf3vif z2KRJFPqq|IGmUaJbDEhs!k#_io-oCj1N^F2piRSx4|I79b+9|lP#hGZ#3y&I<5EVA z>jLH-w{>!jH>rb|F}nkefGG=ri73d9GsjzW zH+9n9ZZ8gwX`8$B2}qkj^L$Pgg{kiYL##8Gc58RnFW`pXoCCc8_3JmBi=M*JebM_x zcCPtZ`O2aR&0*WhlM{1|2*~rt3v+IG7e%pfDq=xeuR4Ym!o%|lf5aS`goZ{R&yZYm ze#ZooM!t{l(nl}|)>whg@zw#2Cg5YH3O})nVG`U#`8rGUBTI0FGO;x^HP1QhreK&} ztmQzRbSu3G)!PejfJ<{&x^Xd+X;o->wD5`{E-o$|!`>4Hds;8kCAG!HGrhdLySlr< zyw=v%uMA$L_l<61+91X^k-MBT-OKR`t&xjJ@n&o!*f=#+gmBFvV|CTb^PPY|B6*)3 ze1=-r*!bZoap&NQw@H&<;Oh0kxIX6qb|?Z7;WIW>4?Q-3-qwKL(}3PqeAku5NmF?; z7bEInhS+_UDfa#etimkkT=YIyjO%-20Om{B4r=W9b zGKojUjN(;=EGCX)wI{;oWKWdMJBi=yUHUhgtx(Hhu&3%t1xaIY6awx$FIAQhv|*Kt zz33-lKq=JjdYM_WH2e6oatW)-aw=%YtiRI#YMRz3HxTI52mRf1?B2j_%1~ZTR{kL6 zfW)GkR8~S{VhshQA~!R+CtF!#JUl!MzOUgwjgWLt9pO}h%^7&<0$Xm_$Y5S`>2*1a ze4V|Va1w0a&!ReGsK37jepnf;$L+5lQrZdREv&8GBT{hrR$^eysMUUH?no*#dcZ`b zb$oQX&UtYhWsQe1=A4zeE2$Q0>guZpOe>f5qdiaQ=p)X44+Ui{XATeDU*t*FVXjyc zRGJVc(@pi>bSiNQ*bRQ@n*jb^*InWd2`=3Sk=HdFafoxig&@xsAsw4ft<#pDv?8%sz0 z^sp~~VPVv@LQ+$@rpCn#jgjG#A4@?dc;F_@E$(;KU0s?>>v`~ZTnNSY(m9#v^*uSM zh_ynfWFA4^-27c8fxNn!;Uw~EY`kM78iqrmV9e3ktaTKTNQ~=>0~Zd5(|n7qw%6Bv z0({QO^qZOu4GyT?y(`9thTB!!mZj%Y=T|dU(QiVZKYkqKQ`>}UD#d7s2bU9{ctu8r z1vC^E);2UW;Hv|J+zX59LukH~eJ~7m2z7UNfA;J%3e|+;&@Tg17``^relr$et&P`y zyoFU&jR7u(PRCd*b|(FR8SUq{88{F3c6VPyPq{V+Eq77pnyXwk3qD-dui-8Vx4oA& ztfIN)l`iAnF+YiK(mDXo?&xJyU~VgK^7aNAaY z|L{Q0nSG|@O!#2G>CoWdBl!s=5{bj%Iy*ZD2M0?^x`gy1^kavor>CdFpE51aoja$j ztfa1?!Jr>biur2L100)urxGPJ0ICmWAJtYmVa6xtYa1rV$H&?1Xq1g_KmbRw6x{DE zdB%FIK@*0nBj*$jr(FuH+)7s|alY+kayw2qL?+f|CIzDTw_4~#Qj4^A6=}`}eYu>! zuq4elK(asj=6D(yDq|yVYv84r+>wP#sT*&+nXc6?H|G6Rc~GFEm{_oG=rA5h^o4}t)4|iWaNHuUc6NF+MZlKugrX7 z&@yl#Wy*@38;soZzMH;H5(j^_QP{y9Z|?1l2)C0>G7CEWM|J~p#&s9#c^huu{{$*@6QU4 zIT&OORy8#dv$C>;+d7VpHmmWj03)32#Nh2K_+<_m49S?Cqc}sh!^f7GYbn}n&qgA@gWJBDsH%FW-DV}8@B&@ zM&+*+9xi)MDu_)L&XS6Rgr5PgiAh3rD}DL>`$b^bxiZ~DLdFVChy~SfLXxyTg1KJ| znQ4gG_H#Ry41`swqqndpjU|X{P9?tfxo=HWQLrfa+bx_!qLBUTz`&i>^V*9dM z=X#yDE6LMk?m_a3198|S8*9hP3zPt~C4RasZ}m*u?0tD?9IFa#FpQr%RFLEW@qT45 z+d4S%0h~Bq;rck2%243UU8r`&&5q49`!RTAWo5S^+lgjo<ABT#1HP$*=!G3eRz&s<0D zx@xHgWPW_6xwgJOgfi$NuAHkfF(s`P`nf)Y?E5{fZhalK_XcR!AtnSio858LF|fEu zrBa)SL=K08E4SO+*zkCF0*9Xw)M*J5kBrhl9M5n)5IA;mH$IfZ&iYbkAJ$h_VR<7~ zI3pti%sZ#6+v!p3tepJrog#kMk&9L|@$~czsaGX<)`*CRNT~Y*)T+yj4q)QHefuVt zf7Dmy0`OEb-Hk&a0c%H>%RY!7+>SS?va;<>&ImxhX#@&7H&5!{x>ch!&5nAX)0B~eJ0HySrDV2`KDn%& zjz4;3>CZpG1k`Y6Lj^qMHB)#Bci;olty2qv(j1RN6|x0ay;ugeiRr}Io;NnnjLn=3 z_UPoF3LTR|c!GlNY-*-#Xf3mi?6b`~p@rYiQbtgmY&#$HzO_m zEy|T_2*7GlHuaJ`V}(*pGh7-Cm2Zd#CmiyFu7D1Pj54S_U)EKJ5?gdtFvVyZGq6Q? zS=aHI4+PHHBsFfL#!B@CWdIWq^94H7kkZz$J>$k%6f%4ecYMrh2$;i0l86nHlYuR} z;B!G-&hAEe^*I3nf!T{|+jHN60q{RiwVRup;XCuK)BX^%ZPg*2n6(iDg5*ry)0u8S zqJ5>F!Lie;V}_r94%p_VJjo8Q4HJiiM>6PA_2!qtd@pv z+DS_0#q7%oq0wjrMVpkBoqg$Y%bzGkbVGw6&@BW7y&UNO{($6j`X}RKW@Z91vD-5k zKvd>v#Isl|`c5FA+M9y!#XjF%_y!m;J=(bSgfESp>E_U=CNBln3|$-MoE%F?Na(dY zVOHl}li#zC);0BW(Yhbd+knpVG?FQ{lxlTp9*&;kUzQWua1lXUU(*QD9TbeSagkaT zkomNyN@N*Y6ROgEaDj1y*l)gVP+t$Sk!eU@i)A zNxsl^7HxA!Qh#!80#4p9MxlDYeS3++aoXdPKc$)Hvugk)xSSjV^Fal(4pG*n=AY6K zyRW#}PaXQ?1u$0`{r#c5?*9J%Okp!KGo-M3!0ZPCIkTEb-PpiJ()xTE2*x;x6f9`t zbs$1n8lM>d(lm{e8+09-JNo1E&8HH$G-C-cI#)VEVOA%jnf~DWZZYYXW9r2krQ|qf zp>W;!a4MS0cRMquQP{hha((bAa@B@(p{`g{Lzv*{a6At@v7%X9;ERc~N8LdzW8c|a z^mY@xXhBUWHeHO$k^uC0PgwzF>d0OarfWx*wbK{#Mve_pwWQ% z0hAG<4z1+Wz?pD6u~1c2jTe%p?%=V1k6ZJ~D9+<-bz+4hqoM%&bz5DXHEa+SbC5&Q zIa)9n(q+&9JX<*|zwPbHQu915*#49DOcS8sDs9V`mzTwro!8dvGoKPUCqm+U$cqPo zIHnix;_hNM5FW%EcJ1^zr5mfo)-*O=1b?aQ3~BoqcY-F@kLxun#WV z`P{H$*4Z$-Pz~lKV&m(f3RCgkH_68cQ8|^4PL#G)z&n|F?5|o?b1xh3V4F2Bh?iRX zz$an1{AMqE7$Gy*1Bz*eVA$R9aKI;NYJA`uKou&Ck9QUmqY7X|t6WVHxxsj#Rg$8j z_9*G6R=(LhGpD(rN|coc=ArvtP2V)5%E35guX_B>p@QuHTBFFw@OMoBDqUSD#g1iz zvY{GR-kiJl;@H9SYu-?G+>jhVSWXEh)#H8vtaGoj)?M>Zm6b|jfo|ubk9OZ<=2;VK zJ!?yCRv{q=g$Bv$8W=@Iu#x~8lZse)DGYwsL~iq? z(FI;k>2Xe50kcriUpie<0e74Ha?e&A487TLkuMRWFfktgL1>~gMLzt&+C}iYo1HTz zuEoNm#Pi|LX6OfP0XCzPR2JA4h(`2nxc+NvzE@ef)mJF`j@TL z23?ao8-|A^_dA8Ha$8a)^ntw!c%bieO9MA3;Kra(sfS6f#9NcGLaZ>w5Zt%4g|_!M zp-C*UEfs}I!6v!VzF@UxPCLDN zD;*QM@oITTH+pw?{^U$sW#JsT{CTEQwdBbkfBmMM>E|1eou6H$FaaU9kZmglt&E$T zskr8toxL_HHQyjv%JF2cSFXS}z$fvxSVHF@O#;WL#@o5MamNZcPo4_yW}n(YB7@D$ z28M?hR#sLHhV(I7#dH?hugOeJ2AQ8f!)TbpLT8u<;(DyL<-NTZ*xgss?8m;o8>7V`n5q-Oo5jF!4Hyhk7?|0& zomeDte{Idkm^e~|?p&R1=<%AFQhxR(a(|n?JZjUE z0j{D`X{iUu6qQ*2+cx5XiE=7&{z1h$vB0NluiFv|BmJ=Z`^(M{Aj#_k+nz;T66*@) zUk<}I(iKhqrL^x2=eNpPAnfxCxFh8%J26L#SdTf{=9GD!=6fqgXM6jq zv3VSIzcT>{CAK#q z*Uqk@x!^hwx&`0a+1cE2COO#I*(r(|@bbLkF)+~3sBpty_TxiNL!2=A_~!^89~1_I z;Ut}+)MUT(!C2E?8)cn)icQ64;`bn6X*&&jNI zS#)P@q6+0ED*100T*B7d2;7$TFzX>3G>Cdvb?!g+ody(3imx#c^>$mtGZ)8)MS{^m z#i_ReZ(FNGPVjT=iFej}lCC)>O)<0@TOFt0FY|d#oAa47My3(EE6#hf3}C;iDcjwE z&nqgNoc`BtSXbxav)Pk?{IiMSuYz%1&BHN zXJuCQnXI@tK>VwG5TH}%!YlECPa;-I^6J1?hJZnP54Nl`8>vPAr97+sF9F11Mn;94wRJZIK^DYAA?d<$PNXFFMyvw2j zlrcmBLg0ePjrB}G+79Fx=yRDv5)wI{@3yx$_nB;fiK|_JTyT%x)&{P%Zw#x%H#CGn zpF|w2nw=4n+5l)4kVj1+lz{om6*ls~O^AZoL^40d$!TV?+KqcSW}%|2ob>Kp%fX=b z&0@uJI)eeUOQ)%OrSZ~w!z7Z1ni_3c8c$!%siqvL0cJE1@+vjey1l(UG0}{10iMZq z$WBaW3LgYfA8@+17f&k2vo5K}AxCBt-Qt`fMicf~{AvO~>$JuxS4h6BgqWJmSi$a^ zyME4Usj2fTrlDX?pCR@E@?WTZKs?pU&vd!~F1+S9mj$>5Ve7Gs@h`Nn?hG#vsGj1x zVitA0=60-bX36qs=sB6edER=A-L|=aE|Jw?TO5pcI6>9s<9>FYJ|1UpF&wIP#<&nenLCfJ>8e=Gz6X72k8WdEELix-j zUq{QT8Ky9C#k;>1_y(}r|&`Jvk3=G6wCr!+q^!g9aG~X^5 z3J-_I1dk!X+x(>HJ$6$F5XHOM-^koBZk5p3o<%FfY@S~OnnHFouDo19xwJ}DOIw>k zC60{{n_5zl(`^YVxCr_d5y%ZzGcttRy38aB5UcCGy?ab%lSggMC0h;u@0K^lfmCQ~ zwkZTAQ|_#sNxhwVPf;;#D3|%Uw${BnDGv(m%+Jp^e(Dn%s#zcu*h(W@^mLl()Aa|8 zQMGfE4N4IXIDbEg%gxzZ&+fLiw&mnZ^E}pbcXydCXW%9ikb!M%N&tzOZ4!(xPcjeC z#aY>oO*b$N4RoZqt7Z&`O=1tkT)KGibGd@v?pLY`e0^_na+1skfj~&CNzDK4Ew$S- z;eb|q)#)(@2tVJP9Ce6%y0N6BBqIl5V6=UlDaaYBbu{m!3r{(Rx%&5BifU&+0~Api zzszdgD9eAdpac2F2ESM}S+`n=8qUpW5vByj30t4URluUknsx8yp|^OElbX=mgF^9X z0M($kdH~;9iL9e)N*o}8l|oKfpS)uS9O@VbEFEq7l{m|14%#L#XtxzkT|K~a3#YvV4nQ_6}#-6oSp>);QTiJ?WAbO0dg=95Y*QCJpFQS zZvG80FeJ_d5Is5IIbmfw(2bSv-;Mr_H^0D-lai7KNz3u=6j)GMY)Z4N6jx7wb;+9= z*0LkA%a2GZ8Dh>-&2P2l!1~o@M{J(XwUfWF`Oe+VsY>8I>tW2s+1)#5?|q~9NQ0h^oels1dQI>H2mnx# zrsO|q$Voz(M~jRkXg$HEUI1|Z(%D4@q^4g106HCKRaHGbCl8p1my?GlucoRhujg|Q z2WRM00Pq_{KK3<9B(f^vh))@fRE9UcAfRCYzW|D+oyjzZDFLvJU}9EoiF{3G#B%v! zI@qxJ#ULFR`FbYnI@Jq?x+tXzYLzD_dE*Zt3#H62M%h?G7HaxUQiD9So(k}ZHHl|b zwgEy@R7AS~UO93=mHSdR1+O+m*6n`GRFSo3BpOTxG-8vw5fA&NSkOs7*?qS*re^qN zen7vd*s|SVa-f~#Pe-H)Tms`Mw0kZ90Q-L5_Y*m#c>KcL^nwE(O4w=1IdR?a;8*0^ z#GTZ=C8q?K9`zDNt>@>5C_LN9%DgeIMdrY$5#WT|x0D*A%`&6=>AxQhmpfAC&dAnl zzsJQ%cmDjEkX?5D?b&F>Q>kf>sp#N6(%q9W{|(?-s&YzkV{Z8IGh26`5=bl_OQ|B^ zPQ(kGv_*y-$KL3hk^W*KdO2#-EF5P8rjHg&plASAlP`1N#)#JC?{R^mE`+Z4H`ki zl$B)^>{%C6r`onoG8Ppw}1eVu$&AXxLI> zDfw-|(g~q?yi2cH^!Z!bpXZSmskpqe1Pg54dd_k;#zOfZeyp8WGU_O*PDA2-*jIgu zU*z7()gO7-qgu1P`e4$GmZ8Zxu6^tk+zO%Jatiw%E_%a&pr}~;&^`$UhqoiP7H?mv z*}O4f7j9#)ij%SDGP&b(nWHhomOvv&9vymVmBuQ9=?C;he7uH^a zm()D3=?WeL-^zQOr`XTroUSdr67SYAIY;v~6vc~pR~63~ubXh#(KJ`Z0&00ZqfMJ5 z2+4cTtR?eQX`V7aQ&J@*>yv(WQN}NtPp?6lW}0qUK6>JhUlm=F_D&V*PGeQY=H1F2 z{F& zssThwyTR?>k{l_H9~eJ)7@FMNyBT=-T_A${=RL^<|&NZ;FYP}l6 z$J#3Sbkjs-=(!Qaim2-c9Xs={`>WbMREHcs;sM%^RlPnrpd8*gAQzr>lQFb@ncBEJ zlCIUm*F)bEaEs!W(ycE;F2xST!Nqq*{YM){?be>Jv97I*61+W;%1BYt?so1nEKT%j?7Hpb?y@Zth6sH;cWz75$i`oVO32x7bj8~* zx_)p*_iaQ>f4qows(IJ!l-sA7dkrnT{>pJy#*aFdv`G1HN&=Hq;BjeCNf_wy+tcpHo7f&G&vAU zm8_FYM13$)G=dxL8vRCT4q2>-uDA^uL-ja}(=Mce(y-q?7N-?A`=q(ySY}w_ZuH;S zee6Cev3j&hx0kM7bi2Wk&cQ*dDtTMishe`eT#k+1G3OD+hLS(`wVki$~mkO>`-=1bsf# z5liFfuxwHw+~3(Cn#?@wnnf0-75>*yPy(!Q0M2|+cEslMAsc}cq)V|A|G|0rNJ=;dlc+zFLiyRc|D~8eU0XEzM8_eAH;IwrsOBFW&MiBuxBu$ z=n!ho^w61ZuyUh7Lu4LWlXi`lGgqV1`{1q+NS6*F4;; z@MhMF91l&EN}o$LNQT*$%#Y_(vn?$3U^9I0naY^P2g*f$U`BjZnh)ny(G$O-J74Io z!*yvZA}vx^tsy5SNB>I|TI*iI0@5gHPi{g!NcF(!rLMYns}!v+Ll$&vV~lQ<`^@3lR485*hG zmse%kM(9uAck8m@bRr!NO*sKzN*LzW9g)#_Z?XGk{z>Z z`^&aTPYekzh(U9skeh5t)+kTZ$C5u~>ga_T<44wmfxB?G(a}k*s?N0)@Jil_JBnzE zDb=cXxj*>x{_oDwCmo#{$e7I-HxLMvk*Li*6`JB z$%qTc51`&tBdoZRZ-3s`h3mvBB20tE`C~+mrY>%Z)nE z9e$$Sm{8TjVuca~jbP8?X>h5z$I4S8cFp)+32aAaMZS+P5OUzsIOz5J&u=lyOn5z^ zb(N#aU3h{MEmX7n$7gSi&6jRnd)(4L&ad>SVf1*J`hamcq~heu(?++cKQ@gvy-lW! z1P0ny87J>g2VPD0YjkN03aAUj#36ZjIOy5yx1RJQP8WVHywuwnY;+1=@%oI%9nTy- z>@_(W-Z;M>-HR!K7i~W$d@d>lwF$KG)b+p>HVcl5d&YtYXhIFHt)3_4`jPU9-ge)C zeOuvAU7{jB@Z?CG7_ogQ)SoFuI&JdWgCRNq;4cIKq2U0qe@c=|0N{Na0G4e40Q3O> z*gO)fThsx7iC*);eFMMI)d`HpfFn2c20q#z%1^dTetN$=Yq92Q7_i$q96FI5h~!4y zx`tj_RJjTkV)P81!k7M>fIZ{!?|CPjQcnK5%~W@;4~aeOR653prcKmkb|Yx#YLy;BTZ1EU)?L_d_T*o%PHwkevobz{;x`xKU_qn+zxqT+OJ39WSL??@CNkzqs zSdmf;>wz*gsc$qHtHPZqWcK3f;N^oZ-QAYG_-$2H)f8>vx-V=XT0YaxdQ4ZLd?p(! zYl*gw;pai0S|oNpHd9Vt3?UvuL~ix-fEq~_jwT4+V2MiVgfx9KN`p$XYbNXz;G_?P zH62?55Bw7ZyB*4nKIcm%Y31u(N1gIY9G#vc^>%wQ8}Gh{!W5e#zugX_g?U@i1Qo zm+CIPyz38&6-iS0@-czt%&3J}CJKst$S8G5rCITtH*c7JfV1Pf9dI?lwzjqo{vgX_ z?EH*Yeno8K1q_vAC>apCtCf&HR;;eBJ|p+&+RE-h*AA@r#Zw1I$B?Dt*3I_z_FAJK z9y({>hLtH9_+SZ7c+(WjZmOAo`{i1R5V!U@Y^0!|n0PYgUgen9U22_q7&9w3A&L~# z^uGaP3R@EAvH<`a%f36BAQ2VUY`s4-a~-h2wb9D`{ryR{1w6j6prHR5XPZFekFT6a zC-MQ0zjSLQYlvmybYM)aut}^DTe! z1@83myhogrQL&#T#?6>fZQ;ZM-1R7KrC?4TlDYw1o_l`rt+PmZ-vYJ=*N{omriK^K zuo2aN2}Eq*6R+_o2L4&>%f+NC){81Utc8;I02Ba#NhKxo?s`aaz=#3t?0M^MT*JNt zaeboJXRXF_`o#{`VU}5xM8@s3;h8|)+LU|Yz!D53BKCb*out18VH24VU0S0X{;+IO z;4eZqtgI|1*~&|144R?ny?kJPjI(Y2b@+?9CuKv_4rMfD2KQklBrkjHDV=1n^tqlcZOg+C24sfZz>XGyU8Z?2GHSd(Q{ zT6MO!3r)6(XaP}0RcYz(f}c>?l}a;E?5tL{o<*-YslH-Gme5wEI=XiZ5>5ulNNx^2 zwYAM2fyN@0c#{1wNf2jK0G*t-Lo7>(uuW~0y&Tn~n{Z^%^a-SfbTB^7B&sz&`^22>c`PkHG&80YX?N=_l3cDFyIx WK+VX9{44$0zM5)}9+arqy!aoXs8v;7>489(fq%(I zD9M4_OAa+M;C9Vj#l#Z?qN4xzM+QnwzXJkY*K|@)(9v;(ctJcJA@2876%_8ddqC`+ zTw6FvcL`+=tw}a*JjgnXmhNR|_~+|}w;67w zs~B{An7FP2|Gb>_pus`z%V*Ol1M?Sq9J2+r!2y`vT|5%sf=)*hhWv+VLT}OW#;s>N zgT(d1r6i^}VA?7jy{)0-3@VG4?$NII|8}_C2?Fg?`1?V zDomJV0!?M#Eu;iRlYvqmDBmZ4*$jGP@Lo@Vy!|&QtK=ou<4f&jWT7T?&y>kSTQ7rr zVvJ*HUcLc^rpOBnfbP8@2Pv@A4^rIIpvZbMhFC1Lau07%Ap^DdCk`g7j|tyyNE~7D zJ=kAd=3M^mS}AYM^pMv6+A3FiavC?i3YtP==mrSHf*busd?6c)USC~Ww@15Tjyki6 zF8dHJ8BXj1vE?iIB@nIFFlNSTZEcdmy_>AU8{txD2AVMhIWFKVgi+VBOs|gw;G+Cr z5MHup{LtxmQU9%$@NguTXRZ$ zzV$X{iff3HDJA9-m$iyWd}!{y&Cj=WxjK71a>)zjo#QN2xDNz9Zi`2ozdVmc_1t^* zh47_C`RTX!Rk{>^$-Q4T{^Vf!(v{^o>Lo&B5t@|kGRjiJE*)BzT`;P?;q3(q{t{yo zI{Ngc{nu%0^RE$#Zz7FZc)J;G){I3BVWKu{#$rAUtnC@r7|Q$PQK9rZl$K$%zg<7X z#wr_Y7%Tkb+2O9CYoau|_A%_~Z$7SspV~D%0d%+yzqb;VvBpXzynCGNoEbcpd^hQ> z5>)Qr?2TX5y3WZH`%INe)kOuRBABb6D>Fvxl&-Nru0EC~Ry!Ww z`)9TOwnXRWWsPgu_w{nW(W!~r$*x_>%X}uEl2xQTSeWsbvgor!rm3o1mXD5*encU? zh<7T_U>c)Bf39H8#7L@>gVU*_(56?@wYiV%ipe9%V=fz4H_@9Pk9==Xf1>`_t@8E( zvkP+|bHSZ@W}y^~{LACXDLt>BAfK_OIDDu14lyu(bn+;OA+C=uQ6kYW5t@iDdTroo zAUok-MD@DZfTq~eFgRbypjv08@Y-L-;)y~wgXY5LWkY(tdN7?~U8|fntBVikHq=$0 z!j-Z@1UR-x*t5MpGG>NqSx}T+K zr5!$LZVR`UZ^IsrJv`P2&ph2B>|EbT-MO+Weqw#XbK-U~%rnqp{=_wwy{56+@#CDd z=Cs+;X}7;~1Ja$r#dyufX{@~#f21*TNBgJ7%kKtO;00*~zYk;4LY6+3CYJbCcJ1nP z-i*m7Zat_Dv&Gi_z&8>y(st)Vn-nr#GUS;7nGtRIZCne|ZQRgFXbSWyApC+G6Aq6ANITO3 zE6zrE)1zIR1AE&W7P)GTBQQ7r#1BVlH3KK5) z_QOZdiXLz2*64e^^EwrnELk{u6UB8T~meAS5sdndD)b$&1N?;t#1zXXZT8)h?>L(y$JtK zms};g_VJ#)j?f+LwF0mv8~s68TDZ1iTXuA|Ze@LgnpFHc+%WOv#hhfY!nxxoZ6)t6 z;cMD@{Qhyw@r~Svnuf0?=SzmC-%SgOe@p+Co})+YlvKlyd{Y0QUfZ6ZKY@DHf8DK* zH6fz!bd?+Dd5B_g4c8dWsnILbxyh&@^poGu?2egVgG#H4>0jXmd*T+3o9Y8&P-|?3 zPD4fYRD+)hc99FA*v!ZsrO)L1_^E|nldYfA{#bCoV}DiBh0e0Ep22~$|Lj4G-p8@L zJO%y_Mk0xY-+KGOWk;rsHVoE@wgwpIic%LRkm}t!6m0Ah;p@7XSs=yPy5uRtk~!^Rx3BPd9NP<&c`u>HG_SF6r$Fm zyJiD*C8&cR&vQ-!;2{?Mh8Ly9^=;FNnq5W0eJw2%GLyEO z>Ran>`sn6wxwT(>3;98TD>@rLhW^a{8C*ySsXwONR`KT_lR$Wv25`3>*_hp zR!9x8(yrZY@$60eo8dnuG?<&$A~J~INaGPpW6A@{6WmJN(J(j%2P-v8>w)c1!csw1 z0sU|v)R5%A?OBXoxLC%k4;vGv_Nj1D!-z8f!b1;CabdYcH+MHj%aFe`HlI*BgbKzq zV44=XTRBo55MB~>4oA;zx(i0MPi4?SM1s(1*dd;0EK?Y8oA+!~^fW=B03Hx1^dks_ zBLVkK5Xk!p2(d1ZQGJma{AWlSq4_idhz^L-5- z+Q6cqB7e)P6OC(!Jo8*1NNJ?sBY4`chXHH)@A?mc|8ocsq80CwhnBY23!@3o7=&6K zi3I=p`2f#o7E%1HpSqr&tA0H#ETX~>n+;qG(|>pC%Ne-hyiJ=8n=|j8bR_)w^odpp zf%Q>SQ+vB@w`F#U$W}+$*_!h~_|Uok1`5gldPf6{%n{RsPo2)@UlBrh`8A zA1yZZKJe`(Y!_?3Mn)Bh?CPw$LMCk=#OzuWqo8T6vt3F=p2u1rXPBRq%-gelNlXhj zV^p-?)@D~UE9r-W9zp1x=B?=xvnUeLg4Ej0O*h>zfqckO*l;bbib~9}B7!9K9+7rG z0`$g3)c+tCX&H#CMOqH_nd;`LC3+4wziAJ}M^ @f;&RR>V2OLM+BzW*aK=CZjv! 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 RG5^-Bs;Kp{|D-umB|1A literal 0 HcmV?d00001 diff --git a/assets/share/dungeon/obtain/OBTAIN_1.png b/assets/share/dungeon/obtain/OBTAIN_1.png new file mode 100644 index 0000000000000000000000000000000000000000..1e485c2294062927473a9bec2704ad61289c48c7 GIT binary patch literal 16545 zcmeJD`8V77`v#7u?aZrMlxa<=En_-prnR;9wUfMBilEUcrKG4T5o#ACI#WVRX-jRf zj8WUzs`fQ)Z7n581c{{tK|~OQte?!x`+U#$KluFcc|Fg`dFGttJeT{vACLRG?(2H$ z1J0Fb_6^IsSM@U8gk(NT$`;?q^2?Gf?m#3P$KVE};SnZG|r0D1We0KiG8 zKLq0B^e{L)IP78YqaSWSAU`|`4SwJs=nDWuQwp4;Tr)UlE$2DDGPl7~TlLw2zX5(c zUoG)T_RgQOuK->NvZt^1B_^M|E%*I*`8L1y{yuTirXYFoj|;7SzdTEJOZoM#@g{Jl zya^q{VlsJ?yiR&Poj-yi$UOe;w8qPoKLY?JutamyDPX#zP4DNf$49@n`TETdvMWO$ z{l4u30Je^yqA}rbt@uakf&oVXk0!K#Iz6x;deZKs;R5*KCE)9ir|FVMUcdmKv~PWq z1AI~dymfB4e-e-h00g4qz&`^1k_K$--mv@@Fjbi`BM+GRQ@K(C@calM_k#6LN3V7O z?)~~3?9$Qq9|3<p!XG8J+8hDAA9_8UZ8v^JuJ!e(V${yo*G09(kAd}IFL{vcgA+?X z=4a#1pRu7IvmcQH02B|$J_wDiUeH&T7FHh616jL$e+m&>!9QB6ZO;okQ;&WFkaZxi zDDdTF^07w)N8U!@5tZ%$%5A{I`9qH@)DwTWog9rhOhFm*uc{UnJAE=&Q9dat$yE0) z?$V{DSc0ohOnLi$2>{@~ zfBg3I+h2}LeUPhue=7fjO6Pm|qh9~{?^UUv-kZyU|FiU1`DC#|)-UI7y*Hiurs?f5 z#Xr8wTaa!1pl2*i|9blFZ;VIs|GB47_)(z~d*Yj4K1rPMQZr5d&O2@D#}Ntn+_Z0g z^s>=^_4v&XYsqpqf9(4d`sQdQIN+s+&G{XzP&t$5cdv3^P(S@}`5FINr?p<@Z|`m% zBOQ&n+ExKneAfR**jTu}jK|}wKM`Y!jj9%poBu2yvs;Y_2cVy&`8*!eJM|!S+Uss= z(zSa}T@^J3&iX978r)7d@KJF!iu_*beW4di;-{l2kI!sKcqYhx4E+7Y3u{+I^pgP_?+fP;=VH&5 zE3}-`$+a*0mYALU>6Y%~Wu;ud9GRTpUtNFR|2gjamxHHYo4&sN8uOa|*R5Z}ezlrF z{Uv#;>Q|X6&)ev->%TTQ(JD`n&Q?uSs{GnfX;?b~i-M&)5pKduy5WM~xjxeGu!-0I zz}l4D@@eTm_gw94|7G88S1>vB_A$mKL=M#3{oUb)edM=1rAVDvduK@4Uk|DuynIlw z5;A<`RDb=~EtBbd+Yz-9>5*8iV_H^P_2ht>2Q`=)BT5XVo8rw3Wu9d+D6EJ_1y>6$ z73`TJO*s)K+6~)pwkNhzBM9r3L>~rf?Zn>T-l?_OG3!0#ZpohfI*ZI20~+b-OBlX5 z7pQ0Kv-RbL&nhCvzjbUYVWHx?p@;1+dmomJ-6+v))!0n(qZvfYTRx}`R`#yepYxqT zn0tBqU#7n4ebcwp4I0iwedInt<%{xZ6|z~lSzKs%#FxmxN1co} z3;I2ky_G#{E`CaW#bE_(1Nkz$7elABBeUh5k>$_IgNkB?UN0+dwvRUv_eyXbxS5)1 zBx&}GMIX9~2i3+YefH?FVEwYY#c^E>`9dow$CZC%zo6@QMta`yMra4ao(c)Jcf+P#T@4s^31qHg7gFY0&3^% zw!^pgj(j+>e5C6)#oro^A&vpR$^EAExWTv2zwP*sj+Ev1V5t+*cYvW8!SCgFln#Ba z%=lf(8~c7xqTqWsC9dul4SQAccWGFH#Jnsfxa`n=?Mt&n@>lD?fQ? z%YSLF-)wXa4+-a7B3Ccn;rVr?ZKqeJwwX}_nnNS~ssjRHfj%3!*2k@Xwf+{JbZ~7u z`*ir3pH48JF7;0JT0EtN9i<26UCCd{yOkC0Q@cF#XPf-W+Q?90l=&TlJ1^pl6LU^y zzq49?@&njON5OHqJP4|CW+wrc=ybBI@P2S8eCn(A)nd&s7tqL#DM%GV?R@OJLIeS&bJbbz3g9I6k;|F++L@wSpW} z%19dIES*0LV^hBmOtc>>X@u1}ojPl;Q*klcUBNxN)uzkFjdW%HfpGosyyWj^(TUQoc9N-gi zdnhD@GH2T|$Yj_s-Y|lyId|}Nwp{_gOpIc;q-&J6pD!!Hk+yT;#W4YUX~T`fgTu!R zybPG`Uz)3_JH>`2`!NOa9z(YUHB~L$(;5^qel{wWed>wI_hxgf%#F^bi_tEKu50MM zpz+P$XVVqEmD=$unAukAD966S^w`qaA%<#FYjp?Ih+~Z&h zj+qzL^vLb!Qy+g>{-8Nu78gr%S}h*wndupx&y8={lVI4OE{>bxBdWDZkbhVdcd=Ge zOA1sxRjj*PL>QJH%_m~OAl;?#kAe#p+lNcPXVA*;mlqZ|SXq#AXHXn_elEh}rbm|_ zgivTDAHHed0_S{Y&(YI8wmKi}{>bsBs<+tHSkzirV;%_>Z0g*D8NQq$ri*!P_%4)T zHpU{xa|7N_giRhyYI_u+x>)@iN-aSev&yaN?Ry82`%L+$lbiN49x>_Bb^LCMVEs5( zW<9=9SnvBD`Ssx5`+J0*J2I?OCz1+xuK%&p%Hu00Nby6}xusk7O*A zzbijO7{uHbp%`IR^m)M|&yL{ApW2cA?8`VCM+RV!MoAGeSkP zT3PM$16{z}3;e4>C-xZEXP|u4kz+}Z6Y_O932dJFc+nLx+Wf)C1_lKHV$=bE$4>x& zLy`Er1^`6p0s!mx007e*06;$Y6};~{0AMh35$HCO!DO%8r&CJo0F?D}hNKh!VlxNLn1|!~|b-E=dD_5cY z$Av#G1e6&Y6rn6*-4MPjyg8W;{mm9jj`Ou^d`!J}{8TKn>GICgPoLt=S-ksVaPq(7 ze-Zd!1pXI+|3%>cs}XolJ8vo;m!zk-;JMZTH9byPuZkwDY(H&GH}kNQ+UO5Mtn!L8 zPlJ>7vS0zypgvi!Ehynd*w3ZA0w+ZBc0K$QWct9FxYP}7ubpr7ALl?o2BogHf^trR zEX7a;;-6RMzY}Q4;GOoVXlfo_r&LnQ8C9Zr_dYjBO$4pOJ5?=#5Pw+pOv1cg-$6W5 zpn0Rwd12b6hS1ukpoE1?7}Zd(Y;SKZ&@ec!^Se}MTfP2nmb0xA3y2-(2p*EMU>&sx zsz#|C%_kQ{qu;;$bLq$fk9irjgv7)U39s`?!AlEc6-=I&DKp8-)m5RcjsQ+(KBSMQ z$p`+`gtLg_9(bge&O1RQK>g2u=zRIxk#Ft+0761P?PiU7X$~xBm^)Un)^3^b_N}g) zhlf(SbxI2D`Byi@4N#(!QG0j8bTun!JNlL_s8p)wMVKPi3yDP2uK3t{80kYDKqK_o z#YJPfJ}yG(L#uzDENq?wCEAx0^Vzym;x_?$r<#;c2U<#%B5^aGZ<*C>M_>nP)v;d| z_RJtb!Fzi3+hgWq_sao*uOT7RzJ$Z_h`y4WRDEP>W#;oaq=XvZ=eRLewmoRI-Ai9_ zIo}RM+pb_G<%ijV==otm)JXjRUU%w1N!e%6veyMAM4??J8&xEJ%6DU?X?n=UG-~rx zJ+dOqR;g>b%wPP@ss87WCCOfwq$B4d*TR=SY^77_hC*LaG41aRtpbh#%q;^dL469& z62&a+VYMTu8R~57AL?v7-Pr1cSZ&wtCm9awN)h+&r0SL4!j2n?Z|Vc`T}_$}_e4CT z&O`}xNzKDe36YsRjD*1@dkyrwV?PaM3o_`}#f>BtU%He!JC)(3Mep zlgSNwn5PWcSc!PI`{&xA72o+k4!5r!J1PcLei!H(2`^x&dLwi}^i0s3>3A+;Qo6^< zt}^3BZE2ZuKk`aI9t>fWna`%IQ$F4&iT_-fN)53E_19*Nfr+oZEPCAn987wRi#Vku zwE~I6vWEy6N5NieK95DW(_1D%qmk6He%ARD^=sxmu6C}JtoT)!fcTav=;#lBube?Pg~M-?V#q!X+>~NJp(Jd zc;8&ru|H$`;dw>E3cAwE%j*@)4PImfB^pAd{AW^|wE`)Io(hTjU$}=cEjhTpG7Hq= z)^FCVX#-D+;T=j_t1o@U!#yifJI%1~ML8SeGZ0)8qA+;>@_GiKItUTK z)wdn?Hd9P84$3Pob7viVip6lsftZ7fYQ3`FlQL`+-iK}iVkN8P9Q`L?`G;s%N_66B zXPenZ9W)X~4C)MZ=ECe0T1s4fYQ+0cxO&!GIZZPSoMy>(b00K?b_2@XKEn~FM|IIs zICBby$TYi-2ee_x6@Me7O1eB{CNG{3kL=XK`7ZTX%H;U#Th1U~`W<-o8~9bge*j;Nij zr{pFMcBwQlu<^NGuL906yI2ZxW4m6@r8J>gWkQ2a3ZyoMZ|~3g=^&9|O5QQ|=DS2p zys#y)fQnfWVFt>B52@Y#!}0}P%-f|=H>e&>-AqIIr(ISRWyBWLsAsHjTkPI^dy(t++tu~ zB!S!@-=Bi$8JxtgKC5vzL>~lOhAfhbajL`cs*@cn&D~OS~@WT;(fANC1+}A2; zF0jAu-W(+6<$dmT-47V*E?PkrvF~Wr%WqK+jV89s?xaHCWb$ncb2hk8|2UZ0v!=ro zEIOKReMLgx;1i%Sx$*Jwh_ZV}9RG{GcSO9CP{t~NSXr?6v0r$vuRsPXaoGxxntGCx zZM8u{>&q8D(oHGbq)~!%$7pJl4wAPaFWSKK$9aP>TzYVHpJeyKgddZ_Et=mRv1(_^ z)JAialAOvI9w4<+IiCuHURic)*TFS{3eruYLvwk7NH1vdTYgD1anOjMqd7C=UME`t z#RACfm&c>dze#dBD{mbexmmN<{``EU+LtnP&Fx?Q-DNb$Q*Sg%Zy6Rk>1W5<#%_vcZsTG0@3RcP%VQ}eJNk!V%fyl zwAxHn^7}I>>Z9WBo}t(tz6bSsik`WMp~L4|bO8B7GKKM89jy_k z12*WTxINzs8PF2DTGw_?ypRO7I#h`lkX_8VQTQelZ+AWAlpK=+Y#GlcEo+qC zwR^7D45bp1La10-ZqO%C3-Xj&fy9(4@xb$h$rMPM(X*6(C{LTyQ7Kz7I}paN7{%P=zB zEXxdaFd(^sYt$1)BYLW>cAn4kXT)%1-^TKq%M)LCKO1nI-3G?*yKJ_Js?1blf@^w~e)p)0V-JB(sRc@^VKX#U z&8p2M>n+bd2J+ui<3mI)GL{$Vea$yK;MZwo46IyFCr)%Y-|76_Z<=RS#L~}zwN!|L zty#eJlAH4~O=MZxL;aI#nQn*^JxNQd9p#dzF?l+v_RUSAq0jf<{0-qo^a1JDk+zjZGNAID#X!>{X^$EMqc$Y_ zA&IP5Mm&5-3arjNWuR{YG!CC62*1QroAwr=E{4GA-7Aap}Aa%kWm^<~eJlr40m>3x6F0F^_kpWqeSyQmhgZlOYxlFVw+j!QJ8R66QOlc2D6z2iNBn% zHdwj#M8DI`~QkGq+9z7HGOm6}PG>N9h3tq$Vw0=VT5Ch(h z;E{txWS4pVwEIZA_fSR=YGPiv$Lkgz)=?t&zcq_`&Y8v7nMAC9L=-i?ji5T>1(S6h z+&pbhBM(6m8pxjr<~?so@G{vYI-++|%p&i$(Kc?JG@wJR_&|(^aJ8Qv@yT_1$_qUpVBw!H{MlGh#qhed46}~hyviTsKL*Zv(v2;A%V79 z$_%qLF+z@jdSQ^0di^to&}Wr68Hfuk)r78v+gCP^VK6*J6u592j$ike7k!cuRRN#>e;w`&ST>1jsWKL<^%T+46V`kSr+aAs zE^pW>W_zRty~s!#_+sPMRx^D!3R`FVc)>~UU9iaFddFrzZf6R8fXcgG7pqo=KWz;c?$lXf_*)|?9WE|e1Zo4b zmUqhDut<<^R|}lbC5<*iE4}pn+iTPHBG|ut`%jLpyaEU1X(Z?tMVOSkK_*|9`DgW` z*!T6ckgCY}nT~|nLmg8jY!TunyZE?G^YGa9 zapyYWQ=NI9B)of@4df0HF!L8{RUMSg5E0BzTJfRwyH2p{5g+l$ zpJUV|d?>L{rek?95Ral?%VtIo;o`S8>o%#KzC%v9j3m{<+6ZV6%+oEe5t_@KiQLp> z4M0bV9q8z_9^JaO8+wZB$IQ1~k4gi!4x$IY*I{;GR+AKH4@r$kVzW4d62(aB*y4Cu zN~D&B|73Gj%SH^Pwy(UfftO3K-oSHIthSK6O{;_7M0=WW&P=)BlMGtKv)tOWpl_UH zl(z&^xjLSQX_MV6>nIf~US)@f8-)p?!wLpw=}2n(%-x;-Y&4-DUAZd4l+wI>C4TQI zBFLE2(T(8Xy4iyk8AGbZ-%ZjP^lAq>A-=z5!;Nyg0!7_D^m*( zD^6I;d{cXK=(M{09GWnP$x$&zw-dZN-_m{$t2GIp^u356(yAI>gc8Mz#~P^w$*<)I z;tmGU`+#GPy5w4b7|G@}SE`W}>Fi?CXlmZ}{mi_w1YNCC89_J-by!SG-?Vra?0Y!n z#Ok6;S{<~@cOMJ~Q(`vPv*$T;K%V1#mvH&G<&F}I!QN>uV8&ufV#!TO57PO=(AaFu z_H1_7PHcPxeS+nR7;%%a){fah++L@|MDOmtv>sY}EjeNudXUmmS4Tb;RTi|oRR6?U zRg4p@RZSiV+x!YV@7!_AQMP(?e|hRyHWRfozW8w^vePHTOFsZMf`;>odvo%d&uOLJ zlKm$)4S;-{tFwxCwTYB}w86FPNr|v;dEB*;^UTgzwas3n8;w1@AnQBMea=^-DWytLfcKU&s;>H zYpU9?vOJ8Kw&dhp2Y&lcF0PoAd0i+@P=H*}>&pQLf)#Zd{Yl!F(vl8G3pO7NOrBD1 z6&TF#)UkMj@^#(oe=5N_dGew|YaR>Z%r2bIsUxTgmumd1n5bAQL5F)ShE@6cxRA#Z zkRBEXxhq<*LGxQB^K6SK^XLFm`wogs&sGQiAk7Ff?;gY-7Hv~SydBxWi*xPEv!a#W zYxIp)G;7R`KyCAlV7xpt;E?Y&-N4(>^x*R~932YX3hJURqVbljBv@ERC@ui*Y+u>G z4zhU+kOI6v_cO#s%uEALAc-U=oE$6}0ySA#TaW8BgQ4&i6J2GiP=cUDhM$^m(1?mWJmGqs6o8<9Q?jn5@x%2S(C7` zP>0|8FjUtOglspo`0f-d{@|JpdT-~6%-j|GrN7ZE z*%7~G#1gY!HtV1R&)+PPDqaHH*0~B3!6LdKP<0tU)pK6A3Uql zW`!wXU)etNxf@7z`WS6)Ym#)@?u1@hvCCC4uJm~U0H^{99h=n95xrg~A5gJW#P~{q zon%T1j!51f&lqtt_X~^NK3Ea$@pKNke!*5zmAIPVuKiaN*?1)NIOam;=lJ-^@&V@k zckG=ooz_(}>u{oDQ~0sCr^Rw_XD3xN+fKW6r;z7Z(8N0jaZN+Fj2aE3D^E8e*IsF= zpw?PiyyP}TOZ-iDVqt@IEby+14zh40=}gt~CviKU+igN~@H^_k)V8davN3P%jGL<9 zRmg_z22T+zMlTR}KW%@7DgysaR_y*b0#Mad&w?RpK&7sTD}$0H%G_H4$QFlC6@#iF zJSP~lSb`Tw0{QH8xTsn((vfVrU4mzSwBpgd)_Pg|MGrA-GQe|V;nDjI@UZozt-o9Z zRN0T~gR5oK4%m>iqxLWt>9R7>ijgKn7ZR-Rt1vIZ0^!=oqty2GhiH8@uE59Ygvu^q1` zhUJ5;LK($joP0qkb+coAP;j)i9W2A z!~%`G;?}Y938j4s*sW=q;Zt$Q1HU>e*7^9T3w^rh${_>X%#U1IN9EY9YXsrp__9X1 zTDd97F!6>q1HB5qU?5exB-e2`+8A{HU#N9OwWW#l0&H8^D{HILPALzn$;{v=<0j6- z&34yPbfN{rMwsnGLacDwY@nr!Jt>^koRdu};y;1k0TH6-TsW$6yCVei!(4iJL!Yn; zkB=bu`b7-zk}O5(CVWfGIBNqP>*#Bt?ZUPB%S9~3x(R*vwFeA@$C_QX^GWeMI_!qpWZ7r6gdj^& zam5cmh{xHFt_M6dDfg-nbf#X1&S&oxJCv)@O;ww$)AHkp9MPzJurR6IlA|uOVb#iF z8(Gqrbq;IyKS3`LOL**Ac;}u!fh*6$?>Dh^+Jj*D{d{@L=bnaXBGKb*|+;uMGAiV=XB6OgsqE_CLwRo--(y$jhvWu{#~x;Xc4Pe^dMj;r%GwBo#c;>8k9bZO>g%pQyu-_WM3u5phE>Py(m zuY>j%=|y1!E{>aK?eAEt>&<;y0}jfzA#VYs&T}PjwX{fjtY))gyOquyJ;GEP& zBlC#RrKw$hm^{x*VNe#*>Bpjj|H}Bhw7a&9)|_-CFt)XoS-97hlcX%QQwDu`)UxMl zQ4OZM#aYHhlw(qVYZdvq`C%3Hy9NV|{{Ef*{+DwbP3Vb{LxX|zdYb=Gt(_IOo&$e0 zcBNbJ+-m=Q@iMKv-%1E<$?7{DnZ0~IDVQtZwbx-5b|%b(0-YJ2Op!5tm-1^vpG6V1 zB{F76on7NN9UJlrny_yx3;2I!73m3YdYe2giZct zcK_zVx`}+h1lPuZVrlu=rlL=>iIJc)ArE{)tyjTYs`9f* zf*e8tJ4L`ok}%wh@qhk3jyrBJH4iWuL zP;n6hBNh-iY?E88YUZImqsQfY^xT(NKG=Fb4=>^|Gcu0z2y;7Q@dsb#&ExLRGiByF zJ1tfE7CsznNza>J@Im7sAYXK|Dp;0x#hJq}wyp7Rn>*)*%X{Lu%VwM08Y>F5*V3a=)xn`B6fI;l zC#>Wb$afhx@BQ9T8{n*rJ)gZX9Udw;py*n4V=g3&m(?71C~RGx+g@u5J-p*CQ+Pqs zrHmFys#E52bTG@P3}f4RB%@5jP_0*OB~v5AkklI4&Unxh7-0jF_CSVux+*;HM>4o1D-zaZO98rO981Qd2dN zss8!_h-3)x0ny7`Jb;N_+y)r^J#bY4+ty>9lHBZUpzb~!wo~zoWt+dR-Jy3Lj6vX( z_|;-7E=>|>ae2BanzwdFrm5#6)pO=9MrfWXdn)14w)vMNAcjqD4d;>uvph1iE;6e8 zg3~2uKX&4>YfVDJ1@FSfxo<6P7Y=qwYyMr)lOsKvw@2obH%%L9B#$U7cHjIv%eo|= zBKZ}nvm3B|52ae_hEVqYOFa`7I?|Mxk8`s62JMK7(c(}mHILoOXf!aX7@j<+crU<~V7k%JxX!GRWs ze<6V#T^m-RZ~UW-P*a!BwYMwd%1Fh?6*RmbYHYD9(4~@s4hNqw%F7~V`m^z?R02Vs zlu7P+`wk{A?;{=IUz{bEu2r57o=jBmP6dfo^L4+{bLC#12&wx1+Lq%A|1v2g&iUDF zXJ&EQb*Q~wziwvG{6Zfo(3YV|m^SPR3y$5LdJ>F=HZ)$rFPSX(Iu`Y2FvLS%-Gp+T zgl^0xG1j538fa=!u!mq{8e?zi%rkUuXD3kI?si(LJwVF9jl`fe8SQ4~%$a^c28y6C>pN8}~=9V$QkfCrP1 zM3E+&G?JVfw%@3TzEs5rvR4*XFBQ6}dgCAtLsh<)6$gw{HJG}RdI_0f67`ww+XWek zHl~mo|RPFwC7-e!~(X8C= zP+lj$R;$RI-=sy=v5eua+OFeyht!WT#;Cq50X%PB_!QyEZ4AcWpM1HtMq@GOEa&6N z!sVN5S>+x|;ONg0De}w!4dMesl{~he;HhvD)2zyhXH$$jFE{GMGIf_m6JN*el8ihK z$sMXHaFp;;Z%_r)R<98y(Z*Wm>NlB`$$?#*lR@Q*upsaG*O$d62|a9a9i;s6zaRso zUX~xKgs7Yiv@yX%vaE0O_L^R!-CI1GQ8QLC+$p+JLiOBU^^*p*<| z(K{DKJQ)q<((cYEL1wbmf`suh?+zy{LXHm+kVm zi^JR__`0B3F{1Y?eTg|A$DB)Ut{6po*}+@ zMJH*KUxY(c`9fZ1k-{>jUd1wk(+U2f}la0D3*#6M^REiw2f@M3O z^wtv$ExDm;`^ls?2PT1b@s=nFai*B=8pKMS{)Y-+1D--$!ZMO-Ny@_Y6}Xl^uad~I z*NElTj+AM}FKTKR!KZ?A^E;Vg%e12IbzEV+zAf9CL5zy$`*8xNo0=_gjxJ#oQ^xHg z(M-7(l6qL^7$?McZyWJ~7X0S;VP?&{vyC&`@0T`^zNSF(17>3|)+3DK$7O(V0yZY_@?6|jbVpTgZSAsY@8@WejNtzGDYfhu^*(hgKfc17e9AQE~cN~(`afLQ&QAic>Cuov49!jxA^S>P>&d;dTP zsR^~R%#X5Q35W|pY%Ed=KG=+k->oqtv@b0r0lUM351zu&pTC_Y!g=F#vRhb=a@Q_D5 z%?P<*APo`APhrdsH=lL8r%fw{+1jbr#X2E5lL|0fy#z|?c%0#zOt5ruzn3TM3n(ez zTm661zk)3m^Qts|If#UrQ4#0L{I)!Px7f%tX3fq>=QY(TikRYgr~{Gqm4KeH^>4TI zXs72Xc*chAWjXbHjP}1gXFhoN>XCq0gpO2VQ(|)WnAJ+Ji|f<~EA{#&1LHEFHm*x5?v=A;{%zFI4G2@7?5-=VtGv;KRUnRL&%9Ase>h&|ig@l}bpiM< zPYoS08IXmf7E2YA6$2#H`Kwj)Y$f2~hgN1j3$?9NN;&0$ybhv2pfrI_c_quUV)_rw zy0!b=?4GtkLsr`rRty>qmU6)P!=dudC-hoMils`WO3S>?`aDD8xZPNVAt#<9Bo#DK zoZgfURT`8HZ~;qzkjl4LJMuyUHK7%1WlmTp@J7cg80?pSlA8KPAuJ={gztrj3SSAm z#F;m816Et(^Q(x-l^cV4^r>O&p8qpYs)io@!JI);Rprx4e{ia}+8}huVU)K2~s9(W5 zxj1~Ue%i%AzXtICV>4D&O{7d`TeW3bTZMMTfaFSlAEJIGd6ZyVQR(k(U%czPigUw_ z{e)EnOEv6`>x*f_Od|vy5a=zTp31X4g_clDl!cwDd|ImH9R!mqCFgis)fpsYE-tRz zJh!e(!rZo&RMJ|L4UkqEUb`Fi%tNeu_?$GMD+UBDj^h5$(3E>QA=5h$CgrtiC!yCn zn&vF_SSUGXLu~b$P3Q_ftGv9OkAcWf$V-q4g9O>@8D&|N#!8m$xsUzTE3y@;EA`7lC zAX$>cp1nK&2Om!RR5xKy+g6H~oVeJECL=S)FOU>hh%j$yHPXB9q7df-g2aI(D}uj3 zFF~{*IU`A#K4v4OBt;|DC3icxbw=k)HyK@(Y5DjjKZN=={XQ}u_lgA%GWOBQ@V zJyjm2Rorjtez6TI-Y>gUTiBl9H-ooB)H(*V}y;c!H-Xs32h*Y&qCy0$+Q0Mvb zOqdp0OB^uqu8=l-LE1xFtxOqbn5ft<*~SW}Q7d=FDmqtV|IZCrAvRU!geI$z)kuoo zCqXNw2{WWJXsJ2*)pNZBg-}fO8YRkiReVGGEv%_x9}GNW^Vt9^yDs)1mCAtnBIU;W z+gFvvIY@MZ42t2`vtpLmPnAH?ug}a2Q#RzjS1(hxb7A*eE}@Wl`ci777vnxvb^lmK zM{KD5q~6O3rkUfDvfv=+L9f+-R4*S~P=I$uQ>GJF(Yr=n?AugJ)dZP0&l+XHd{RMu z-BlqzpTngqpQ6q$V-;Y@pcQ3sWobh3Mc-u5fcPrj_G+c1VcWraRzQ$nl<9`c}ICuexJQxub{%c^!H4x~=PyssD^MOL&R;ak9<8D8&P@NNU z3v}pkh1NUWD^GOqgZ%F3?)jlL>Fy49y|2HboWI=id*6=p1$W2){H`|O$JD!CX_v9q z^ClxSMm$j}ktq9>4I+w2#U=1`Zhp1r+k4YbfBwT-Pr`l8%4%!Tg=(G4{PurO0cAeX|9-xEwK%Wln5$)WRjRL*1 zxb#jB^bQPqj(+*e4iFgx3L(VV9|D!_0?n;lu-yt8D19(u02+8=RH_Ba*aUj~ox?Yq zf2apty?hI0w)yp2(4Q4Q9J2W0b>*g;S89$sY`)3f3W~|}%+~qgD(L28d$TUkLF>&R zr19SFEeD;q{CVtS)2CA3h@{%{n?SESA9m+BeLSI8`|$n#*rkO}T^ee5a=PTO!&vJ&$e*F2#U`+(Ewy*St>*Z~xP4$9v*h8o^rigE6(p?cLb}Mmu)y zl$iP#v5!rp*=kOVg?~y*kOS|khlvZI$h;pO(_cTmy%Fgb7E_7J6m@=K4+~R>4y)Ue z5;roTAI48sW6i!!UG)5;*6q;6HUbNE?>~_ChvR!@5|1yMJlfd)Aa)n1BH8t|OU*V$ zXUOY>`AwVCc6)w|0sWA$Y@WV<=RJ^az{%qT1qk$@X)WuAqnkk8InO3QpoY(2>|bO9 zXO0#K^q)U(egFK&ZQ5`2DqioVygAVD+F-Nam;d=e`j^BeV1OJ z8Tf+ne9Qhnzseiaef0)ry-W0I5B8Qg!r;rRVCq|NL-O`7etf63*AH^$?pOYq1BbX; z29GnpIOKQ!PyC1LCw07UK6(=TVgD;*#Lb!~v=2_7qpySFQ!{ZlKfreX znm*`HM^-ViVKh1KmN1q=k8K!uU6UJGlgCkLjR0$vqYzq z?mZcuXWE^wkL-MA{vh{lZyTS>d7tw!`17_I(ai0YSnXZOyKc9g z_y2BR@IKN$8ko7y^09Nt)}EZl?=BtdKW_LqAXg_h{Iciw^6%rmzSptm;hBf-4-+1W z$}U~TU$*NblX-MG9=Kdzdb*N}ibZ9)@qYGw+UUFS z`#Nsdi{zxA{!Bjq^b(HA*_Q#)=N!Mb=xNW7a^E603(mdhk9n~+=Nz+DX&7Tkaz-QZ zWxrPZdhgeQ>B#O)yE)aL7T|*vM=pfBi%T-!Vs2+%%@3mgN>89$4H1VLhx{c`5`Bqy zNE#hc@I!%Ff$U7^8AbH=y3=((*Co{rNAqTFdvIdH%ywCaZ1>FQ2M1Z`%2S!atduYP zU}AObq}J)|eIYPw+(K72?sIT%VC{#6J7a%+)%jc9y_Vmeez@@TNbR@tDFG9wZW!49 zS`lvKUvW}!Z6x@tpMT)-;b$$+S|=N!-Q?iBU+X@+>bc(4_0DXhVxXdZE)(8I38wlr z4mJ#{GLnkVQCc0-PN%g!8hRAhx%JV-M~aGEcUyOYyUe}6;sPHlHWP>PJwn_JJ@U2l z&*XR3{6){Fx5VU!3iZbIvY;QKGIZDwY)&<|V=iy*O9@==CpVRc%6SL6jP@Mh+g!Ja zi@F}&6%!KCAbvJ>vbnUSw0XuOz%ZZ)U$A`2pu{P=b5Bl8jsZH9mP!jNBz8WW+CN|S zk#KM%ulEyNu%y4}vi0hwH=Cw5v2X3a^>Rz_7LzX?e^I>c)Ygox^@VESuq=ny8q7il7iw2yQOF z_GWEa?X4RrtLGMT_FUil&34HjlPv=+h(9Lqn?)gcCn%G7mmXcmRZfjOVHr%%a6757 zXRn;Pk{xHAl)ESAh27NcgZ6HgVAm;H*u?{Tm+s^zxt?o$lJVr{YG$qD+56K4?hobG zqc-u#_1}_Re~RXu*zSrtO1QW0KI`!EMfd;k1r@x($U$>BnL?&KMePcF^y+B;G3Iwn z*I$nweXwhiFdf=q_#mZ2F?o0mzdZbPNRso1r>{_zZoBoJE&n=t0|Um~s6Ed<@6~%k z_^Wz$?eNau_2byt&)sUDzZ|H&ab@w-p{8^7`iIld2C)_}Z&ckcHv$(v#&`bK`Qp)r z+qfs{@=_3CWGNGM`(tr2^62j#Cm)uQ+dIQ5SG-ubul*kWa#%nph8y!e?otU__6smhTZ^kd)qy!^Mm#L*GzG z!Sy0ykSw$NRd+}CmQ#MGB$%$6is7ZA(@*8Zf<*65_YHbEvvKg-A%4?nENOZ7?E_!i z2&Ck>2FB4F9>MH$@v^Xw^S_T~?e{mVYno0Nt#ycXZKYn#=cMAG>)|0IRZUGV zwuholXBeM96ET&4Gkhptk{8Q}@cMrD+aIUi91)hpktW#59QqFb zbT}icz{JFG*M9cWFWd)Xv=_9!yp9BS4MB`A7YR4Ul}$o=Bdsl_6C{C@iRNPrsntm)i z0kF-3xbvusAP~_M1iE<}1X|Mo*BKBf`WOf_dldvalM4bFgx~jV{Rsp*@$kZbe!O&J zXl|77yjbLIvKl}V;^!sjf+WG6US?NnesS`7{)RJf)zEU_YK**p;J+34|7islG#Q`uL7<4d zIGH%%Pla1>L9m#fr2DAz96g$mC!ZVCD&pbkj3ixDDns_3ln%R-jo$&8qy&fJ=|u`% zoJ+Ohz1ML35729Q(}8)4xxrk~ke3rwk`Sv1)+!>Sf)6C490h|u{qDGQdOz6pfNj)p z``#n=S{sS0S5;j?(r30pcQ-=y=b^Wczo`c_9Ll#jSk>kC7#k zK0g2ZJMT$Hi|05)Ucu>+Mo#Ia{4*Dqt;yu^v;BVGHD@EW*qUe`Nj&gQWIzDJ$F=9A zPV2ZiEMl&-I5boX-*@f1l|j0Gn=h5m$5l% zFcAfnC#oSW^4E!Z=ni}+TSlma7DpOkh9%~0#`fv3VhY|k`GsdDw1g1WeC&>{T-u~k zEmsnk-XYj28fwDUtGYKgfj*_}e(lj<-2z5yL%4X1D?S(@`iSg;GxblOegrKJ41=|j z@mi6H?&T4bBcz4Wd5lcKOZ_TC15VLt@EjDTaC$#FjGj~U%Gmy{pTt@Unr4j6Yg zP_bC7&8UeqLOVi>`EO75^#zsDQ@|K;z0(U?X=oTWQq{7t`55KReGCZnI`G9X?P4kU z_#NH0NURwLjFuKLvhhAuqJo0JyrA?zrh`r91cws^P9{e|*CyKw2w@Dl?)ZFPjAD;v zP8uqT%i=c<9~KNgo@Q0{Gx#;i!;d;!)f*(7v!P+9j3^lLV(^1Qp;KUVdMWunzLY^D z8>F>T5r({awRzc>0ZaM+-bd6_pYI-=qbWlW986dA5qH3zQ99g+It(Uu$sg{5&w$QN z50`Qw$zWtxWHq+e9k-)&l7h2fr5L00rU{7d`p&#gx9K6GRxugtPvs9Y+9((uycm{b zjD}w02{Kq*C=H9UFrCt~cQ#E12bCjQ{JNky@406fvUg!Rp4-6OV`p9iN5>Y{a_k~L z9oXH~)zt%@7BQGwv8)$4FoG>1*D(7hthJHbAsjL%(iujl)iD^h(LpqZTd;5_5b;bf zktp1w>d#M65)o4r{WRdKCfCOCqCFG2_QB@GwO;+e84N-ku&ZX**VZwQX`2?4qk`zn zn))BDUC{X@Yj1a2dRuS<<70LA90Y9vy*Ai)6S)H-Ge(}bv028F(&5DvR6ENV9c-RH zG2r#rDY>qQ)j!ma9`=6JfVFVi$2U*PMrPTRpWz`m$1l$7mmq( z%7Rt|&yG$0!D5g6fEFH$-o&r2&XZ}zN$Z!eLg9`*f-y07L7 z%Fn6)?iJ7U`j>aTB++4TT2ojB2i0-5U!a^{dFn-Sj8r%St?Tq@4rO_MOHe2>6iy>A zy(hI30sT&x~+? zAIfw_&4yC3(v)z8E@Sb@<7*=CwU#mEW!?W-Ba2}h^!ZZR06N`N0bV|KhwSgdU>YO$ zIqrn$5Io7*<3V{IJq^_1k@b_zT=d;QYCmKHi`vy`O@jW(e^<&a87O#7qs=aB&MU8rkQNy+?9_^JkeDuWCj~>2b5W^~FpHF_ zC~ZiEJ6_8#;MB#mh{1~<6h^pq8W=Zph1%1ar>$T>vskZ6dQMNcXyfd6Fj z@JG;>pqrN5um?5i$SzjfNv5qC#}AfF>2X^+rkxgvwFpW_hZ2mEr=GOcGN{2>*Wh=~ z?S>Z@b(tf?sbYg|auYkkY)dH)Q;x=CM37hS?K27BS%25Ol8j|;Wf5O;xwG_D!QQ~N z6^cQqH*q)O&e;}7zb>+UBC5JFG%S9$sCB@<3^5sbzIZZH3mzI0782s~=kF28)!V?J zn?1QVX@sy)FAI+BWO$fG4by|2(t50-AAL8Hkr(4&@({}xpW&TiuBl#j^7TY4^W|J8 z;b7Ud^EyTtM&#-(lMQaCC2MkFtj&ZNBC23dRi|W0x|FdcY%AgO-4LTXQ4vAlB{iy- zez;sPyts|2XseB0{m@oOjKD9y{t2E4=1`DYeqKCS%W<-Rs8uBMiF(XmcI#-MlC;qI zio)nWHox7+F#<5dB`3gFGX)i4qnx1}PmGD-RJE!v>hGarMrASK{nF{}g? zrCr2ucf0_-*kk0c&87i8Y!1@v)pmx-4J;c1u0o?vfqee*tTRp6vSxa2H zCoi_F`y_9BE6I{#*Y3acQ67-SX__`l9CjBHS1z%Y+{5|X!<5Hs`x$A9RJGt<$F<%fqCMyj9ipUq*3R| zezO0?onPNMk&gJg5!Zl>CVGiHLUr|dWFVv$ylg!;|8f_+-}s~Tv!X7x0*rJ#!W@Si zP#7bL^KcRMfn&nzt!!C}#!I$3HN1ZQNq~efrv|(TvaWYku(6^5P2Vpg9ne&1!?f0)LTmRwG&TNJe< z4{cH0g|;CCMUPK%xj`=Q@DK|pZn=ySHP3DXwh`yvkw*qTMA@5+G)E}<3PN+VQGBjy zl~YPl-1jh6P4L2+W}*$-`u&Es=lGmWY%{Fdn2BvrR=?ynins-$c;&@Hc_`*5*K3Mstvt-1WSMD(0jN&ZJBc84mbU@R&1~iti>(a z54O7~MRy!P<>*QX`1Th8`$+&rvG%Sc%$K8F7&0FS&H5#0ZjS@qiy|SS|bM~O?q<9ne|5!ix3_xc@ z-79@!7Hmq;Y`vSF#0TgbNVpGi zfpf$*F?Pr&+-~XhU=B7;alpijPV+N5eOezXDy<=e0Z4oo`tXJ4&=&20{Z4=Wvu$q3 zJ?2iwSUm!8O$#P!picAcouN4t{5+(W54<{hxlP;vuTL zV@HVn1o@N=3K(1*+cL zg*%&n6_+a999>}MR{D&ZT>tWoMqa$z|I;_o9mw|LIcG+)wcQ5)m@wIDQClWr)u6v|eb`tzx6Ptx$UuePE*c{Iw-pGq#hkWIg)~sBhe0pHx%8?5|V2 zCU~^u0`r%gKy|u6S9E8bjY@aS>$$(Cq>}Qs?_$W)Wek-l2Nm9KK+cY+45-(LI$Y=>Mxo_7*=1t4PjnfNH z6$j$v=Q>}`lGL)qgq50<1m#35LVQtZE8i<7CCbeE=NeQjjF=?!b{UD+6smJ}hTjn1 zD)(g*-YlQy#dFUY7<*=}Ew*J*;^lCNvpL5uQDezyq@^1|N?39MZeQ{g#~<9*opG(> zT5y0Z15tXA;y1VHOVFq5to7u6jvh&?XN0JQ1XjY>-?3Wu-pi?HHZ|VHN*K3z)b~1f z%A+Ynb8AbMo&2*$oRIhERV1%^;faV$GPzM9ZThU*VB5y8yxC^#yp5aaFWt%%thWeN zH6A|E-S8}RA6=0+$@bXg>a*~*PXgq^2kU5 zI!lni9~9C2a9|#MeYHR_B7bARUQ%{Ee`h*Hj&a9-A83>Uo*BlZo?O3t_!4`b@wsmfg>jAd9PL#0vv~WEQ zW1GlL+LmP-^EoR@>S#v%g{F#x)j}ykN*H#H4`Yqgj!k>K8Y=0Nmp*U^ zi(6yPkJiV|BU*axzbk%rpOl3T1rVwkOZvW4c0&^?MFY6txp@~b^6KID19Hj?M6Mg< zQam|o5p01ppC}9AoUNK(|BTBLs%Nrt*jByqR$<-E;cU%-JJtBiy+-*(%MFIBUA%O$ zTN*d4)3|0R0BdGl)nnyVg!W*cMR60UZ8&kw)kk$$v+}8bgPmfWsjMP&AF*A0cSNXB zA2C)hOjW7c>5>W2fjB{Hzb(zhRbr=kf>F;NR*Y^e^6VC7(kdGRh3c2VVs;xa%9m;v z^L72P>COJ|5;Ah+fYw~Ck#*0%;D*Kb7)}q7?n3VHk?DbouJYEXYR_~)R$=5J9mT9r ziMr4-qsB4MFVsPZ=1_>|Hpt#6uNhNhJH?DVx z3zu7(b*RMk#Yo}4m6#}yy5xs{OKB{JxNP3y6*75(vZjK z!a4<&UY+9R2s9;%{oUv=JiP~t58gIg`qDDbqIOQj6D|*0DrdtRGy?gfrbzjlqfx4^ zLc0LpGjX3Y74ZxF_=&+-+iFF$Wy5dyaAgKi$dUf|sq0*siRx*+v}xmWz9y%tPS&jB zo7fXARa$9gRXcg`1Szdi#mPY|O$H=iUum~duiP6J#D1Q)$uzP#9^!LahU*uuoLU~S z*Pd{*Pd63m`a427wbj@zMjJTgeyrx2QHOV-dds;lHUVj<#^=FdL||+IJBf?5O-oKP z1@`CRGz!u&GJs)hl_@nVJo7@fWYv@)-71pSFXg#v)>g!Ji?`eOJ&VUwgVOi~jt6_5 zT0c*0-`eJ$Vp1pKrs&u;BFu6`Ski~${sP4xv5B+2#02@W&e;{E3D(3ej(?8l6>1kh zxq9#Hyh(DmD1OEYOYH70RCe02<|*peMcaJV%b2@x`wWm;)ky=y+#o58Z;Wz5aQwc5 z6%QdLkf{;-T7~{5F!ww8KWu3dn_P597w6nFQo?G@<@kMua2ZVfkLr!)>FIL;v;gz4 z$0D^;(*h|gQT@R|s(Ke)rL2B%#3f$EHdz%Uu52hq<&VW-2{Wb!l)op`zS;TVFz?-+ zO1q8sQq`PNpgJ6GEM8xIa0CDlInLE+y-S%0K*ca#er}?1!^YJF9x;Dyxq;ZJP+iU3 z2#=c>KJBOltK>(60oIm$+{|FYnvBTr^aJZR>&7}9N~*0k3f?(!?5~5Az;MSfAgV@d z@3V^U9(j9-&nR(49rSB4LND*Rz;-V#D)}!9j?kVcI`7Kt7GO<+s`G}XT@|tev0wxfI5Bx&DVIJk`<{?iBM6_ zfx!VCrYX9?64Ed(F}k1VIlXL=ATRe>1~%gKHj9QEXJY!jk-6%X2eC$R^EvUKz3ntr zLUolf;q#cNY0QSYyk507r?k7#zPx+4UmRCG#cUXRD2?YyC8M$Ov8N6^_-d+bc3@h- z#vB3quGH5VbHuV~9PU6nXPNKE-kZXNs)C?G%w1@~2=%m;{@Sq`yJ^rb+cn zfk>FpCdI}KUXyCp3a!Nr<-#?t@M7~$TUD@KtVGd(BeeIkS4|q1Un+!a!**-U;mTp5 ze1IgB-S_^7&t-s0;7rRrAv#Bvp}y=kpgXsupF7>b$}W*jtgDkq=F zxy3Aroub5H{)S>}4tweS7Lyx`5=ES&yUlp)`Q9=|`BfY*YvF+`kIik(?1+mnl;h

Dc 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 TWnTLGRxX@#`A?<&)!+XQ7jlCN literal 0 HcmV?d00001 diff --git a/assets/share/dungeon/obtain/OBTAIN_3.png b/assets/share/dungeon/obtain/OBTAIN_3.png new file mode 100644 index 0000000000000000000000000000000000000000..1c615952dab0a52588edb0c8384f070231800f36 GIT binary patch literal 15084 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 literal 0 HcmV?d00001 diff --git a/tasks/dungeon/assets/assets_dungeon_obtain.py b/tasks/dungeon/assets/assets_dungeon_obtain.py new file mode 100644 index 000000000..7e82bf234 --- /dev/null +++ b/tasks/dungeon/assets/assets_dungeon_obtain.py @@ -0,0 +1,65 @@ +from module.base.button import Button, ButtonWrapper + +# This file was auto-generated, do not modify it manually. To generate: +# ``` python -m dev_tools.button_extract ``` + +ITEM_AMOUNT = ButtonWrapper( + name='ITEM_AMOUNT', + share=Button( + file='./assets/share/dungeon/obtain/ITEM_AMOUNT.png', + area=(190, 521, 490, 539), + search=(170, 501, 510, 559), + color=(195, 190, 188), + button=(190, 521, 490, 539), + ), +) +ITEM_CLOSE = ButtonWrapper( + name='ITEM_CLOSE', + share=Button( + file='./assets/share/dungeon/obtain/ITEM_CLOSE.png', + area=(1043, 185, 1073, 215), + search=(1023, 165, 1093, 235), + color=(170, 170, 170), + button=(1043, 185, 1073, 215), + ), +) +ITEM_NAME = ButtonWrapper( + name='ITEM_NAME', + share=Button( + file='./assets/share/dungeon/obtain/ITEM_NAME.png', + area=(495, 187, 855, 211), + search=(475, 167, 875, 231), + color=(176, 177, 179), + button=(495, 187, 855, 211), + ), +) +OBTAIN_1 = ButtonWrapper( + name='OBTAIN_1', + share=Button( + file='./assets/share/dungeon/obtain/OBTAIN_1.png', + area=(813, 414, 877, 478), + search=(793, 394, 897, 498), + color=(118, 96, 131), + button=(813, 414, 877, 478), + ), +) +OBTAIN_2 = ButtonWrapper( + name='OBTAIN_2', + share=Button( + file='./assets/share/dungeon/obtain/OBTAIN_2.png', + area=(889, 414, 953, 478), + search=(869, 394, 973, 498), + color=(71, 96, 145), + button=(889, 414, 953, 478), + ), +) +OBTAIN_3 = ButtonWrapper( + name='OBTAIN_3', + share=Button( + file='./assets/share/dungeon/obtain/OBTAIN_3.png', + area=(965, 414, 1029, 478), + search=(945, 394, 1049, 498), + color=(76, 101, 109), + button=(965, 414, 1029, 478), + ), +) diff --git a/tasks/dungeon/obtain.py b/tasks/dungeon/obtain.py new file mode 100644 index 000000000..ecd440d0e --- /dev/null +++ b/tasks/dungeon/obtain.py @@ -0,0 +1,222 @@ +import re + +from pydantic import BaseModel + +from module.base.timer import Timer +from module.exception import ScriptError +from module.logger import logger +from module.ocr.ocr import Digit +from tasks.base.ui import UI +from tasks.combat.assets.assets_combat_prepare import COMBAT_PREPARE +from tasks.dungeon.assets.assets_dungeon_obtain import * +from tasks.dungeon.keywords import DungeonList +from tasks.planner.keywords import ITEM_CLASSES +from tasks.planner.keywords.classes import ItemBase +from tasks.planner.result import OcrItemName + + +class ItemAmount(BaseModel): + item: ItemBase + amount: int + + +class OcrItemAmount(Digit): + def format_result(self, result): + res = re.split(r'[::;;]', result) + result = res[-1] + return super().format_result(result) + + +class DungeonObtain(UI): + """ + Parse items that can be obtained from dungeon + + Pages: + in: COMBAT_PREPARE + """ + + def _obtain_enter(self, entry, skip_first_screenshot=True): + """ + Args: + entry: Item entry + skip_first_screenshot: + + Pages: + in: COMBAT_PREPARE + out: ITEM_CLOSE + """ + logger.info(f'Obtain enter {entry}') + self.interval_clear(COMBAT_PREPARE) + while 1: + if skip_first_screenshot: + skip_first_screenshot = False + else: + self.device.screenshot() + + if self.appear(ITEM_CLOSE): + break + if self.appear(COMBAT_PREPARE, interval=2): + self.device.click(entry) + self.interval_reset(COMBAT_PREPARE) + continue + + # Wait animation + timeout = Timer(1.4, count=7).start() + skip_first_screenshot = True + while 1: + if skip_first_screenshot: + skip_first_screenshot = False + else: + self.device.screenshot() + + if self.image_color_count(ITEM_NAME, color=(0, 0, 0), threshold=221, count=50): + break + if timeout.reached(): + logger.warning('Wait obtain item timeout') + break + + def _obtain_close(self, skip_first_screenshot=True): + """ + Args: + skip_first_screenshot: + + Pages: + in: ITEM_CLOSE + out: COMBAT_PREPARE + """ + logger.info(f'Obtain close') + self.interval_clear(ITEM_CLOSE) + while 1: + if skip_first_screenshot: + skip_first_screenshot = False + else: + self.device.screenshot() + + if self.appear(COMBAT_PREPARE): + break + if self.appear_then_click(ITEM_CLOSE, interval=2): + continue + + @staticmethod + def _obtain_get_entry(dungeon: DungeonList, index: int = 1, prev: ItemAmount = None): + """ + Args: + dungeon: Current dungeon + index: 1 to 3, index to check + prev: Previous item checked + + Returns: + ButtonWrapper: Item entry, or None if no more check needed + """ + if (index > 1 and prev is None) or (index <= 1 and prev is not None): + raise ScriptError(f'_obtain_get_entry: index and prev must be set together, index={index}, prev={prev}') + + if index > 3: + return None + + def may_obtain_one(): + if prev is None: + return OBTAIN_1 + else: + return None + + def may_obtain_multi(): + if prev is None: + return OBTAIN_1 + # End at the item with the lowest rarity + if prev.item.is_rarity_green: + return None + if index == 2: + return OBTAIN_2 + if index == 3: + return OBTAIN_3 + + if dungeon is None: + return may_obtain_multi() + if dungeon.is_Echo_of_War: + return may_obtain_one() + if dungeon.is_Cavern_of_Corrosion: + return None + if dungeon.is_Stagnant_Shadow: + return may_obtain_one() + if dungeon.is_Calyx_Golden: + if dungeon.is_Calyx_Golden_Treasures: + return may_obtain_one() + else: + return may_obtain_multi() + if dungeon.is_Calyx_Crimson: + return may_obtain_multi() + + raise ScriptError(f'_obtain_get_entry: Cannot get entry from {dungeon}') + + def _obtain_parse(self) -> ItemAmount | None: + """ + Pages: + in: ITEM_CLOSE + """ + ocr = OcrItemName(ITEM_NAME) + item = ocr.matched_single_line(self.device.image, keyword_classes=ITEM_CLASSES) + ocr = OcrItemAmount(ITEM_AMOUNT) + amount = ocr.ocr_single_line(self.device.image) + + if item is None: + logger.warning('_obtain_parse: Unknown item name') + return None + + logger.info(f'Item amount: item={item}, amount={amount}') + return ItemAmount( + item=item, + amount=amount, + ) + + def obtain_get(self, dungeon=None, skip_first_screenshot=True) -> list[ItemAmount]: + """ + Args: + dungeon: Current dungeon, + or None for no early stop optimization + skip_first_screenshot: + + Returns: + list[ItemAmount]: + + Pages: + in: COMBAT_PREPARE + out: COMBAT_PREPARE + """ + logger.hr('Obtain get', level=2) + if not skip_first_screenshot: + self.device.screenshot() + + index = 1 + prev = None + items = [] + for _ in range(5): + entry = self._obtain_get_entry(dungeon, index=index, prev=prev) + if entry is None: + logger.info('Obtain get end') + break + + self._obtain_enter(entry) + item = self._obtain_parse() + if item is not None: + items.append(item) + index += 1 + prev = item + self._obtain_close() + + logger.hr('Obtain get result') + for item in items: + logger.info(f'ItemAmount: {item.item.name}, {item.amount}') + """ + <<< OBTAIN GET RESULT >>> + ItemAmount: Arrow_of_the_Starchaser, 15 + ItemAmount: Arrow_of_the_Demon_Slayer, 68 + ItemAmount: Arrow_of_the_Beast_Hunter, 85 + """ + return items + + +if __name__ == '__main__': + self = DungeonObtain('src') + self.device.screenshot() + self.obtain_get() diff --git a/tasks/planner/keywords/classes.py b/tasks/planner/keywords/classes.py index 2066209ed..46619e76c 100644 --- a/tasks/planner/keywords/classes.py +++ b/tasks/planner/keywords/classes.py @@ -28,6 +28,22 @@ class ItemBase(Keyword): else: return None + @cached_property + def is_rarity_gold(self): + return self.rarity == 'SuperRare' + + @cached_property + def is_rarity_purple(self): + return self.rarity == 'VeryRare' + + @cached_property + def is_rarity_blue(self): + return self.rarity == 'Rare' + + @cached_property + def is_rarity_green(self): + return self.rarity == 'NotNormal' + @dataclass(repr=False) class ItemAscension(ItemBase): diff --git a/tasks/planner/result.py b/tasks/planner/result.py index e4725e3aa..742624d33 100644 --- a/tasks/planner/result.py +++ b/tasks/planner/result.py @@ -1,10 +1,12 @@ +import re + import cv2 from pponnxcr.predict_system import BoxedResult from pydantic import BaseModel from module.base.utils import area_center, area_in_area from module.logger import logger -from module.ocr.ocr import OcrWhiteLetterOnComplexBackground +from module.ocr.ocr import Ocr, OcrWhiteLetterOnComplexBackground from module.ui.scroll import AdaptiveScroll from tasks.daily.synthesize import SynthesizeUI from tasks.planner.assets.assets_planner_result import * @@ -26,7 +28,16 @@ class PlannerResultRow(BaseModel): return self.item == other.item -class OcrPlannerResult(OcrWhiteLetterOnComplexBackground): +class OcrItemName(Ocr): + def after_process(self, result): + result = result.replace('念火之心', '忿火之心') + result = re.sub('工造机$', '工造机杼', result) + result = re.sub('工造轮', '工造迴轮', result) + result = re.sub('月狂牙', '月狂獠牙', result) + return result + + +class OcrPlannerResult(OcrWhiteLetterOnComplexBackground, OcrItemName): def __init__(self): # Planner currently CN only super().__init__(OCR_RESULT, lang='cn') @@ -146,7 +157,7 @@ class PlannerResult(SynthesizeUI): Pages: in: planner result """ - logger.hr('Parse planner result') + logger.hr('Parse planner result', level=2) scroll = AdaptiveScroll(RESULT_SCROLL.button, name=RESULT_SCROLL.name) scroll.drag_threshold = 0.1 scroll.edge_threshold = 0.1 From 117704273fc1f892c814720abd54b215d11e9b31 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Thu, 16 May 2024 02:57:49 +0800 Subject: [PATCH 05/37] Add: Planner progress in config --- config/template.json | 51 +++ module/config/argument/args.json | 296 +++++++++++++ module/config/argument/argument.yaml | 2 + module/config/argument/stored.json | 539 +++++++++++++++++++++++ module/config/argument/task.yaml | 1 + module/config/config_generated.py | 51 +++ module/config/config_updater.py | 46 +- module/config/i18n/en-US.json | 202 +++++++++ module/config/i18n/es-ES.json | 202 +++++++++ module/config/i18n/ja-JP.json | 220 ++++++++- module/config/i18n/zh-CN.json | 202 +++++++++ module/config/i18n/zh-TW.json | 202 +++++++++ module/config/stored/classes.py | 6 + module/config/stored/stored_generated.py | 50 +++ tasks/planner/keywords/classes.py | 89 +++- 15 files changed, 2140 insertions(+), 19 deletions(-) diff --git a/config/template.json b/config/template.json index 9d666a02e..7dac4f56c 100644 --- a/config/template.json +++ b/config/template.json @@ -46,6 +46,57 @@ "Command": "Dungeon", "ServerUpdate": "04:00" }, + "Planner": { + "Item_Credit": {}, + "Item_Traveler_Guide": {}, + "Item_Refined_Aether": {}, + "Item_Lost_Crystal": {}, + "Item_Broken_Teeth_of_Iron_Wolf": {}, + "Item_Endotherm_Chitin": {}, + "Item_Horn_of_Snow": {}, + "Item_Lightning_Crown_of_the_Past_Shadow": {}, + "Item_Storm_Eye": {}, + "Item_Void_Cast_Iron": {}, + "Item_Golden_Crown_of_the_Past_Shadow": {}, + "Item_Netherworld_Token": {}, + "Item_Searing_Steel_Blade": {}, + "Item_Gelid_Chitin": {}, + "Item_Shape_Shifter_Lightning_Staff": {}, + "Item_Ascendant_Debris": {}, + "Item_Nail_of_the_Ape": {}, + "Item_Suppressing_Edict": {}, + "Item_IPC_Work_Permit": {}, + "Item_Raging_Heart": {}, + "Item_Dream_Fridge": {}, + "Item_Dream_Flamer": {}, + "Item_Worldbreaker_Blade": {}, + "Item_Arrow_of_the_Starchaser": {}, + "Item_Key_of_Wisdom": {}, + "Item_Safeguard_of_Amber": {}, + "Item_Obsidian_of_Obsession": {}, + "Item_Stellaris_Symphony": {}, + "Item_Flower_of_Eternity": {}, + "Item_Moon_Madness_Fang": {}, + "Item_Countertemporal_Shot": {}, + "Item_Divine_Amber": {}, + "Item_Heaven_Incinerator": {}, + "Item_Heavenly_Melody": {}, + "Item_Myriad_Fruit": {}, + "Item_Tracks_of_Destiny": {}, + "Item_Destroyer_Final_Road": {}, + "Item_Guardian_Lament": {}, + "Item_Regret_of_Infinite_Ochema": {}, + "Item_Past_Evils_of_the_Borehole_Planet_Disaster": {}, + "Item_Lost_Echo_of_the_Shared_Wish": {}, + "Item_Squirming_Core": {}, + "Item_Conqueror_Will": {}, + "Item_Silvermane_Medal": {}, + "Item_Ancient_Engine": {}, + "Item_Immortal_Lumintwig": {}, + "Item_Artifex_Gyreheart": {}, + "Item_Dream_Making_Engine": {}, + "Item_Shards_of_Desires": {} + }, "Dungeon": { "Name": "Calyx_Golden_Treasures", "NameAtDoubleCalyx": "Calyx_Golden_Treasures", diff --git a/module/config/argument/args.json b/module/config/argument/args.json index 64f0ee68d..701c96285 100644 --- a/module/config/argument/args.json +++ b/module/config/argument/args.json @@ -219,6 +219,302 @@ "display": "hide" } }, + "Planner": { + "Item_Credit": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredPlanner" + }, + "Item_Traveler_Guide": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredPlanner" + }, + "Item_Refined_Aether": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredPlanner" + }, + "Item_Lost_Crystal": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredPlanner" + }, + "Item_Broken_Teeth_of_Iron_Wolf": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredPlanner" + }, + "Item_Endotherm_Chitin": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredPlanner" + }, + "Item_Horn_of_Snow": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredPlanner" + }, + "Item_Lightning_Crown_of_the_Past_Shadow": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredPlanner" + }, + "Item_Storm_Eye": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredPlanner" + }, + "Item_Void_Cast_Iron": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredPlanner" + }, + "Item_Golden_Crown_of_the_Past_Shadow": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredPlanner" + }, + "Item_Netherworld_Token": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredPlanner" + }, + "Item_Searing_Steel_Blade": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredPlanner" + }, + "Item_Gelid_Chitin": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredPlanner" + }, + "Item_Shape_Shifter_Lightning_Staff": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredPlanner" + }, + "Item_Ascendant_Debris": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredPlanner" + }, + "Item_Nail_of_the_Ape": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredPlanner" + }, + "Item_Suppressing_Edict": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredPlanner" + }, + "Item_IPC_Work_Permit": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredPlanner" + }, + "Item_Raging_Heart": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredPlanner" + }, + "Item_Dream_Fridge": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredPlanner" + }, + "Item_Dream_Flamer": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredPlanner" + }, + "Item_Worldbreaker_Blade": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredPlanner" + }, + "Item_Arrow_of_the_Starchaser": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredPlanner" + }, + "Item_Key_of_Wisdom": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredPlanner" + }, + "Item_Safeguard_of_Amber": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredPlanner" + }, + "Item_Obsidian_of_Obsession": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredPlanner" + }, + "Item_Stellaris_Symphony": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredPlanner" + }, + "Item_Flower_of_Eternity": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredPlanner" + }, + "Item_Moon_Madness_Fang": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredPlanner" + }, + "Item_Countertemporal_Shot": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredPlanner" + }, + "Item_Divine_Amber": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredPlanner" + }, + "Item_Heaven_Incinerator": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredPlanner" + }, + "Item_Heavenly_Melody": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredPlanner" + }, + "Item_Myriad_Fruit": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredPlanner" + }, + "Item_Tracks_of_Destiny": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredPlanner" + }, + "Item_Destroyer_Final_Road": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredPlanner" + }, + "Item_Guardian_Lament": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredPlanner" + }, + "Item_Regret_of_Infinite_Ochema": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredPlanner" + }, + "Item_Past_Evils_of_the_Borehole_Planet_Disaster": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredPlanner" + }, + "Item_Lost_Echo_of_the_Shared_Wish": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredPlanner" + }, + "Item_Squirming_Core": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredPlanner" + }, + "Item_Conqueror_Will": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredPlanner" + }, + "Item_Silvermane_Medal": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredPlanner" + }, + "Item_Ancient_Engine": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredPlanner" + }, + "Item_Immortal_Lumintwig": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredPlanner" + }, + "Item_Artifex_Gyreheart": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredPlanner" + }, + "Item_Dream_Making_Engine": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredPlanner" + }, + "Item_Shards_of_Desires": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredPlanner" + } + }, "Dungeon": { "Name": { "type": "select", diff --git a/module/config/argument/argument.yaml b/module/config/argument/argument.yaml index 0dc814a8f..bb96f750d 100644 --- a/module/config/argument/argument.yaml +++ b/module/config/argument/argument.yaml @@ -151,6 +151,8 @@ DungeonStorage: color: "#8fb5fe" SupportReward: Collect: true +Planner: {} + # Items will be injected in config updater Weekly: Name: diff --git a/module/config/argument/stored.json b/module/config/argument/stored.json index b9c86336a..b07c15952 100644 --- a/module/config/argument/stored.json +++ b/module/config/argument/stored.json @@ -137,6 +137,545 @@ "order": 0, "color": "#777777" }, + "Item_Credit": { + "name": "Item_Credit", + "path": "Dungeon.Planner.Item_Credit", + "i18n": "Planner.Item_Credit.name", + "stored": "StoredPlanner", + "attrs": { + "time": "2020-01-01 00:00:00" + }, + "order": 0, + "color": "#777777" + }, + "Item_Traveler_Guide": { + "name": "Item_Traveler_Guide", + "path": "Dungeon.Planner.Item_Traveler_Guide", + "i18n": "Planner.Item_Traveler_Guide.name", + "stored": "StoredPlanner", + "attrs": { + "time": "2020-01-01 00:00:00" + }, + "order": 0, + "color": "#777777" + }, + "Item_Refined_Aether": { + "name": "Item_Refined_Aether", + "path": "Dungeon.Planner.Item_Refined_Aether", + "i18n": "Planner.Item_Refined_Aether.name", + "stored": "StoredPlanner", + "attrs": { + "time": "2020-01-01 00:00:00" + }, + "order": 0, + "color": "#777777" + }, + "Item_Lost_Crystal": { + "name": "Item_Lost_Crystal", + "path": "Dungeon.Planner.Item_Lost_Crystal", + "i18n": "Planner.Item_Lost_Crystal.name", + "stored": "StoredPlanner", + "attrs": { + "time": "2020-01-01 00:00:00" + }, + "order": 0, + "color": "#777777" + }, + "Item_Broken_Teeth_of_Iron_Wolf": { + "name": "Item_Broken_Teeth_of_Iron_Wolf", + "path": "Dungeon.Planner.Item_Broken_Teeth_of_Iron_Wolf", + "i18n": "Planner.Item_Broken_Teeth_of_Iron_Wolf.name", + "stored": "StoredPlanner", + "attrs": { + "time": "2020-01-01 00:00:00" + }, + "order": 0, + "color": "#777777" + }, + "Item_Endotherm_Chitin": { + "name": "Item_Endotherm_Chitin", + "path": "Dungeon.Planner.Item_Endotherm_Chitin", + "i18n": "Planner.Item_Endotherm_Chitin.name", + "stored": "StoredPlanner", + "attrs": { + "time": "2020-01-01 00:00:00" + }, + "order": 0, + "color": "#777777" + }, + "Item_Horn_of_Snow": { + "name": "Item_Horn_of_Snow", + "path": "Dungeon.Planner.Item_Horn_of_Snow", + "i18n": "Planner.Item_Horn_of_Snow.name", + "stored": "StoredPlanner", + "attrs": { + "time": "2020-01-01 00:00:00" + }, + "order": 0, + "color": "#777777" + }, + "Item_Lightning_Crown_of_the_Past_Shadow": { + "name": "Item_Lightning_Crown_of_the_Past_Shadow", + "path": "Dungeon.Planner.Item_Lightning_Crown_of_the_Past_Shadow", + "i18n": "Planner.Item_Lightning_Crown_of_the_Past_Shadow.name", + "stored": "StoredPlanner", + "attrs": { + "time": "2020-01-01 00:00:00" + }, + "order": 0, + "color": "#777777" + }, + "Item_Storm_Eye": { + "name": "Item_Storm_Eye", + "path": "Dungeon.Planner.Item_Storm_Eye", + "i18n": "Planner.Item_Storm_Eye.name", + "stored": "StoredPlanner", + "attrs": { + "time": "2020-01-01 00:00:00" + }, + "order": 0, + "color": "#777777" + }, + "Item_Void_Cast_Iron": { + "name": "Item_Void_Cast_Iron", + "path": "Dungeon.Planner.Item_Void_Cast_Iron", + "i18n": "Planner.Item_Void_Cast_Iron.name", + "stored": "StoredPlanner", + "attrs": { + "time": "2020-01-01 00:00:00" + }, + "order": 0, + "color": "#777777" + }, + "Item_Golden_Crown_of_the_Past_Shadow": { + "name": "Item_Golden_Crown_of_the_Past_Shadow", + "path": "Dungeon.Planner.Item_Golden_Crown_of_the_Past_Shadow", + "i18n": "Planner.Item_Golden_Crown_of_the_Past_Shadow.name", + "stored": "StoredPlanner", + "attrs": { + "time": "2020-01-01 00:00:00" + }, + "order": 0, + "color": "#777777" + }, + "Item_Netherworld_Token": { + "name": "Item_Netherworld_Token", + "path": "Dungeon.Planner.Item_Netherworld_Token", + "i18n": "Planner.Item_Netherworld_Token.name", + "stored": "StoredPlanner", + "attrs": { + "time": "2020-01-01 00:00:00" + }, + "order": 0, + "color": "#777777" + }, + "Item_Searing_Steel_Blade": { + "name": "Item_Searing_Steel_Blade", + "path": "Dungeon.Planner.Item_Searing_Steel_Blade", + "i18n": "Planner.Item_Searing_Steel_Blade.name", + "stored": "StoredPlanner", + "attrs": { + "time": "2020-01-01 00:00:00" + }, + "order": 0, + "color": "#777777" + }, + "Item_Gelid_Chitin": { + "name": "Item_Gelid_Chitin", + "path": "Dungeon.Planner.Item_Gelid_Chitin", + "i18n": "Planner.Item_Gelid_Chitin.name", + "stored": "StoredPlanner", + "attrs": { + "time": "2020-01-01 00:00:00" + }, + "order": 0, + "color": "#777777" + }, + "Item_Shape_Shifter_Lightning_Staff": { + "name": "Item_Shape_Shifter_Lightning_Staff", + "path": "Dungeon.Planner.Item_Shape_Shifter_Lightning_Staff", + "i18n": "Planner.Item_Shape_Shifter_Lightning_Staff.name", + "stored": "StoredPlanner", + "attrs": { + "time": "2020-01-01 00:00:00" + }, + "order": 0, + "color": "#777777" + }, + "Item_Ascendant_Debris": { + "name": "Item_Ascendant_Debris", + "path": "Dungeon.Planner.Item_Ascendant_Debris", + "i18n": "Planner.Item_Ascendant_Debris.name", + "stored": "StoredPlanner", + "attrs": { + "time": "2020-01-01 00:00:00" + }, + "order": 0, + "color": "#777777" + }, + "Item_Nail_of_the_Ape": { + "name": "Item_Nail_of_the_Ape", + "path": "Dungeon.Planner.Item_Nail_of_the_Ape", + "i18n": "Planner.Item_Nail_of_the_Ape.name", + "stored": "StoredPlanner", + "attrs": { + "time": "2020-01-01 00:00:00" + }, + "order": 0, + "color": "#777777" + }, + "Item_Suppressing_Edict": { + "name": "Item_Suppressing_Edict", + "path": "Dungeon.Planner.Item_Suppressing_Edict", + "i18n": "Planner.Item_Suppressing_Edict.name", + "stored": "StoredPlanner", + "attrs": { + "time": "2020-01-01 00:00:00" + }, + "order": 0, + "color": "#777777" + }, + "Item_IPC_Work_Permit": { + "name": "Item_IPC_Work_Permit", + "path": "Dungeon.Planner.Item_IPC_Work_Permit", + "i18n": "Planner.Item_IPC_Work_Permit.name", + "stored": "StoredPlanner", + "attrs": { + "time": "2020-01-01 00:00:00" + }, + "order": 0, + "color": "#777777" + }, + "Item_Raging_Heart": { + "name": "Item_Raging_Heart", + "path": "Dungeon.Planner.Item_Raging_Heart", + "i18n": "Planner.Item_Raging_Heart.name", + "stored": "StoredPlanner", + "attrs": { + "time": "2020-01-01 00:00:00" + }, + "order": 0, + "color": "#777777" + }, + "Item_Dream_Fridge": { + "name": "Item_Dream_Fridge", + "path": "Dungeon.Planner.Item_Dream_Fridge", + "i18n": "Planner.Item_Dream_Fridge.name", + "stored": "StoredPlanner", + "attrs": { + "time": "2020-01-01 00:00:00" + }, + "order": 0, + "color": "#777777" + }, + "Item_Dream_Flamer": { + "name": "Item_Dream_Flamer", + "path": "Dungeon.Planner.Item_Dream_Flamer", + "i18n": "Planner.Item_Dream_Flamer.name", + "stored": "StoredPlanner", + "attrs": { + "time": "2020-01-01 00:00:00" + }, + "order": 0, + "color": "#777777" + }, + "Item_Worldbreaker_Blade": { + "name": "Item_Worldbreaker_Blade", + "path": "Dungeon.Planner.Item_Worldbreaker_Blade", + "i18n": "Planner.Item_Worldbreaker_Blade.name", + "stored": "StoredPlanner", + "attrs": { + "time": "2020-01-01 00:00:00" + }, + "order": 0, + "color": "#777777" + }, + "Item_Arrow_of_the_Starchaser": { + "name": "Item_Arrow_of_the_Starchaser", + "path": "Dungeon.Planner.Item_Arrow_of_the_Starchaser", + "i18n": "Planner.Item_Arrow_of_the_Starchaser.name", + "stored": "StoredPlanner", + "attrs": { + "time": "2020-01-01 00:00:00" + }, + "order": 0, + "color": "#777777" + }, + "Item_Key_of_Wisdom": { + "name": "Item_Key_of_Wisdom", + "path": "Dungeon.Planner.Item_Key_of_Wisdom", + "i18n": "Planner.Item_Key_of_Wisdom.name", + "stored": "StoredPlanner", + "attrs": { + "time": "2020-01-01 00:00:00" + }, + "order": 0, + "color": "#777777" + }, + "Item_Safeguard_of_Amber": { + "name": "Item_Safeguard_of_Amber", + "path": "Dungeon.Planner.Item_Safeguard_of_Amber", + "i18n": "Planner.Item_Safeguard_of_Amber.name", + "stored": "StoredPlanner", + "attrs": { + "time": "2020-01-01 00:00:00" + }, + "order": 0, + "color": "#777777" + }, + "Item_Obsidian_of_Obsession": { + "name": "Item_Obsidian_of_Obsession", + "path": "Dungeon.Planner.Item_Obsidian_of_Obsession", + "i18n": "Planner.Item_Obsidian_of_Obsession.name", + "stored": "StoredPlanner", + "attrs": { + "time": "2020-01-01 00:00:00" + }, + "order": 0, + "color": "#777777" + }, + "Item_Stellaris_Symphony": { + "name": "Item_Stellaris_Symphony", + "path": "Dungeon.Planner.Item_Stellaris_Symphony", + "i18n": "Planner.Item_Stellaris_Symphony.name", + "stored": "StoredPlanner", + "attrs": { + "time": "2020-01-01 00:00:00" + }, + "order": 0, + "color": "#777777" + }, + "Item_Flower_of_Eternity": { + "name": "Item_Flower_of_Eternity", + "path": "Dungeon.Planner.Item_Flower_of_Eternity", + "i18n": "Planner.Item_Flower_of_Eternity.name", + "stored": "StoredPlanner", + "attrs": { + "time": "2020-01-01 00:00:00" + }, + "order": 0, + "color": "#777777" + }, + "Item_Moon_Madness_Fang": { + "name": "Item_Moon_Madness_Fang", + "path": "Dungeon.Planner.Item_Moon_Madness_Fang", + "i18n": "Planner.Item_Moon_Madness_Fang.name", + "stored": "StoredPlanner", + "attrs": { + "time": "2020-01-01 00:00:00" + }, + "order": 0, + "color": "#777777" + }, + "Item_Countertemporal_Shot": { + "name": "Item_Countertemporal_Shot", + "path": "Dungeon.Planner.Item_Countertemporal_Shot", + "i18n": "Planner.Item_Countertemporal_Shot.name", + "stored": "StoredPlanner", + "attrs": { + "time": "2020-01-01 00:00:00" + }, + "order": 0, + "color": "#777777" + }, + "Item_Divine_Amber": { + "name": "Item_Divine_Amber", + "path": "Dungeon.Planner.Item_Divine_Amber", + "i18n": "Planner.Item_Divine_Amber.name", + "stored": "StoredPlanner", + "attrs": { + "time": "2020-01-01 00:00:00" + }, + "order": 0, + "color": "#777777" + }, + "Item_Heaven_Incinerator": { + "name": "Item_Heaven_Incinerator", + "path": "Dungeon.Planner.Item_Heaven_Incinerator", + "i18n": "Planner.Item_Heaven_Incinerator.name", + "stored": "StoredPlanner", + "attrs": { + "time": "2020-01-01 00:00:00" + }, + "order": 0, + "color": "#777777" + }, + "Item_Heavenly_Melody": { + "name": "Item_Heavenly_Melody", + "path": "Dungeon.Planner.Item_Heavenly_Melody", + "i18n": "Planner.Item_Heavenly_Melody.name", + "stored": "StoredPlanner", + "attrs": { + "time": "2020-01-01 00:00:00" + }, + "order": 0, + "color": "#777777" + }, + "Item_Myriad_Fruit": { + "name": "Item_Myriad_Fruit", + "path": "Dungeon.Planner.Item_Myriad_Fruit", + "i18n": "Planner.Item_Myriad_Fruit.name", + "stored": "StoredPlanner", + "attrs": { + "time": "2020-01-01 00:00:00" + }, + "order": 0, + "color": "#777777" + }, + "Item_Tracks_of_Destiny": { + "name": "Item_Tracks_of_Destiny", + "path": "Dungeon.Planner.Item_Tracks_of_Destiny", + "i18n": "Planner.Item_Tracks_of_Destiny.name", + "stored": "StoredPlanner", + "attrs": { + "time": "2020-01-01 00:00:00" + }, + "order": 0, + "color": "#777777" + }, + "Item_Destroyer_Final_Road": { + "name": "Item_Destroyer_Final_Road", + "path": "Dungeon.Planner.Item_Destroyer_Final_Road", + "i18n": "Planner.Item_Destroyer_Final_Road.name", + "stored": "StoredPlanner", + "attrs": { + "time": "2020-01-01 00:00:00" + }, + "order": 0, + "color": "#777777" + }, + "Item_Guardian_Lament": { + "name": "Item_Guardian_Lament", + "path": "Dungeon.Planner.Item_Guardian_Lament", + "i18n": "Planner.Item_Guardian_Lament.name", + "stored": "StoredPlanner", + "attrs": { + "time": "2020-01-01 00:00:00" + }, + "order": 0, + "color": "#777777" + }, + "Item_Regret_of_Infinite_Ochema": { + "name": "Item_Regret_of_Infinite_Ochema", + "path": "Dungeon.Planner.Item_Regret_of_Infinite_Ochema", + "i18n": "Planner.Item_Regret_of_Infinite_Ochema.name", + "stored": "StoredPlanner", + "attrs": { + "time": "2020-01-01 00:00:00" + }, + "order": 0, + "color": "#777777" + }, + "Item_Past_Evils_of_the_Borehole_Planet_Disaster": { + "name": "Item_Past_Evils_of_the_Borehole_Planet_Disaster", + "path": "Dungeon.Planner.Item_Past_Evils_of_the_Borehole_Planet_Disaster", + "i18n": "Planner.Item_Past_Evils_of_the_Borehole_Planet_Disaster.name", + "stored": "StoredPlanner", + "attrs": { + "time": "2020-01-01 00:00:00" + }, + "order": 0, + "color": "#777777" + }, + "Item_Lost_Echo_of_the_Shared_Wish": { + "name": "Item_Lost_Echo_of_the_Shared_Wish", + "path": "Dungeon.Planner.Item_Lost_Echo_of_the_Shared_Wish", + "i18n": "Planner.Item_Lost_Echo_of_the_Shared_Wish.name", + "stored": "StoredPlanner", + "attrs": { + "time": "2020-01-01 00:00:00" + }, + "order": 0, + "color": "#777777" + }, + "Item_Squirming_Core": { + "name": "Item_Squirming_Core", + "path": "Dungeon.Planner.Item_Squirming_Core", + "i18n": "Planner.Item_Squirming_Core.name", + "stored": "StoredPlanner", + "attrs": { + "time": "2020-01-01 00:00:00" + }, + "order": 0, + "color": "#777777" + }, + "Item_Conqueror_Will": { + "name": "Item_Conqueror_Will", + "path": "Dungeon.Planner.Item_Conqueror_Will", + "i18n": "Planner.Item_Conqueror_Will.name", + "stored": "StoredPlanner", + "attrs": { + "time": "2020-01-01 00:00:00" + }, + "order": 0, + "color": "#777777" + }, + "Item_Silvermane_Medal": { + "name": "Item_Silvermane_Medal", + "path": "Dungeon.Planner.Item_Silvermane_Medal", + "i18n": "Planner.Item_Silvermane_Medal.name", + "stored": "StoredPlanner", + "attrs": { + "time": "2020-01-01 00:00:00" + }, + "order": 0, + "color": "#777777" + }, + "Item_Ancient_Engine": { + "name": "Item_Ancient_Engine", + "path": "Dungeon.Planner.Item_Ancient_Engine", + "i18n": "Planner.Item_Ancient_Engine.name", + "stored": "StoredPlanner", + "attrs": { + "time": "2020-01-01 00:00:00" + }, + "order": 0, + "color": "#777777" + }, + "Item_Immortal_Lumintwig": { + "name": "Item_Immortal_Lumintwig", + "path": "Dungeon.Planner.Item_Immortal_Lumintwig", + "i18n": "Planner.Item_Immortal_Lumintwig.name", + "stored": "StoredPlanner", + "attrs": { + "time": "2020-01-01 00:00:00" + }, + "order": 0, + "color": "#777777" + }, + "Item_Artifex_Gyreheart": { + "name": "Item_Artifex_Gyreheart", + "path": "Dungeon.Planner.Item_Artifex_Gyreheart", + "i18n": "Planner.Item_Artifex_Gyreheart.name", + "stored": "StoredPlanner", + "attrs": { + "time": "2020-01-01 00:00:00" + }, + "order": 0, + "color": "#777777" + }, + "Item_Dream_Making_Engine": { + "name": "Item_Dream_Making_Engine", + "path": "Dungeon.Planner.Item_Dream_Making_Engine", + "i18n": "Planner.Item_Dream_Making_Engine.name", + "stored": "StoredPlanner", + "attrs": { + "time": "2020-01-01 00:00:00" + }, + "order": 0, + "color": "#777777" + }, + "Item_Shards_of_Desires": { + "name": "Item_Shards_of_Desires", + "path": "Dungeon.Planner.Item_Shards_of_Desires", + "i18n": "Planner.Item_Shards_of_Desires.name", + "stored": "StoredPlanner", + "attrs": { + "time": "2020-01-01 00:00:00" + }, + "order": 0, + "color": "#777777" + }, "Immersifier": { "name": "Immersifier", "path": "Dungeon.DungeonStorage.Immersifier", diff --git a/module/config/argument/task.yaml b/module/config/argument/task.yaml index 5e10c85ea..f8adc6a7b 100644 --- a/module/config/argument/task.yaml +++ b/module/config/argument/task.yaml @@ -25,6 +25,7 @@ Daily: tasks: Dungeon: - Scheduler + - Planner - Dungeon - DungeonDaily - DungeonSupport diff --git a/module/config/config_generated.py b/module/config/config_generated.py index efb301bda..aa7408d3f 100644 --- a/module/config/config_generated.py +++ b/module/config/config_generated.py @@ -71,6 +71,57 @@ class GeneratedConfig: # Group `SupportReward` SupportReward_Collect = True + # Group `Planner` + Planner_Item_Credit = {} + Planner_Item_Traveler_Guide = {} + Planner_Item_Refined_Aether = {} + Planner_Item_Lost_Crystal = {} + Planner_Item_Broken_Teeth_of_Iron_Wolf = {} + Planner_Item_Endotherm_Chitin = {} + Planner_Item_Horn_of_Snow = {} + Planner_Item_Lightning_Crown_of_the_Past_Shadow = {} + Planner_Item_Storm_Eye = {} + Planner_Item_Void_Cast_Iron = {} + Planner_Item_Golden_Crown_of_the_Past_Shadow = {} + Planner_Item_Netherworld_Token = {} + Planner_Item_Searing_Steel_Blade = {} + Planner_Item_Gelid_Chitin = {} + Planner_Item_Shape_Shifter_Lightning_Staff = {} + Planner_Item_Ascendant_Debris = {} + Planner_Item_Nail_of_the_Ape = {} + Planner_Item_Suppressing_Edict = {} + Planner_Item_IPC_Work_Permit = {} + Planner_Item_Raging_Heart = {} + Planner_Item_Dream_Fridge = {} + Planner_Item_Dream_Flamer = {} + Planner_Item_Worldbreaker_Blade = {} + Planner_Item_Arrow_of_the_Starchaser = {} + Planner_Item_Key_of_Wisdom = {} + Planner_Item_Safeguard_of_Amber = {} + Planner_Item_Obsidian_of_Obsession = {} + Planner_Item_Stellaris_Symphony = {} + Planner_Item_Flower_of_Eternity = {} + Planner_Item_Moon_Madness_Fang = {} + Planner_Item_Countertemporal_Shot = {} + Planner_Item_Divine_Amber = {} + Planner_Item_Heaven_Incinerator = {} + Planner_Item_Heavenly_Melody = {} + Planner_Item_Myriad_Fruit = {} + Planner_Item_Tracks_of_Destiny = {} + Planner_Item_Destroyer_Final_Road = {} + Planner_Item_Guardian_Lament = {} + Planner_Item_Regret_of_Infinite_Ochema = {} + Planner_Item_Past_Evils_of_the_Borehole_Planet_Disaster = {} + Planner_Item_Lost_Echo_of_the_Shared_Wish = {} + Planner_Item_Squirming_Core = {} + Planner_Item_Conqueror_Will = {} + Planner_Item_Silvermane_Medal = {} + Planner_Item_Ancient_Engine = {} + Planner_Item_Immortal_Lumintwig = {} + Planner_Item_Artifex_Gyreheart = {} + Planner_Item_Dream_Making_Engine = {} + Planner_Item_Shards_of_Desires = {} + # Group `Weekly` Weekly_Name = 'Echo_of_War_Divine_Seed' # Echo_of_War_Destruction_Beginning, Echo_of_War_End_of_the_Eternal_Freeze, Echo_of_War_Divine_Seed, Echo_of_War_Borehole_Planet_Old_Crater, Echo_of_War_Salutations_of_Ashen_Dreams Weekly_Team = 1 # 1, 2, 3, 4, 5, 6, 7, 8, 9 diff --git a/module/config/config_updater.py b/module/config/config_updater.py index 1a1f06ed6..e7cbffd44 100644 --- a/module/config/config_updater.py +++ b/module/config/config_updater.py @@ -66,8 +66,9 @@ class ConfigGenerator: # Insert dungeons from tasks.dungeon.keywords import DungeonList calyx_golden = [dungeon.name for dungeon in DungeonList.instances.values() if dungeon.is_Calyx_Golden_Memories] \ - + [dungeon.name for dungeon in DungeonList.instances.values() if dungeon.is_Calyx_Golden_Aether] \ - + [dungeon.name for dungeon in DungeonList.instances.values() if dungeon.is_Calyx_Golden_Treasures] + + [dungeon.name for dungeon in DungeonList.instances.values() if dungeon.is_Calyx_Golden_Aether] \ + + [dungeon.name for dungeon in DungeonList.instances.values() if + dungeon.is_Calyx_Golden_Treasures] # calyx_crimson from tasks.rogue.keywords import KEYWORDS_ROGUE_PATH as Path order = [Path.Destruction, Path.Preservation, Path.The_Hunt, Path.Abundance, @@ -82,7 +83,8 @@ class ConfigGenerator: for type_ in CombatType.instances.values(): stagnant_shadow += [dungeon.name for dungeon in DungeonList.instances.values() if dungeon.Stagnant_Shadow_Combat_Type == type_] - cavern_of_corrosion = [dungeon.name for dungeon in DungeonList.instances.values() if dungeon.is_Cavern_of_Corrosion] + cavern_of_corrosion = [dungeon.name for dungeon in DungeonList.instances.values() if + dungeon.is_Cavern_of_Corrosion] option_add( keys='Dungeon.Name.option', options=calyx_golden + calyx_crimson + stagnant_shadow + cavern_of_corrosion @@ -120,6 +122,11 @@ class ConfigGenerator: assignments = [entry.name for entry in AssignmentEntry.instances.values()] for i in range(4): option_add(keys=f'Assignment.Name_{i + 1}.option', options=assignments) + # Insert planner items + from tasks.planner.keywords.classes import ItemBase + for item in ItemBase.instances.values(): + base = item.group_base + deep_set(raw, keys=['Planner', f'Item_{base.name}'], value={'stored': 'StoredPlanner'}) # Load for path, value in deep_iter(raw, depth=2): @@ -381,7 +388,7 @@ class ConfigGenerator: i18n_aether = { 'cn': '材料:武器经验({dungeon})', 'cht': '材料:武器經驗({dungeon})', - 'jp': '素材:武器経験({dungeon}):', + 'jp': '素材:武器経験({dungeon})', 'en': 'Material: Light Cone EXP ({dungeon})', 'es': 'Material: EXP de conos de luz ({dungeon})', } @@ -497,6 +504,37 @@ class ConfigGenerator: name = deep_get(new, keys=['RogueWorld', 'World', dungeon.name], default=None) if name: deep_set(new, keys=['RogueWorld', 'World', dungeon.name], value=dungeon.__getattribute__(ingame_lang)) + # Planner items + from tasks.planner.keywords.classes import ItemBase + for item in ItemBase.instances.values(): + item: ItemBase = item + name = f'Item_{item.name}' + if item.is_ItemCurrency or item.name == 'Tracks_of_Destiny': + i18n = item.__getattribute__(ingame_lang) + elif item.is_ItemExp and item.is_group_base: + dungeon = item.dungeon + if dungeon is None: + i18n = item.__getattribute__(ingame_lang) + elif dungeon.is_Calyx_Golden_Memories: + i18n = i18n_memories[ingame_lang] + elif dungeon.is_Calyx_Golden_Aether: + i18n = i18n_aether[ingame_lang] + else: + continue + if res := re.search(r'[::](.*)[((]', i18n): + i18n = res.group(1) + elif item.is_ItemAscension or (item.is_ItemTrace and item.is_group_base): + dungeon = item.group_base.dungeon.name + i18n = deep_get(new, keys=['Dungeon', 'Name', dungeon], default='Unknown_Dungeon_Come_From') + elif item.is_ItemWeekly: + dungeon = item.dungeon.name + i18n = deep_get(new, keys=['Weekly', 'Name', dungeon], default='Unknown_Dungeon_Come_From') + elif item.is_ItemCalyx and item.is_group_base: + i18n = item.__getattribute__(ingame_lang) + else: + continue + deep_set(new, keys=['Planner', name, 'name'], value=i18n) + deep_set(new, keys=['Planner', name, 'help'], value='') # GUI i18n for path, _ in deep_iter(self.gui, depth=2): diff --git a/module/config/i18n/en-US.json b/module/config/i18n/en-US.json index 2c6f0adc4..7b5ded006 100644 --- a/module/config/i18n/en-US.json +++ b/module/config/i18n/en-US.json @@ -529,6 +529,208 @@ "help": "" } }, + "Planner": { + "_info": { + "name": "Character Planner Progress", + "help": "" + }, + "Item_Credit": { + "name": "Credit", + "help": "" + }, + "Item_Traveler_Guide": { + "name": " Character EXP ", + "help": "" + }, + "Item_Refined_Aether": { + "name": " Light Cone EXP ", + "help": "" + }, + "Item_Lost_Crystal": { + "name": "Lost Crystal", + "help": "" + }, + "Item_Broken_Teeth_of_Iron_Wolf": { + "name": "Ascension: Physical (Natasha / Clara / Luka / Sushang)", + "help": "" + }, + "Item_Endotherm_Chitin": { + "name": "Ascension: Fire (Himeko / Asta / Hook)", + "help": "" + }, + "Item_Horn_of_Snow": { + "name": "Ascension: Ice (March 7th / Herta / Gepard / Pela)", + "help": "" + }, + "Item_Lightning_Crown_of_the_Past_Shadow": { + "name": "Ascension: Lightning (Arlan / Serval / Tingyun / Bailu)", + "help": "" + }, + "Item_Storm_Eye": { + "name": "Ascension: Wind (Dan Heng / Bronya / Sampo)", + "help": "" + }, + "Item_Void_Cast_Iron": { + "name": "Ascension: Quantum (Silver Wolf / Seele / Qingque)", + "help": "" + }, + "Item_Golden_Crown_of_the_Past_Shadow": { + "name": "Ascension: Imaginary (Welt / Luocha / Yukong)", + "help": "" + }, + "Item_Netherworld_Token": { + "name": "Ascension: Physical (Hanya / Argenti)", + "help": "" + }, + "Item_Searing_Steel_Blade": { + "name": "Ascension: Fire (Guinaifen / Topaz & Numby)", + "help": "" + }, + "Item_Gelid_Chitin": { + "name": "Ascension: Ice (Yanqing / Jingliu / Ruan Mei)", + "help": "" + }, + "Item_Shape_Shifter_Lightning_Staff": { + "name": "Ascension: Lightning (Kafka / Jing Yuan / Acheron)", + "help": "" + }, + "Item_Ascendant_Debris": { + "name": "Ascension: Wind (Blade / Huohuo / Black Swan)", + "help": "" + }, + "Item_Nail_of_the_Ape": { + "name": "Ascension: Quantum (Lynx / Fu Xuan / Xueyi)", + "help": "" + }, + "Item_Suppressing_Edict": { + "name": "Ascension: Imaginary (Dan Heng • Imbibitor Lunae / Aventurine / Dr. Ratio)", + "help": "" + }, + "Item_IPC_Work_Permit": { + "name": "Ascension: Physical (Boothill / Robin)", + "help": "" + }, + "Item_Raging_Heart": { + "name": "Ascension: Fire (Gallagher)", + "help": "" + }, + "Item_Dream_Fridge": { + "name": "Ascension: Ice (Misha)", + "help": "" + }, + "Item_Dream_Flamer": { + "name": "Ascension: Quantum (Sparkle)", + "help": "" + }, + "Item_Worldbreaker_Blade": { + "name": "Trace: Destruction (Storage Zone)", + "help": "" + }, + "Item_Arrow_of_the_Starchaser": { + "name": "Trace: The Hunt (Outlying Snow Plains)", + "help": "" + }, + "Item_Key_of_Wisdom": { + "name": "Trace: Erudition (Rivet Town)", + "help": "" + }, + "Item_Safeguard_of_Amber": { + "name": "Trace: Preservation (Supply Zone)", + "help": "" + }, + "Item_Obsidian_of_Obsession": { + "name": "Trace: Nihility (Great Mine)", + "help": "" + }, + "Item_Stellaris_Symphony": { + "name": "Trace: The Harmony (Robot Settlement)", + "help": "" + }, + "Item_Flower_of_Eternity": { + "name": "Trace: Abundance (Backwater Pass)", + "help": "" + }, + "Item_Moon_Madness_Fang": { + "name": "Trace: Destruction (Scalegorge Waterscape)", + "help": "" + }, + "Item_Countertemporal_Shot": { + "name": "Trace: The Hunt (SoulGlad Scorchsand Audition Venue)", + "help": "" + }, + "Item_Divine_Amber": { + "name": "Trace: Preservation (Clock Studios Theme Park)", + "help": "" + }, + "Item_Heaven_Incinerator": { + "name": "Trace: Nihility (Alchemy Commission)", + "help": "" + }, + "Item_Heavenly_Melody": { + "name": "Trace: The Harmony (The Reverie (Dreamscape))", + "help": "" + }, + "Item_Myriad_Fruit": { + "name": "Trace: Abundance (Fyxestroll Garden)", + "help": "" + }, + "Item_Tracks_of_Destiny": { + "name": "Tracks of Destiny", + "help": "" + }, + "Item_Destroyer_Final_Road": { + "name": "Destruction's Beginning (Herta Space Station)", + "help": "" + }, + "Item_Guardian_Lament": { + "name": "End of the Eternal Freeze (Jarilo-VI)", + "help": "" + }, + "Item_Regret_of_Infinite_Ochema": { + "name": "Divine Seed (The Xianzhou Luofu)", + "help": "" + }, + "Item_Past_Evils_of_the_Borehole_Planet_Disaster": { + "name": "Borehole Planet's Old Crater (Herta Space Station)", + "help": "" + }, + "Item_Lost_Echo_of_the_Shared_Wish": { + "name": "Salutations of Ashen Dreams (Penacony)", + "help": "" + }, + "Item_Squirming_Core": { + "name": "Squirming Core", + "help": "" + }, + "Item_Conqueror_Will": { + "name": "Conqueror's Will", + "help": "" + }, + "Item_Silvermane_Medal": { + "name": "Silvermane Medal", + "help": "" + }, + "Item_Ancient_Engine": { + "name": "Ancient Engine", + "help": "" + }, + "Item_Immortal_Lumintwig": { + "name": "Immortal Lumintwig", + "help": "" + }, + "Item_Artifex_Gyreheart": { + "name": "Artifex's Gyreheart", + "help": "" + }, + "Item_Dream_Making_Engine": { + "name": "Dream Making Engine", + "help": "" + }, + "Item_Shards_of_Desires": { + "name": "Shards of Desires", + "help": "" + } + }, "Weekly": { "_info": { "name": "Echo of War Settings", diff --git a/module/config/i18n/es-ES.json b/module/config/i18n/es-ES.json index 058d97844..8216819e8 100644 --- a/module/config/i18n/es-ES.json +++ b/module/config/i18n/es-ES.json @@ -529,6 +529,208 @@ "help": "" } }, + "Planner": { + "_info": { + "name": "Progreso del planificador de personajes", + "help": "" + }, + "Item_Credit": { + "name": "Crédito", + "help": "" + }, + "Item_Traveler_Guide": { + "name": " EXP de personaje ", + "help": "" + }, + "Item_Refined_Aether": { + "name": " EXP de conos de luz ", + "help": "" + }, + "Item_Lost_Crystal": { + "name": "Cristal perdido", + "help": "" + }, + "Item_Broken_Teeth_of_Iron_Wolf": { + "name": "Ascension: Físico (Natasha / Clara / Luka / Sushang)", + "help": "" + }, + "Item_Endotherm_Chitin": { + "name": "Ascension: Fuego (Himeko / Asta / Hook)", + "help": "" + }, + "Item_Horn_of_Snow": { + "name": "Ascension: Hielo (Siete de Marzo / Herta / Gepard / Pela)", + "help": "" + }, + "Item_Lightning_Crown_of_the_Past_Shadow": { + "name": "Ascension: Rayo (Arlan / Serval / Tingyun / Bailu)", + "help": "" + }, + "Item_Storm_Eye": { + "name": "Ascension: Viento (Dan Heng / Bronya / Sampo)", + "help": "" + }, + "Item_Void_Cast_Iron": { + "name": "Ascension: Cuántico (Silver Wolf / Seele / Qingque)", + "help": "" + }, + "Item_Golden_Crown_of_the_Past_Shadow": { + "name": "Ascension: Imaginario (Welt / Luocha / Yukong)", + "help": "" + }, + "Item_Netherworld_Token": { + "name": "Ascension: Físico (Hanya / Argenti)", + "help": "" + }, + "Item_Searing_Steel_Blade": { + "name": "Ascension: Fuego (Guinaifen / Topaz y Conti)", + "help": "" + }, + "Item_Gelid_Chitin": { + "name": "Ascension: Hielo (Yanqing / Jingliu / Ruan Mei)", + "help": "" + }, + "Item_Shape_Shifter_Lightning_Staff": { + "name": "Ascension: Rayo (Kafka / Jing Yuan / Acheron)", + "help": "" + }, + "Item_Ascendant_Debris": { + "name": "Ascension: Viento (Blade / Huohuo / Cisne Negro)", + "help": "" + }, + "Item_Nail_of_the_Ape": { + "name": "Ascension: Cuántico (Lynx / Fu Xuan / Xueyi)", + "help": "" + }, + "Item_Suppressing_Edict": { + "name": "Ascension: Imaginario (Dan Heng - Imbibitor Lunae / Aventurino / Dr. Ratio)", + "help": "" + }, + "Item_IPC_Work_Permit": { + "name": "Ascension: Físico (Boothill / Robin)", + "help": "" + }, + "Item_Raging_Heart": { + "name": "Ascension: Fuego (Gallagher)", + "help": "" + }, + "Item_Dream_Fridge": { + "name": "Ascension: Hielo (Misha)", + "help": "" + }, + "Item_Dream_Flamer": { + "name": "Ascension: Cuántico (Sparkle)", + "help": "" + }, + "Item_Worldbreaker_Blade": { + "name": "Rastros: Destrucción (Zona de almacenamiento)", + "help": "" + }, + "Item_Arrow_of_the_Starchaser": { + "name": "Rastros: Cacería (Llanuras nevadas de las afueras)", + "help": "" + }, + "Item_Key_of_Wisdom": { + "name": "Rastros: Erudición (Villarremache)", + "help": "" + }, + "Item_Safeguard_of_Amber": { + "name": "Rastros: Conservación (Zona de suministros)", + "help": "" + }, + "Item_Obsidian_of_Obsession": { + "name": "Rastros: Nihilidad (Mina principal)", + "help": "" + }, + "Item_Stellaris_Symphony": { + "name": "Rastros: Armonía (Asentamiento robot)", + "help": "" + }, + "Item_Flower_of_Eternity": { + "name": "Rastros: Abundancia (Paso del Remanso)", + "help": "" + }, + "Item_Moon_Madness_Fang": { + "name": "Rastros: Destrucción (Desfiladero de Escamas)", + "help": "" + }, + "Item_Countertemporal_Shot": { + "name": "Rastros: Cacería (Recinto de las Audiciones FelizAlma en la Arena Ardiente)", + "help": "" + }, + "Item_Divine_Amber": { + "name": "Rastros: Conservación (Parque temático de los Estudios Reloj)", + "help": "" + }, + "Item_Heaven_Incinerator": { + "name": "Rastros: Nihilidad (Comisión de Alquimia)", + "help": "" + }, + "Item_Heavenly_Melody": { + "name": "Rastros: Armonía (Hotel Fantasía (paisaje onírico))", + "help": "" + }, + "Item_Myriad_Fruit": { + "name": "Rastros: Abundancia (Jardín del Sosiego)", + "help": "" + }, + "Item_Tracks_of_Destiny": { + "name": "Huellas del destino", + "help": "" + }, + "Item_Destroyer_Final_Road": { + "name": "El principio de la Destrucción (Estación Espacial Herta)", + "help": "" + }, + "Item_Guardian_Lament": { + "name": "El fin del Hielo Eterno (Jarilo-VI)", + "help": "" + }, + "Item_Regret_of_Infinite_Ochema": { + "name": "Semilla divina (El Luofu de Xianzhou)", + "help": "" + }, + "Item_Past_Evils_of_the_Borehole_Planet_Disaster": { + "name": "Cráter del planeta devorado (Estación Espacial Herta)", + "help": "" + }, + "Item_Lost_Echo_of_the_Shared_Wish": { + "name": "Ecos de la guerra: Tributo del sueño ceniciento (Colonipenal)", + "help": "" + }, + "Item_Squirming_Core": { + "name": "Núcleo serpenteante", + "help": "" + }, + "Item_Conqueror_Will": { + "name": "Voluntad de conquista", + "help": "" + }, + "Item_Silvermane_Medal": { + "name": "Medalla del guardia", + "help": "" + }, + "Item_Ancient_Engine": { + "name": "Motor antiguo", + "help": "" + }, + "Item_Immortal_Lumintwig": { + "name": "Rama gloriosa inmortal", + "help": "" + }, + "Item_Artifex_Gyreheart": { + "name": "Corazón armonioso mecánico", + "help": "" + }, + "Item_Dream_Making_Engine": { + "name": "Motor creasueños", + "help": "" + }, + "Item_Shards_of_Desires": { + "name": "Fragmento de deseos", + "help": "" + } + }, "Weekly": { "_info": { "name": "Ajustes de Ecos de la guerra", diff --git a/module/config/i18n/ja-JP.json b/module/config/i18n/ja-JP.json index 436e36592..3448cfdaa 100644 --- a/module/config/i18n/ja-JP.json +++ b/module/config/i18n/ja-JP.json @@ -251,9 +251,9 @@ "Calyx_Golden_Memories_Jarilo_VI": "素材:役割経験(回憶の蕾・ヤリーロ-Ⅵ):", "Calyx_Golden_Memories_The_Xianzhou_Luofu": "素材:役割経験(回憶の蕾・仙舟羅浮):", "Calyx_Golden_Memories_Penacony": "素材:役割経験(回憶の蕾・ピノコニー):", - "Calyx_Golden_Aether_Jarilo_VI": "素材:武器経験(エーテルの蕾・ヤリーロ-Ⅵ):", - "Calyx_Golden_Aether_The_Xianzhou_Luofu": "素材:武器経験(エーテルの蕾・仙舟羅浮):", - "Calyx_Golden_Aether_Penacony": "素材:武器経験(エーテルの蕾・ピノコニー):", + "Calyx_Golden_Aether_Jarilo_VI": "素材:武器経験(エーテルの蕾・ヤリーロ-Ⅵ)", + "Calyx_Golden_Aether_The_Xianzhou_Luofu": "素材:武器経験(エーテルの蕾・仙舟羅浮)", + "Calyx_Golden_Aether_Penacony": "素材:武器経験(エーテルの蕾・ピノコニー)", "Calyx_Golden_Treasures_Jarilo_VI": "素材:クレジット(秘蔵の蕾・ヤリーロ-Ⅵ)", "Calyx_Golden_Treasures_The_Xianzhou_Luofu": "素材:クレジット(秘蔵の蕾・仙舟羅浮)", "Calyx_Golden_Treasures_Penacony": "素材:クレジット(秘蔵の蕾・ピノコニー)", @@ -304,9 +304,9 @@ "Calyx_Golden_Memories_Jarilo_VI": "素材:役割経験(回憶の蕾・ヤリーロ-Ⅵ):", "Calyx_Golden_Memories_The_Xianzhou_Luofu": "素材:役割経験(回憶の蕾・仙舟羅浮):", "Calyx_Golden_Memories_Penacony": "素材:役割経験(回憶の蕾・ピノコニー):", - "Calyx_Golden_Aether_Jarilo_VI": "素材:武器経験(エーテルの蕾・ヤリーロ-Ⅵ):", - "Calyx_Golden_Aether_The_Xianzhou_Luofu": "素材:武器経験(エーテルの蕾・仙舟羅浮):", - "Calyx_Golden_Aether_Penacony": "素材:武器経験(エーテルの蕾・ピノコニー):", + "Calyx_Golden_Aether_Jarilo_VI": "素材:武器経験(エーテルの蕾・ヤリーロ-Ⅵ)", + "Calyx_Golden_Aether_The_Xianzhou_Luofu": "素材:武器経験(エーテルの蕾・仙舟羅浮)", + "Calyx_Golden_Aether_Penacony": "素材:武器経験(エーテルの蕾・ピノコニー)", "Calyx_Golden_Treasures_Jarilo_VI": "素材:クレジット(秘蔵の蕾・ヤリーロ-Ⅵ)", "Calyx_Golden_Treasures_The_Xianzhou_Luofu": "素材:クレジット(秘蔵の蕾・仙舟羅浮)", "Calyx_Golden_Treasures_Penacony": "素材:クレジット(秘蔵の蕾・ピノコニー)", @@ -363,9 +363,9 @@ "Calyx_Golden_Memories_Jarilo_VI": "素材:役割経験(回憶の蕾・ヤリーロ-Ⅵ):", "Calyx_Golden_Memories_The_Xianzhou_Luofu": "素材:役割経験(回憶の蕾・仙舟羅浮):", "Calyx_Golden_Memories_Penacony": "素材:役割経験(回憶の蕾・ピノコニー):", - "Calyx_Golden_Aether_Jarilo_VI": "素材:武器経験(エーテルの蕾・ヤリーロ-Ⅵ):", - "Calyx_Golden_Aether_The_Xianzhou_Luofu": "素材:武器経験(エーテルの蕾・仙舟羅浮):", - "Calyx_Golden_Aether_Penacony": "素材:武器経験(エーテルの蕾・ピノコニー):", + "Calyx_Golden_Aether_Jarilo_VI": "素材:武器経験(エーテルの蕾・ヤリーロ-Ⅵ)", + "Calyx_Golden_Aether_The_Xianzhou_Luofu": "素材:武器経験(エーテルの蕾・仙舟羅浮)", + "Calyx_Golden_Aether_Penacony": "素材:武器経験(エーテルの蕾・ピノコニー)", "Calyx_Golden_Treasures_Jarilo_VI": "素材:クレジット(秘蔵の蕾・ヤリーロ-Ⅵ)", "Calyx_Golden_Treasures_The_Xianzhou_Luofu": "素材:クレジット(秘蔵の蕾・仙舟羅浮)", "Calyx_Golden_Treasures_Penacony": "素材:クレジット(秘蔵の蕾・ピノコニー)" @@ -529,6 +529,208 @@ "help": "" } }, + "Planner": { + "_info": { + "name": "Planner._info.name", + "help": "Planner._info.help" + }, + "Item_Credit": { + "name": "信用ポイント", + "help": "" + }, + "Item_Traveler_Guide": { + "name": "役割経験", + "help": "" + }, + "Item_Refined_Aether": { + "name": "武器経験", + "help": "" + }, + "Item_Lost_Crystal": { + "name": "遺失晶塊", + "help": "" + }, + "Item_Broken_Teeth_of_Iron_Wolf": { + "name": "キャラクター昇格素材:物理(ナターシャ / クラーラ / ルカ / 素裳)", + "help": "" + }, + "Item_Endotherm_Chitin": { + "name": "キャラクター昇格素材:炎(姫子 / アスター / フック)", + "help": "" + }, + "Item_Horn_of_Snow": { + "name": "キャラクター昇格素材:氷(三月なのか / ヘルタ / ジェパード / ペラ)", + "help": "" + }, + "Item_Lightning_Crown_of_the_Past_Shadow": { + "name": "キャラクター昇格素材:雷(アーラン / セーバル / 停雲 / 白露)", + "help": "" + }, + "Item_Storm_Eye": { + "name": "キャラクター昇格素材:風(丹恒 / ブローニャ / サンポ)", + "help": "" + }, + "Item_Void_Cast_Iron": { + "name": "キャラクター昇格素材:量子(銀狼 / ゼーレ / 青雀)", + "help": "" + }, + "Item_Golden_Crown_of_the_Past_Shadow": { + "name": "キャラクター昇格素材:虚数(ヴェルト / 羅刹 / 御空)", + "help": "" + }, + "Item_Netherworld_Token": { + "name": "キャラクター昇格素材:物理(寒鴉 / アルジェンティ)", + "help": "" + }, + "Item_Searing_Steel_Blade": { + "name": "キャラクター昇格素材:炎(桂乃芬 / トパーズ&カブ)", + "help": "" + }, + "Item_Gelid_Chitin": { + "name": "キャラクター昇格素材:氷(彦卿 / 鏡流 / ルアン・メェイ)", + "help": "" + }, + "Item_Shape_Shifter_Lightning_Staff": { + "name": "キャラクター昇格素材:雷(カフカ / 景元 / 黄泉)", + "help": "" + }, + "Item_Ascendant_Debris": { + "name": "キャラクター昇格素材:風(刃 / フォフォ / ブラックスワン)", + "help": "" + }, + "Item_Nail_of_the_Ape": { + "name": "キャラクター昇格素材:量子(リンクス / 符玄 / 雪衣)", + "help": "" + }, + "Item_Suppressing_Edict": { + "name": "キャラクター昇格素材:虚数(丹恒・飲月 / アベンチュリン / Dr.レイシオ)", + "help": "" + }, + "Item_IPC_Work_Permit": { + "name": "キャラクター昇格素材:物理(ブートヒル / ロビン)", + "help": "" + }, + "Item_Raging_Heart": { + "name": "キャラクター昇格素材:炎(ギャラガー)", + "help": "" + }, + "Item_Dream_Fridge": { + "name": "キャラクター昇格素材:氷(ミーシャ)", + "help": "" + }, + "Item_Dream_Flamer": { + "name": "キャラクター昇格素材:量子(花火)", + "help": "" + }, + "Item_Worldbreaker_Blade": { + "name": "軌跡素材:壊滅(収容部分)", + "help": "" + }, + "Item_Arrow_of_the_Starchaser": { + "name": "軌跡素材:巡狩(郊外雪原)", + "help": "" + }, + "Item_Key_of_Wisdom": { + "name": "軌跡素材:知恵(リベットタウン)", + "help": "" + }, + "Item_Safeguard_of_Amber": { + "name": "軌跡素材:存護(サポート部分)", + "help": "" + }, + "Item_Obsidian_of_Obsession": { + "name": "軌跡素材:虚無(大鉱区)", + "help": "" + }, + "Item_Stellaris_Symphony": { + "name": "軌跡素材:調和(機械集落)", + "help": "" + }, + "Item_Flower_of_Eternity": { + "name": "軌跡素材:豊穣(外縁通路)", + "help": "" + }, + "Item_Moon_Madness_Fang": { + "name": "軌跡素材:壊滅(鱗淵境)", + "help": "" + }, + "Item_Countertemporal_Shot": { + "name": "軌跡素材:巡狩(スラーダ熱砂オーディション会場)", + "help": "" + }, + "Item_Divine_Amber": { + "name": "軌跡素材:存護(クラークフィルムランド)", + "help": "" + }, + "Item_Heaven_Incinerator": { + "name": "軌跡素材:虚無(丹鼎司)", + "help": "" + }, + "Item_Heavenly_Melody": { + "name": "軌跡素材:調和(ホテル・レバリー-夢境)", + "help": "" + }, + "Item_Myriad_Fruit": { + "name": "軌跡素材:豊穣(綏園)", + "help": "" + }, + "Item_Tracks_of_Destiny": { + "name": "運命の足跡", + "help": "" + }, + "Item_Destroyer_Final_Road": { + "name": "歴戦余韻・壊滅の始まり (宇宙ステーション「ヘルタ」)", + "help": "" + }, + "Item_Guardian_Lament": { + "name": "歴戦余韻・寒波の幕切れ (ヤリーロ-VI)", + "help": "" + }, + "Item_Regret_of_Infinite_Ochema": { + "name": "歴戦余韻・不死の神実 (仙舟「羅浮」)", + "help": "" + }, + "Item_Past_Evils_of_the_Borehole_Planet_Disaster": { + "name": "歴戦余韻・星を蝕む往日の面影 (宇宙ステーション「ヘルタ」)", + "help": "" + }, + "Item_Lost_Echo_of_the_Shared_Wish": { + "name": "歴戦余韻・現世の夢の礼賛 (ピノコニー)", + "help": "" + }, + "Item_Squirming_Core": { + "name": "脈動する原核", + "help": "" + }, + "Item_Conqueror_Will": { + "name": "踏みにじる意志", + "help": "" + }, + "Item_Silvermane_Medal": { + "name": "シルバーメインの勲章", + "help": "" + }, + "Item_Ancient_Engine": { + "name": "古代エンジン", + "help": "" + }, + "Item_Immortal_Lumintwig": { + "name": "永寿の栄枝", + "help": "" + }, + "Item_Artifex_Gyreheart": { + "name": "工造渾心", + "help": "" + }, + "Item_Dream_Making_Engine": { + "name": "ドリームメイキングモーター", + "help": "" + }, + "Item_Shards_of_Desires": { + "name": "砕けた欲望の鏡", + "help": "" + } + }, "Weekly": { "_info": { "name": "Weekly._info.name", diff --git a/module/config/i18n/zh-CN.json b/module/config/i18n/zh-CN.json index a0a857a7a..2e4fbaaca 100644 --- a/module/config/i18n/zh-CN.json +++ b/module/config/i18n/zh-CN.json @@ -529,6 +529,208 @@ "help": "" } }, + "Planner": { + "_info": { + "name": "养成规划进度", + "help": "" + }, + "Item_Credit": { + "name": "信用点", + "help": "" + }, + "Item_Traveler_Guide": { + "name": "角色经验", + "help": "" + }, + "Item_Refined_Aether": { + "name": "武器经验", + "help": "" + }, + "Item_Lost_Crystal": { + "name": "遗失晶块", + "help": "" + }, + "Item_Broken_Teeth_of_Iron_Wolf": { + "name": "角色晋阶材料:物理(娜塔莎 / 克拉拉 / 卢卡 / 素裳)", + "help": "" + }, + "Item_Endotherm_Chitin": { + "name": "角色晋阶材料:火(姬子 / 艾丝妲 / 虎克)", + "help": "" + }, + "Item_Horn_of_Snow": { + "name": "角色晋阶材料:冰(三月七 / 黑塔 / 杰帕德 / 佩拉)", + "help": "" + }, + "Item_Lightning_Crown_of_the_Past_Shadow": { + "name": "角色晋阶材料:雷(阿兰 / 希露瓦 / 停云 / 白露)", + "help": "" + }, + "Item_Storm_Eye": { + "name": "角色晋阶材料:风(丹恒 / 布洛妮娅 / 桑博)", + "help": "" + }, + "Item_Void_Cast_Iron": { + "name": "角色晋阶材料:量子(银狼 / 希儿 / 青雀)", + "help": "" + }, + "Item_Golden_Crown_of_the_Past_Shadow": { + "name": "角色晋阶材料:虚数(瓦尔特 / 罗刹 / 驭空)", + "help": "" + }, + "Item_Netherworld_Token": { + "name": "角色晋阶材料:物理(寒鸦 / 银枝)", + "help": "" + }, + "Item_Searing_Steel_Blade": { + "name": "角色晋阶材料:火(桂乃芬 / 托帕&账账)", + "help": "" + }, + "Item_Gelid_Chitin": { + "name": "角色晋阶材料:冰(彦卿 / 镜流 / 阮•梅)", + "help": "" + }, + "Item_Shape_Shifter_Lightning_Staff": { + "name": "角色晋阶材料:雷(卡芙卡 / 景元 / 黄泉)", + "help": "" + }, + "Item_Ascendant_Debris": { + "name": "角色晋阶材料:风(刃 / 藿藿 / 黑天鹅)", + "help": "" + }, + "Item_Nail_of_the_Ape": { + "name": "角色晋阶材料:量子(玲可 / 符玄 / 雪衣)", + "help": "" + }, + "Item_Suppressing_Edict": { + "name": "角色晋阶材料:虚数(丹恒•饮月 / 砂金 / 真理医生)", + "help": "" + }, + "Item_IPC_Work_Permit": { + "name": "角色晋阶材料:物理(波提欧 / 知更鸟)", + "help": "" + }, + "Item_Raging_Heart": { + "name": "角色晋阶材料:火(加拉赫)", + "help": "" + }, + "Item_Dream_Fridge": { + "name": "角色晋阶材料:冰(米沙)", + "help": "" + }, + "Item_Dream_Flamer": { + "name": "角色晋阶材料:量子(花火)", + "help": "" + }, + "Item_Worldbreaker_Blade": { + "name": "行迹材料:毁灭(收容舱段)", + "help": "" + }, + "Item_Arrow_of_the_Starchaser": { + "name": "行迹材料:巡猎(城郊雪原)", + "help": "" + }, + "Item_Key_of_Wisdom": { + "name": "行迹材料:智识(铆钉镇)", + "help": "" + }, + "Item_Safeguard_of_Amber": { + "name": "行迹材料:存护(支援舱段)", + "help": "" + }, + "Item_Obsidian_of_Obsession": { + "name": "行迹材料:虚无(大矿区)", + "help": "" + }, + "Item_Stellaris_Symphony": { + "name": "行迹材料:同谐(机械聚落)", + "help": "" + }, + "Item_Flower_of_Eternity": { + "name": "行迹材料:丰饶(边缘通路)", + "help": "" + }, + "Item_Moon_Madness_Fang": { + "name": "行迹材料:毁灭(鳞渊境)", + "help": "" + }, + "Item_Countertemporal_Shot": { + "name": "行迹材料:巡猎(苏乐达热砂海选会场)", + "help": "" + }, + "Item_Divine_Amber": { + "name": "行迹材料:存护(克劳克影视乐园)", + "help": "" + }, + "Item_Heaven_Incinerator": { + "name": "行迹材料:虚无(丹鼎司)", + "help": "" + }, + "Item_Heavenly_Melody": { + "name": "行迹材料:同谐(白日梦酒店-梦境)", + "help": "" + }, + "Item_Myriad_Fruit": { + "name": "行迹材料:丰饶(绥园)", + "help": "" + }, + "Item_Tracks_of_Destiny": { + "name": "命运的足迹", + "help": "" + }, + "Item_Destroyer_Final_Road": { + "name": "毁灭的开端•历战余响 (空间站「黑塔」)", + "help": "" + }, + "Item_Guardian_Lament": { + "name": "寒潮的落幕•历战余响 (雅利洛-Ⅵ)", + "help": "" + }, + "Item_Regret_of_Infinite_Ochema": { + "name": "不死的神实•历战余响 (仙舟「罗浮」)", + "help": "" + }, + "Item_Past_Evils_of_the_Borehole_Planet_Disaster": { + "name": "蛀星的旧靥•历战余响 (空间站「黑塔」)", + "help": "" + }, + "Item_Lost_Echo_of_the_Shared_Wish": { + "name": "尘梦的赞礼•历战余响 (匹诺康尼)", + "help": "" + }, + "Item_Squirming_Core": { + "name": "蠢动原核", + "help": "" + }, + "Item_Conqueror_Will": { + "name": "践踏的意志", + "help": "" + }, + "Item_Silvermane_Medal": { + "name": "铁卫勋章", + "help": "" + }, + "Item_Ancient_Engine": { + "name": "古代引擎", + "help": "" + }, + "Item_Immortal_Lumintwig": { + "name": "永寿荣枝", + "help": "" + }, + "Item_Artifex_Gyreheart": { + "name": "工造浑心", + "help": "" + }, + "Item_Dream_Making_Engine": { + "name": "造梦马达", + "help": "" + }, + "Item_Shards_of_Desires": { + "name": "欲念碎镜", + "help": "" + } + }, "Weekly": { "_info": { "name": "历战余响设置", diff --git a/module/config/i18n/zh-TW.json b/module/config/i18n/zh-TW.json index f3eb18c55..0230d865d 100644 --- a/module/config/i18n/zh-TW.json +++ b/module/config/i18n/zh-TW.json @@ -529,6 +529,208 @@ "help": "" } }, + "Planner": { + "_info": { + "name": "養成規劃進度", + "help": "" + }, + "Item_Credit": { + "name": "信用點", + "help": "" + }, + "Item_Traveler_Guide": { + "name": "角色經驗", + "help": "" + }, + "Item_Refined_Aether": { + "name": "武器經驗", + "help": "" + }, + "Item_Lost_Crystal": { + "name": "遺失晶塊", + "help": "" + }, + "Item_Broken_Teeth_of_Iron_Wolf": { + "name": "角色晉階材料:物理(娜塔莎 / 克拉拉 / 盧卡 / 素裳)", + "help": "" + }, + "Item_Endotherm_Chitin": { + "name": "角色晉階材料:火(姬子 / 艾絲妲 / 虎克)", + "help": "" + }, + "Item_Horn_of_Snow": { + "name": "角色晉階材料:冰(三月七 / 黑塔 / 傑帕德 / 佩拉)", + "help": "" + }, + "Item_Lightning_Crown_of_the_Past_Shadow": { + "name": "角色晉階材料:雷(阿蘭 / 希露瓦 / 停雲 / 白露)", + "help": "" + }, + "Item_Storm_Eye": { + "name": "角色晉階材料:風(丹恆 / 布洛妮婭 / 桑博)", + "help": "" + }, + "Item_Void_Cast_Iron": { + "name": "角色晉階材料:量子(銀狼 / 希兒 / 青雀)", + "help": "" + }, + "Item_Golden_Crown_of_the_Past_Shadow": { + "name": "角色晉階材料:虛數(瓦爾特 / 羅剎 / 馭空)", + "help": "" + }, + "Item_Netherworld_Token": { + "name": "角色晉階材料:物理(寒鴉 / 銀枝)", + "help": "" + }, + "Item_Searing_Steel_Blade": { + "name": "角色晉階材料:火(桂乃芬 / 托帕&帳帳)", + "help": "" + }, + "Item_Gelid_Chitin": { + "name": "角色晉階材料:冰(彥卿 / 鏡流 / 阮•梅)", + "help": "" + }, + "Item_Shape_Shifter_Lightning_Staff": { + "name": "角色晉階材料:雷(卡芙卡 / 景元 / 黃泉)", + "help": "" + }, + "Item_Ascendant_Debris": { + "name": "角色晉階材料:風(刃 / 藿藿 / 黑天鵝)", + "help": "" + }, + "Item_Nail_of_the_Ape": { + "name": "角色晉階材料:量子(玲可 / 符玄 / 雪衣)", + "help": "" + }, + "Item_Suppressing_Edict": { + "name": "角色晉階材料:虛數(丹恆•飲月 / 砂金 / 真理醫生)", + "help": "" + }, + "Item_IPC_Work_Permit": { + "name": "角色晉階材料:物理(波提歐 / 知更鳥)", + "help": "" + }, + "Item_Raging_Heart": { + "name": "角色晉階材料:火(加拉赫)", + "help": "" + }, + "Item_Dream_Fridge": { + "name": "角色晉階材料:冰(米沙)", + "help": "" + }, + "Item_Dream_Flamer": { + "name": "角色晉階材料:量子(花火)", + "help": "" + }, + "Item_Worldbreaker_Blade": { + "name": "行跡材料:毀滅(收容艙段)", + "help": "" + }, + "Item_Arrow_of_the_Starchaser": { + "name": "行跡材料:巡獵(城郊雪原)", + "help": "" + }, + "Item_Key_of_Wisdom": { + "name": "行跡材料:智識(鉚釘鎮)", + "help": "" + }, + "Item_Safeguard_of_Amber": { + "name": "行跡材料:存護(支援艙段)", + "help": "" + }, + "Item_Obsidian_of_Obsession": { + "name": "行跡材料:虛無(大礦區)", + "help": "" + }, + "Item_Stellaris_Symphony": { + "name": "行跡材料:同諧(機械聚落)", + "help": "" + }, + "Item_Flower_of_Eternity": { + "name": "行跡材料:豐饒(邊緣通道)", + "help": "" + }, + "Item_Moon_Madness_Fang": { + "name": "行跡材料:毀滅(鱗淵境)", + "help": "" + }, + "Item_Countertemporal_Shot": { + "name": "行跡材料:巡獵(蘇樂達熱砂海選會場)", + "help": "" + }, + "Item_Divine_Amber": { + "name": "行跡材料:存護(克勞克影視樂園)", + "help": "" + }, + "Item_Heaven_Incinerator": { + "name": "行跡材料:虛無(丹鼎司)", + "help": "" + }, + "Item_Heavenly_Melody": { + "name": "行跡材料:同諧(白日夢飯店-夢境)", + "help": "" + }, + "Item_Myriad_Fruit": { + "name": "行跡材料:豐饒(綏園)", + "help": "" + }, + "Item_Tracks_of_Destiny": { + "name": "命運的足跡", + "help": "" + }, + "Item_Destroyer_Final_Road": { + "name": "毀滅的開端•歷戰餘響 (太空站「黑塔」)", + "help": "" + }, + "Item_Guardian_Lament": { + "name": "寒潮的落幕•歷戰餘響 (雅利洛-Ⅵ)", + "help": "" + }, + "Item_Regret_of_Infinite_Ochema": { + "name": "不死的神實•歷戰餘響 (仙舟「羅浮」)", + "help": "" + }, + "Item_Past_Evils_of_the_Borehole_Planet_Disaster": { + "name": "蛀星的舊靨•歷戰餘響 (太空站「黑塔」)", + "help": "" + }, + "Item_Lost_Echo_of_the_Shared_Wish": { + "name": "塵夢的讚禮•歷戰餘響 (匹諾康尼)", + "help": "" + }, + "Item_Squirming_Core": { + "name": "蠢動原核", + "help": "" + }, + "Item_Conqueror_Will": { + "name": "踐踏的意志", + "help": "" + }, + "Item_Silvermane_Medal": { + "name": "鐵衛勳章", + "help": "" + }, + "Item_Ancient_Engine": { + "name": "古代引擎", + "help": "" + }, + "Item_Immortal_Lumintwig": { + "name": "永壽榮枝", + "help": "" + }, + "Item_Artifex_Gyreheart": { + "name": "工造渾心", + "help": "" + }, + "Item_Dream_Making_Engine": { + "name": "造夢馬達", + "help": "" + }, + "Item_Shards_of_Desires": { + "name": "欲念碎鏡", + "help": "" + } + }, "Weekly": { "_info": { "name": "歷戰餘響設定", diff --git a/module/config/stored/classes.py b/module/config/stored/classes.py index e66cb299a..0251570eb 100644 --- a/module/config/stored/classes.py +++ b/module/config/stored/classes.py @@ -398,3 +398,9 @@ class StoredBattlePassQuestCavernOfCorrosion(StoredCounter): class StoredBattlePassQuestTrailblazePower(StoredCounter): # Dynamic total from 100 to 1400 LIST_TOTAL = [100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100, 1200, 1300, 1400] + + +class StoredPlanner(StoredBase): + value: int + total: int + synthesize: int diff --git a/module/config/stored/stored_generated.py b/module/config/stored/stored_generated.py index bcb853b5c..a3a99b164 100644 --- a/module/config/stored/stored_generated.py +++ b/module/config/stored/stored_generated.py @@ -19,6 +19,7 @@ from module.config.stored.classes import ( StoredExpiredAtMonday0400, StoredImmersifier, StoredInt, + StoredPlanner, StoredSimulatedUniverse, StoredSimulatedUniverseElite, StoredTrailblazePower, @@ -32,6 +33,55 @@ class StoredGenerated: CloudRemainSeasonPass = StoredInt("Alas.CloudStorage.CloudRemainSeasonPass") CloudRemainPaid = StoredInt("Alas.CloudStorage.CloudRemainPaid") CloudRemainFree = StoredInt("Alas.CloudStorage.CloudRemainFree") + Item_Credit = StoredPlanner("Dungeon.Planner.Item_Credit") + Item_Traveler_Guide = StoredPlanner("Dungeon.Planner.Item_Traveler_Guide") + Item_Refined_Aether = StoredPlanner("Dungeon.Planner.Item_Refined_Aether") + Item_Lost_Crystal = StoredPlanner("Dungeon.Planner.Item_Lost_Crystal") + Item_Broken_Teeth_of_Iron_Wolf = StoredPlanner("Dungeon.Planner.Item_Broken_Teeth_of_Iron_Wolf") + Item_Endotherm_Chitin = StoredPlanner("Dungeon.Planner.Item_Endotherm_Chitin") + Item_Horn_of_Snow = StoredPlanner("Dungeon.Planner.Item_Horn_of_Snow") + Item_Lightning_Crown_of_the_Past_Shadow = StoredPlanner("Dungeon.Planner.Item_Lightning_Crown_of_the_Past_Shadow") + Item_Storm_Eye = StoredPlanner("Dungeon.Planner.Item_Storm_Eye") + Item_Void_Cast_Iron = StoredPlanner("Dungeon.Planner.Item_Void_Cast_Iron") + Item_Golden_Crown_of_the_Past_Shadow = StoredPlanner("Dungeon.Planner.Item_Golden_Crown_of_the_Past_Shadow") + Item_Netherworld_Token = StoredPlanner("Dungeon.Planner.Item_Netherworld_Token") + Item_Searing_Steel_Blade = StoredPlanner("Dungeon.Planner.Item_Searing_Steel_Blade") + Item_Gelid_Chitin = StoredPlanner("Dungeon.Planner.Item_Gelid_Chitin") + Item_Shape_Shifter_Lightning_Staff = StoredPlanner("Dungeon.Planner.Item_Shape_Shifter_Lightning_Staff") + Item_Ascendant_Debris = StoredPlanner("Dungeon.Planner.Item_Ascendant_Debris") + Item_Nail_of_the_Ape = StoredPlanner("Dungeon.Planner.Item_Nail_of_the_Ape") + Item_Suppressing_Edict = StoredPlanner("Dungeon.Planner.Item_Suppressing_Edict") + Item_IPC_Work_Permit = StoredPlanner("Dungeon.Planner.Item_IPC_Work_Permit") + Item_Raging_Heart = StoredPlanner("Dungeon.Planner.Item_Raging_Heart") + Item_Dream_Fridge = StoredPlanner("Dungeon.Planner.Item_Dream_Fridge") + Item_Dream_Flamer = StoredPlanner("Dungeon.Planner.Item_Dream_Flamer") + Item_Worldbreaker_Blade = StoredPlanner("Dungeon.Planner.Item_Worldbreaker_Blade") + Item_Arrow_of_the_Starchaser = StoredPlanner("Dungeon.Planner.Item_Arrow_of_the_Starchaser") + Item_Key_of_Wisdom = StoredPlanner("Dungeon.Planner.Item_Key_of_Wisdom") + Item_Safeguard_of_Amber = StoredPlanner("Dungeon.Planner.Item_Safeguard_of_Amber") + Item_Obsidian_of_Obsession = StoredPlanner("Dungeon.Planner.Item_Obsidian_of_Obsession") + Item_Stellaris_Symphony = StoredPlanner("Dungeon.Planner.Item_Stellaris_Symphony") + Item_Flower_of_Eternity = StoredPlanner("Dungeon.Planner.Item_Flower_of_Eternity") + Item_Moon_Madness_Fang = StoredPlanner("Dungeon.Planner.Item_Moon_Madness_Fang") + Item_Countertemporal_Shot = StoredPlanner("Dungeon.Planner.Item_Countertemporal_Shot") + Item_Divine_Amber = StoredPlanner("Dungeon.Planner.Item_Divine_Amber") + Item_Heaven_Incinerator = StoredPlanner("Dungeon.Planner.Item_Heaven_Incinerator") + Item_Heavenly_Melody = StoredPlanner("Dungeon.Planner.Item_Heavenly_Melody") + Item_Myriad_Fruit = StoredPlanner("Dungeon.Planner.Item_Myriad_Fruit") + Item_Tracks_of_Destiny = StoredPlanner("Dungeon.Planner.Item_Tracks_of_Destiny") + Item_Destroyer_Final_Road = StoredPlanner("Dungeon.Planner.Item_Destroyer_Final_Road") + Item_Guardian_Lament = StoredPlanner("Dungeon.Planner.Item_Guardian_Lament") + Item_Regret_of_Infinite_Ochema = StoredPlanner("Dungeon.Planner.Item_Regret_of_Infinite_Ochema") + Item_Past_Evils_of_the_Borehole_Planet_Disaster = StoredPlanner("Dungeon.Planner.Item_Past_Evils_of_the_Borehole_Planet_Disaster") + Item_Lost_Echo_of_the_Shared_Wish = StoredPlanner("Dungeon.Planner.Item_Lost_Echo_of_the_Shared_Wish") + Item_Squirming_Core = StoredPlanner("Dungeon.Planner.Item_Squirming_Core") + Item_Conqueror_Will = StoredPlanner("Dungeon.Planner.Item_Conqueror_Will") + Item_Silvermane_Medal = StoredPlanner("Dungeon.Planner.Item_Silvermane_Medal") + Item_Ancient_Engine = StoredPlanner("Dungeon.Planner.Item_Ancient_Engine") + Item_Immortal_Lumintwig = StoredPlanner("Dungeon.Planner.Item_Immortal_Lumintwig") + Item_Artifex_Gyreheart = StoredPlanner("Dungeon.Planner.Item_Artifex_Gyreheart") + Item_Dream_Making_Engine = StoredPlanner("Dungeon.Planner.Item_Dream_Making_Engine") + Item_Shards_of_Desires = StoredPlanner("Dungeon.Planner.Item_Shards_of_Desires") TrailblazePower = StoredTrailblazePower("Dungeon.DungeonStorage.TrailblazePower") Immersifier = StoredImmersifier("Dungeon.DungeonStorage.Immersifier") DungeonDouble = StoredDungeonDouble("Dungeon.DungeonStorage.DungeonDouble") diff --git a/tasks/planner/keywords/classes.py b/tasks/planner/keywords/classes.py index 46619e76c..d58970c03 100644 --- a/tasks/planner/keywords/classes.py +++ b/tasks/planner/keywords/classes.py @@ -2,6 +2,7 @@ from dataclasses import dataclass from functools import cached_property from typing import ClassVar +from module.exception import ScriptError from module.ocr.keyword import Keyword @@ -14,6 +15,29 @@ class ItemBase(Keyword): item_group: int dungeon_id: int + def __post_init__(self): + self.__class__.instances[self.name] = self + + @classmethod + def find_name(cls, name): + """ + Args: + name: Attribute name of keyword. + + Returns: + Keyword instance. + + Raises: + ScriptError: If nothing found. + """ + if isinstance(name, Keyword): + return name + try: + return cls.instances[name] + except KeyError: + # Not found + raise ScriptError(f'Cannot find a {cls.__name__} instance that matches "{name}"') + @cached_property def dungeon(self): """ @@ -44,32 +68,85 @@ class ItemBase(Keyword): def is_rarity_green(self): return self.rarity == 'NotNormal' + @cached_property + def is_ItemAscension(self): + return self.__class__.__name__ == 'ItemAscension' + + @cached_property + def is_ItemCalyx(self): + return self.__class__.__name__ == 'ItemCalyx' + + @cached_property + def is_ItemCurrency(self): + return self.__class__.__name__ == 'ItemCurrency' + + @cached_property + def is_ItemExp(self): + return self.__class__.__name__ == 'ItemExp' + + @cached_property + def is_ItemTrace(self): + return self.__class__.__name__ == 'ItemTrace' + + @cached_property + def is_ItemWeekly(self): + return self.__class__.__name__ == 'ItemWeekly' + + @cached_property + def group_base(self): + if not self.has_group_base: + return self + if self.item_group <= 0: + raise ScriptError(f'Item {self} has no item_group defined, cannot find group_base') + + for instance in self.__class__.instances.values(): + if instance.item_group == self.item_group and instance.is_rarity_purple: + return instance + + raise ScriptError(f'Item {self} has no group_base') + + @cached_property + def has_group_base(self): + return self.is_ItemCalyx or self.is_ItemExp or self.is_ItemTrace + + @cached_property + def is_group_base(self): + if self.has_group_base: + return self.is_rarity_purple + else: + return True + + +""" +Sub item genres don't have `instances` defined, so all objects are in ItemBase.instances +""" + @dataclass(repr=False) class ItemAscension(ItemBase): - instances: ClassVar = {} + pass @dataclass(repr=False) class ItemCalyx(ItemBase): - instances: ClassVar = {} + pass @dataclass(repr=False) class ItemCurrency(ItemBase): - instances: ClassVar = {} + pass @dataclass(repr=False) class ItemExp(ItemBase): - instances: ClassVar = {} + pass @dataclass(repr=False) class ItemTrace(ItemBase): - instances: ClassVar = {} + pass @dataclass(repr=False) class ItemWeekly(ItemBase): - instances: ClassVar = {} + pass From d8ea34757aeb7408f6fc79ee680e2b73a6b9eab8 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Thu, 16 May 2024 02:59:04 +0800 Subject: [PATCH 06/37] Add: Write planner results into config --- module/ui/scroll.py | 2 +- tasks/dungeon/obtain.py | 40 ++-- tasks/planner/keywords/__init__.py | 3 + tasks/planner/model.py | 373 +++++++++++++++++++++++++++++ tasks/planner/result.py | 38 +-- 5 files changed, 419 insertions(+), 37 deletions(-) create mode 100644 tasks/planner/model.py diff --git a/module/ui/scroll.py b/module/ui/scroll.py index 358e6a01f..6d9387612 100644 --- a/module/ui/scroll.py +++ b/module/ui/scroll.py @@ -218,7 +218,7 @@ class AdaptiveScroll(Scroll): """ Args: area (Button, tuple): A button or area of the whole scroll. - prominence (dict): Parameters passing to scipy.find_peaks + parameters (dict): Parameters passing to scipy.find_peaks background (int): is_vertical (bool): True if vertical, false if horizontal. name (str): diff --git a/tasks/dungeon/obtain.py b/tasks/dungeon/obtain.py index ecd440d0e..1d8c8eab6 100644 --- a/tasks/dungeon/obtain.py +++ b/tasks/dungeon/obtain.py @@ -1,25 +1,17 @@ import re -from pydantic import BaseModel - from module.base.timer import Timer from module.exception import ScriptError from module.logger import logger from module.ocr.ocr import Digit -from tasks.base.ui import UI -from tasks.combat.assets.assets_combat_prepare import COMBAT_PREPARE +from tasks.combat.assets.assets_combat_prepare import COMBAT_PREPARE, WAVE_MINUS, WAVE_PLUS from tasks.dungeon.assets.assets_dungeon_obtain import * from tasks.dungeon.keywords import DungeonList from tasks.planner.keywords import ITEM_CLASSES -from tasks.planner.keywords.classes import ItemBase +from tasks.planner.model import ObtainedAmmount, PlannerProgressMixin from tasks.planner.result import OcrItemName -class ItemAmount(BaseModel): - item: ItemBase - amount: int - - class OcrItemAmount(Digit): def format_result(self, result): res = re.split(r'[::;;]', result) @@ -27,7 +19,7 @@ class OcrItemAmount(Digit): return super().format_result(result) -class DungeonObtain(UI): +class DungeonObtain(PlannerProgressMixin): """ Parse items that can be obtained from dungeon @@ -92,13 +84,15 @@ class DungeonObtain(UI): else: self.device.screenshot() - if self.appear(COMBAT_PREPARE): - break + if not self.appear(ITEM_CLOSE) and self.appear(COMBAT_PREPARE): + if self.image_color_count(WAVE_MINUS, color=(246, 246, 246), threshold=221, count=100) \ + or self.image_color_count(WAVE_PLUS, color=(246, 246, 246), threshold=221, count=100): + break if self.appear_then_click(ITEM_CLOSE, interval=2): continue @staticmethod - def _obtain_get_entry(dungeon: DungeonList, index: int = 1, prev: ItemAmount = None): + def _obtain_get_entry(dungeon: DungeonList, index: int = 1, prev: ObtainedAmmount = None): """ Args: dungeon: Current dungeon @@ -149,7 +143,7 @@ class DungeonObtain(UI): raise ScriptError(f'_obtain_get_entry: Cannot get entry from {dungeon}') - def _obtain_parse(self) -> ItemAmount | None: + def _obtain_parse(self) -> ObtainedAmmount | None: """ Pages: in: ITEM_CLOSE @@ -163,13 +157,13 @@ class DungeonObtain(UI): logger.warning('_obtain_parse: Unknown item name') return None - logger.info(f'Item amount: item={item}, amount={amount}') - return ItemAmount( + # logger.info(f'ObtainedAmmount: item={item}, value={amount}') + return ObtainedAmmount( item=item, - amount=amount, + value=amount, ) - def obtain_get(self, dungeon=None, skip_first_screenshot=True) -> list[ItemAmount]: + def obtain_get(self, dungeon=None, skip_first_screenshot=True) -> list[ObtainedAmmount]: """ Args: dungeon: Current dungeon, @@ -177,7 +171,7 @@ class DungeonObtain(UI): skip_first_screenshot: Returns: - list[ItemAmount]: + list[ObtainedAmmount]: Pages: in: COMBAT_PREPARE @@ -204,15 +198,17 @@ class DungeonObtain(UI): prev = item self._obtain_close() - logger.hr('Obtain get result') + logger.hr('Obtained Result') for item in items: - logger.info(f'ItemAmount: {item.item.name}, {item.amount}') + logger.info(f'Obtained item: {item.item.name}, {item.value}') """ <<< OBTAIN GET RESULT >>> ItemAmount: Arrow_of_the_Starchaser, 15 ItemAmount: Arrow_of_the_Demon_Slayer, 68 ItemAmount: Arrow_of_the_Beast_Hunter, 85 """ + self.planner.load_obtained_amount(items) + self.planner_write() return items diff --git a/tasks/planner/keywords/__init__.py b/tasks/planner/keywords/__init__.py index 81a8d2fc4..4bb746d0a 100644 --- a/tasks/planner/keywords/__init__.py +++ b/tasks/planner/keywords/__init__.py @@ -1,3 +1,5 @@ +from typing import Union + import tasks.planner.keywords.item_ascension as KEYWORDS_ITEM_ASCENSION import tasks.planner.keywords.item_calyx as KEYWORDS_ITEM_CALYX import tasks.planner.keywords.item_currency as KEYWORDS_ITEM_CURRENCY @@ -7,3 +9,4 @@ import tasks.planner.keywords.item_weekly as KEYWORDS_ITEM_WEEKLY from tasks.planner.keywords.classes import ItemAscension, ItemCalyx, ItemCurrency, ItemExp, ItemTrace, ItemWeekly ITEM_CLASSES = [ItemAscension, ItemCalyx, ItemCurrency, ItemExp, ItemTrace, ItemWeekly] +ITEM_TYPES = Union[ItemAscension, ItemCalyx, ItemCurrency, ItemExp, ItemTrace, ItemWeekly] diff --git a/tasks/planner/model.py b/tasks/planner/model.py new file mode 100644 index 000000000..fb01b5fd8 --- /dev/null +++ b/tasks/planner/model.py @@ -0,0 +1,373 @@ +import typing as t +from datetime import datetime +from functools import partial + +from pydantic import BaseModel, ValidationError, WrapValidator, field_validator, model_validator + +from module.base.decorator import cached_property, del_cached_property +from module.config.stored.classes import now +from module.config.utils import DEFAULT_TIME +from module.exception import ScriptError +from module.logger import logger +from tasks.base.ui import UI +from tasks.dungeon.keywords import DungeonList +from tasks.planner.keywords import ITEM_TYPES +from tasks.planner.keywords.classes import ItemBase + + +class PlannerResultRow(BaseModel): + """ + A row of data from planner result page + """ + item: ITEM_TYPES + total: int + synthesize: int + demand: int + + def __eq__(self, other): + return self.item == other.item + + +class ObtainedAmmount(BaseModel): + """ + A row of data from DungeonObtain detection + """ + item: ITEM_TYPES + value: int + + +def _fallback_to_default_validator( + get_default: t.Callable[[], t.Any], + v: t.Any, + next_: t.Callable[[t.Any], t.Any], +) -> t.Any: + try: + return next_(v) + except ValueError as e: + logger.error(e) + return get_default() + + +class BaseModelWithFallback(BaseModel): + """ + Pydantic model that fallbacks to default on error + https://github.com/pydantic/pydantic/discussions/8579 + """ + + @classmethod + def __pydantic_init_subclass__(cls, **kwargs: t.Any) -> None: + for field in cls.model_fields.values(): + if not field.is_required(): + validator = WrapValidator(partial(_fallback_to_default_validator, field.get_default)) + field.metadata.append(validator) + + cls.model_rebuild(force=True) + + +class MultiValue(BaseModelWithFallback): + green: int = 0 + blue: int = 0 + purple: int = 0 + + +class StoredPlannerProxy(BaseModelWithFallback): + item: ITEM_TYPES + value: int | MultiValue = 0 + total: int | MultiValue = 0 + synthesize: int | MultiValue = 0 + progress: float = 0. + time: datetime = DEFAULT_TIME + + @field_validator('item', mode='before') + def val_item(cls, v, info): + if isinstance(v, str): + v = ItemBase.find_name(v) + return v + + @model_validator(mode='after') + def val_value(self): + if self.item.has_group_base: + if not isinstance(self.value, MultiValue): + self.value = MultiValue() + if not isinstance(self.total, MultiValue): + self.total = MultiValue() + if not isinstance(self.synthesize, MultiValue): + self.synthesize = MultiValue() + else: + if not isinstance(self.value, int): + self.value = 0 + if not isinstance(self.total, int): + self.total = 0 + if not isinstance(self.synthesize, int): + self.synthesize = 0 + return self + + def update_synthesize(self): + if self.item.has_group_base: + green = self.value.green - self.total.green + blue = self.value.blue - self.total.blue + purple = self.value.purple - self.total.purple + syn_blue = 0 + syn_purple = 0 + if green >= 3 and blue < 0: + syn = min(green // 3, -blue) + syn_blue += syn + green -= syn * 3 + # blue += syn + if blue >= 3 and purple < 0: + syn = min(blue // 3, -purple) + syn_purple += syn + blue -= syn * 3 + purple += syn + if green >= 9 and purple < 0: + syn = min(green // 9, -purple) + syn_purple += syn + syn_blue += syn * 3 + green -= syn * 9 + # purple += syn + self.synthesize.green = 0 + self.synthesize.blue = syn_blue + self.synthesize.purple = syn_purple + else: + self.synthesize = 0 + + def revert_synthesize(self): + if self.item.has_group_base: + self.value.green += self.synthesize.blue * 3 + if self.synthesize.blue > 0: + self.value.green += self.synthesize.purple * 9 + else: + self.value.blue += self.synthesize.blue * 3 + + def update_progress(self): + if self.item.has_group_base: + total = self.total.green + self.total.blue * 3 + self.total.purple * 9 + green = min(self.value.green, self.total.green) + blue = min(self.value.blue + self.synthesize.blue, self.total.blue) + purple = min(self.value.purple + self.synthesize.purple, self.total.purple) + value = green + blue * 3 + purple * 9 + progress = value / total * 100 + self.progress = round(min(max(progress, 0), 100), 2) + else: + progress = self.value / self.total * 100 + self.progress = round(min(max(progress, 0), 100), 2) + + def update(self): + self.update_progress() + self.update_synthesize() + self.time = now() + + def load_value_total(self, item: ItemBase, value=None, total=None, synthesize=None): + """ + Update data from PlannerResultRow to self + """ + if self.item.has_group_base: + if item.group_base != self.item: + raise ScriptError( + f'load_value_total: Trying to load {item} into {self} but they are different items') + else: + if item != self.item: + raise ScriptError( + f'load_value_total: Trying to load {item} into {self} but they are different items') + if self.item.has_group_base: + if not self.item.is_rarity_purple: + raise ScriptError( + f'load_value_total: Trying to load {item} into {self} but self is not in rarity purple') + if item.is_rarity_green: + if value is not None: + self.value.green = value + if total is not None: + self.total.green = total + # if synthesize is not None: + # self.synthesize.green = synthesize + elif item.is_rarity_blue: + if value is not None: + self.value.blue = value + if total is not None: + self.total.blue = total + if synthesize is not None: + self.synthesize.blue = synthesize + elif item.is_rarity_purple: + if value is not None: + self.value.purple = value + if total is not None: + self.total.purple = total + if synthesize is not None: + self.synthesize.purple = synthesize + else: + raise ScriptError( + f'load_value_total: Trying to load {item} in to {self} but item is in invalid rarity') + else: + if value is not None: + self.value = value + if total is not None: + self.total = total + + def need_farm(self): + return self.progress < 100 + + def need_synthesize(self): + if self.item.has_group_base: + return self.synthesize.green > 0 or self.synthesize.blue > 0 or self.synthesize.purple > 0 + else: + return self.synthesize > 0 + + def load_planner_result(self, row: PlannerResultRow): + """ + Update data from PlannerResultRow to self + """ + # Approximate value, accurate value can be update in DungeonObtain + value = row.total - row.synthesize - row.demand + self.load_value_total(item=row.item, value=value, total=row.total, synthesize=row.synthesize) + + def load_item_amount(self, row: ObtainedAmmount): + """ + Update data from ObtainedAmmount to self + """ + value = row.value + self.load_value_total(item=row.item, value=value) + + +class PlannerProgressParser: + def __init__(self): + self.rows: dict[str, StoredPlannerProxy] = {} + + def from_planner_results(self, results: list[PlannerResultRow]): + self.rows = {} + # Create objects of base items first + # them load value and total + for row in results: + base = row.item.group_base + if base.name not in self.rows: + try: + obj = StoredPlannerProxy(item=base) + except ScriptError as e: + logger.error(e) + continue + self.rows[base.name] = obj + else: + obj = self.rows[base.name] + obj.load_planner_result(row) + + rows = {} + for name, row in self.rows.items(): + row.revert_synthesize() + row.update() + if row.need_farm() or row.need_synthesize(): + rows[name] = row + self.rows = rows + return self + + def load_obtained_amount(self, results: list[ObtainedAmmount]): + for row in results: + base = row.item.group_base + try: + obj = self.rows[base.name] + except KeyError: + logger.warning( + f'load_obtained_amount() drops {row} because no need to farm') + continue + obj.load_item_amount(row) + obj.update() + return self + + def from_config(self, data): + self.rows = {} + for row in data.values(): + if not row: + continue + try: + row = StoredPlannerProxy(**row) + except (ScriptError, ValidationError) as e: + logger.error(e) + continue + if not row.item.is_group_base: + logger.error(f'from_config: item is not group base {row}') + continue + self.rows[row.item.name] = row + return self + + def to_config(self) -> dict: + data = {} + for row in self.rows.values(): + name = f'Item_{row.item.name}' + dic = row.model_dump() + dic['item'] = row.item.name + data[name] = dic + return data + + def iter_item_to_farm(self) -> t.Iterable[ItemBase]: + rows = [row for row in self.rows.values() if row.need_farm()] + for row in rows: + if row.item.is_ItemWeekly: + yield row.item + for row in rows: + if row.item.is_ItemAscension: + yield row.item + for row in rows: + if row.item.is_ItemTrace: + yield row.item + for row in rows: + if row.item.is_ItemExp: + yield row.item + for row in rows: + if row.item.is_ItemCurrency: + yield row.item + + def get_dungeon(self) -> DungeonList | None: + """ + Get dungeon to farm, or None if planner finished or the remaining items cannot be farmed + """ + for item in self.iter_item_to_farm(): + if item.is_ItemWeekly: + continue + dungeon = item.dungeon + if dungeon is None: + logger.error(f'Item {item} has nowhere to be farmed') + continue + logger.info(f'Planner farm: {dungeon}') + return dungeon + + logger.info('Planner farm empty') + return None + + def get_weekly(self) -> DungeonList | None: + for item in self.iter_item_to_farm(): + if not item.is_ItemWeekly: + continue + dungeon = item.dungeon + if dungeon is None: + logger.error(f'Item {item} has nowhere to be farmed') + continue + logger.info(f'Planner farm: {dungeon}') + return dungeon + + logger.info('Planner farm empty') + return None + + +class PlannerProgressMixin(UI): + def planner_write_results(self, results: list[PlannerResultRow]): + """ + Write planner detection results info user config + """ + model = PlannerProgressParser().from_planner_results(results) + data = model.to_config() + self.config.cross_set('Dungeon.Planner', data) + + @cached_property + def planner(self) -> PlannerProgressParser: + data = self.config.cross_get('Dungeon.Planner', default={}) + model = PlannerProgressParser().from_config(data) + logger.hr('Planner') + for row in model.rows.values(): + logger.info(row) + return model + + def planner_write(self): + """ + Write planner into user config, delete planner object + """ + data = self.planner.to_config() + self.config.cross_set('Dungeon.Planner', data) + del_cached_property(self, 'planner') diff --git a/tasks/planner/result.py b/tasks/planner/result.py index 742624d33..ffea2ea38 100644 --- a/tasks/planner/result.py +++ b/tasks/planner/result.py @@ -2,7 +2,6 @@ import re import cv2 from pponnxcr.predict_system import BoxedResult -from pydantic import BaseModel from module.base.utils import area_center, area_in_area from module.logger import logger @@ -11,21 +10,15 @@ from module.ui.scroll import AdaptiveScroll from tasks.daily.synthesize import SynthesizeUI from tasks.planner.assets.assets_planner_result import * from tasks.planner.keywords import ITEM_CLASSES -from tasks.planner.keywords.classes import ItemBase, ItemCurrency +from tasks.planner.keywords.classes import ItemCurrency +from tasks.planner.model import PlannerProgressMixin, PlannerResultRow CALCULATE_TITLE.load_search(RESULT_CHECK.search) MATERIAL_TITLE.load_search(RESULT_CHECK.search) DETAIL_TITLE.load_search(RESULT_CHECK.search) -class PlannerResultRow(BaseModel): - item: ItemBase - total: int - synthesize: int - demand: int - def __eq__(self, other): - return self.item == other.item class OcrItemName(Ocr): @@ -44,6 +37,21 @@ class OcrPlannerResult(OcrWhiteLetterOnComplexBackground, OcrItemName): self.limited_area = OCR_RESULT.area self.limit_y = 720 + def _match_result( + self, + result: str, + keyword_classes, + lang: str = 'cn', + ignore_punctuation=True, + ignore_digit=True): + return super()._match_result( + result, + keyword_classes, + lang, + ignore_punctuation, + ignore_digit, + ) + def filter_detected(self, result: BoxedResult) -> bool: if not area_in_area(result.box, self.limited_area, threshold=0): return False @@ -67,7 +75,7 @@ class OcrPlannerResult(OcrWhiteLetterOnComplexBackground, OcrItemName): return image -class PlannerResult(SynthesizeUI): +class PlannerResult(SynthesizeUI, PlannerProgressMixin): def is_in_planner_result(self): if self.appear(RESULT_CHECK): return True @@ -116,12 +124,12 @@ class PlannerResult(SynthesizeUI): if y_match(number, y_item): total = int(number.ocr_text) break - synthesize = -1 + synthesize = 0 for number in list_synthesize: if y_match(number, y_item): synthesize = int(number.ocr_text) break - demand = -1 + demand = 0 for number in list_demand: if y_match(number, y_item): demand = int(number.ocr_text) @@ -139,7 +147,7 @@ class PlannerResult(SynthesizeUI): logger.warning(f'Planner row with total <= 0, {row}') continue if row.synthesize < 0: - # Credits always have synthesize="-" + # Credits always have `synthesize`=="-" if row.item.__class__ != ItemCurrency: logger.warning(f'Planner row with synthesize < 0, {row}') continue @@ -203,7 +211,9 @@ class PlannerResult(SynthesizeUI): logger.hr('Planner Result') for row in out: - logger.info(f'Item: {row.item.name}, {row.total}, {row.synthesize}, {row.demand}') + logger.info(f'Planner item: {row.item.name}, {row.total}, {row.synthesize}, {row.demand}') + + self.planner_write_results(out) return out From 9cd6f488f46c19a57a5204744225185dcaf38293 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Thu, 16 May 2024 15:27:45 +0800 Subject: [PATCH 07/37] Chore: Remove unavailable items --- dev_tools/keywords/item.py | 13 ++ tasks/planner/keywords/__init__.py | 12 +- tasks/planner/keywords/item_ascension.py | 49 ++---- tasks/planner/keywords/item_currency.py | 210 +---------------------- tasks/planner/keywords/item_exp.py | 13 -- tasks/planner/keywords/item_trace.py | 91 +++++----- 6 files changed, 81 insertions(+), 307 deletions(-) diff --git a/dev_tools/keywords/item.py b/dev_tools/keywords/item.py index e6b26be7e..1b55f9ee4 100644 --- a/dev_tools/keywords/item.py +++ b/dev_tools/keywords/item.py @@ -7,6 +7,7 @@ from module.config.utils import deep_get class GenerateItemBase(GenerateKeyword): purpose_type = [] + blacklist = [] def iter_items(self) -> t.Iterable[dict]: for data in SHARE_DATA.ItemConfig.values(): @@ -29,6 +30,8 @@ class GenerateItemBase(GenerateKeyword): def iter_keywords(self) -> t.Iterable[dict]: for data in self.iter_items(): if data['subtype'] == 'Material' and data['purpose'] in self.purpose_type: + if data['item_id'] in self.blacklist: + continue data['dungeon_id'] = self.dict_itemid_to_dungeonid.get(data['item_id'], -1) yield data @@ -68,26 +71,36 @@ class GenerateItemBase(GenerateKeyword): class GenerateItemCurrency(GenerateItemBase): output_file = './tasks/planner/keywords/item_currency.py' + # Leave 'Credit' only + whitelist = [2] def iter_keywords(self) -> t.Iterable[dict]: for data in self.iter_items(): if data['subtype'] == 'Virtual' and data['item_id'] < 100: + if data['item_id'] not in self.whitelist: + continue yield data class GenerateItemExp(GenerateItemBase): output_file = './tasks/planner/keywords/item_exp.py' purpose_type = [1, 5, 6] + # 'Lost_Essence' is not available in game currently + blacklist = [234] class GenerateItemAscension(GenerateItemBase): output_file = './tasks/planner/keywords/item_ascension.py' purpose_type = [2] + # 'Enigmatic_Ectostella' is not available in game currently + blacklist = [110400] class GenerateItemTrace(GenerateItemBase): output_file = './tasks/planner/keywords/item_trace.py' purpose_type = [3] + # Can't farm Tears_of_Dreams + blacklist = [110101] class GenerateItemWeekly(GenerateItemBase): diff --git a/tasks/planner/keywords/__init__.py b/tasks/planner/keywords/__init__.py index 4bb746d0a..c3be88e18 100644 --- a/tasks/planner/keywords/__init__.py +++ b/tasks/planner/keywords/__init__.py @@ -1,11 +1,19 @@ from typing import Union -import tasks.planner.keywords.item_ascension as KEYWORDS_ITEM_ASCENSION -import tasks.planner.keywords.item_calyx as KEYWORDS_ITEM_CALYX +# Import order matters, DO NOT optimize imports +# 1 import tasks.planner.keywords.item_currency as KEYWORDS_ITEM_CURRENCY +# 2 import tasks.planner.keywords.item_exp as KEYWORDS_ITEM_EXP +# 3 +import tasks.planner.keywords.item_ascension as KEYWORDS_ITEM_ASCENSION +# 4 import tasks.planner.keywords.item_trace as KEYWORDS_ITEM_TRACE +# 5 import tasks.planner.keywords.item_weekly as KEYWORDS_ITEM_WEEKLY +# 6 +import tasks.planner.keywords.item_calyx as KEYWORDS_ITEM_CALYX + from tasks.planner.keywords.classes import ItemAscension, ItemCalyx, ItemCurrency, ItemExp, ItemTrace, ItemWeekly ITEM_CLASSES = [ItemAscension, ItemCalyx, ItemCurrency, ItemExp, ItemTrace, ItemWeekly] diff --git a/tasks/planner/keywords/item_ascension.py b/tasks/planner/keywords/item_ascension.py index 1da25b870..5bd00204b 100644 --- a/tasks/planner/keywords/item_ascension.py +++ b/tasks/planner/keywords/item_ascension.py @@ -3,21 +3,8 @@ from .classes import ItemAscension # This file was auto-generated, do not modify it manually. To generate: # ``` python -m dev_tools.keyword_extract ``` -Enigmatic_Ectostella = ItemAscension( - id=1, - name='Enigmatic_Ectostella', - cn='深邃的星外质', - cht='深邃的星外質', - en='Enigmatic Ectostella', - jp='深邃な星外物質', - es='Ectoestela profunda', - rarity='VeryRare', - item_id=110400, - item_group=1100, - dungeon_id=-1, -) Broken_Teeth_of_Iron_Wolf = ItemAscension( - id=2, + id=1, name='Broken_Teeth_of_Iron_Wolf', cn='铁狼碎齿', cht='鐵狼碎齒', @@ -30,7 +17,7 @@ Broken_Teeth_of_Iron_Wolf = ItemAscension( dungeon_id=1105, ) Endotherm_Chitin = ItemAscension( - id=3, + id=2, name='Endotherm_Chitin', cn='恒温晶壳', cht='恆溫晶殼', @@ -43,7 +30,7 @@ Endotherm_Chitin = ItemAscension( dungeon_id=1104, ) Horn_of_Snow = ItemAscension( - id=4, + id=3, name='Horn_of_Snow', cn='风雪之角', cht='風雪之角', @@ -56,7 +43,7 @@ Horn_of_Snow = ItemAscension( dungeon_id=1106, ) Lightning_Crown_of_the_Past_Shadow = ItemAscension( - id=5, + id=4, name='Lightning_Crown_of_the_Past_Shadow', cn='往日之影的雷冠', cht='往日之影的雷冠', @@ -69,7 +56,7 @@ Lightning_Crown_of_the_Past_Shadow = ItemAscension( dungeon_id=1103, ) Storm_Eye = ItemAscension( - id=6, + id=5, name='Storm_Eye', cn='暴风之眼', cht='暴風之眼', @@ -82,7 +69,7 @@ Storm_Eye = ItemAscension( dungeon_id=1102, ) Void_Cast_Iron = ItemAscension( - id=7, + id=6, name='Void_Cast_Iron', cn='虚幻铸铁', cht='虛幻鑄鐵', @@ -95,7 +82,7 @@ Void_Cast_Iron = ItemAscension( dungeon_id=1101, ) Golden_Crown_of_the_Past_Shadow = ItemAscension( - id=8, + id=7, name='Golden_Crown_of_the_Past_Shadow', cn='往日之影的金饰', cht='往日之影的金飾', @@ -108,7 +95,7 @@ Golden_Crown_of_the_Past_Shadow = ItemAscension( dungeon_id=1107, ) Netherworld_Token = ItemAscension( - id=9, + id=8, name='Netherworld_Token', cn='幽府通令', cht='幽府通令', @@ -121,7 +108,7 @@ Netherworld_Token = ItemAscension( dungeon_id=1114, ) Searing_Steel_Blade = ItemAscension( - id=10, + id=9, name='Searing_Steel_Blade', cn='过热钢刃', cht='過熱鋼刃', @@ -134,7 +121,7 @@ Searing_Steel_Blade = ItemAscension( dungeon_id=1112, ) Gelid_Chitin = ItemAscension( - id=11, + id=10, name='Gelid_Chitin', cn='苦寒晶壳', cht='苦寒晶殼', @@ -147,7 +134,7 @@ Gelid_Chitin = ItemAscension( dungeon_id=1108, ) Shape_Shifter_Lightning_Staff = ItemAscension( - id=12, + id=11, name='Shape_Shifter_Lightning_Staff', cn='炼形者雷枝', cht='煉形者雷枝', @@ -160,7 +147,7 @@ Shape_Shifter_Lightning_Staff = ItemAscension( dungeon_id=1109, ) Ascendant_Debris = ItemAscension( - id=13, + id=12, name='Ascendant_Debris', cn='天人遗垢', cht='天人遺垢', @@ -173,7 +160,7 @@ Ascendant_Debris = ItemAscension( dungeon_id=1113, ) Nail_of_the_Ape = ItemAscension( - id=14, + id=13, name='Nail_of_the_Ape', cn='苍猿之钉', cht='蒼猿之釘', @@ -186,7 +173,7 @@ Nail_of_the_Ape = ItemAscension( dungeon_id=1111, ) Suppressing_Edict = ItemAscension( - id=15, + id=14, name='Suppressing_Edict', cn='镇灵敕符', cht='鎮靈敕符', @@ -199,7 +186,7 @@ Suppressing_Edict = ItemAscension( dungeon_id=1110, ) IPC_Work_Permit = ItemAscension( - id=16, + id=15, name='IPC_Work_Permit', cn='星际和平工作证', cht='星際和平工作證', @@ -212,7 +199,7 @@ IPC_Work_Permit = ItemAscension( dungeon_id=1118, ) Raging_Heart = ItemAscension( - id=17, + id=16, name='Raging_Heart', cn='忿火之心', cht='忿火之心', @@ -225,7 +212,7 @@ Raging_Heart = ItemAscension( dungeon_id=1117, ) Dream_Fridge = ItemAscension( - id=18, + id=17, name='Dream_Fridge', cn='冷藏梦箱', cht='冷藏夢箱', @@ -238,7 +225,7 @@ Dream_Fridge = ItemAscension( dungeon_id=1115, ) Dream_Flamer = ItemAscension( - id=19, + id=18, name='Dream_Flamer', cn='炙梦喷枪', cht='炙夢噴槍', diff --git a/tasks/planner/keywords/item_currency.py b/tasks/planner/keywords/item_currency.py index 90514f8ed..9ba85a4b7 100644 --- a/tasks/planner/keywords/item_currency.py +++ b/tasks/planner/keywords/item_currency.py @@ -3,21 +3,8 @@ from .classes import ItemCurrency # This file was auto-generated, do not modify it manually. To generate: # ``` python -m dev_tools.keyword_extract ``` -Stellar_Jade = ItemCurrency( - id=1, - name='Stellar_Jade', - cn='星琼', - cht='星瓊', - en='Stellar Jade', - jp='星玉', - es='Jade estelar', - rarity='SuperRare', - item_id=1, - item_group=0, - dungeon_id=-1, -) Credit = ItemCurrency( - id=2, + id=1, name='Credit', cn='信用点', cht='信用點', @@ -29,198 +16,3 @@ Credit = ItemCurrency( item_group=0, dungeon_id=-1, ) -Oneiric_Shard = ItemCurrency( - id=3, - name='Oneiric_Shard', - cn='古老梦华', - cht='古老夢華', - en='Oneiric Shard', - jp='往日の夢華', - es='Esquirla onírica', - rarity='SuperRare', - item_id=3, - item_group=0, - dungeon_id=-1, -) -Trailblaze_Power = ItemCurrency( - id=4, - name='Trailblaze_Power', - cn='开拓力', - cht='開拓力', - en='Trailblaze Power', - jp='開拓力', - es='Poder trazacaminos', - rarity='VeryRare', - item_id=11, - item_group=0, - dungeon_id=-1, -) -Reserved_Trailblaze_Power = ItemCurrency( - id=5, - name='Reserved_Trailblaze_Power', - cn='后备开拓力', - cht='後備開拓力', - en='Reserved Trailblaze Power', - jp='予備開拓力', - es='Poder trazacaminos de reserva', - rarity='VeryRare', - item_id=12, - item_group=0, - dungeon_id=-1, -) -EXP = ItemCurrency( - id=6, - name='EXP', - cn='经验', - cht='經驗', - en='EXP', - jp='経験', - es='EXP', - rarity='Rare', - item_id=21, - item_group=0, - dungeon_id=-1, -) -Trailblaze_EXP = ItemCurrency( - id=7, - name='Trailblaze_EXP', - cn='里程', - cht='里程', - en='Trailblaze EXP', - jp='マイレージ', - es='EXP trazacaminos', - rarity='Rare', - item_id=22, - item_group=0, - dungeon_id=-1, -) -Activity = ItemCurrency( - id=8, - name='Activity', - cn='活跃度', - cht='活躍度', - en='Activity', - jp='アクティブ度', - es='Actividad', - rarity='Rare', - item_id=23, - item_group=0, - dungeon_id=-1, -) -Trailblaze_Timer = ItemCurrency( - id=9, - name='Trailblaze_Timer', - cn='开拓进行时', - cht='開拓進行時', - en='Trailblaze Timer', - jp='開拓進行計', - es='Trazascopio', - rarity='SuperRare', - item_id=24, - item_group=0, - dungeon_id=-1, -) -The_Returning_Trail = ItemCurrency( - id=10, - name='The_Returning_Trail', - cn='归程轨迹', - cht='歸程軌跡', - en='The Returning Trail', - jp='帰還の軌跡', - es='Trayectoria de regreso', - rarity='Rare', - item_id=25, - item_group=0, - dungeon_id=-1, -) -Cosmic_Fragment = ItemCurrency( - id=11, - name='Cosmic_Fragment', - cn='宇宙碎片', - cht='宇宙碎片', - en='Cosmic Fragment', - jp='宇宙の欠片', - es='Fragmentos cósmicos', - rarity='Rare', - item_id=31, - item_group=0, - dungeon_id=-1, -) -Ability_Point = ItemCurrency( - id=12, - name='Ability_Point', - cn='技能点', - cht='技能點', - en='Ability Point', - jp='アビリティポイント', - es='Punto de habilidad', - rarity='Rare', - item_id=32, - item_group=0, - dungeon_id=-1, -) -Immersifier = ItemCurrency( - id=13, - name='Immersifier', - cn='沉浸器', - cht='沉浸器', - en='Immersifier', - jp='没入器', - es='Inmersor', - rarity='VeryRare', - item_id=33, - item_group=0, - dungeon_id=-1, -) -Achievement_Points = ItemCurrency( - id=14, - name='Achievement_Points', - cn='成就点数', - cht='成就點數', - en='Achievement Points', - jp='アチーブメントポイント', - es='Puntos de logro', - rarity='Rare', - item_id=41, - item_group=0, - dungeon_id=-1, -) -The_Nameless_EXP = ItemCurrency( - id=15, - name='The_Nameless_EXP', - cn='无名客的经验', - cht='無名客的經驗', - en='The Nameless EXP', - jp='ナナシビトの経験', - es='EXP anónima', - rarity='Rare', - item_id=51, - item_group=0, - dungeon_id=-1, -) -The_Nameless_EXP = ItemCurrency( - id=16, - name='The_Nameless_EXP', - cn='无名客的经验', - cht='無名客的經驗', - en='The Nameless EXP', - jp='ナナシビトの経験', - es='EXP anónima', - rarity='Rare', - item_id=52, - item_group=0, - dungeon_id=-1, -) -Development_Fund = ItemCurrency( - id=17, - name='Development_Fund', - cn='发展资金', - cht='發展資金', - en='Development Fund', - jp='発展資金', - es='Fondos de desarrollo', - rarity='Rare', - item_id=53, - item_group=0, - dungeon_id=-1, -) diff --git a/tasks/planner/keywords/item_exp.py b/tasks/planner/keywords/item_exp.py index b1d81fc29..1be68e0df 100644 --- a/tasks/planner/keywords/item_exp.py +++ b/tasks/planner/keywords/item_exp.py @@ -120,16 +120,3 @@ Lost_Crystal = ItemExp( item_group=1030, dungeon_id=-1, ) -Lost_Essence = ItemExp( - id=10, - name='Lost_Essence', - cn='遗失精粹', - cht='遺失精粹', - en='Lost Essence', - jp='遺失精華', - es='Esencia perdida', - rarity='SuperRare', - item_id=234, - item_group=1030, - dungeon_id=-1, -) diff --git a/tasks/planner/keywords/item_trace.py b/tasks/planner/keywords/item_trace.py index a8ee7bc4e..cc545b3ac 100644 --- a/tasks/planner/keywords/item_trace.py +++ b/tasks/planner/keywords/item_trace.py @@ -3,21 +3,8 @@ from .classes import ItemTrace # This file was auto-generated, do not modify it manually. To generate: # ``` python -m dev_tools.keyword_extract ``` -Tears_of_Dreams = ItemTrace( - id=1, - name='Tears_of_Dreams', - cn='梦之珠泪', - cht='夢之珠淚', - en='Tears of Dreams', - jp='夢の涙', - es='Lágrima de los sueños', - rarity='VeryRare', - item_id=110101, - item_group=1200, - dungeon_id=-1, -) Shattered_Blade = ItemTrace( - id=2, + id=1, name='Shattered_Blade', cn='破碎残刃', cht='破碎殘刃', @@ -30,7 +17,7 @@ Shattered_Blade = ItemTrace( dungeon_id=1004, ) Lifeless_Blade = ItemTrace( - id=3, + id=2, name='Lifeless_Blade', cn='无生残刃', cht='無生殘刃', @@ -43,7 +30,7 @@ Lifeless_Blade = ItemTrace( dungeon_id=1004, ) Worldbreaker_Blade = ItemTrace( - id=4, + id=3, name='Worldbreaker_Blade', cn='净世残刃', cht='淨世殘刃', @@ -56,7 +43,7 @@ Worldbreaker_Blade = ItemTrace( dungeon_id=1004, ) Arrow_of_the_Beast_Hunter = ItemTrace( - id=5, + id=4, name='Arrow_of_the_Beast_Hunter', cn='猎兽之矢', cht='獵獸之矢', @@ -69,7 +56,7 @@ Arrow_of_the_Beast_Hunter = ItemTrace( dungeon_id=1006, ) Arrow_of_the_Demon_Slayer = ItemTrace( - id=6, + id=5, name='Arrow_of_the_Demon_Slayer', cn='屠魔之矢', cht='屠魔之矢', @@ -82,7 +69,7 @@ Arrow_of_the_Demon_Slayer = ItemTrace( dungeon_id=1006, ) Arrow_of_the_Starchaser = ItemTrace( - id=7, + id=6, name='Arrow_of_the_Starchaser', cn='逐星之矢', cht='逐星之矢', @@ -95,7 +82,7 @@ Arrow_of_the_Starchaser = ItemTrace( dungeon_id=1006, ) Key_of_Inspiration = ItemTrace( - id=8, + id=7, name='Key_of_Inspiration', cn='灵感之钥', cht='靈感之鑰', @@ -108,7 +95,7 @@ Key_of_Inspiration = ItemTrace( dungeon_id=1008, ) Key_of_Knowledge = ItemTrace( - id=9, + id=8, name='Key_of_Knowledge', cn='启迪之钥', cht='啟迪之鑰', @@ -121,7 +108,7 @@ Key_of_Knowledge = ItemTrace( dungeon_id=1008, ) Key_of_Wisdom = ItemTrace( - id=10, + id=9, name='Key_of_Wisdom', cn='智识之钥', cht='智識之鑰', @@ -134,7 +121,7 @@ Key_of_Wisdom = ItemTrace( dungeon_id=1008, ) Endurance_of_Bronze = ItemTrace( - id=11, + id=10, name='Endurance_of_Bronze', cn='青铜的执着', cht='青銅的執著', @@ -147,7 +134,7 @@ Endurance_of_Bronze = ItemTrace( dungeon_id=1005, ) Oath_of_Steel = ItemTrace( - id=12, + id=11, name='Oath_of_Steel', cn='寒铁的誓言', cht='寒鐵的誓言', @@ -160,7 +147,7 @@ Oath_of_Steel = ItemTrace( dungeon_id=1005, ) Safeguard_of_Amber = ItemTrace( - id=13, + id=12, name='Safeguard_of_Amber', cn='琥珀的坚守', cht='琥珀的堅守', @@ -173,7 +160,7 @@ Safeguard_of_Amber = ItemTrace( dungeon_id=1005, ) Obsidian_of_Dread = ItemTrace( - id=14, + id=13, name='Obsidian_of_Dread', cn='黯淡黑曜', cht='黯淡黑曜', @@ -186,7 +173,7 @@ Obsidian_of_Dread = ItemTrace( dungeon_id=1010, ) Obsidian_of_Desolation = ItemTrace( - id=15, + id=14, name='Obsidian_of_Desolation', cn='虚空黑曜', cht='虛空黑曜', @@ -199,7 +186,7 @@ Obsidian_of_Desolation = ItemTrace( dungeon_id=1010, ) Obsidian_of_Obsession = ItemTrace( - id=16, + id=15, name='Obsidian_of_Obsession', cn='沉沦黑曜', cht='沉淪黑曜', @@ -212,7 +199,7 @@ Obsidian_of_Obsession = ItemTrace( dungeon_id=1010, ) Harmonic_Tune = ItemTrace( - id=17, + id=16, name='Harmonic_Tune', cn='谐乐小调', cht='諧樂小調', @@ -225,7 +212,7 @@ Harmonic_Tune = ItemTrace( dungeon_id=1009, ) Ancestral_Hymn = ItemTrace( - id=18, + id=17, name='Ancestral_Hymn', cn='家族颂歌', cht='家族頌歌', @@ -238,7 +225,7 @@ Ancestral_Hymn = ItemTrace( dungeon_id=1009, ) Stellaris_Symphony = ItemTrace( - id=19, + id=18, name='Stellaris_Symphony', cn='群星乐章', cht='群星樂章', @@ -251,7 +238,7 @@ Stellaris_Symphony = ItemTrace( dungeon_id=1009, ) Seed_of_Abundance = ItemTrace( - id=20, + id=19, name='Seed_of_Abundance', cn='丰饶之种', cht='豐饒之種', @@ -264,7 +251,7 @@ Seed_of_Abundance = ItemTrace( dungeon_id=1007, ) Sprout_of_Life = ItemTrace( - id=21, + id=20, name='Sprout_of_Life', cn='生命之芽', cht='生命之芽', @@ -277,7 +264,7 @@ Sprout_of_Life = ItemTrace( dungeon_id=1007, ) Flower_of_Eternity = ItemTrace( - id=22, + id=21, name='Flower_of_Eternity', cn='永恒之花', cht='永恆之花', @@ -290,7 +277,7 @@ Flower_of_Eternity = ItemTrace( dungeon_id=1007, ) Borisin_Teeth = ItemTrace( - id=23, + id=22, name='Borisin_Teeth', cn='步离犬牙', cht='步離犬牙', @@ -303,7 +290,7 @@ Borisin_Teeth = ItemTrace( dungeon_id=1018, ) Lupitoxin_Sawteeth = ItemTrace( - id=24, + id=23, name='Lupitoxin_Sawteeth', cn='狼毒锯牙', cht='狼毒鋸牙', @@ -316,7 +303,7 @@ Lupitoxin_Sawteeth = ItemTrace( dungeon_id=1018, ) Moon_Madness_Fang = ItemTrace( - id=25, + id=24, name='Moon_Madness_Fang', cn='月狂獠牙', cht='月狂獠牙', @@ -329,7 +316,7 @@ Moon_Madness_Fang = ItemTrace( dungeon_id=1018, ) Meteoric_Bullet = ItemTrace( - id=26, + id=25, name='Meteoric_Bullet', cn='陨铁弹丸', cht='隕鐵彈丸', @@ -342,7 +329,7 @@ Meteoric_Bullet = ItemTrace( dungeon_id=1022, ) Destined_Expiration = ItemTrace( - id=27, + id=26, name='Destined_Expiration', cn='命定死因', cht='命定死因', @@ -355,7 +342,7 @@ Destined_Expiration = ItemTrace( dungeon_id=1022, ) Countertemporal_Shot = ItemTrace( - id=28, + id=27, name='Countertemporal_Shot', cn='逆时一击', cht='逆時一擊', @@ -368,7 +355,7 @@ Countertemporal_Shot = ItemTrace( dungeon_id=1022, ) Scattered_Stardust = ItemTrace( - id=29, + id=28, name='Scattered_Stardust', cn='散逸星砂', cht='散逸星砂', @@ -381,7 +368,7 @@ Scattered_Stardust = ItemTrace( dungeon_id=1020, ) Crystal_Meteorites = ItemTrace( - id=30, + id=29, name='Crystal_Meteorites', cn='流星棱晶', cht='流星稜晶', @@ -394,7 +381,7 @@ Crystal_Meteorites = ItemTrace( dungeon_id=1020, ) Divine_Amber = ItemTrace( - id=31, + id=30, name='Divine_Amber', cn='神体琥珀', cht='神體琥珀', @@ -407,7 +394,7 @@ Divine_Amber = ItemTrace( dungeon_id=1020, ) Fiery_Spirit = ItemTrace( - id=32, + id=31, name='Fiery_Spirit', cn='炽情之灵', cht='熾情之靈', @@ -420,7 +407,7 @@ Fiery_Spirit = ItemTrace( dungeon_id=1017, ) Starfire_Essence = ItemTrace( - id=33, + id=32, name='Starfire_Essence', cn='星火之精', cht='星火之精', @@ -433,7 +420,7 @@ Starfire_Essence = ItemTrace( dungeon_id=1017, ) Heaven_Incinerator = ItemTrace( - id=34, + id=33, name='Heaven_Incinerator', cn='焚天之魔', cht='焚天之魔', @@ -446,7 +433,7 @@ Heaven_Incinerator = ItemTrace( dungeon_id=1017, ) Firmament_Note = ItemTrace( - id=35, + id=34, name='Firmament_Note', cn='云际音符', cht='雲際音符', @@ -459,7 +446,7 @@ Firmament_Note = ItemTrace( dungeon_id=1019, ) Celestial_Section = ItemTrace( - id=36, + id=35, name='Celestial_Section', cn='空际小节', cht='空際小節', @@ -472,7 +459,7 @@ Celestial_Section = ItemTrace( dungeon_id=1019, ) Heavenly_Melody = ItemTrace( - id=37, + id=36, name='Heavenly_Melody', cn='天外乐章', cht='天外樂章', @@ -485,7 +472,7 @@ Heavenly_Melody = ItemTrace( dungeon_id=1019, ) Alien_Tree_Seed = ItemTrace( - id=38, + id=37, name='Alien_Tree_Seed', cn='异木种籽', cht='異木種籽', @@ -498,7 +485,7 @@ Alien_Tree_Seed = ItemTrace( dungeon_id=1021, ) Nourishing_Honey = ItemTrace( - id=39, + id=38, name='Nourishing_Honey', cn='滋长花蜜', cht='滋長花蜜', @@ -511,7 +498,7 @@ Nourishing_Honey = ItemTrace( dungeon_id=1021, ) Myriad_Fruit = ItemTrace( - id=40, + id=39, name='Myriad_Fruit', cn='万相果实', cht='萬相果實', From da135402d43bfd129369202855d032c542706c48 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Sat, 18 May 2024 00:51:27 +0800 Subject: [PATCH 08/37] Add: Show planner on gui --- assets/gui/css/alas.css | 7 ++ module/config/argument/args.json | 196 +++++++++++++++---------------- module/config/config_updater.py | 10 +- module/webui/widgets.py | 39 +++++- 4 files changed, 149 insertions(+), 103 deletions(-) diff --git a/assets/gui/css/alas.css b/assets/gui/css/alas.css index 5c7610226..890eda71b 100644 --- a/assets/gui/css/alas.css +++ b/assets/gui/css/alas.css @@ -435,6 +435,13 @@ pre.rich-traceback-code { overflow-wrap: break-word; } +*[style*="--dashboard-bold--"] { + font-size: 1rem; + font-weight: bold; + color: #7a77bb; + overflow-wrap: break-word; +} + [id^="pywebio-scope-dashboard-row-"] p { margin-bottom: 0; } diff --git a/module/config/argument/args.json b/module/config/argument/args.json index 701c96285..a08473dd0 100644 --- a/module/config/argument/args.json +++ b/module/config/argument/args.json @@ -221,297 +221,297 @@ }, "Planner": { "Item_Credit": { - "type": "stored", + "type": "planner", "value": {}, - "display": "hide", + "display": "display", "stored": "StoredPlanner" }, "Item_Traveler_Guide": { - "type": "stored", + "type": "planner", "value": {}, - "display": "hide", + "display": "display", "stored": "StoredPlanner" }, "Item_Refined_Aether": { - "type": "stored", + "type": "planner", "value": {}, - "display": "hide", + "display": "display", "stored": "StoredPlanner" }, "Item_Lost_Crystal": { - "type": "stored", + "type": "planner", "value": {}, - "display": "hide", + "display": "display", "stored": "StoredPlanner" }, "Item_Broken_Teeth_of_Iron_Wolf": { - "type": "stored", + "type": "planner", "value": {}, - "display": "hide", + "display": "display", "stored": "StoredPlanner" }, "Item_Endotherm_Chitin": { - "type": "stored", + "type": "planner", "value": {}, - "display": "hide", + "display": "display", "stored": "StoredPlanner" }, "Item_Horn_of_Snow": { - "type": "stored", + "type": "planner", "value": {}, - "display": "hide", + "display": "display", "stored": "StoredPlanner" }, "Item_Lightning_Crown_of_the_Past_Shadow": { - "type": "stored", + "type": "planner", "value": {}, - "display": "hide", + "display": "display", "stored": "StoredPlanner" }, "Item_Storm_Eye": { - "type": "stored", + "type": "planner", "value": {}, - "display": "hide", + "display": "display", "stored": "StoredPlanner" }, "Item_Void_Cast_Iron": { - "type": "stored", + "type": "planner", "value": {}, - "display": "hide", + "display": "display", "stored": "StoredPlanner" }, "Item_Golden_Crown_of_the_Past_Shadow": { - "type": "stored", + "type": "planner", "value": {}, - "display": "hide", + "display": "display", "stored": "StoredPlanner" }, "Item_Netherworld_Token": { - "type": "stored", + "type": "planner", "value": {}, - "display": "hide", + "display": "display", "stored": "StoredPlanner" }, "Item_Searing_Steel_Blade": { - "type": "stored", + "type": "planner", "value": {}, - "display": "hide", + "display": "display", "stored": "StoredPlanner" }, "Item_Gelid_Chitin": { - "type": "stored", + "type": "planner", "value": {}, - "display": "hide", + "display": "display", "stored": "StoredPlanner" }, "Item_Shape_Shifter_Lightning_Staff": { - "type": "stored", + "type": "planner", "value": {}, - "display": "hide", + "display": "display", "stored": "StoredPlanner" }, "Item_Ascendant_Debris": { - "type": "stored", + "type": "planner", "value": {}, - "display": "hide", + "display": "display", "stored": "StoredPlanner" }, "Item_Nail_of_the_Ape": { - "type": "stored", + "type": "planner", "value": {}, - "display": "hide", + "display": "display", "stored": "StoredPlanner" }, "Item_Suppressing_Edict": { - "type": "stored", + "type": "planner", "value": {}, - "display": "hide", + "display": "display", "stored": "StoredPlanner" }, "Item_IPC_Work_Permit": { - "type": "stored", + "type": "planner", "value": {}, - "display": "hide", + "display": "display", "stored": "StoredPlanner" }, "Item_Raging_Heart": { - "type": "stored", + "type": "planner", "value": {}, - "display": "hide", + "display": "display", "stored": "StoredPlanner" }, "Item_Dream_Fridge": { - "type": "stored", + "type": "planner", "value": {}, - "display": "hide", + "display": "display", "stored": "StoredPlanner" }, "Item_Dream_Flamer": { - "type": "stored", + "type": "planner", "value": {}, - "display": "hide", + "display": "display", "stored": "StoredPlanner" }, "Item_Worldbreaker_Blade": { - "type": "stored", + "type": "planner", "value": {}, - "display": "hide", + "display": "display", "stored": "StoredPlanner" }, "Item_Arrow_of_the_Starchaser": { - "type": "stored", + "type": "planner", "value": {}, - "display": "hide", + "display": "display", "stored": "StoredPlanner" }, "Item_Key_of_Wisdom": { - "type": "stored", + "type": "planner", "value": {}, - "display": "hide", + "display": "display", "stored": "StoredPlanner" }, "Item_Safeguard_of_Amber": { - "type": "stored", + "type": "planner", "value": {}, - "display": "hide", + "display": "display", "stored": "StoredPlanner" }, "Item_Obsidian_of_Obsession": { - "type": "stored", + "type": "planner", "value": {}, - "display": "hide", + "display": "display", "stored": "StoredPlanner" }, "Item_Stellaris_Symphony": { - "type": "stored", + "type": "planner", "value": {}, - "display": "hide", + "display": "display", "stored": "StoredPlanner" }, "Item_Flower_of_Eternity": { - "type": "stored", + "type": "planner", "value": {}, - "display": "hide", + "display": "display", "stored": "StoredPlanner" }, "Item_Moon_Madness_Fang": { - "type": "stored", + "type": "planner", "value": {}, - "display": "hide", + "display": "display", "stored": "StoredPlanner" }, "Item_Countertemporal_Shot": { - "type": "stored", + "type": "planner", "value": {}, - "display": "hide", + "display": "display", "stored": "StoredPlanner" }, "Item_Divine_Amber": { - "type": "stored", + "type": "planner", "value": {}, - "display": "hide", + "display": "display", "stored": "StoredPlanner" }, "Item_Heaven_Incinerator": { - "type": "stored", + "type": "planner", "value": {}, - "display": "hide", + "display": "display", "stored": "StoredPlanner" }, "Item_Heavenly_Melody": { - "type": "stored", + "type": "planner", "value": {}, - "display": "hide", + "display": "display", "stored": "StoredPlanner" }, "Item_Myriad_Fruit": { - "type": "stored", + "type": "planner", "value": {}, - "display": "hide", + "display": "display", "stored": "StoredPlanner" }, "Item_Tracks_of_Destiny": { - "type": "stored", + "type": "planner", "value": {}, - "display": "hide", + "display": "display", "stored": "StoredPlanner" }, "Item_Destroyer_Final_Road": { - "type": "stored", + "type": "planner", "value": {}, - "display": "hide", + "display": "display", "stored": "StoredPlanner" }, "Item_Guardian_Lament": { - "type": "stored", + "type": "planner", "value": {}, - "display": "hide", + "display": "display", "stored": "StoredPlanner" }, "Item_Regret_of_Infinite_Ochema": { - "type": "stored", + "type": "planner", "value": {}, - "display": "hide", + "display": "display", "stored": "StoredPlanner" }, "Item_Past_Evils_of_the_Borehole_Planet_Disaster": { - "type": "stored", + "type": "planner", "value": {}, - "display": "hide", + "display": "display", "stored": "StoredPlanner" }, "Item_Lost_Echo_of_the_Shared_Wish": { - "type": "stored", + "type": "planner", "value": {}, - "display": "hide", + "display": "display", "stored": "StoredPlanner" }, "Item_Squirming_Core": { - "type": "stored", + "type": "planner", "value": {}, - "display": "hide", + "display": "display", "stored": "StoredPlanner" }, "Item_Conqueror_Will": { - "type": "stored", + "type": "planner", "value": {}, - "display": "hide", + "display": "display", "stored": "StoredPlanner" }, "Item_Silvermane_Medal": { - "type": "stored", + "type": "planner", "value": {}, - "display": "hide", + "display": "display", "stored": "StoredPlanner" }, "Item_Ancient_Engine": { - "type": "stored", + "type": "planner", "value": {}, - "display": "hide", + "display": "display", "stored": "StoredPlanner" }, "Item_Immortal_Lumintwig": { - "type": "stored", + "type": "planner", "value": {}, - "display": "hide", + "display": "display", "stored": "StoredPlanner" }, "Item_Artifex_Gyreheart": { - "type": "stored", + "type": "planner", "value": {}, - "display": "hide", + "display": "display", "stored": "StoredPlanner" }, "Item_Dream_Making_Engine": { - "type": "stored", + "type": "planner", "value": {}, - "display": "hide", + "display": "display", "stored": "StoredPlanner" }, "Item_Shards_of_Desires": { - "type": "stored", + "type": "planner", "value": {}, - "display": "hide", + "display": "display", "stored": "StoredPlanner" } }, diff --git a/module/config/config_updater.py b/module/config/config_updater.py index e7cbffd44..e1bb55ca5 100644 --- a/module/config/config_updater.py +++ b/module/config/config_updater.py @@ -126,7 +126,11 @@ class ConfigGenerator: from tasks.planner.keywords.classes import ItemBase for item in ItemBase.instances.values(): base = item.group_base - deep_set(raw, keys=['Planner', f'Item_{base.name}'], value={'stored': 'StoredPlanner'}) + deep_set(raw, keys=['Planner', f'Item_{base.name}'], value={ + 'stored': 'StoredPlanner', + 'display': 'display', + 'type': 'planner', + }) # Load for path, value in deep_iter(raw, depth=2): @@ -138,7 +142,7 @@ class ConfigGenerator: if not isinstance(value, dict): value = {'value': value} arg['type'] = data_to_type(value, arg=path[1]) - if arg['type'] == 'stored': + if arg['type'] in ['stored', 'planner']: value['value'] = {} arg['display'] = 'hide' # Hide `stored` by default if isinstance(value['value'], datetime): @@ -593,7 +597,7 @@ class ConfigGenerator: import module.config.stored.classes as classes data = {} for path, value in deep_iter(self.args, depth=3): - if value.get('type') != 'stored': + if value.get('type') not in ['stored', 'planner']: continue name = path[-1] stored = value.get('stored') diff --git a/module/webui/widgets.py b/module/webui/widgets.py index 97fe7aad4..aea0d2fa9 100644 --- a/module/webui/widgets.py +++ b/module/webui/widgets.py @@ -340,7 +340,7 @@ def product_stored_row(key, value): def put_arg_stored(kwargs: T_Output_Kwargs) -> Output: name: str = kwargs["name"] - kwargs["disabled"] = True + # kwargs["disabled"] = True values = kwargs.pop("value", {}) value = values.pop("value", "") @@ -348,22 +348,26 @@ def put_arg_stored(kwargs: T_Output_Kwargs) -> Output: time_ = values.pop("time", "") if value != "" and total != "": + # 0 / 100 rows = [put_scope(f"dashboard-value-{name}", [ put_text(value).style("--dashboard-value--"), put_text(f" / {total}").style("--dashboard-time--"), ])] elif value != "": + # 100 rows = [put_scope(f"dashboard-value-{name}", [ put_text(value).style("--dashboard-value--") ])] else: + # Empty rows = [] + # Add other key-value in stored if values: rows += [ put_scope(f"dashboard-value-{name}-{key}", product_stored_row(key, value)) for key, value in values.items() if value != "" ] - + # Add time if time_: rows.append( put_text(time_).style("--dashboard-time--") @@ -380,6 +384,36 @@ def put_arg_stored(kwargs: T_Output_Kwargs) -> Output: ] ) +def put_arg_planner(kwargs: T_Output_Kwargs) -> Output | None: + name: str = kwargs["name"] + + values = kwargs.pop("value", {}) + try: + progress = values["progress"] + except KeyError: + # Hide items not needed by the planner + return None + + value = values.pop('value', 0) + if isinstance(value, dict): + value = tuple(value.values()) + total = values.pop('total', 0) + if isinstance(total, dict): + total = tuple(total.values()) + + row = put_scope(f"arg_stored-stored-value-{name}", [ + put_text(f"{progress:.2f}%").style("--dashboard-bold--"), + put_text(f"{value} / {total}").style("--dashboard-time--"), + ]) + + return put_scope( + f"arg_container-planner-{name}", + [ + get_title_help(kwargs), + row, + ] + ) + def put_arg_select(kwargs: T_Output_Kwargs) -> Output: name: str = kwargs["name"] @@ -528,6 +562,7 @@ _widget_type_to_func: Dict[str, Callable] = { "storage": put_arg_storage, "state": put_arg_state, "stored": put_arg_stored, + "planner": put_arg_planner, } From d854a90c4c376b2f0f41d809d0ffe5cec496cba2 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Sat, 18 May 2024 02:35:13 +0800 Subject: [PATCH 09/37] Chore: Move dungeon/obtain to combat/obtain Since obtain info is indeed getting before battle --- .../obtain/ITEM_AMOUNT.png | Bin .../{dungeon => combat}/obtain/ITEM_CLOSE.png | Bin .../{dungeon => combat}/obtain/ITEM_NAME.png | Bin .../{dungeon => combat}/obtain/OBTAIN_1.png | Bin .../{dungeon => combat}/obtain/OBTAIN_2.png | Bin .../{dungeon => combat}/obtain/OBTAIN_3.png | Bin .../assets/assets_combat_obtain.py} | 12 +++--- tasks/{dungeon => combat}/obtain.py | 40 ++++++++++++++++-- tasks/planner/model.py | 3 +- tasks/planner/result.py | 4 +- 10 files changed, 46 insertions(+), 13 deletions(-) rename assets/share/{dungeon => combat}/obtain/ITEM_AMOUNT.png (100%) rename assets/share/{dungeon => combat}/obtain/ITEM_CLOSE.png (100%) rename assets/share/{dungeon => combat}/obtain/ITEM_NAME.png (100%) rename assets/share/{dungeon => combat}/obtain/OBTAIN_1.png (100%) rename assets/share/{dungeon => combat}/obtain/OBTAIN_2.png (100%) rename assets/share/{dungeon => combat}/obtain/OBTAIN_3.png (100%) rename tasks/{dungeon/assets/assets_dungeon_obtain.py => combat/assets/assets_combat_obtain.py} (80%) rename tasks/{dungeon => combat}/obtain.py (83%) diff --git a/assets/share/dungeon/obtain/ITEM_AMOUNT.png b/assets/share/combat/obtain/ITEM_AMOUNT.png similarity index 100% rename from assets/share/dungeon/obtain/ITEM_AMOUNT.png rename to assets/share/combat/obtain/ITEM_AMOUNT.png diff --git a/assets/share/dungeon/obtain/ITEM_CLOSE.png b/assets/share/combat/obtain/ITEM_CLOSE.png similarity index 100% rename from assets/share/dungeon/obtain/ITEM_CLOSE.png rename to assets/share/combat/obtain/ITEM_CLOSE.png diff --git a/assets/share/dungeon/obtain/ITEM_NAME.png b/assets/share/combat/obtain/ITEM_NAME.png similarity index 100% rename from assets/share/dungeon/obtain/ITEM_NAME.png rename to assets/share/combat/obtain/ITEM_NAME.png diff --git a/assets/share/dungeon/obtain/OBTAIN_1.png b/assets/share/combat/obtain/OBTAIN_1.png similarity index 100% rename from assets/share/dungeon/obtain/OBTAIN_1.png rename to assets/share/combat/obtain/OBTAIN_1.png diff --git a/assets/share/dungeon/obtain/OBTAIN_2.png b/assets/share/combat/obtain/OBTAIN_2.png similarity index 100% rename from assets/share/dungeon/obtain/OBTAIN_2.png rename to assets/share/combat/obtain/OBTAIN_2.png diff --git a/assets/share/dungeon/obtain/OBTAIN_3.png b/assets/share/combat/obtain/OBTAIN_3.png similarity index 100% rename from assets/share/dungeon/obtain/OBTAIN_3.png rename to assets/share/combat/obtain/OBTAIN_3.png diff --git a/tasks/dungeon/assets/assets_dungeon_obtain.py b/tasks/combat/assets/assets_combat_obtain.py similarity index 80% rename from tasks/dungeon/assets/assets_dungeon_obtain.py rename to tasks/combat/assets/assets_combat_obtain.py index 7e82bf234..98ff57429 100644 --- a/tasks/dungeon/assets/assets_dungeon_obtain.py +++ b/tasks/combat/assets/assets_combat_obtain.py @@ -6,7 +6,7 @@ from module.base.button import Button, ButtonWrapper ITEM_AMOUNT = ButtonWrapper( name='ITEM_AMOUNT', share=Button( - file='./assets/share/dungeon/obtain/ITEM_AMOUNT.png', + file='./assets/share/combat/obtain/ITEM_AMOUNT.png', area=(190, 521, 490, 539), search=(170, 501, 510, 559), color=(195, 190, 188), @@ -16,7 +16,7 @@ ITEM_AMOUNT = ButtonWrapper( ITEM_CLOSE = ButtonWrapper( name='ITEM_CLOSE', share=Button( - file='./assets/share/dungeon/obtain/ITEM_CLOSE.png', + file='./assets/share/combat/obtain/ITEM_CLOSE.png', area=(1043, 185, 1073, 215), search=(1023, 165, 1093, 235), color=(170, 170, 170), @@ -26,7 +26,7 @@ ITEM_CLOSE = ButtonWrapper( ITEM_NAME = ButtonWrapper( name='ITEM_NAME', share=Button( - file='./assets/share/dungeon/obtain/ITEM_NAME.png', + file='./assets/share/combat/obtain/ITEM_NAME.png', area=(495, 187, 855, 211), search=(475, 167, 875, 231), color=(176, 177, 179), @@ -36,7 +36,7 @@ ITEM_NAME = ButtonWrapper( OBTAIN_1 = ButtonWrapper( name='OBTAIN_1', share=Button( - file='./assets/share/dungeon/obtain/OBTAIN_1.png', + file='./assets/share/combat/obtain/OBTAIN_1.png', area=(813, 414, 877, 478), search=(793, 394, 897, 498), color=(118, 96, 131), @@ -46,7 +46,7 @@ OBTAIN_1 = ButtonWrapper( OBTAIN_2 = ButtonWrapper( name='OBTAIN_2', share=Button( - file='./assets/share/dungeon/obtain/OBTAIN_2.png', + file='./assets/share/combat/obtain/OBTAIN_2.png', area=(889, 414, 953, 478), search=(869, 394, 973, 498), color=(71, 96, 145), @@ -56,7 +56,7 @@ OBTAIN_2 = ButtonWrapper( OBTAIN_3 = ButtonWrapper( name='OBTAIN_3', share=Button( - file='./assets/share/dungeon/obtain/OBTAIN_3.png', + file='./assets/share/combat/obtain/OBTAIN_3.png', area=(965, 414, 1029, 478), search=(945, 394, 1049, 498), color=(76, 101, 109), diff --git a/tasks/dungeon/obtain.py b/tasks/combat/obtain.py similarity index 83% rename from tasks/dungeon/obtain.py rename to tasks/combat/obtain.py index 1d8c8eab6..e6c1efbc1 100644 --- a/tasks/dungeon/obtain.py +++ b/tasks/combat/obtain.py @@ -5,10 +5,10 @@ from module.exception import ScriptError from module.logger import logger from module.ocr.ocr import Digit from tasks.combat.assets.assets_combat_prepare import COMBAT_PREPARE, WAVE_MINUS, WAVE_PLUS -from tasks.dungeon.assets.assets_dungeon_obtain import * +from tasks.combat.assets.assets_combat_obtain import * from tasks.dungeon.keywords import DungeonList from tasks.planner.keywords import ITEM_CLASSES -from tasks.planner.model import ObtainedAmmount, PlannerProgressMixin +from tasks.planner.model import ObtainedAmmount, PlannerMixin from tasks.planner.result import OcrItemName @@ -19,13 +19,16 @@ class OcrItemAmount(Digit): return super().format_result(result) -class DungeonObtain(PlannerProgressMixin): +class CombatObtain(PlannerMixin): """ Parse items that can be obtained from dungeon Pages: in: COMBAT_PREPARE """ + # False to click again when combat ends + # True to exit and reenter to get obtained items + obtain_frequent_check = False def _obtain_enter(self, entry, skip_first_screenshot=True): """ @@ -200,6 +203,8 @@ class DungeonObtain(PlannerProgressMixin): logger.hr('Obtained Result') for item in items: + # Pretend everything is full + # item.value += 1000 logger.info(f'Obtained item: {item.item.name}, {item.value}') """ <<< OBTAIN GET RESULT >>> @@ -211,8 +216,35 @@ class DungeonObtain(PlannerProgressMixin): self.planner_write() return items + def obtained_is_full(self, dungeon: DungeonList | None) -> bool: + if dungeon is None: + self.obtain_frequent_check = False + return False + row = self.planner.row_come_from_dungeon(dungeon) + if row is None: + self.obtain_frequent_check = False + return False + + # Update + self.obtain_get(dungeon) + + # Check progress + row = self.planner.row_come_from_dungeon(dungeon) + if row is None: + logger.error(f'obtained_is_full: Row disappeared after obtain_get') + self.obtain_frequent_check = False + return False + if not row.need_farm(): + logger.info('Planner row full') + self.obtain_frequent_check = False + return True + + # obtain_frequent_check + self.obtain_frequent_check = True + return False + if __name__ == '__main__': - self = DungeonObtain('src') + self = CombatObtain('src') self.device.screenshot() self.obtain_get() diff --git a/tasks/planner/model.py b/tasks/planner/model.py index fb01b5fd8..632e18874 100644 --- a/tasks/planner/model.py +++ b/tasks/planner/model.py @@ -346,7 +346,8 @@ class PlannerProgressParser: return None -class PlannerProgressMixin(UI): + +class PlannerMixin(UI): def planner_write_results(self, results: list[PlannerResultRow]): """ Write planner detection results info user config diff --git a/tasks/planner/result.py b/tasks/planner/result.py index ffea2ea38..91ad258c8 100644 --- a/tasks/planner/result.py +++ b/tasks/planner/result.py @@ -11,7 +11,7 @@ from tasks.daily.synthesize import SynthesizeUI from tasks.planner.assets.assets_planner_result import * from tasks.planner.keywords import ITEM_CLASSES from tasks.planner.keywords.classes import ItemCurrency -from tasks.planner.model import PlannerProgressMixin, PlannerResultRow +from tasks.planner.model import PlannerMixin, PlannerResultRow CALCULATE_TITLE.load_search(RESULT_CHECK.search) MATERIAL_TITLE.load_search(RESULT_CHECK.search) @@ -75,7 +75,7 @@ class OcrPlannerResult(OcrWhiteLetterOnComplexBackground, OcrItemName): return image -class PlannerResult(SynthesizeUI, PlannerProgressMixin): +class PlannerResult(SynthesizeUI, PlannerMixin): def is_in_planner_result(self): if self.appear(RESULT_CHECK): return True From e8dcd8ac95ca154461e13f9302d5ddd385481bf6 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Sat, 18 May 2024 02:35:44 +0800 Subject: [PATCH 10/37] Add: Do planner requested dungeon first --- tasks/combat/combat.py | 15 ++++++++++- tasks/dungeon/dungeon.py | 9 +++++++ tasks/planner/model.py | 55 +++++++++++++++++++++++++++++++--------- 3 files changed, 66 insertions(+), 13 deletions(-) diff --git a/tasks/combat/combat.py b/tasks/combat/combat.py index c460b1f77..4a6503a2e 100644 --- a/tasks/combat/combat.py +++ b/tasks/combat/combat.py @@ -5,15 +5,20 @@ from tasks.combat.assets.assets_combat_finish import COMBAT_AGAIN, COMBAT_EXIT from tasks.combat.assets.assets_combat_prepare import COMBAT_PREPARE from tasks.combat.assets.assets_combat_team import COMBAT_TEAM_PREPARE, COMBAT_TEAM_SUPPORT from tasks.combat.interact import CombatInteract +from tasks.combat.obtain import CombatObtain from tasks.combat.prepare import CombatPrepare from tasks.combat.skill import CombatSkill from tasks.combat.state import CombatState from tasks.combat.support import CombatSupport from tasks.combat.team import CombatTeam +from tasks.dungeon.keywords import DungeonList from tasks.map.control.joystick import MapControlJoystick -class Combat(CombatInteract, CombatPrepare, CombatState, CombatTeam, CombatSupport, CombatSkill, MapControlJoystick): +class Combat(CombatInteract, CombatPrepare, CombatState, CombatTeam, CombatSupport, CombatSkill, CombatObtain, + MapControlJoystick): + dungeon: DungeonList | None = None + def handle_combat_prepare(self): """ Returns: @@ -120,6 +125,9 @@ class Combat(CombatInteract, CombatPrepare, CombatState, CombatTeam, CombatSuppo self.interval_reset(COMBAT_PREPARE) self.map_A_timer.reset() if self.appear(COMBAT_PREPARE, interval=2): + if self.obtained_is_full(self.dungeon): + self.combat_exit() + self.config.task_stop() if not self.handle_combat_prepare(): return False self.device.click(COMBAT_PREPARE) @@ -191,6 +199,11 @@ class Combat(CombatInteract, CombatPrepare, CombatState, CombatTeam, CombatSuppo logger.info(f'Combat wave limit: {self.combat_wave_done}/{self.combat_wave_limit}, ' f'can not run again') return False + # Planner + logger.attr('obtain_frequent_check', self.obtain_frequent_check) + if self.obtain_frequent_check: + logger.info('Exit combat to check obtained items') + return False # Cost limit if self.combat_wave_cost == 10: diff --git a/tasks/dungeon/dungeon.py b/tasks/dungeon/dungeon.py index a2affe666..2887d1582 100644 --- a/tasks/dungeon/dungeon.py +++ b/tasks/dungeon/dungeon.py @@ -75,7 +75,9 @@ class Dungeon(DungeonStamina, DungeonEvent, Combat): if relic == 0: return 0 # Combat + self.dungeon = dungeon count = self.combat(team=team, wave_limit=wave_limit, support_character=support_character) + self.dungeon = None # Update quest states with self.config.multi_set(): @@ -225,10 +227,13 @@ class Dungeon(DungeonStamina, DungeonEvent, Combat): ran_calyx_golden = False ran_calyx_crimson = False ran_cavern_of_corrosion = False + planner = self.planner.get_dungeon(double_calyx=True) # Double calyx if self.config.stored.DungeonDouble.calyx > 0: logger.info('Run double calyx') dungeon = DungeonList.find(self.config.Dungeon_NameAtDoubleCalyx) + if planner is not None: + dungeon = planner self.running_double = True if self.dungeon_run(dungeon=dungeon, wave_limit=self.config.stored.DungeonDouble.calyx): if dungeon.is_Calyx_Golden: @@ -258,6 +263,10 @@ class Dungeon(DungeonStamina, DungeonEvent, Combat): final = KEYWORDS_DUNGEON_LIST.Simulated_Universe_World_1 else: final = DungeonList.find(self.config.Dungeon_Name) + # Planner + planner = self.planner.get_dungeon() + if planner is not None: + final = planner # Run dungeon that required by daily quests # Calyx_Golden diff --git a/tasks/planner/model.py b/tasks/planner/model.py index 632e18874..672ec832d 100644 --- a/tasks/planner/model.py +++ b/tasks/planner/model.py @@ -284,6 +284,8 @@ class PlannerProgressParser: if not row.item.is_group_base: logger.error(f'from_config: item is not group base {row}') continue + row.update_synthesize() + row.update_progress() self.rows[row.item.name] = row return self @@ -296,43 +298,61 @@ class PlannerProgressParser: data[name] = dic return data - def iter_item_to_farm(self) -> t.Iterable[ItemBase]: - rows = [row for row in self.rows.values() if row.need_farm()] + def iter_row_to_farm(self, need_farm=True) -> t.Iterable[StoredPlannerProxy]: + """ + Args: + need_farm: True if filter rows that need farm + + Yields: + + """ + if need_farm: + rows = [row for row in self.rows.values() if row.need_farm()] + else: + rows = self.rows.values() + for row in rows: if row.item.is_ItemWeekly: - yield row.item + yield row for row in rows: if row.item.is_ItemAscension: - yield row.item + yield row for row in rows: if row.item.is_ItemTrace: - yield row.item + yield row for row in rows: if row.item.is_ItemExp: - yield row.item + yield row for row in rows: if row.item.is_ItemCurrency: - yield row.item + yield row - def get_dungeon(self) -> DungeonList | None: + def get_dungeon(self, double_calyx=False) -> DungeonList | None: """ Get dungeon to farm, or None if planner finished or the remaining items cannot be farmed """ - for item in self.iter_item_to_farm(): + for row in self.iter_row_to_farm(): + item = row.item if item.is_ItemWeekly: continue dungeon = item.dungeon if dungeon is None: logger.error(f'Item {item} has nowhere to be farmed') continue - logger.info(f'Planner farm: {dungeon}') - return dungeon + if double_calyx: + if dungeon.is_Calyx: + logger.info(f'Planner farm (double_calyx): {dungeon}') + return dungeon + else: + logger.info(f'Planner farm: {dungeon}') + return dungeon logger.info('Planner farm empty') return None def get_weekly(self) -> DungeonList | None: - for item in self.iter_item_to_farm(): + for row in self.iter_row_to_farm(): + item = row.item if not item.is_ItemWeekly: continue dungeon = item.dungeon @@ -345,6 +365,17 @@ class PlannerProgressParser: logger.info('Planner farm empty') return None + def row_come_from_dungeon(self, dungeon: DungeonList | None) -> StoredPlannerProxy | None: + """ + If any items in planner is able to be farmed from given dungeon + """ + if dungeon is None: + return None + for row in self.iter_row_to_farm(need_farm=False): + if row.item.dungeon == dungeon: + logger.info(f'Planner {row} come from {dungeon}') + return row + return None class PlannerMixin(UI): From fa85ebe2b6fba729bfbeb9be7b35c74e2d1c0a1f Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Sat, 18 May 2024 02:47:33 +0800 Subject: [PATCH 11/37] Fix: Empty planner rows are not preserved in config --- tasks/planner/model.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/tasks/planner/model.py b/tasks/planner/model.py index 672ec832d..3b16307b5 100644 --- a/tasks/planner/model.py +++ b/tasks/planner/model.py @@ -383,9 +383,8 @@ class PlannerMixin(UI): """ Write planner detection results info user config """ - model = PlannerProgressParser().from_planner_results(results) - data = model.to_config() - self.config.cross_set('Dungeon.Planner', data) + planner = PlannerProgressParser().from_planner_results(results) + self.planner_write(planner) @cached_property def planner(self) -> PlannerProgressParser: @@ -396,10 +395,16 @@ class PlannerMixin(UI): logger.info(row) return model - def planner_write(self): + def planner_write(self, planner=None): """ Write planner into user config, delete planner object """ - data = self.planner.to_config() - self.config.cross_set('Dungeon.Planner', data) + if planner is None: + planner = self.planner + + data = planner.to_config() + + with self.config.multi_set(): + for key, value in data.items(): + self.config.cross_set(f'Dungeon.Planner.{key}', value) del_cached_property(self, 'planner') From b04610e1e8dc552a02cb5c18d6fcc1740fed7952 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Sat, 18 May 2024 02:58:17 +0800 Subject: [PATCH 12/37] Fix: Progress should be calculated after synthesize --- tasks/planner/model.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tasks/planner/model.py b/tasks/planner/model.py index 3b16307b5..09a53e70f 100644 --- a/tasks/planner/model.py +++ b/tasks/planner/model.py @@ -137,7 +137,7 @@ class StoredPlannerProxy(BaseModelWithFallback): if self.synthesize.blue > 0: self.value.green += self.synthesize.purple * 9 else: - self.value.blue += self.synthesize.blue * 3 + self.value.blue += self.synthesize.purple * 3 def update_progress(self): if self.item.has_group_base: @@ -153,8 +153,8 @@ class StoredPlannerProxy(BaseModelWithFallback): self.progress = round(min(max(progress, 0), 100), 2) def update(self): - self.update_progress() self.update_synthesize() + self.update_progress() self.time = now() def load_value_total(self, item: ItemBase, value=None, total=None, synthesize=None): From 82f4f255ad55f77fad05fc3bdc5765d3ecf4c5c3 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Sun, 19 May 2024 17:17:30 +0800 Subject: [PATCH 13/37] Fix: Re-enter dungeon to check obtained items instead of leaving --- tasks/combat/combat.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/tasks/combat/combat.py b/tasks/combat/combat.py index 4a6503a2e..f5a7cc7c4 100644 --- a/tasks/combat/combat.py +++ b/tasks/combat/combat.py @@ -126,8 +126,9 @@ class Combat(CombatInteract, CombatPrepare, CombatState, CombatTeam, CombatSuppo self.map_A_timer.reset() if self.appear(COMBAT_PREPARE, interval=2): if self.obtained_is_full(self.dungeon): - self.combat_exit() - self.config.task_stop() + # Update stamina so task can be delayed if both obtained_is_full and stamina exhausted + self.combat_get_trailblaze_power() + return False if not self.handle_combat_prepare(): return False self.device.click(COMBAT_PREPARE) @@ -193,18 +194,17 @@ class Combat(CombatInteract, CombatPrepare, CombatState, CombatTeam, CombatSuppo in: COMBAT_AGAIN """ current = self.combat_get_trailblaze_power(expect_reduce=self.combat_wave_cost > 0) + # Planner + logger.attr('obtain_frequent_check', self.obtain_frequent_check) + if self.obtain_frequent_check: + logger.info('Exit combat to check obtained items') + return False # Wave limit if self.combat_wave_limit: if self.combat_wave_done + self.combat_waves > self.combat_wave_limit: logger.info(f'Combat wave limit: {self.combat_wave_done}/{self.combat_wave_limit}, ' f'can not run again') return False - # Planner - logger.attr('obtain_frequent_check', self.obtain_frequent_check) - if self.obtain_frequent_check: - logger.info('Exit combat to check obtained items') - return False - # Cost limit if self.combat_wave_cost == 10: if current >= self.combat_wave_cost * self.combat_waves: @@ -229,6 +229,12 @@ class Combat(CombatInteract, CombatPrepare, CombatState, CombatTeam, CombatSuppo Returns: bool: True to re-enter combat and run with another wave settings """ + # Planner + logger.attr('obtain_frequent_check', self.obtain_frequent_check) + if self.obtain_frequent_check: + logger.info('Re-enter combat to check obtained items') + return True + # Stamina if self.config.stored.TrailblazePower.value < self.combat_wave_cost: logger.info('Current trailblaze power is not enough for next run') return False From 9795103f4d3ed3787f5861e8341b063e7f44f2cc Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Sun, 19 May 2024 19:26:20 +0800 Subject: [PATCH 14/37] Add: Planner result scanner on GUI --- config/template.json | 5 +++ module/config/argument/args.json | 8 ++++ module/config/argument/argument.yaml | 2 + module/config/argument/menu.json | 3 +- module/config/argument/task.yaml | 2 + module/config/config_generated.py | 3 ++ module/config/i18n/en-US.json | 14 ++++++ module/config/i18n/es-ES.json | 14 ++++++ module/config/i18n/ja-JP.json | 14 ++++++ module/config/i18n/zh-CN.json | 14 ++++++ module/config/i18n/zh-TW.json | 14 ++++++ module/webui/process_manager.py | 4 ++ tasks/planner/model.py | 64 ++++++++++++++++++++++++++++ tasks/planner/{result.py => scan.py} | 11 ++--- 14 files changed, 166 insertions(+), 6 deletions(-) rename tasks/planner/{result.py => scan.py} (97%) diff --git a/config/template.json b/config/template.json index 7dac4f56c..43698ad48 100644 --- a/config/template.json +++ b/config/template.json @@ -272,5 +272,10 @@ "Enable": true, "AimClicker": "do_not_click" } + }, + "PlannerScan": { + "PlannerScan": { + "ResultAdd": false + } } } \ No newline at end of file diff --git a/module/config/argument/args.json b/module/config/argument/args.json index a08473dd0..08f603e6d 100644 --- a/module/config/argument/args.json +++ b/module/config/argument/args.json @@ -1865,5 +1865,13 @@ ] } } + }, + "PlannerScan": { + "PlannerScan": { + "ResultAdd": { + "type": "checkbox", + "value": false + } + } } } \ No newline at end of file diff --git a/module/config/argument/argument.yaml b/module/config/argument/argument.yaml index bb96f750d..dbfaf0725 100644 --- a/module/config/argument/argument.yaml +++ b/module/config/argument/argument.yaml @@ -307,3 +307,5 @@ Daemon: AimClicker: value: do_not_click option: [ item_enemy, item, enemy, do_not_click ] +PlannerScan: + ResultAdd: false diff --git a/module/config/argument/menu.json b/module/config/argument/menu.json index 1d71e0235..43ff195a6 100644 --- a/module/config/argument/menu.json +++ b/module/config/argument/menu.json @@ -31,7 +31,8 @@ "menu": "list", "page": "tool", "tasks": [ - "Daemon" + "Daemon", + "PlannerScan" ] } } \ No newline at end of file diff --git a/module/config/argument/task.yaml b/module/config/argument/task.yaml index f8adc6a7b..326bbe993 100644 --- a/module/config/argument/task.yaml +++ b/module/config/argument/task.yaml @@ -71,3 +71,5 @@ Tool: tasks: Daemon: - Daemon + PlannerScan: + - PlannerScan diff --git a/module/config/config_generated.py b/module/config/config_generated.py index aa7408d3f..14a1910c3 100644 --- a/module/config/config_generated.py +++ b/module/config/config_generated.py @@ -208,3 +208,6 @@ class GeneratedConfig: # Group `Daemon` Daemon_Enable = True # True Daemon_AimClicker = 'do_not_click' # item_enemy, item, enemy, do_not_click + + # Group `PlannerScan` + PlannerScan_ResultAdd = False diff --git a/module/config/i18n/en-US.json b/module/config/i18n/en-US.json index 7b5ded006..d5c2b7e5e 100644 --- a/module/config/i18n/en-US.json +++ b/module/config/i18n/en-US.json @@ -61,6 +61,10 @@ "Daemon": { "name": "Dialogue Clicker", "help": "" + }, + "PlannerScan": { + "name": "Character Planner", + "help": "" } }, "Scheduler": { @@ -1265,6 +1269,16 @@ "do_not_click": "Don't click" } }, + "PlannerScan": { + "_info": { + "name": "Scan Character Planner Results", + "help": "Tools need to stop the scheduler and then run independently\nBefore use, set planner goal in the in-game planner, calculate the results, and start scanning from the result page. Detailed usage see: https://github.com/LmeSzinc/StarRailCopilot/wiki/planner_cn" + }, + "ResultAdd": { + "name": "Accumulate multiple scan results", + "help": "Turn on when planning multiple characters, will raise multiple characters all together\nTurn off when planning one character, planning goal will be refreshed at every scan" + } + }, "Gui": { "Aside": { "Install": "Install", diff --git a/module/config/i18n/es-ES.json b/module/config/i18n/es-ES.json index 8216819e8..37d09e63d 100644 --- a/module/config/i18n/es-ES.json +++ b/module/config/i18n/es-ES.json @@ -61,6 +61,10 @@ "Daemon": { "name": "Clic de diálogo", "help": "" + }, + "PlannerScan": { + "name": "Planificador de personajes", + "help": "" } }, "Scheduler": { @@ -1265,6 +1269,16 @@ "do_not_click": "No hacer clic" } }, + "PlannerScan": { + "_info": { + "name": "Escanear resultados del planificador de caracteres", + "help": "Las herramientas deben detener el programador y luego ejecutarse de forma independiente\nAntes de usarlo, establezca el objetivo del planificador en el planificador del juego, calcule los resultados y comience a escanear desde la página de resultados. Para uso detallado, consulte: https://github .com/LmeSzinc/StarRailCopilot/wiki/planner_cn" + }, + "ResultAdd": { + "name": "Acumular múltiples resultados de escaneo", + "help": "Activar cuando se planifican varios personajes, generará varios personajes todos juntos\nDesactivar cuando se planifica un personaje, el objetivo de planificación se actualizará en cada escaneo" + } + }, "Gui": { "Aside": { "Install": "Instalar", diff --git a/module/config/i18n/ja-JP.json b/module/config/i18n/ja-JP.json index 3448cfdaa..bc67d3ae8 100644 --- a/module/config/i18n/ja-JP.json +++ b/module/config/i18n/ja-JP.json @@ -61,6 +61,10 @@ "Daemon": { "name": "Task.Daemon.name", "help": "Task.Daemon.help" + }, + "PlannerScan": { + "name": "Task.PlannerScan.name", + "help": "Task.PlannerScan.help" } }, "Scheduler": { @@ -1265,6 +1269,16 @@ "do_not_click": "do_not_click" } }, + "PlannerScan": { + "_info": { + "name": "PlannerScan._info.name", + "help": "PlannerScan._info.help" + }, + "ResultAdd": { + "name": "PlannerScan.ResultAdd.name", + "help": "PlannerScan.ResultAdd.help" + } + }, "Gui": { "Aside": { "Install": "インストール", diff --git a/module/config/i18n/zh-CN.json b/module/config/i18n/zh-CN.json index 2e4fbaaca..d42da45f6 100644 --- a/module/config/i18n/zh-CN.json +++ b/module/config/i18n/zh-CN.json @@ -61,6 +61,10 @@ "Daemon": { "name": "剧情连点器", "help": "" + }, + "PlannerScan": { + "name": "角色养成规划", + "help": "" } }, "Scheduler": { @@ -1265,6 +1269,16 @@ "do_not_click": "不点击" } }, + "PlannerScan": { + "_info": { + "name": "识别角色养成规划计算结果", + "help": "工具需要停止调度器再单独运行\n使用前需要在游戏内养成计算器中设定养成目标,并计算出结果,在计算结果页面启动养成规划识别。详细使用教程见:\nhttps://github.com/LmeSzinc/StarRailCopilot/wiki/planner_cn" + }, + "ResultAdd": { + "name": "累加多次扫描结果", + "help": "需要养成多个角色时勾选,同时养成多个角色\n仅养成一个角色不勾选,每次扫描时刷新养成目标" + } + }, "Gui": { "Aside": { "Install": "安装", diff --git a/module/config/i18n/zh-TW.json b/module/config/i18n/zh-TW.json index 0230d865d..740d642b7 100644 --- a/module/config/i18n/zh-TW.json +++ b/module/config/i18n/zh-TW.json @@ -61,6 +61,10 @@ "Daemon": { "name": "劇情連點器", "help": "" + }, + "PlannerScan": { + "name": "角色養成規劃", + "help": "" } }, "Scheduler": { @@ -1265,6 +1269,16 @@ "do_not_click": "不點擊" } }, + "PlannerScan": { + "_info": { + "name": "辨識角色養成規劃計算結果", + "help": "工具需要停止調度器再單獨運行\n使用前需要在遊戲內養成計算器中設定養成目標,併計算出結果,在計算結果頁面啟動養成規劃識別。詳細使用教程 請參閱:\nhttps://github.com/LmeSzinc/StarRailCopilot/wiki/planner_cn" + }, + "ResultAdd": { + "name": "累加多次掃描結果", + "help": "需要養成多個角色時勾選,同時養成多個角色\n只養成一個角色不勾選,每次掃描時刷新養成目標" + } + }, "Gui": { "Aside": { "Install": "安裝", diff --git a/module/webui/process_manager.py b/module/webui/process_manager.py index f414654f8..ca94c289c 100644 --- a/module/webui/process_manager.py +++ b/module/webui/process_manager.py @@ -151,6 +151,10 @@ class ProcessManager: from tasks.base.daemon import Daemon Daemon(config=config_name, task="Daemon").run() + elif func == "PlannerScan": + from tasks.planner.scan import PlannerScan + + PlannerScan(config=config_name, task="PlannerScan").run() else: logger.critical(f"No function matched: {func}") logger.info(f"[{config_name}] exited. Reason: Finish\n") diff --git a/tasks/planner/model.py b/tasks/planner/model.py index 09a53e70f..fda50e8c7 100644 --- a/tasks/planner/model.py +++ b/tasks/planner/model.py @@ -69,6 +69,11 @@ class MultiValue(BaseModelWithFallback): blue: int = 0 purple: int = 0 + def add(self, other: "MultiValue"): + self.green += other.green + self.blue += other.blue + self.purple += other.purple + class StoredPlannerProxy(BaseModelWithFallback): item: ITEM_TYPES @@ -178,8 +183,10 @@ class StoredPlannerProxy(BaseModelWithFallback): self.value.green = value if total is not None: self.total.green = total + # Cannot synthesize green # if synthesize is not None: # self.synthesize.green = synthesize + self.synthesize.green = 0 elif item.is_rarity_blue: if value is not None: self.value.blue = value @@ -198,11 +205,39 @@ class StoredPlannerProxy(BaseModelWithFallback): raise ScriptError( f'load_value_total: Trying to load {item} in to {self} but item is in invalid rarity') else: + # Cannot synthesize if item doesn't have multiple rarity + self.synthesize = 0 if value is not None: self.value = value if total is not None: self.total = total + def add_planner_result(self, row: "StoredPlannerProxy"): + """ + Add data from another StoredPlannerProxy to self + """ + item = row.item + if self.item.has_group_base: + if item.group_base != self.item: + raise ScriptError( + f'load_value_total: Trying to load {item} into {self} but they are different items') + else: + if item != self.item: + raise ScriptError( + f'load_value_total: Trying to load {item} into {self} but they are different items') + if self.item.has_group_base: + if not self.item.is_rarity_purple: + raise ScriptError( + f'load_value_total: Trying to load {item} into {self} but self is not in rarity purple') + # Add `total` only + # `synthesize` will be updated later + # `value` remains unchanged since you still having that many items + self.total.add(row.total) + else: + self.value += row.value + self.total += row.total + self.synthesize += row.synthesize + def need_farm(self): return self.progress < 100 @@ -289,6 +324,20 @@ class PlannerProgressParser: self.rows[row.item.name] = row return self + def add_planner_result(self, planner: "PlannerProgressParser"): + """ + Add another planner result to self + """ + for name, row in planner.rows.items(): + if name in self.rows: + self_row = self.rows[name] + self_row.add_planner_result(row) + else: + self.rows[name] = row + + for row in self.rows.values(): + row.update() + def to_config(self) -> dict: data = {} for row in self.rows.values(): @@ -383,7 +432,13 @@ class PlannerMixin(UI): """ Write planner detection results info user config """ + add = self.config.PlannerScan_ResultAdd + logger.attr('ResultAdd', add) + planner = PlannerProgressParser().from_planner_results(results) + if add: + planner.add_planner_result(self.planner) + self.planner_write(planner) @cached_property @@ -405,6 +460,15 @@ class PlannerMixin(UI): data = planner.to_config() with self.config.multi_set(): + # Set value for key, value in data.items(): self.config.cross_set(f'Dungeon.Planner.{key}', value) + # Remove other value + remove = [] + for key, value in self.config.cross_get('Dungeon.Planner', default={}).items(): + if value != {} and key not in data: + remove.append(key) + for key in remove: + self.config.cross_set(f'Dungeon.Planner.{key}', {}) + del_cached_property(self, 'planner') diff --git a/tasks/planner/result.py b/tasks/planner/scan.py similarity index 97% rename from tasks/planner/result.py rename to tasks/planner/scan.py index 91ad258c8..a57d54eb8 100644 --- a/tasks/planner/result.py +++ b/tasks/planner/scan.py @@ -18,9 +18,6 @@ MATERIAL_TITLE.load_search(RESULT_CHECK.search) DETAIL_TITLE.load_search(RESULT_CHECK.search) - - - class OcrItemName(Ocr): def after_process(self, result): result = result.replace('念火之心', '忿火之心') @@ -75,7 +72,7 @@ class OcrPlannerResult(OcrWhiteLetterOnComplexBackground, OcrItemName): return image -class PlannerResult(SynthesizeUI, PlannerMixin): +class PlannerScan(SynthesizeUI, PlannerMixin): def is_in_planner_result(self): if self.appear(RESULT_CHECK): return True @@ -216,8 +213,12 @@ class PlannerResult(SynthesizeUI, PlannerMixin): self.planner_write_results(out) return out + def run(self): + self.device.screenshot() + self.parse_planner_result() + if __name__ == '__main__': - self = PlannerResult('src') + self = PlannerScan('src', task='PlannerScan') self.device.screenshot() self.parse_planner_result() From 17c4a13d4fee21e3fb83d7962343c8d8cc7704a0 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Sun, 19 May 2024 20:41:19 +0800 Subject: [PATCH 15/37] Fix: Patch TextDetector to have a better detection on single digit --- module/ocr/ocr.py | 48 ++++++++++++++++++++++++++++++++++++++----- tasks/planner/scan.py | 11 +++++----- 2 files changed, 49 insertions(+), 10 deletions(-) diff --git a/module/ocr/ocr.py b/module/ocr/ocr.py index 3c5b86e64..8740d0ff8 100644 --- a/module/ocr/ocr.py +++ b/module/ocr/ocr.py @@ -1,15 +1,13 @@ -import re import time from datetime import timedelta -import cv2 import numpy as np from pponnxcr.predict_system import BoxedResult import module.config.server as server from module.base.button import ButtonWrapper from module.base.decorator import cached_property -from module.base.utils import area_pad, corner2area, crop, extract_white_letters, float2str +from module.base.utils import * from module.exception import ScriptError from module.logger import logger from module.ocr.models import OCR_MODEL, TextSystem @@ -423,6 +421,12 @@ class Duration(Ocr): class OcrWhiteLetterOnComplexBackground(Ocr): white_preprocess = True + # 0.6 by default, 0.2 for lower + box_thresh = 0.2 + # (x, y) Enlarge detected boxes to `min_boxes` + # So standalone digits can be better detected + # Note that min_box should be 4px larger than the actual letter + min_box = None def pre_process(self, image): if self.white_preprocess: @@ -430,12 +434,46 @@ class OcrWhiteLetterOnComplexBackground(Ocr): image = cv2.merge([image, image, image]) return image + @staticmethod + def enlarge_box(box, min_box): + area = corner2area(box) + center = (int(x) for x in area_center(area)) + size_x, size_y = area_size(area) + min_x, min_y = min_box + if size_x < min_x or size_y < min_y: + size_x = max(size_x, min_x) // 2 + size_y = max(size_y, min_y) // 2 + area = area_offset((-size_x, -size_y, size_x, size_y), center) + box = area2corner(area) + box = np.array([box[0], box[1], box[3], box[2]]).astype(np.float32) + return box + else: + return box + + def enlarge_boxes(self, boxes): + if self.min_box is None: + return boxes + + boxes = [self.enlarge_box(box, self.min_box) for box in boxes] + boxes = np.array(boxes) + return boxes + def detect_and_ocr(self, *args, **kwargs): # Try hard to lower TextSystem.box_thresh backup = self.model.text_detector.box_thresh self.model.text_detector.box_thresh = 0.2 + # Patch TextDetector + text_detector = self.model.text_detector - result = super().detect_and_ocr(*args, **kwargs) + def text_detector_with_min_box(*args, **kwargs): + dt_boxes, elapse = text_detector(*args, **kwargs) + dt_boxes = self.enlarge_boxes(dt_boxes) + return dt_boxes, elapse - self.model.text_detector.box_thresh = backup + self.model.text_detector = text_detector_with_min_box + try: + result = super().detect_and_ocr(*args, **kwargs) + finally: + self.model.text_detector.box_thresh = backup + self.model.text_detector = text_detector return result diff --git a/tasks/planner/scan.py b/tasks/planner/scan.py index a57d54eb8..80d064340 100644 --- a/tasks/planner/scan.py +++ b/tasks/planner/scan.py @@ -1,6 +1,5 @@ import re -import cv2 from pponnxcr.predict_system import BoxedResult from module.base.utils import area_center, area_in_area @@ -28,6 +27,8 @@ class OcrItemName(Ocr): class OcrPlannerResult(OcrWhiteLetterOnComplexBackground, OcrItemName): + min_box = (16, 20) + def __init__(self): # Planner currently CN only super().__init__(OCR_RESULT, lang='cn') @@ -65,10 +66,10 @@ class OcrPlannerResult(OcrWhiteLetterOnComplexBackground, OcrItemName): return super().detect_and_ocr(image, *args, **kwargs) def pre_process(self, image): - r, g, b = cv2.split(image) - cv2.max(r, g, dst=r) - cv2.max(r, b, dst=r) - image = cv2.merge([r, r, r]) + # gray = rgb2gray(image) + # from PIL import Image + # Image.fromarray(gray).show() + # image = cv2.merge([gray, gray, gray]) return image From 2e05ff729c688937d0ffe9558272d040a1cf7de4 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Mon, 20 May 2024 00:21:56 +0800 Subject: [PATCH 16/37] Fix: Import tasks.planner.scan --- tasks/combat/obtain.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tasks/combat/obtain.py b/tasks/combat/obtain.py index e6c1efbc1..f7ca5eaa7 100644 --- a/tasks/combat/obtain.py +++ b/tasks/combat/obtain.py @@ -4,12 +4,12 @@ from module.base.timer import Timer from module.exception import ScriptError from module.logger import logger from module.ocr.ocr import Digit -from tasks.combat.assets.assets_combat_prepare import COMBAT_PREPARE, WAVE_MINUS, WAVE_PLUS from tasks.combat.assets.assets_combat_obtain import * +from tasks.combat.assets.assets_combat_prepare import COMBAT_PREPARE, WAVE_MINUS, WAVE_PLUS from tasks.dungeon.keywords import DungeonList from tasks.planner.keywords import ITEM_CLASSES from tasks.planner.model import ObtainedAmmount, PlannerMixin -from tasks.planner.result import OcrItemName +from tasks.planner.scan import OcrItemName class OcrItemAmount(Digit): From 384c4302a2303357256d8bac9ff197a3ba1aa8bf Mon Sep 17 00:00:00 2001 From: Yinhr <110515845+Yinhaoran1128@users.noreply.github.com> Date: Mon, 20 May 2024 01:49:50 +0800 Subject: [PATCH 17/37] Fix: Obtain position & closed (#464) * Fix: Obtain position & closed * Add: find may_obtain to locate obtain_1 * Fix: stupid bug --- assets/cn/combat/obtain/MAY_OBTAIN.SEARCH.png | Bin 0 -> 11353 bytes assets/cn/combat/obtain/MAY_OBTAIN.png | Bin 0 -> 7880 bytes assets/en/combat/obtain/MAY_OBTAIN.SEARCH.png | Bin 0 -> 11628 bytes assets/en/combat/obtain/MAY_OBTAIN.png | Bin 0 -> 7853 bytes tasks/combat/assets/assets_combat_obtain.py | 17 ++++++++++++++ tasks/combat/obtain.py | 22 ++++++++++++++---- 6 files changed, 34 insertions(+), 5 deletions(-) create mode 100644 assets/cn/combat/obtain/MAY_OBTAIN.SEARCH.png create mode 100644 assets/cn/combat/obtain/MAY_OBTAIN.png create mode 100644 assets/en/combat/obtain/MAY_OBTAIN.SEARCH.png create mode 100644 assets/en/combat/obtain/MAY_OBTAIN.png diff --git a/assets/cn/combat/obtain/MAY_OBTAIN.SEARCH.png b/assets/cn/combat/obtain/MAY_OBTAIN.SEARCH.png new file mode 100644 index 0000000000000000000000000000000000000000..dee97cbe346f292aad3c08aa9d9df18c41730cd4 GIT binary patch literal 11353 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 UhHLp`@wNR`-{@qn?#18!9}Pq8EdT%j literal 0 HcmV?d00001 diff --git a/assets/cn/combat/obtain/MAY_OBTAIN.png b/assets/cn/combat/obtain/MAY_OBTAIN.png new file mode 100644 index 0000000000000000000000000000000000000000..92e9953eacc91bf5433960c2b7968a436dc797c1 GIT binary patch literal 7880 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 literal 0 HcmV?d00001 diff --git a/assets/en/combat/obtain/MAY_OBTAIN.SEARCH.png b/assets/en/combat/obtain/MAY_OBTAIN.SEARCH.png new file mode 100644 index 0000000000000000000000000000000000000000..d06b424e6c9fccdf87a85335607124dd968287cc GIT binary patch literal 11628 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 literal 0 HcmV?d00001 diff --git a/assets/en/combat/obtain/MAY_OBTAIN.png b/assets/en/combat/obtain/MAY_OBTAIN.png new file mode 100644 index 0000000000000000000000000000000000000000..4e366be79c2fd5ddea80a57dc19cb544fa94709f GIT binary patch literal 7853 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 literal 0 HcmV?d00001 diff --git a/tasks/combat/assets/assets_combat_obtain.py b/tasks/combat/assets/assets_combat_obtain.py index 98ff57429..3f0a01df7 100644 --- a/tasks/combat/assets/assets_combat_obtain.py +++ b/tasks/combat/assets/assets_combat_obtain.py @@ -33,6 +33,23 @@ ITEM_NAME = ButtonWrapper( button=(495, 187, 855, 211), ), ) +MAY_OBTAIN = ButtonWrapper( + name='MAY_OBTAIN', + cn=Button( + file='./assets/cn/combat/obtain/MAY_OBTAIN.png', + area=(813, 379, 893, 397), + search=(812, 373, 895, 468), + color=(63, 71, 87), + button=(813, 379, 893, 397), + ), + en=Button( + file='./assets/en/combat/obtain/MAY_OBTAIN.png', + area=(813, 379, 922, 397), + search=(813, 373, 923, 468), + color=(53, 61, 78), + button=(813, 379, 922, 397), + ), +) OBTAIN_1 = ButtonWrapper( name='OBTAIN_1', share=Button( diff --git a/tasks/combat/obtain.py b/tasks/combat/obtain.py index f7ca5eaa7..3ee5d5505 100644 --- a/tasks/combat/obtain.py +++ b/tasks/combat/obtain.py @@ -5,7 +5,7 @@ from module.exception import ScriptError from module.logger import logger from module.ocr.ocr import Digit from tasks.combat.assets.assets_combat_obtain import * -from tasks.combat.assets.assets_combat_prepare import COMBAT_PREPARE, WAVE_MINUS, WAVE_PLUS +from tasks.combat.assets.assets_combat_prepare import COMBAT_PREPARE from tasks.dungeon.keywords import DungeonList from tasks.planner.keywords import ITEM_CLASSES from tasks.planner.model import ObtainedAmmount, PlannerMixin @@ -87,10 +87,8 @@ class CombatObtain(PlannerMixin): else: self.device.screenshot() - if not self.appear(ITEM_CLOSE) and self.appear(COMBAT_PREPARE): - if self.image_color_count(WAVE_MINUS, color=(246, 246, 246), threshold=221, count=100) \ - or self.image_color_count(WAVE_PLUS, color=(246, 246, 246), threshold=221, count=100): - break + if not self.appear(ITEM_CLOSE) and self.appear(COMBAT_PREPARE) and self.appear(MAY_OBTAIN): + break if self.appear_then_click(ITEM_CLOSE, interval=2): continue @@ -187,6 +185,9 @@ class CombatObtain(PlannerMixin): index = 1 prev = None items = [] + + self._find_may_obtain() + for _ in range(5): entry = self._obtain_get_entry(dungeon, index=index, prev=prev) if entry is None: @@ -243,6 +244,17 @@ class CombatObtain(PlannerMixin): self.obtain_frequent_check = True return False + def _find_may_obtain(self, skip_first_screenshot=True): + logger.info('Find may obtain') + while 1: + if skip_first_screenshot: + skip_first_screenshot = False + else: + self.device.screenshot() + if MAY_OBTAIN.match_template(self.device.image): + OBTAIN_1.load_offset(MAY_OBTAIN) + return True + if __name__ == '__main__': self = CombatObtain('src') From 8fa0c47a967ab25346fd8abd9301107f7834c02e Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Mon, 20 May 2024 00:41:34 +0800 Subject: [PATCH 18/37] Fix: No popup names in SRC (cherry picked from commit 6e5e529a0eb3ae3fdab3e9629b8057edce0f762d) --- tasks/dungeon/ui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks/dungeon/ui.py b/tasks/dungeon/ui.py index 32725c09c..01d0659cb 100644 --- a/tasks/dungeon/ui.py +++ b/tasks/dungeon/ui.py @@ -592,7 +592,7 @@ class DungeonUI(DungeonState): # Additional # Popup that confirm character switch - if self.handle_popup_confirm('DUNGEON_ENTER'): + if self.handle_popup_confirm(): continue # Click teleport From 616f072a2b46a18475af8f1f701198821d8f6d46 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Mon, 20 May 2024 04:28:38 +0800 Subject: [PATCH 19/37] Opt: Ignore obtain_frequent_check if combat will be reentered later --- tasks/combat/combat.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tasks/combat/combat.py b/tasks/combat/combat.py index f5a7cc7c4..bffed1a96 100644 --- a/tasks/combat/combat.py +++ b/tasks/combat/combat.py @@ -232,8 +232,15 @@ class Combat(CombatInteract, CombatPrepare, CombatState, CombatTeam, CombatSuppo # Planner logger.attr('obtain_frequent_check', self.obtain_frequent_check) if self.obtain_frequent_check: - logger.info('Re-enter combat to check obtained items') - return True + if self.config.stored.TrailblazePower.value >= self.combat_wave_cost \ + and (self.combat_wave_limit and self.combat_wave_done < self.combat_wave_limit): + logger.info(f'Stall having some trailblaze power ' + f'but wave limit reached {self.combat_wave_done}/{self.combat_wave_limit}, ' + f'ignore obtain_frequent_check cause will reenter later') + return False + else: + logger.info('Re-enter combat to check obtained items') + return True # Stamina if self.config.stored.TrailblazePower.value < self.combat_wave_cost: logger.info('Current trailblaze power is not enough for next run') From cf31bd93af84a4e0331f5155643b753241091ec0 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Tue, 21 May 2024 04:44:42 +0800 Subject: [PATCH 20/37] Add: is_approaching_total(), no use yet --- tasks/combat/obtain.py | 4 +++- tasks/planner/model.py | 33 ++++++++++++++++++++++++++++++++- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/tasks/combat/obtain.py b/tasks/combat/obtain.py index 3ee5d5505..1db3021f5 100644 --- a/tasks/combat/obtain.py +++ b/tasks/combat/obtain.py @@ -241,7 +241,9 @@ class CombatObtain(PlannerMixin): return True # obtain_frequent_check - self.obtain_frequent_check = True + # approaching = row.is_approaching_total() + # logger.attr('is_approaching_total', approaching) + # self.obtain_frequent_check = approaching return False def _find_may_obtain(self, skip_first_screenshot=True): diff --git a/tasks/planner/model.py b/tasks/planner/model.py index fda50e8c7..e089cb194 100644 --- a/tasks/planner/model.py +++ b/tasks/planner/model.py @@ -74,6 +74,9 @@ class MultiValue(BaseModelWithFallback): self.blue += other.blue self.purple += other.purple + def equivalent_green(self): + return self.green + self.blue * 3 + self.purple * 9 + class StoredPlannerProxy(BaseModelWithFallback): item: ITEM_TYPES @@ -144,9 +147,37 @@ class StoredPlannerProxy(BaseModelWithFallback): else: self.value.blue += self.synthesize.purple * 3 + def is_approaching_total(self): + """ + Returns: + bool: True if the future value may >= total after next combat + """ + if self.item.dungeon.is_Calyx_Golden_Treasures: + return self.value + 24000 >= self.total + if self.item.dungeon.is_Calyx_Golden_Memories: + # purple, blue, green = 5, 1, 0 + value = self.value.equivalent_green() + total = self.total.equivalent_green() + return value + 48 >= total + if self.item.dungeon.Calyx_Golden_Aether: + # purple, blue, green = 1, 2, 2.5 + value = self.value.equivalent_green() + total = self.total.equivalent_green() + return value + 17.5 >= total + if self.item.is_ItemAscension: + return self.value + 3 >= self.total + if self.item.is_ItemTrace: + # purple, blue, green = 0.155, 1, 1.25 + value = self.value.equivalent_green() + total = self.total.equivalent_green() + return value + 33.87 >= total + if self.item.is_ItemWeekly: + return self.value + 3 >= self.total + return False + def update_progress(self): if self.item.has_group_base: - total = self.total.green + self.total.blue * 3 + self.total.purple * 9 + total = self.total.equivalent_green() green = min(self.value.green, self.total.green) blue = min(self.value.blue + self.synthesize.blue, self.total.blue) purple = min(self.value.purple + self.synthesize.purple, self.total.purple) From 3f291925fa888b9db7303825f2ed0fcda124679e Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Tue, 21 May 2024 12:35:45 +0800 Subject: [PATCH 21/37] Fix: OBTAIN_* should be moved together --- tasks/combat/obtain.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tasks/combat/obtain.py b/tasks/combat/obtain.py index 1db3021f5..c1f61b3d3 100644 --- a/tasks/combat/obtain.py +++ b/tasks/combat/obtain.py @@ -253,8 +253,11 @@ class CombatObtain(PlannerMixin): skip_first_screenshot = False else: self.device.screenshot() + if MAY_OBTAIN.match_template(self.device.image): OBTAIN_1.load_offset(MAY_OBTAIN) + OBTAIN_2.load_offset(MAY_OBTAIN) + OBTAIN_3.load_offset(MAY_OBTAIN) return True From 4ef59d05fafa53134c9a2575f8986168e3a25f93 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Tue, 21 May 2024 12:36:46 +0800 Subject: [PATCH 22/37] Dev: Sync dungeon_id of the basic item to all calyx items --- dev_tools/keywords/item.py | 15 +++++++++++++ tasks/planner/keywords/item_calyx.py | 32 ++++++++++++++-------------- 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/dev_tools/keywords/item.py b/dev_tools/keywords/item.py index 1b55f9ee4..9a0662622 100644 --- a/dev_tools/keywords/item.py +++ b/dev_tools/keywords/item.py @@ -112,6 +112,21 @@ class GenerateItemCalyx(GenerateItemBase): output_file = './tasks/planner/keywords/item_calyx.py' purpose_type = [7] + def iter_keywords(self) -> t.Iterable[dict]: + items = list(super().iter_keywords()) + + # Copy dungeon_id from green item to all items in group + dic_group_to_dungeonid = {} + for item in items: + dungeon = item['dungeon_id'] + if dungeon > 0: + dic_group_to_dungeonid[item['item_group']] = dungeon + for item in items: + dungeon = dic_group_to_dungeonid[item['item_group']] + item['dungeon_id'] = dungeon + + yield from items + def generate_items(): GenerateItemCurrency()() diff --git a/tasks/planner/keywords/item_calyx.py b/tasks/planner/keywords/item_calyx.py index d46e7be26..be2208de8 100644 --- a/tasks/planner/keywords/item_calyx.py +++ b/tasks/planner/keywords/item_calyx.py @@ -27,7 +27,7 @@ Glimmering_Core = ItemCalyx( rarity='Rare', item_id=111002, item_group=1401, - dungeon_id=-1, + dungeon_id=1011, ) Squirming_Core = ItemCalyx( id=3, @@ -40,7 +40,7 @@ Squirming_Core = ItemCalyx( rarity='VeryRare', item_id=111003, item_group=1401, - dungeon_id=-1, + dungeon_id=1011, ) Thief_Instinct = ItemCalyx( id=4, @@ -66,7 +66,7 @@ Usurper_Scheme = ItemCalyx( rarity='Rare', item_id=111012, item_group=1402, - dungeon_id=-1, + dungeon_id=1001, ) Conqueror_Will = ItemCalyx( id=6, @@ -79,7 +79,7 @@ Conqueror_Will = ItemCalyx( rarity='VeryRare', item_id=111013, item_group=1402, - dungeon_id=-1, + dungeon_id=1001, ) Silvermane_Badge = ItemCalyx( id=7, @@ -105,7 +105,7 @@ Silvermane_Insignia = ItemCalyx( rarity='Rare', item_id=112002, item_group=1403, - dungeon_id=-1, + dungeon_id=1001, ) Silvermane_Medal = ItemCalyx( id=9, @@ -118,7 +118,7 @@ Silvermane_Medal = ItemCalyx( rarity='VeryRare', item_id=112003, item_group=1403, - dungeon_id=-1, + dungeon_id=1001, ) Ancient_Part = ItemCalyx( id=10, @@ -144,7 +144,7 @@ Ancient_Spindle = ItemCalyx( rarity='Rare', item_id=112012, item_group=1404, - dungeon_id=-1, + dungeon_id=1001, ) Ancient_Engine = ItemCalyx( id=12, @@ -157,7 +157,7 @@ Ancient_Engine = ItemCalyx( rarity='VeryRare', item_id=112013, item_group=1404, - dungeon_id=-1, + dungeon_id=1001, ) Immortal_Scionette = ItemCalyx( id=13, @@ -183,7 +183,7 @@ Immortal_Aeroblossom = ItemCalyx( rarity='Rare', item_id=113002, item_group=1405, - dungeon_id=-1, + dungeon_id=1011, ) Immortal_Lumintwig = ItemCalyx( id=15, @@ -196,7 +196,7 @@ Immortal_Lumintwig = ItemCalyx( rarity='VeryRare', item_id=113003, item_group=1405, - dungeon_id=-1, + dungeon_id=1011, ) Artifex_Module = ItemCalyx( id=16, @@ -222,7 +222,7 @@ Artifex_Cogwheel = ItemCalyx( rarity='Rare', item_id=113012, item_group=1406, - dungeon_id=-1, + dungeon_id=1011, ) Artifex_Gyreheart = ItemCalyx( id=18, @@ -235,7 +235,7 @@ Artifex_Gyreheart = ItemCalyx( rarity='VeryRare', item_id=113013, item_group=1406, - dungeon_id=-1, + dungeon_id=1011, ) Dream_Collection_Component = ItemCalyx( id=19, @@ -261,7 +261,7 @@ Dream_Flow_Valve = ItemCalyx( rarity='Rare', item_id=114002, item_group=1407, - dungeon_id=-1, + dungeon_id=1014, ) Dream_Making_Engine = ItemCalyx( id=21, @@ -274,7 +274,7 @@ Dream_Making_Engine = ItemCalyx( rarity='VeryRare', item_id=114003, item_group=1407, - dungeon_id=-1, + dungeon_id=1014, ) Tatters_of_Thought = ItemCalyx( id=22, @@ -300,7 +300,7 @@ Fragments_of_Impression = ItemCalyx( rarity='Rare', item_id=114012, item_group=1408, - dungeon_id=-1, + dungeon_id=1014, ) Shards_of_Desires = ItemCalyx( id=24, @@ -313,5 +313,5 @@ Shards_of_Desires = ItemCalyx( rarity='VeryRare', item_id=114013, item_group=1408, - dungeon_id=-1, + dungeon_id=1014, ) From 783658be0baa4608b852904f4ef74ab87a22c794 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Sun, 26 May 2024 23:05:18 +0800 Subject: [PATCH 23/37] Fix: Do double rogue first --- tasks/dungeon/dungeon.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tasks/dungeon/dungeon.py b/tasks/dungeon/dungeon.py index 2887d1582..01fea599d 100644 --- a/tasks/dungeon/dungeon.py +++ b/tasks/dungeon/dungeon.py @@ -263,10 +263,10 @@ class Dungeon(DungeonStamina, DungeonEvent, Combat): final = KEYWORDS_DUNGEON_LIST.Simulated_Universe_World_1 else: final = DungeonList.find(self.config.Dungeon_Name) - # Planner - planner = self.planner.get_dungeon() - if planner is not None: - final = planner + # Planner + planner = self.planner.get_dungeon() + if planner is not None: + final = planner # Run dungeon that required by daily quests # Calyx_Golden From d6158869934fc0d4c2fe61dda1ab8dc089765194 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Mon, 27 May 2024 02:54:51 +0800 Subject: [PATCH 24/37] Dev: [ALAS] Extract ButtonGrid assets --- dev_tools/button_extract.py | 108 ++++++++++++++++++++++++++++++++---- module/base/button.py | 3 +- 2 files changed, 98 insertions(+), 13 deletions(-) diff --git a/dev_tools/button_extract.py b/dev_tools/button_extract.py index 67a623acc..38e0bfa82 100644 --- a/dev_tools/button_extract.py +++ b/dev_tools/button_extract.py @@ -3,11 +3,13 @@ import re import typing as t from dataclasses import dataclass +import cv2 import numpy as np from tqdm import tqdm from module.base.code_generator import CodeGenerator -from module.base.utils import SelectedGrids, area_limit, area_pad, get_bbox, get_color, image_size, load_image +from module.base.utils import ( + SelectedGrids, area_center, area_limit, area_pad, corner2area, get_bbox, get_color, image_size, load_image) from module.config.config_manual import ManualConfig as AzurLaneConfig from module.config.server import VALID_LANG from module.config.utils import deep_get, deep_iter, deep_set, iter_folder @@ -17,6 +19,44 @@ SHARE_SERVER = 'share' ASSET_SERVER = [SHARE_SERVER] + VALID_LANG +def parse_grid(image): + """ + Args: + image: + + Returns: + dict: Key: Grid position (x, y) + Value: Area on image + """ + image = cv2.inRange(image, (127, 127, 127), (255, 255, 255)) + contours, _ = cv2.findContours(image, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) + dic_rect = {} + for corners in contours: + area = corner2area(corners.reshape(4, 2)) + (0, 0, 1, 1) + center = area_center(area) + dic_rect[center] = area + # for k, v in dic_rect.items(): + # print(k, v) + + dic_grid = {} + prev_y = -100 + grid_y = -1 + stack_center = [] + for center in sorted(dic_rect.keys(), key=lambda x: x[1]): + if center[1] > prev_y + 3: + for x, c in enumerate(sorted(stack_center, key=lambda x: x[0])): + dic_grid[(x, grid_y)] = tuple(dic_rect[c].astype(int)) + grid_y += 1 + stack_center = [] + stack_center.append(center) + prev_y = center[1] + for x, c in enumerate(sorted(stack_center, key=lambda x: x[0])): + dic_grid[(x, grid_y)] = tuple(dic_rect[c].astype(int)) + # for k, v in dic_grid.items(): + # print(k, v) + return dic_grid + + class AssetsImage: REGEX_ASSETS = re.compile( f'^{AzurLaneConfig.ASSETS_FOLDER}/' @@ -24,7 +64,7 @@ class AssetsImage: f'(?P[a-zA-Z0-9_/]+?)/' f'(?P\w+)' f'(?P\.\d+)?' - f'(?P\.AREA|\.SEARCH|\.COLOR|\.BUTTON)?' + f'(?P\.AREA|\.SEARCH|\.COLOR|\.BUTTON|\.GRID)?' f'\.png$' ) @@ -46,6 +86,7 @@ class AssetsImage: self.assets = '' self.frame = 1 self.attr = '' + self.posi = None if res: self.valid = True @@ -66,6 +107,7 @@ class AssetsImage: self.bbox: t.Tuple = () self.mean: t.Tuple = () + self.grids = {} def parse(self): image = load_image(self.file) @@ -79,6 +121,10 @@ class AssetsImage: mean = tuple(np.rint(mean).astype(int)) self.bbox = bbox self.mean = mean + + if self.attr == 'GRID': + self.grids = parse_grid(image) + return bbox, mean def __str__(self): @@ -87,6 +133,26 @@ class AssetsImage: else: return f'AssetsImage(file={self.file}, valid={self.valid})' + @property + def is_GRID(self): + return self.attr == 'GRID' + + @property + def is_base(self): + return self.attr == '' + + def iter_grids(self): + frame = 0 + for posi, rect in self.grids.items(): + frame += 1 + image = AssetsImage(self.file) + image.attr = '' + image.bbox = rect + image.mean = self.mean + image.frame = frame + image.posi = posi + yield image + def iter_images(): for server in ASSET_SERVER: @@ -97,6 +163,12 @@ def iter_images(): yield AssetsImage(file) +def iter_grids(images): + for image in images: + for grid in image.iter_grids(): + yield grid + + @dataclass class DataAssets: module: str @@ -104,6 +176,7 @@ class DataAssets: server: str frame: int file: str = '' + posi = None area: t.Tuple[int, int, int, int] = () search: t.Tuple[int, int, int, int] = () color: t.Tuple[int, int, int] = () @@ -126,7 +199,6 @@ class DataAssets: Product DataAssets from AssetsImage with attr="" """ data = cls(module=image.module, assets=image.assets, server=image.server, frame=image.frame, file=image.file) - data.load_image(image) return data def load_image(self, image: AssetsImage): @@ -135,6 +207,7 @@ class DataAssets: self.area = image.bbox self.color = image.mean self.button = image.bbox + self.posi = image.posi elif image.attr == 'AREA': self.area = image.bbox self.has_raw_area = True @@ -147,6 +220,8 @@ class DataAssets: elif image.attr == 'BUTTON': self.button = image.bbox self.has_raw_button = True + elif image.attr == 'GRID': + pass else: logger.warning(f'Trying to load an image with unknown attribute: {image}') @@ -161,17 +236,22 @@ def iter_assets(): for image in tqdm(images): image.parse() + images += list(iter_grids(images)) + # Validate images images = SelectedGrids(images).select(valid=True) images.create_index('module', 'assets', 'server', 'frame', 'attr') for image in images.filter(lambda x: bool(x.attr)): image: AssetsImage = image - if not images.indexed_select(image.module, image.assets, image.server, image.frame, ''): - logger.warning(f'Attribute assets has no parent assets: {image.file}') - image.valid = False - if not images.indexed_select(image.module, image.assets, image.server, 1, ''): - logger.warning(f'Attribute assets has no first frame: {image.file}') - image.valid = False + if image.is_GRID: + pass + else: + if not images.indexed_select(image.module, image.assets, image.server, image.frame, ''): + logger.warning(f'Attribute assets has no parent assets: {image.file}') + image.valid = False + if not images.indexed_select(image.module, image.assets, image.server, 1, ''): + logger.warning(f'Attribute assets has no first frame: {image.file}') + image.valid = False if image.attr == 'SEARCH' and image.frame > 1: logger.warning(f'Attribute SEARCH with frame > 1 is not allowed: {image.file}') image.valid = False @@ -180,18 +260,18 @@ def iter_assets(): # Convert to DataAssets data = {} for image in images: - if image.attr == '': + if image.is_base: row = DataAssets.product(image) row.load_image(image) deep_set(data, keys=[image.module, image.assets, image.server, image.frame], value=row) # Load attribute images for image in images: - if image.attr != '': + if not image.is_base: row = deep_get(data, keys=[image.module, image.assets, image.server, image.frame]) row.load_image(image) # Set `search` for path, frames in deep_iter(data, depth=3): - print(path, frames) + # print(path, frames) for frame in frames.values(): # Generate `search` from `area` if not frame.has_raw_search: @@ -253,6 +333,8 @@ def generate_code(): gen.ObjectAttr(key='search', value=frame.search) gen.ObjectAttr(key='color', value=frame.color) gen.ObjectAttr(key='button', value=frame.button) + if frame.posi is not None: + gen.ObjectAttr(key='posi', value=frame.posi) elif len(frames) == 1: frame = frames[0] with gen.ObjectAttr(key=server, value=gen.Object(object_class='Button')): @@ -261,6 +343,8 @@ def generate_code(): gen.ObjectAttr(key='search', value=frame.search) gen.ObjectAttr(key='color', value=frame.color) gen.ObjectAttr(key='button', value=frame.button) + if frame.posi is not None: + gen.ObjectAttr(key='posi', value=frame.posi) else: gen.ObjectAttr(key=server, value=None) gen.write(os.path.join(output, f'assets_{module.replace("/", "_")}.py')) diff --git a/module/base/button.py b/module/base/button.py index 153df61df..4c206838d 100644 --- a/module/base/button.py +++ b/module/base/button.py @@ -6,7 +6,7 @@ from module.exception import ScriptError class Button(Resource): - def __init__(self, file, area, search, color, button): + def __init__(self, file, area, search, color, button, posi=None): """ Args: file: Filepath to an assets @@ -20,6 +20,7 @@ class Button(Resource): self.search: t.Tuple[int, int, int, int] = search self.color: t.Tuple[int, int, int] = color self._button: t.Tuple[int, int, int, int] = button + self.posi: t.Optional[t.Tuple[int, int]] = posi self.resource_add(self.file) self._button_offset: t.Tuple[int, int] = (0, 0) From 8cd2b9034698fae7a8eb90e390313bc75558dc02 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Mon, 27 May 2024 03:03:11 +0800 Subject: [PATCH 25/37] Add: Do weekly for planner --- tasks/dungeon/weekly.py | 4 ++++ tasks/planner/model.py | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/tasks/dungeon/weekly.py b/tasks/dungeon/weekly.py index facf25d01..039078ae9 100644 --- a/tasks/dungeon/weekly.py +++ b/tasks/dungeon/weekly.py @@ -51,6 +51,10 @@ class WeeklyDungeon(Dungeon): self.weekly_quests = self.config.stored.BattlePassWeeklyQuest.load_quests() dungeon = DungeonList.find(self.config.Weekly_Name) + planner = self.planner.get_weekly() + if planner is not None: + dungeon = planner + logger.info('DungeonWeekly', dungeon) # UI switches self.dungeon_tab_goto(KEYWORDS_DUNGEON_TAB.Survival_Index) diff --git a/tasks/planner/model.py b/tasks/planner/model.py index e089cb194..750c5eb7f 100644 --- a/tasks/planner/model.py +++ b/tasks/planner/model.py @@ -439,10 +439,10 @@ class PlannerProgressParser: if dungeon is None: logger.error(f'Item {item} has nowhere to be farmed') continue - logger.info(f'Planner farm: {dungeon}') + logger.info(f'Planner weekly farm: {dungeon}') return dungeon - logger.info('Planner farm empty') + logger.info('Planner weekly farm empty') return None def row_come_from_dungeon(self, dungeon: DungeonList | None) -> StoredPlannerProxy | None: From 0da3a9f3288a94d44eb87edf98e55871744992bf Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Mon, 27 May 2024 03:15:42 +0800 Subject: [PATCH 26/37] Del: Delete DungeonDaily group since dailies require dungeons less often --- config/template.json | 14 ++--- module/config/argument/args.json | 79 ---------------------------- module/config/argument/argument.yaml | 14 ----- module/config/argument/task.yaml | 1 - module/config/config_generated.py | 6 --- module/config/config_updater.py | 41 +++------------ module/config/i18n/en-US.json | 75 -------------------------- module/config/i18n/es-ES.json | 75 -------------------------- module/config/i18n/ja-JP.json | 75 -------------------------- module/config/i18n/zh-CN.json | 75 -------------------------- module/config/i18n/zh-TW.json | 75 -------------------------- tasks/dungeon/dungeon.py | 45 +--------------- tasks/dungeon/weekly.py | 2 +- 13 files changed, 15 insertions(+), 562 deletions(-) diff --git a/config/template.json b/config/template.json index 43698ad48..6397786b0 100644 --- a/config/template.json +++ b/config/template.json @@ -103,12 +103,6 @@ "NameAtDoubleRelic": "Cavern_of_Corrosion_Path_of_Providence", "Team": 1 }, - "DungeonDaily": { - "CalyxGolden": "Calyx_Golden_Treasures_Jarilo_VI", - "CalyxCrimson": "Calyx_Crimson_Destruction_Herta_StorageZone", - "StagnantShadow": "Stagnant_Shadow_Quanta", - "CavernOfCorrosion": "Cavern_of_Corrosion_Path_of_Providence" - }, "DungeonSupport": { "Use": "when_daily", "Character": "FirstCharacter" @@ -130,9 +124,9 @@ }, "AchievableQuest": { "Complete_1_Daily_Mission": "not_supported", - "Clear_Calyx_Golden_1_times": "achievable", - "Clear_Stagnant_Shadow_1_times": "achievable", - "Clear_Cavern_of_Corrosion_1_times": "achievable", + "Clear_Calyx_Golden_1_times": "not_set", + "Clear_Stagnant_Shadow_1_times": "not_set", + "Clear_Cavern_of_Corrosion_1_times": "not_set", "In_a_single_battle_inflict_3_Weakness_Break_of_different_Types": "achievable", "Inflict_Weakness_Break_5_times": "achievable", "Defeat_a_total_of_20_enemies": "achievable", @@ -144,7 +138,7 @@ "Log_in_to_the_game": "achievable", "Dispatch_1_assignments": "achievable", "Complete_Simulated_Universe_1_times": "not_set", - "Clear_Calyx_Crimson_1_times": "achievable", + "Clear_Calyx_Crimson_1_times": "not_set", "Enter_combat_by_attacking_enemie_Weakness_and_win_3_times": "achievable", "Use_Technique_2_times": "achievable", "Destroy_3_destructible_objects": "achievable", diff --git a/module/config/argument/args.json b/module/config/argument/args.json index 08f603e6d..b74308ba1 100644 --- a/module/config/argument/args.json +++ b/module/config/argument/args.json @@ -630,85 +630,6 @@ ] } }, - "DungeonDaily": { - "CalyxGolden": { - "type": "select", - "value": "Calyx_Golden_Treasures_Jarilo_VI", - "option": [ - "do_not_achieve", - "Calyx_Golden_Memories_Jarilo_VI", - "Calyx_Golden_Memories_The_Xianzhou_Luofu", - "Calyx_Golden_Memories_Penacony", - "Calyx_Golden_Aether_Jarilo_VI", - "Calyx_Golden_Aether_The_Xianzhou_Luofu", - "Calyx_Golden_Aether_Penacony", - "Calyx_Golden_Treasures_Jarilo_VI", - "Calyx_Golden_Treasures_The_Xianzhou_Luofu", - "Calyx_Golden_Treasures_Penacony" - ] - }, - "CalyxCrimson": { - "type": "select", - "value": "Calyx_Crimson_Destruction_Herta_StorageZone", - "option": [ - "do_not_achieve", - "Calyx_Crimson_Destruction_Herta_StorageZone", - "Calyx_Crimson_Destruction_Luofu_ScalegorgeWaterscape", - "Calyx_Crimson_Preservation_Herta_SupplyZone", - "Calyx_Crimson_Preservation_Penacony_ClockStudiosThemePark", - "Calyx_Crimson_The_Hunt_Jarilo_OutlyingSnowPlains", - "Calyx_Crimson_The_Hunt_Penacony_SoulGladScorchsandAuditionVenue", - "Calyx_Crimson_Abundance_Jarilo_BackwaterPass", - "Calyx_Crimson_Abundance_Luofu_FyxestrollGarden", - "Calyx_Crimson_Erudition_Jarilo_RivetTown", - "Calyx_Crimson_Harmony_Jarilo_RobotSettlement", - "Calyx_Crimson_Harmony_Penacony_TheReverieDreamscape", - "Calyx_Crimson_Nihility_Jarilo_GreatMine", - "Calyx_Crimson_Nihility_Luofu_AlchemyCommission" - ] - }, - "StagnantShadow": { - "type": "select", - "value": "Stagnant_Shadow_Quanta", - "option": [ - "do_not_achieve", - "Stagnant_Shadow_Spike", - "Stagnant_Shadow_Perdition", - "Stagnant_Shadow_Duty", - "Stagnant_Shadow_Blaze", - "Stagnant_Shadow_Scorch", - "Stagnant_Shadow_Ire", - "Stagnant_Shadow_Rime", - "Stagnant_Shadow_Icicle", - "Stagnant_Shadow_Nectar", - "Stagnant_Shadow_Fulmination", - "Stagnant_Shadow_Doom", - "Stagnant_Shadow_Gust", - "Stagnant_Shadow_Celestial", - "Stagnant_Shadow_Quanta", - "Stagnant_Shadow_Abomination", - "Stagnant_Shadow_Roast", - "Stagnant_Shadow_Mirage", - "Stagnant_Shadow_Puppetry" - ] - }, - "CavernOfCorrosion": { - "type": "select", - "value": "Cavern_of_Corrosion_Path_of_Providence", - "option": [ - "do_not_achieve", - "Cavern_of_Corrosion_Path_of_Gelid_Wind", - "Cavern_of_Corrosion_Path_of_Jabbing_Punch", - "Cavern_of_Corrosion_Path_of_Drifting", - "Cavern_of_Corrosion_Path_of_Providence", - "Cavern_of_Corrosion_Path_of_Holy_Hymn", - "Cavern_of_Corrosion_Path_of_Conflagration", - "Cavern_of_Corrosion_Path_of_Elixir_Seekers", - "Cavern_of_Corrosion_Path_of_Darkness", - "Cavern_of_Corrosion_Path_of_Dreamdive" - ] - } - }, "DungeonSupport": { "Use": { "type": "select", diff --git a/module/config/argument/argument.yaml b/module/config/argument/argument.yaml index dbfaf0725..4de2f6541 100644 --- a/module/config/argument/argument.yaml +++ b/module/config/argument/argument.yaml @@ -110,20 +110,6 @@ Dungeon: Team: value: 1 option: [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ] -DungeonDaily: - # Dungeon names will be injected in config updater - CalyxGolden: - value: Calyx_Golden_Treasures_Jarilo_VI - option: [ do_not_achieve, ] - CalyxCrimson: - value: Calyx_Crimson_Destruction_Herta_StorageZone - option: [ do_not_achieve, ] - StagnantShadow: - value: Stagnant_Shadow_Quanta - option: [ do_not_achieve, ] - CavernOfCorrosion: - value: Cavern_of_Corrosion_Path_of_Providence - option: [ do_not_achieve, ] DungeonSupport: Use: value: when_daily diff --git a/module/config/argument/task.yaml b/module/config/argument/task.yaml index 326bbe993..80cfb4dda 100644 --- a/module/config/argument/task.yaml +++ b/module/config/argument/task.yaml @@ -27,7 +27,6 @@ Daily: - Scheduler - Planner - Dungeon - - DungeonDaily - DungeonSupport - DungeonStorage DailyQuest: diff --git a/module/config/config_generated.py b/module/config/config_generated.py index 14a1910c3..1de5b0cea 100644 --- a/module/config/config_generated.py +++ b/module/config/config_generated.py @@ -51,12 +51,6 @@ class GeneratedConfig: Dungeon_NameAtDoubleRelic = 'Cavern_of_Corrosion_Path_of_Providence' # Cavern_of_Corrosion_Path_of_Gelid_Wind, Cavern_of_Corrosion_Path_of_Jabbing_Punch, Cavern_of_Corrosion_Path_of_Drifting, Cavern_of_Corrosion_Path_of_Providence, Cavern_of_Corrosion_Path_of_Holy_Hymn, Cavern_of_Corrosion_Path_of_Conflagration, Cavern_of_Corrosion_Path_of_Elixir_Seekers, Cavern_of_Corrosion_Path_of_Darkness, Cavern_of_Corrosion_Path_of_Dreamdive Dungeon_Team = 1 # 1, 2, 3, 4, 5, 6, 7, 8, 9 - # Group `DungeonDaily` - DungeonDaily_CalyxGolden = 'Calyx_Golden_Treasures_Jarilo_VI' # do_not_achieve, Calyx_Golden_Memories_Jarilo_VI, Calyx_Golden_Memories_The_Xianzhou_Luofu, Calyx_Golden_Memories_Penacony, Calyx_Golden_Aether_Jarilo_VI, Calyx_Golden_Aether_The_Xianzhou_Luofu, Calyx_Golden_Aether_Penacony, Calyx_Golden_Treasures_Jarilo_VI, Calyx_Golden_Treasures_The_Xianzhou_Luofu, Calyx_Golden_Treasures_Penacony - DungeonDaily_CalyxCrimson = 'Calyx_Crimson_Destruction_Herta_StorageZone' # do_not_achieve, Calyx_Crimson_Destruction_Herta_StorageZone, Calyx_Crimson_Destruction_Luofu_ScalegorgeWaterscape, Calyx_Crimson_Preservation_Herta_SupplyZone, Calyx_Crimson_Preservation_Penacony_ClockStudiosThemePark, Calyx_Crimson_The_Hunt_Jarilo_OutlyingSnowPlains, Calyx_Crimson_The_Hunt_Penacony_SoulGladScorchsandAuditionVenue, Calyx_Crimson_Abundance_Jarilo_BackwaterPass, Calyx_Crimson_Abundance_Luofu_FyxestrollGarden, Calyx_Crimson_Erudition_Jarilo_RivetTown, Calyx_Crimson_Harmony_Jarilo_RobotSettlement, Calyx_Crimson_Harmony_Penacony_TheReverieDreamscape, Calyx_Crimson_Nihility_Jarilo_GreatMine, Calyx_Crimson_Nihility_Luofu_AlchemyCommission - DungeonDaily_StagnantShadow = 'Stagnant_Shadow_Quanta' # do_not_achieve, Stagnant_Shadow_Spike, Stagnant_Shadow_Perdition, Stagnant_Shadow_Duty, Stagnant_Shadow_Blaze, Stagnant_Shadow_Scorch, Stagnant_Shadow_Ire, Stagnant_Shadow_Rime, Stagnant_Shadow_Icicle, Stagnant_Shadow_Nectar, Stagnant_Shadow_Fulmination, Stagnant_Shadow_Doom, Stagnant_Shadow_Gust, Stagnant_Shadow_Celestial, Stagnant_Shadow_Quanta, Stagnant_Shadow_Abomination, Stagnant_Shadow_Roast, Stagnant_Shadow_Mirage, Stagnant_Shadow_Puppetry - DungeonDaily_CavernOfCorrosion = 'Cavern_of_Corrosion_Path_of_Providence' # do_not_achieve, Cavern_of_Corrosion_Path_of_Gelid_Wind, Cavern_of_Corrosion_Path_of_Jabbing_Punch, Cavern_of_Corrosion_Path_of_Drifting, Cavern_of_Corrosion_Path_of_Providence, Cavern_of_Corrosion_Path_of_Holy_Hymn, Cavern_of_Corrosion_Path_of_Conflagration, Cavern_of_Corrosion_Path_of_Elixir_Seekers, Cavern_of_Corrosion_Path_of_Darkness, Cavern_of_Corrosion_Path_of_Dreamdive - # Group `DungeonSupport` DungeonSupport_Use = 'when_daily' # always_use, when_daily, do_not_use DungeonSupport_Character = 'FirstCharacter' # FirstCharacter, Acheron, Argenti, Arlan, Asta, Aventurine, Bailu, BlackSwan, Blade, Bronya, Clara, DanHeng, DanHengImbibitorLunae, DrRatio, FuXuan, Gallagher, Gepard, Guinaifen, Hanya, Herta, Himeko, Hook, Huohuo, JingYuan, Jingliu, Kafka, Luka, Luocha, Lynx, March7th, Misha, Natasha, Pela, Qingque, Robin, RuanMei, Sampo, Seele, Serval, SilverWolf, Sparkle, Sushang, Tingyun, TopazNumby, TrailblazerDestruction, TrailblazerPreservation, Welt, Xueyi, Yanqing, Yukong diff --git a/module/config/config_updater.py b/module/config/config_updater.py index e1bb55ca5..f611890d8 100644 --- a/module/config/config_updater.py +++ b/module/config/config_updater.py @@ -92,11 +92,6 @@ class ConfigGenerator: # Double events option_add(keys='Dungeon.NameAtDoubleCalyx.option', options=calyx_golden + calyx_crimson) option_add(keys='Dungeon.NameAtDoubleRelic.option', options=cavern_of_corrosion) - # Dungeon daily - option_add(keys='DungeonDaily.CalyxGolden.option', options=calyx_golden) - option_add(keys='DungeonDaily.CalyxCrimson.option', options=calyx_crimson) - option_add(keys='DungeonDaily.StagnantShadow.option', options=stagnant_shadow) - option_add(keys='DungeonDaily.CavernOfCorrosion.option', options=cavern_of_corrosion) option_add( keys='Weekly.Name.option', options=[dungeon.name for dungeon in DungeonList.instances.values() if dungeon.is_Echo_of_War]) @@ -460,10 +455,6 @@ class ConfigGenerator: update_dungeon_names('Dungeon.NameAtDoubleCalyx') update_dungeon_names('Dungeon.NameAtDoubleRelic') - update_dungeon_names('DungeonDaily.CalyxGolden') - update_dungeon_names('DungeonDaily.CalyxCrimson') - update_dungeon_names('DungeonDaily.StagnantShadow') - update_dungeon_names('DungeonDaily.CavernOfCorrosion') # Character names from tasks.character.keywords import CharacterList @@ -691,10 +682,10 @@ class ConfigGenerator: class ConfigUpdater: # source, target, (optional)convert_func redirection = [ - ('Dungeon.Dungeon.Name', 'Dungeon.Dungeon.Name', convert_20_dungeon), - ('Dungeon.Dungeon.NameAtDoubleCalyx', 'Dungeon.Dungeon.NameAtDoubleCalyx', convert_20_dungeon), - ('Dungeon.DungeonDaily.CalyxGolden', 'Dungeon.DungeonDaily.CalyxGolden', convert_20_dungeon), - ('Dungeon.DungeonDaily.CalyxCrimson', 'Dungeon.DungeonDaily.CalyxCrimson', convert_20_dungeon), + # ('Dungeon.Dungeon.Name', 'Dungeon.Dungeon.Name', convert_20_dungeon), + # ('Dungeon.Dungeon.NameAtDoubleCalyx', 'Dungeon.Dungeon.NameAtDoubleCalyx', convert_20_dungeon), + # ('Dungeon.DungeonDaily.CalyxGolden', 'Dungeon.DungeonDaily.CalyxGolden', convert_20_dungeon), + # ('Dungeon.DungeonDaily.CalyxCrimson', 'Dungeon.DungeonDaily.CalyxCrimson', convert_20_dungeon), ('Rogue.RogueWorld.SimulatedUniverseElite', 'Rogue.RogueWorld.SimulatedUniverseFarm', convert_rogue_farm), ] @@ -794,14 +785,10 @@ class ConfigUpdater: set_daily('Complete_1_Daily_Mission', 'not_supported') # Dungeon dungeon = deep_get(data, keys='Dungeon.Scheduler.Enable') - set_daily('Clear_Calyx_Golden_1_times', - dungeon and deep_get(data, 'Dungeon.DungeonDaily.CalyxGolden') != 'do_not_achieve') - set_daily('Clear_Calyx_Crimson_1_times', - dungeon and deep_get(data, 'Dungeon.DungeonDaily.CalyxCrimson') != 'do_not_achieve') - set_daily('Clear_Stagnant_Shadow_1_times', - dungeon and deep_get(data, 'Dungeon.DungeonDaily.StagnantShadow') != 'do_not_achieve') - set_daily('Clear_Cavern_of_Corrosion_1_times', - dungeon and deep_get(data, 'Dungeon.DungeonDaily.CavernOfCorrosion') != 'do_not_achieve') + set_daily('Clear_Calyx_Golden_1_times', 'not_set') + set_daily('Clear_Calyx_Crimson_1_times', 'not_set') + set_daily('Clear_Stagnant_Shadow_1_times', 'not_set') + set_daily('Clear_Cavern_of_Corrosion_1_times', 'not_set') # Combat requirements set_daily('In_a_single_battle_inflict_3_Weakness_Break_of_different_Types', 'achievable') set_daily('Inflict_Weakness_Break_5_times', 'achievable') @@ -864,22 +851,10 @@ class ConfigUpdater: if key.endswith('Name'): if dungeon.is_Calyx_Golden: yield 'Dungeon.Dungeon.NameAtDoubleCalyx', value - yield 'Dungeon.DungeonDaily.CalyxGolden', value elif dungeon.is_Calyx_Crimson: yield 'Dungeon.Dungeon.NameAtDoubleCalyx', value - yield 'Dungeon.DungeonDaily.CalyxCrimson', value - elif dungeon.is_Stagnant_Shadow: - yield 'Dungeon.DungeonDaily.StagnantShadow', value elif dungeon.is_Cavern_of_Corrosion: yield 'Dungeon.Dungeon.NameAtDoubleRelic', value - yield 'Dungeon.DungeonDaily.CavernOfCorrosion', value - elif key.endswith('NameAtDoubleCalyx'): - if dungeon.is_Calyx_Golden: - yield 'Dungeon.DungeonDaily.CalyxGolden', value - elif dungeon.is_Calyx_Crimson: - yield 'Dungeon.DungeonDaily.CalyxCrimson', value - elif key.endswith('NameAtDoubleRelic'): - yield 'Dungeon.DungeonDaily.CavernOfCorrosion', value elif key.endswith('CavernOfCorrosion'): yield 'Dungeon.Dungeon.NameAtDoubleRelic', value elif key == 'Rogue.RogueWorld.UseImmersifier' and value is False: diff --git a/module/config/i18n/en-US.json b/module/config/i18n/en-US.json index 83f10cec4..4da4bdc81 100644 --- a/module/config/i18n/en-US.json +++ b/module/config/i18n/en-US.json @@ -355,81 +355,6 @@ "9": "9" } }, - "DungeonDaily": { - "_info": { - "name": "Daily Quest Settings", - "help": "Clear required dungeon once to achieve daily quests" - }, - "CalyxGolden": { - "name": "Clear Calyx Golden 1 times", - "help": "", - "do_not_achieve": "Don't Do This Quest", - "Calyx_Golden_Memories_Jarilo_VI": "Material: Character EXP (Bud of Memories (Jarilo-Ⅵ))", - "Calyx_Golden_Memories_The_Xianzhou_Luofu": "Material: Character EXP (Bud of Memories (The Xianzhou Luofu))", - "Calyx_Golden_Memories_Penacony": "Material: Character EXP (Bud of Memories (Penacony))", - "Calyx_Golden_Aether_Jarilo_VI": "Material: Light Cone EXP (Bud of Aether (Jarilo-Ⅵ))", - "Calyx_Golden_Aether_The_Xianzhou_Luofu": "Material: Light Cone EXP (Bud of Aether (The Xianzhou Luofu))", - "Calyx_Golden_Aether_Penacony": "Material: Light Cone EXP (Bud of Aether (Penacony))", - "Calyx_Golden_Treasures_Jarilo_VI": "Material: Credit (Bud of Treasures (Jarilo-Ⅵ))", - "Calyx_Golden_Treasures_The_Xianzhou_Luofu": "Material: Credit (Bud of Treasures (The Xianzhou Luofu))", - "Calyx_Golden_Treasures_Penacony": "Material: Credit (Bud of Treasures (Penacony))" - }, - "CalyxCrimson": { - "name": "Clear Calyx Crimson 1 times", - "help": "", - "do_not_achieve": "Don't Do This Quest", - "Calyx_Crimson_Destruction_Herta_StorageZone": "Trace: Destruction (Storage Zone)", - "Calyx_Crimson_Destruction_Luofu_ScalegorgeWaterscape": "Trace: Destruction (Scalegorge Waterscape)", - "Calyx_Crimson_Preservation_Herta_SupplyZone": "Trace: Preservation (Supply Zone)", - "Calyx_Crimson_Preservation_Penacony_ClockStudiosThemePark": "Trace: Preservation (Clock Studios Theme Park)", - "Calyx_Crimson_The_Hunt_Jarilo_OutlyingSnowPlains": "Trace: The Hunt (Outlying Snow Plains)", - "Calyx_Crimson_The_Hunt_Penacony_SoulGladScorchsandAuditionVenue": "Trace: The Hunt (SoulGlad Scorchsand Audition Venue)", - "Calyx_Crimson_Abundance_Jarilo_BackwaterPass": "Trace: Abundance (Backwater Pass)", - "Calyx_Crimson_Abundance_Luofu_FyxestrollGarden": "Trace: Abundance (Fyxestroll Garden)", - "Calyx_Crimson_Erudition_Jarilo_RivetTown": "Trace: Erudition (Rivet Town)", - "Calyx_Crimson_Harmony_Jarilo_RobotSettlement": "Trace: The Harmony (Robot Settlement)", - "Calyx_Crimson_Harmony_Penacony_TheReverieDreamscape": "Trace: The Harmony (The Reverie (Dreamscape))", - "Calyx_Crimson_Nihility_Jarilo_GreatMine": "Trace: Nihility (Great Mine)", - "Calyx_Crimson_Nihility_Luofu_AlchemyCommission": "Trace: Nihility (Alchemy Commission)" - }, - "StagnantShadow": { - "name": "Clear Stagnant Shadow 1 times", - "help": "", - "do_not_achieve": "Don't Do This Quest", - "Stagnant_Shadow_Spike": "Ascension: Physical (Natasha / Clara / Luka / Sushang)", - "Stagnant_Shadow_Perdition": "Ascension: Physical (Hanya / Argenti)", - "Stagnant_Shadow_Duty": "Ascension: Physical (Boothill / Robin)", - "Stagnant_Shadow_Blaze": "Ascension: Fire (Himeko / Asta / Hook)", - "Stagnant_Shadow_Scorch": "Ascension: Fire (Guinaifen / Topaz & Numby)", - "Stagnant_Shadow_Ire": "Ascension: Fire (Gallagher)", - "Stagnant_Shadow_Rime": "Ascension: Ice (March 7th / Herta / Gepard / Pela)", - "Stagnant_Shadow_Icicle": "Ascension: Ice (Yanqing / Jingliu / Ruan Mei)", - "Stagnant_Shadow_Nectar": "Ascension: Ice (Misha)", - "Stagnant_Shadow_Fulmination": "Ascension: Lightning (Arlan / Serval / Tingyun / Bailu)", - "Stagnant_Shadow_Doom": "Ascension: Lightning (Kafka / Jing Yuan / Acheron)", - "Stagnant_Shadow_Gust": "Ascension: Wind (Dan Heng / Bronya / Sampo)", - "Stagnant_Shadow_Celestial": "Ascension: Wind (Blade / Huohuo / Black Swan)", - "Stagnant_Shadow_Quanta": "Ascension: Quantum (Silver Wolf / Seele / Qingque)", - "Stagnant_Shadow_Abomination": "Ascension: Quantum (Lynx / Fu Xuan / Xueyi)", - "Stagnant_Shadow_Roast": "Ascension: Quantum (Sparkle)", - "Stagnant_Shadow_Mirage": "Ascension: Imaginary (Welt / Luocha / Yukong)", - "Stagnant_Shadow_Puppetry": "Ascension: Imaginary (Dan Heng • Imbibitor Lunae / Aventurine / Dr. Ratio)" - }, - "CavernOfCorrosion": { - "name": "Clear Cavern of Corrosion 1 times", - "help": "", - "do_not_achieve": "Don't Do This Quest", - "Cavern_of_Corrosion_Path_of_Gelid_Wind": "Relics: Ice Set & Wind Set (Path of Gelid Wind)", - "Cavern_of_Corrosion_Path_of_Jabbing_Punch": "Relics: Physical Set & Break Effect Set (Path of Jabbing Punch)", - "Cavern_of_Corrosion_Path_of_Drifting": "Relics: Healing Set & Musketeer Set (Path of Drifting)", - "Cavern_of_Corrosion_Path_of_Providence": "Relics: Guard Set & Quantum Set (Path of Providence)", - "Cavern_of_Corrosion_Path_of_Holy_Hymn": "Relics: DEF Set & Lighting Set (Path of Holy Hymn)", - "Cavern_of_Corrosion_Path_of_Conflagration": "Relics: Fire Set & Imaginary Set (Path of Conflagration)", - "Cavern_of_Corrosion_Path_of_Elixir_Seekers": "Relics: HP Set & SPD Set (Path of Elixir Seekers)", - "Cavern_of_Corrosion_Path_of_Darkness": "Relics: Pursuit Set & Dot Set (Path of Darkness)", - "Cavern_of_Corrosion_Path_of_Dreamdive": "Relics: Debuff Set & Break Effect Set (Path of Dreamdive)" - } - }, "DungeonSupport": { "_info": { "name": "Support Settings", diff --git a/module/config/i18n/es-ES.json b/module/config/i18n/es-ES.json index 37d09e63d..679495977 100644 --- a/module/config/i18n/es-ES.json +++ b/module/config/i18n/es-ES.json @@ -355,81 +355,6 @@ "9": "9" } }, - "DungeonDaily": { - "_info": { - "name": "Ajustes de Entrenamiento diario", - "help": "Se limpiará la mazmorra requerida una vez para completar la misión diaria." - }, - "CalyxGolden": { - "name": "Completar Cáliz (oro) 1 vez", - "help": "", - "do_not_achieve": "No hacer esta misión", - "Calyx_Golden_Memories_Jarilo_VI": "Material: EXP de personaje (Flor de los recuerdos (Jarilo-Ⅵ))", - "Calyx_Golden_Memories_The_Xianzhou_Luofu": "Material: EXP de personaje (Flor de los recuerdos (El Luofu de Xianzhou))", - "Calyx_Golden_Memories_Penacony": "Material: EXP de personaje (Flor de los recuerdos (Colonipenal))", - "Calyx_Golden_Aether_Jarilo_VI": "Material: EXP de conos de luz (Flor de éter (Jarilo-Ⅵ))", - "Calyx_Golden_Aether_The_Xianzhou_Luofu": "Material: EXP de conos de luz (Flor de éter (El Luofu de Xianzhou))", - "Calyx_Golden_Aether_Penacony": "Material: EXP de conos de luz (Flor de éter (Colonipenal))", - "Calyx_Golden_Treasures_Jarilo_VI": "Material: Créditos (Flor de tesoros (Jarilo-Ⅵ))", - "Calyx_Golden_Treasures_The_Xianzhou_Luofu": "Material: Créditos (Flor de tesoros (El Luofu de Xianzhou))", - "Calyx_Golden_Treasures_Penacony": "Material: Créditos (Flor de tesoros (Colonipenal))" - }, - "CalyxCrimson": { - "name": "Completar Cáliz (carmesí) 1 vez", - "help": "", - "do_not_achieve": "No hacer esta misión", - "Calyx_Crimson_Destruction_Herta_StorageZone": "Rastros: Destrucción (Zona de almacenamiento)", - "Calyx_Crimson_Destruction_Luofu_ScalegorgeWaterscape": "Rastros: Destrucción (Desfiladero de Escamas)", - "Calyx_Crimson_Preservation_Herta_SupplyZone": "Rastros: Conservación (Zona de suministros)", - "Calyx_Crimson_Preservation_Penacony_ClockStudiosThemePark": "Rastros: Conservación (Parque temático de los Estudios Reloj)", - "Calyx_Crimson_The_Hunt_Jarilo_OutlyingSnowPlains": "Rastros: Cacería (Llanuras nevadas de las afueras)", - "Calyx_Crimson_The_Hunt_Penacony_SoulGladScorchsandAuditionVenue": "Rastros: Cacería (Recinto de las Audiciones FelizAlma en la Arena Ardiente)", - "Calyx_Crimson_Abundance_Jarilo_BackwaterPass": "Rastros: Abundancia (Paso del Remanso)", - "Calyx_Crimson_Abundance_Luofu_FyxestrollGarden": "Rastros: Abundancia (Jardín del Sosiego)", - "Calyx_Crimson_Erudition_Jarilo_RivetTown": "Rastros: Erudición (Villarremache)", - "Calyx_Crimson_Harmony_Jarilo_RobotSettlement": "Rastros: Armonía (Asentamiento robot)", - "Calyx_Crimson_Harmony_Penacony_TheReverieDreamscape": "Rastros: Armonía (Hotel Fantasía (paisaje onírico))", - "Calyx_Crimson_Nihility_Jarilo_GreatMine": "Rastros: Nihilidad (Mina principal)", - "Calyx_Crimson_Nihility_Luofu_AlchemyCommission": "Rastros: Nihilidad (Comisión de Alquimia)" - }, - "StagnantShadow": { - "name": "Completar Sombra paralizada 1 vez", - "help": "", - "do_not_achieve": "No hacer esta misión", - "Stagnant_Shadow_Spike": "Ascension: Físico (Natasha / Clara / Luka / Sushang)", - "Stagnant_Shadow_Perdition": "Ascension: Físico (Hanya / Argenti)", - "Stagnant_Shadow_Duty": "Ascension: Físico (Boothill / Robin)", - "Stagnant_Shadow_Blaze": "Ascension: Fuego (Himeko / Asta / Hook)", - "Stagnant_Shadow_Scorch": "Ascension: Fuego (Guinaifen / Topaz y Conti)", - "Stagnant_Shadow_Ire": "Ascension: Fuego (Gallagher)", - "Stagnant_Shadow_Rime": "Ascension: Hielo (Siete de Marzo / Herta / Gepard / Pela)", - "Stagnant_Shadow_Icicle": "Ascension: Hielo (Yanqing / Jingliu / Ruan Mei)", - "Stagnant_Shadow_Nectar": "Ascension: Hielo (Misha)", - "Stagnant_Shadow_Fulmination": "Ascension: Rayo (Arlan / Serval / Tingyun / Bailu)", - "Stagnant_Shadow_Doom": "Ascension: Rayo (Kafka / Jing Yuan / Acheron)", - "Stagnant_Shadow_Gust": "Ascension: Viento (Dan Heng / Bronya / Sampo)", - "Stagnant_Shadow_Celestial": "Ascension: Viento (Blade / Huohuo / Cisne Negro)", - "Stagnant_Shadow_Quanta": "Ascension: Cuántico (Silver Wolf / Seele / Qingque)", - "Stagnant_Shadow_Abomination": "Ascension: Cuántico (Lynx / Fu Xuan / Xueyi)", - "Stagnant_Shadow_Roast": "Ascension: Cuántico (Sparkle)", - "Stagnant_Shadow_Mirage": "Ascension: Imaginario (Welt / Luocha / Yukong)", - "Stagnant_Shadow_Puppetry": "Ascension: Imaginario (Dan Heng - Imbibitor Lunae / Aventurino / Dr. Ratio)" - }, - "CavernOfCorrosion": { - "name": "Completar Caverna de la corrosión 1 vez", - "help": "", - "do_not_achieve": "No hacer esta misión", - "Cavern_of_Corrosion_Path_of_Gelid_Wind": "Artefactos: Hielo y Viento (Senda del viento gélido)", - "Cavern_of_Corrosion_Path_of_Jabbing_Punch": "Artefactos: Físico y Efecto de Ruptura (Senda de los puños rápidos)", - "Cavern_of_Corrosion_Path_of_Drifting": "Artefactos: Curación y Pistolera de la espiga silvestre (Senda de la deriva)", - "Cavern_of_Corrosion_Path_of_Providence": "Artefactos: Guardia de la nieve y Cuántico (Senda de al providencia) (Senda de la providencia)", - "Cavern_of_Corrosion_Path_of_Holy_Hymn": "Artefactos: Defensa y Trueno (Senda del himno sagrado)", - "Cavern_of_Corrosion_Path_of_Conflagration": "Artefactos: Fuego e Imaginario (Senda de la conflagración)", - "Cavern_of_Corrosion_Path_of_Elixir_Seekers": "Artefactos: HP y SPD (Senda de los elixires)", - "Cavern_of_Corrosion_Path_of_Darkness": "Artefactos: Persecución y Dot (Senda de la oscuridad)", - "Cavern_of_Corrosion_Path_of_Dreamdive": "Artefactos: Debuff y Efecto de Ruptura (Senda de los sueños)" - } - }, "DungeonSupport": { "_info": { "name": "Ajustes de Apoyo", diff --git a/module/config/i18n/ja-JP.json b/module/config/i18n/ja-JP.json index bc67d3ae8..dd3516a70 100644 --- a/module/config/i18n/ja-JP.json +++ b/module/config/i18n/ja-JP.json @@ -355,81 +355,6 @@ "9": "9" } }, - "DungeonDaily": { - "_info": { - "name": "DungeonDaily._info.name", - "help": "DungeonDaily._info.help" - }, - "CalyxGolden": { - "name": "DungeonDaily.CalyxGolden.name", - "help": "DungeonDaily.CalyxGolden.help", - "do_not_achieve": "do_not_achieve", - "Calyx_Golden_Memories_Jarilo_VI": "素材:役割経験(回憶の蕾・ヤリーロ-Ⅵ):", - "Calyx_Golden_Memories_The_Xianzhou_Luofu": "素材:役割経験(回憶の蕾・仙舟羅浮):", - "Calyx_Golden_Memories_Penacony": "素材:役割経験(回憶の蕾・ピノコニー):", - "Calyx_Golden_Aether_Jarilo_VI": "素材:武器経験(エーテルの蕾・ヤリーロ-Ⅵ)", - "Calyx_Golden_Aether_The_Xianzhou_Luofu": "素材:武器経験(エーテルの蕾・仙舟羅浮)", - "Calyx_Golden_Aether_Penacony": "素材:武器経験(エーテルの蕾・ピノコニー)", - "Calyx_Golden_Treasures_Jarilo_VI": "素材:クレジット(秘蔵の蕾・ヤリーロ-Ⅵ)", - "Calyx_Golden_Treasures_The_Xianzhou_Luofu": "素材:クレジット(秘蔵の蕾・仙舟羅浮)", - "Calyx_Golden_Treasures_Penacony": "素材:クレジット(秘蔵の蕾・ピノコニー)" - }, - "CalyxCrimson": { - "name": "DungeonDaily.CalyxCrimson.name", - "help": "DungeonDaily.CalyxCrimson.help", - "do_not_achieve": "do_not_achieve", - "Calyx_Crimson_Destruction_Herta_StorageZone": "軌跡素材:壊滅(収容部分)", - "Calyx_Crimson_Destruction_Luofu_ScalegorgeWaterscape": "軌跡素材:壊滅(鱗淵境)", - "Calyx_Crimson_Preservation_Herta_SupplyZone": "軌跡素材:存護(サポート部分)", - "Calyx_Crimson_Preservation_Penacony_ClockStudiosThemePark": "軌跡素材:存護(クラークフィルムランド)", - "Calyx_Crimson_The_Hunt_Jarilo_OutlyingSnowPlains": "軌跡素材:巡狩(郊外雪原)", - "Calyx_Crimson_The_Hunt_Penacony_SoulGladScorchsandAuditionVenue": "軌跡素材:巡狩(スラーダ熱砂オーディション会場)", - "Calyx_Crimson_Abundance_Jarilo_BackwaterPass": "軌跡素材:豊穣(外縁通路)", - "Calyx_Crimson_Abundance_Luofu_FyxestrollGarden": "軌跡素材:豊穣(綏園)", - "Calyx_Crimson_Erudition_Jarilo_RivetTown": "軌跡素材:知恵(リベットタウン)", - "Calyx_Crimson_Harmony_Jarilo_RobotSettlement": "軌跡素材:調和(機械集落)", - "Calyx_Crimson_Harmony_Penacony_TheReverieDreamscape": "軌跡素材:調和(ホテル・レバリー-夢境)", - "Calyx_Crimson_Nihility_Jarilo_GreatMine": "軌跡素材:虚無(大鉱区)", - "Calyx_Crimson_Nihility_Luofu_AlchemyCommission": "軌跡素材:虚無(丹鼎司)" - }, - "StagnantShadow": { - "name": "DungeonDaily.StagnantShadow.name", - "help": "DungeonDaily.StagnantShadow.help", - "do_not_achieve": "do_not_achieve", - "Stagnant_Shadow_Spike": "キャラクター昇格素材:物理(ナターシャ / クラーラ / ルカ / 素裳)", - "Stagnant_Shadow_Perdition": "キャラクター昇格素材:物理(寒鴉 / アルジェンティ)", - "Stagnant_Shadow_Duty": "キャラクター昇格素材:物理(ブートヒル / ロビン)", - "Stagnant_Shadow_Blaze": "キャラクター昇格素材:炎(姫子 / アスター / フック)", - "Stagnant_Shadow_Scorch": "キャラクター昇格素材:炎(桂乃芬 / トパーズ&カブ)", - "Stagnant_Shadow_Ire": "キャラクター昇格素材:炎(ギャラガー)", - "Stagnant_Shadow_Rime": "キャラクター昇格素材:氷(三月なのか / ヘルタ / ジェパード / ペラ)", - "Stagnant_Shadow_Icicle": "キャラクター昇格素材:氷(彦卿 / 鏡流 / ルアン・メェイ)", - "Stagnant_Shadow_Nectar": "キャラクター昇格素材:氷(ミーシャ)", - "Stagnant_Shadow_Fulmination": "キャラクター昇格素材:雷(アーラン / セーバル / 停雲 / 白露)", - "Stagnant_Shadow_Doom": "キャラクター昇格素材:雷(カフカ / 景元 / 黄泉)", - "Stagnant_Shadow_Gust": "キャラクター昇格素材:風(丹恒 / ブローニャ / サンポ)", - "Stagnant_Shadow_Celestial": "キャラクター昇格素材:風(刃 / フォフォ / ブラックスワン)", - "Stagnant_Shadow_Quanta": "キャラクター昇格素材:量子(銀狼 / ゼーレ / 青雀)", - "Stagnant_Shadow_Abomination": "キャラクター昇格素材:量子(リンクス / 符玄 / 雪衣)", - "Stagnant_Shadow_Roast": "キャラクター昇格素材:量子(花火)", - "Stagnant_Shadow_Mirage": "キャラクター昇格素材:虚数(ヴェルト / 羅刹 / 御空)", - "Stagnant_Shadow_Puppetry": "キャラクター昇格素材:虚数(丹恒・飲月 / アベンチュリン / Dr.レイシオ)" - }, - "CavernOfCorrosion": { - "name": "DungeonDaily.CavernOfCorrosion.name", - "help": "DungeonDaily.CavernOfCorrosion.help", - "do_not_achieve": "do_not_achieve", - "Cavern_of_Corrosion_Path_of_Gelid_Wind": "侵蝕トンネル・霜風の路(侵蝕トンネル・霜風の路)", - "Cavern_of_Corrosion_Path_of_Jabbing_Punch": "侵蝕トンネル・迅拳の路(侵蝕トンネル・迅拳の路)", - "Cavern_of_Corrosion_Path_of_Drifting": "侵蝕トンネル・漂泊の路(侵蝕トンネル・漂泊の路)", - "Cavern_of_Corrosion_Path_of_Providence": "侵蝕トンネル・睿治の路(侵蝕トンネル・睿治の路)", - "Cavern_of_Corrosion_Path_of_Holy_Hymn": "侵蝕トンネル・聖頌の路(侵蝕トンネル・聖頌の路)", - "Cavern_of_Corrosion_Path_of_Conflagration": "侵蝕トンネル・野焔の路(侵蝕トンネル・野焔の路)", - "Cavern_of_Corrosion_Path_of_Elixir_Seekers": "侵蝕トンネル・薬使の路(侵蝕トンネル・薬使の路)", - "Cavern_of_Corrosion_Path_of_Darkness": "侵蝕トンネル・幽冥の路(侵蝕トンネル・幽冥の路)", - "Cavern_of_Corrosion_Path_of_Dreamdive": "侵蝕トンネル・夢潜の路(侵蝕トンネル・夢潜の路)" - } - }, "DungeonSupport": { "_info": { "name": "DungeonSupport._info.name", diff --git a/module/config/i18n/zh-CN.json b/module/config/i18n/zh-CN.json index b20709e4b..d773499be 100644 --- a/module/config/i18n/zh-CN.json +++ b/module/config/i18n/zh-CN.json @@ -355,81 +355,6 @@ "9": "9" } }, - "DungeonDaily": { - "_info": { - "name": "每日任务设置", - "help": "打一次特定的本,以满足每日任务的要求" - }, - "CalyxGolden": { - "name": "完成1次拟造花萼(金)", - "help": "", - "do_not_achieve": "不完成这个任务", - "Calyx_Golden_Memories_Jarilo_VI": "材料:角色经验(回忆之蕾•雅利洛-Ⅵ)", - "Calyx_Golden_Memories_The_Xianzhou_Luofu": "材料:角色经验(回忆之蕾•仙舟罗浮)", - "Calyx_Golden_Memories_Penacony": "材料:角色经验(回忆之蕾•匹诺康尼)", - "Calyx_Golden_Aether_Jarilo_VI": "材料:武器经验(以太之蕾•雅利洛-Ⅵ)", - "Calyx_Golden_Aether_The_Xianzhou_Luofu": "材料:武器经验(以太之蕾•仙舟罗浮)", - "Calyx_Golden_Aether_Penacony": "材料:武器经验(以太之蕾•匹诺康尼)", - "Calyx_Golden_Treasures_Jarilo_VI": "材料:信用点(藏珍之蕾•雅利洛-Ⅵ)", - "Calyx_Golden_Treasures_The_Xianzhou_Luofu": "材料:信用点(藏珍之蕾•仙舟罗浮)", - "Calyx_Golden_Treasures_Penacony": "材料:信用点(藏珍之蕾•匹诺康尼)" - }, - "CalyxCrimson": { - "name": "完成1次拟造花萼(赤)", - "help": "", - "do_not_achieve": "不完成这个任务", - "Calyx_Crimson_Destruction_Herta_StorageZone": "行迹材料:毁灭(收容舱段)", - "Calyx_Crimson_Destruction_Luofu_ScalegorgeWaterscape": "行迹材料:毁灭(鳞渊境)", - "Calyx_Crimson_Preservation_Herta_SupplyZone": "行迹材料:存护(支援舱段)", - "Calyx_Crimson_Preservation_Penacony_ClockStudiosThemePark": "行迹材料:存护(克劳克影视乐园)", - "Calyx_Crimson_The_Hunt_Jarilo_OutlyingSnowPlains": "行迹材料:巡猎(城郊雪原)", - "Calyx_Crimson_The_Hunt_Penacony_SoulGladScorchsandAuditionVenue": "行迹材料:巡猎(苏乐达热砂海选会场)", - "Calyx_Crimson_Abundance_Jarilo_BackwaterPass": "行迹材料:丰饶(边缘通路)", - "Calyx_Crimson_Abundance_Luofu_FyxestrollGarden": "行迹材料:丰饶(绥园)", - "Calyx_Crimson_Erudition_Jarilo_RivetTown": "行迹材料:智识(铆钉镇)", - "Calyx_Crimson_Harmony_Jarilo_RobotSettlement": "行迹材料:同谐(机械聚落)", - "Calyx_Crimson_Harmony_Penacony_TheReverieDreamscape": "行迹材料:同谐(白日梦酒店-梦境)", - "Calyx_Crimson_Nihility_Jarilo_GreatMine": "行迹材料:虚无(大矿区)", - "Calyx_Crimson_Nihility_Luofu_AlchemyCommission": "行迹材料:虚无(丹鼎司)" - }, - "StagnantShadow": { - "name": "完成1次凝滞虚影", - "help": "", - "do_not_achieve": "不完成这个任务", - "Stagnant_Shadow_Spike": "角色晋阶材料:物理(娜塔莎 / 克拉拉 / 卢卡 / 素裳)", - "Stagnant_Shadow_Perdition": "角色晋阶材料:物理(寒鸦 / 银枝)", - "Stagnant_Shadow_Duty": "角色晋阶材料:物理(波提欧 / 知更鸟)", - "Stagnant_Shadow_Blaze": "角色晋阶材料:火(姬子 / 艾丝妲 / 虎克)", - "Stagnant_Shadow_Scorch": "角色晋阶材料:火(桂乃芬 / 托帕&账账)", - "Stagnant_Shadow_Ire": "角色晋阶材料:火(加拉赫)", - "Stagnant_Shadow_Rime": "角色晋阶材料:冰(三月七 / 黑塔 / 杰帕德 / 佩拉)", - "Stagnant_Shadow_Icicle": "角色晋阶材料:冰(彦卿 / 镜流 / 阮•梅)", - "Stagnant_Shadow_Nectar": "角色晋阶材料:冰(米沙)", - "Stagnant_Shadow_Fulmination": "角色晋阶材料:雷(阿兰 / 希露瓦 / 停云 / 白露)", - "Stagnant_Shadow_Doom": "角色晋阶材料:雷(卡芙卡 / 景元 / 黄泉)", - "Stagnant_Shadow_Gust": "角色晋阶材料:风(丹恒 / 布洛妮娅 / 桑博)", - "Stagnant_Shadow_Celestial": "角色晋阶材料:风(刃 / 藿藿 / 黑天鹅)", - "Stagnant_Shadow_Quanta": "角色晋阶材料:量子(银狼 / 希儿 / 青雀)", - "Stagnant_Shadow_Abomination": "角色晋阶材料:量子(玲可 / 符玄 / 雪衣)", - "Stagnant_Shadow_Roast": "角色晋阶材料:量子(花火)", - "Stagnant_Shadow_Mirage": "角色晋阶材料:虚数(瓦尔特 / 罗刹 / 驭空)", - "Stagnant_Shadow_Puppetry": "角色晋阶材料:虚数(丹恒•饮月 / 砂金 / 真理医生)" - }, - "CavernOfCorrosion": { - "name": "完成1次侵蚀隧洞", - "help": "", - "do_not_achieve": "不完成这个任务", - "Cavern_of_Corrosion_Path_of_Gelid_Wind": "遗器:冰套+风套(霜风之径•侵蚀隧洞)", - "Cavern_of_Corrosion_Path_of_Jabbing_Punch": "遗器:物理套+击破套(迅拳之径•侵蚀隧洞)", - "Cavern_of_Corrosion_Path_of_Drifting": "遗器:治疗套+快枪手(漂泊之径•侵蚀隧洞)", - "Cavern_of_Corrosion_Path_of_Providence": "遗器:铁卫套+量子套(睿治之径•侵蚀隧洞)", - "Cavern_of_Corrosion_Path_of_Holy_Hymn": "遗器:防御套+雷套(圣颂之径•侵蚀隧洞)", - "Cavern_of_Corrosion_Path_of_Conflagration": "遗器:火套+虚数套(野焰之径•侵蚀隧洞)", - "Cavern_of_Corrosion_Path_of_Elixir_Seekers": "遗器:生命套+速度套(药使之径•侵蚀隧洞)", - "Cavern_of_Corrosion_Path_of_Darkness": "遗器:追击套+dot套(幽冥之径•侵蚀隧洞)", - "Cavern_of_Corrosion_Path_of_Dreamdive": "遗器:负面套+击破套(梦潜之径•侵蚀隧洞)" - } - }, "DungeonSupport": { "_info": { "name": "支援设置", diff --git a/module/config/i18n/zh-TW.json b/module/config/i18n/zh-TW.json index 38d7e934f..15c6b9190 100644 --- a/module/config/i18n/zh-TW.json +++ b/module/config/i18n/zh-TW.json @@ -355,81 +355,6 @@ "9": "9" } }, - "DungeonDaily": { - "_info": { - "name": "每日任務設定", - "help": "打一次特定的本,以滿足每日任務的要求" - }, - "CalyxGolden": { - "name": "完成1次擬造花萼(金)", - "help": "", - "do_not_achieve": "不完成這個任務", - "Calyx_Golden_Memories_Jarilo_VI": "材料:角色經驗(回憶之蕾•雅利洛-Ⅵ)", - "Calyx_Golden_Memories_The_Xianzhou_Luofu": "材料:角色經驗(回憶之蕾•仙舟羅浮)", - "Calyx_Golden_Memories_Penacony": "材料:角色經驗(回憶之蕾•匹諾康尼)", - "Calyx_Golden_Aether_Jarilo_VI": "材料:武器經驗(乙太之蕾•雅利洛-Ⅵ)", - "Calyx_Golden_Aether_The_Xianzhou_Luofu": "材料:武器經驗(乙太之蕾•仙舟羅浮)", - "Calyx_Golden_Aether_Penacony": "材料:武器經驗(乙太之蕾•匹諾康尼)", - "Calyx_Golden_Treasures_Jarilo_VI": "材料:信用點(藏珍之蕾•雅利洛-Ⅵ)", - "Calyx_Golden_Treasures_The_Xianzhou_Luofu": "材料:信用點(藏珍之蕾•仙舟羅浮)", - "Calyx_Golden_Treasures_Penacony": "材料:信用點(藏珍之蕾•匹諾康尼)" - }, - "CalyxCrimson": { - "name": "完成1次擬造花萼(赤)", - "help": "", - "do_not_achieve": "不完成這個任務", - "Calyx_Crimson_Destruction_Herta_StorageZone": "行跡材料:毀滅(收容艙段)", - "Calyx_Crimson_Destruction_Luofu_ScalegorgeWaterscape": "行跡材料:毀滅(鱗淵境)", - "Calyx_Crimson_Preservation_Herta_SupplyZone": "行跡材料:存護(支援艙段)", - "Calyx_Crimson_Preservation_Penacony_ClockStudiosThemePark": "行跡材料:存護(克勞克影視樂園)", - "Calyx_Crimson_The_Hunt_Jarilo_OutlyingSnowPlains": "行跡材料:巡獵(城郊雪原)", - "Calyx_Crimson_The_Hunt_Penacony_SoulGladScorchsandAuditionVenue": "行跡材料:巡獵(蘇樂達熱砂海選會場)", - "Calyx_Crimson_Abundance_Jarilo_BackwaterPass": "行跡材料:豐饒(邊緣通道)", - "Calyx_Crimson_Abundance_Luofu_FyxestrollGarden": "行跡材料:豐饒(綏園)", - "Calyx_Crimson_Erudition_Jarilo_RivetTown": "行跡材料:智識(鉚釘鎮)", - "Calyx_Crimson_Harmony_Jarilo_RobotSettlement": "行跡材料:同諧(機械聚落)", - "Calyx_Crimson_Harmony_Penacony_TheReverieDreamscape": "行跡材料:同諧(白日夢飯店-夢境)", - "Calyx_Crimson_Nihility_Jarilo_GreatMine": "行跡材料:虛無(大礦區)", - "Calyx_Crimson_Nihility_Luofu_AlchemyCommission": "行跡材料:虛無(丹鼎司)" - }, - "StagnantShadow": { - "name": "完成1次凝滯虛影", - "help": "", - "do_not_achieve": "不完成這個任務", - "Stagnant_Shadow_Spike": "角色晉階材料:物理(娜塔莎 / 克拉拉 / 盧卡 / 素裳)", - "Stagnant_Shadow_Perdition": "角色晉階材料:物理(寒鴉 / 銀枝)", - "Stagnant_Shadow_Duty": "角色晉階材料:物理(波提歐 / 知更鳥)", - "Stagnant_Shadow_Blaze": "角色晉階材料:火(姬子 / 艾絲妲 / 虎克)", - "Stagnant_Shadow_Scorch": "角色晉階材料:火(桂乃芬 / 托帕&帳帳)", - "Stagnant_Shadow_Ire": "角色晉階材料:火(加拉赫)", - "Stagnant_Shadow_Rime": "角色晉階材料:冰(三月七 / 黑塔 / 傑帕德 / 佩拉)", - "Stagnant_Shadow_Icicle": "角色晉階材料:冰(彥卿 / 鏡流 / 阮•梅)", - "Stagnant_Shadow_Nectar": "角色晉階材料:冰(米沙)", - "Stagnant_Shadow_Fulmination": "角色晉階材料:雷(阿蘭 / 希露瓦 / 停雲 / 白露)", - "Stagnant_Shadow_Doom": "角色晉階材料:雷(卡芙卡 / 景元 / 黃泉)", - "Stagnant_Shadow_Gust": "角色晉階材料:風(丹恆 / 布洛妮婭 / 桑博)", - "Stagnant_Shadow_Celestial": "角色晉階材料:風(刃 / 藿藿 / 黑天鵝)", - "Stagnant_Shadow_Quanta": "角色晉階材料:量子(銀狼 / 希兒 / 青雀)", - "Stagnant_Shadow_Abomination": "角色晉階材料:量子(玲可 / 符玄 / 雪衣)", - "Stagnant_Shadow_Roast": "角色晉階材料:量子(花火)", - "Stagnant_Shadow_Mirage": "角色晉階材料:虛數(瓦爾特 / 羅剎 / 馭空)", - "Stagnant_Shadow_Puppetry": "角色晉階材料:虛數(丹恆•飲月 / 砂金 / 真理醫生)" - }, - "CavernOfCorrosion": { - "name": "完成1次侵蝕隧洞", - "help": "", - "do_not_achieve": "不完成這個任務", - "Cavern_of_Corrosion_Path_of_Gelid_Wind": "遺器:冰套+風套(霜風之徑•侵蝕隧洞)", - "Cavern_of_Corrosion_Path_of_Jabbing_Punch": "遺器:物理套+擊破套(迅拳之徑•侵蝕隧洞)", - "Cavern_of_Corrosion_Path_of_Drifting": "遺器:治療套+快槍手(漂泊之徑•侵蝕隧洞)", - "Cavern_of_Corrosion_Path_of_Providence": "遺器:鐵衛套+量子套(睿治之徑•侵蝕隧洞)", - "Cavern_of_Corrosion_Path_of_Holy_Hymn": "遺器:防禦套+雷套(聖頌之徑•侵蝕隧洞)", - "Cavern_of_Corrosion_Path_of_Conflagration": "遺器:火套+虛數套(野焰之徑•侵蝕隧洞)", - "Cavern_of_Corrosion_Path_of_Elixir_Seekers": "遺器:生命套+速度套(藥使之徑•侵蝕隧洞)", - "Cavern_of_Corrosion_Path_of_Darkness": "遺器:追擊套+dot套(幽冥之徑•侵蝕隧洞)", - "Cavern_of_Corrosion_Path_of_Dreamdive": "遺器:負面套+擊破套(夢潛之徑•侵蝕隧洞)" - } - }, "DungeonSupport": { "_info": { "name": "支援設定", diff --git a/tasks/dungeon/dungeon.py b/tasks/dungeon/dungeon.py index 01fea599d..dd4455971 100644 --- a/tasks/dungeon/dungeon.py +++ b/tasks/dungeon/dungeon.py @@ -224,9 +224,6 @@ class Dungeon(DungeonStamina, DungeonEvent, Combat): self.config.stored.DungeonDouble.rogue = rogue # Run double events - ran_calyx_golden = False - ran_calyx_crimson = False - ran_cavern_of_corrosion = False planner = self.planner.get_dungeon(double_calyx=True) # Double calyx if self.config.stored.DungeonDouble.calyx > 0: @@ -235,18 +232,13 @@ class Dungeon(DungeonStamina, DungeonEvent, Combat): if planner is not None: dungeon = planner self.running_double = True - if self.dungeon_run(dungeon=dungeon, wave_limit=self.config.stored.DungeonDouble.calyx): - if dungeon.is_Calyx_Golden: - ran_calyx_golden = True - if dungeon.is_Calyx_Crimson: - ran_calyx_crimson = True + self.dungeon_run(dungeon=dungeon, wave_limit=self.config.stored.DungeonDouble.calyx) # Double relic if self.config.stored.DungeonDouble.relic > 0: logger.info('Run double relic') dungeon = DungeonList.find(self.config.Dungeon_NameAtDoubleRelic) self.running_double = True - if self.dungeon_run(dungeon=dungeon, wave_limit=self.config.stored.DungeonDouble.relic): - ran_cavern_of_corrosion = True + self.dungeon_run(dungeon=dungeon, wave_limit=self.config.stored.DungeonDouble.relic) self.running_double = False # Dungeon to clear all trailblaze power @@ -268,39 +260,6 @@ class Dungeon(DungeonStamina, DungeonEvent, Combat): if planner is not None: final = planner - # Run dungeon that required by daily quests - # Calyx_Golden - if KEYWORDS_DAILY_QUEST.Clear_Calyx_Golden_1_times in self.daily_quests \ - and self.config.DungeonDaily_CalyxGolden != 'do_not_achieve' \ - and not final.is_Calyx_Golden \ - and not ran_calyx_golden: - logger.info('Run Calyx_Golden once') - dungeon = DungeonList.find(self.config.DungeonDaily_CalyxGolden) - self.dungeon_run(dungeon=dungeon, wave_limit=1) - # Calyx_Crimson - if KEYWORDS_DAILY_QUEST.Clear_Calyx_Crimson_1_times in self.daily_quests \ - and self.config.DungeonDaily_CalyxCrimson != 'do_not_achieve' \ - and not final.is_Calyx_Crimson \ - and not ran_calyx_crimson: - logger.info('Run Calyx_Crimson once') - dungeon = DungeonList.find(self.config.DungeonDaily_CalyxCrimson) - self.dungeon_run(dungeon=dungeon, wave_limit=1) - # Stagnant_Shadow - if KEYWORDS_DAILY_QUEST.Clear_Stagnant_Shadow_1_times in self.daily_quests \ - and self.config.DungeonDaily_StagnantShadow != 'do_not_achieve' \ - and not final.is_Stagnant_Shadow: - logger.info('Run Stagnant_Shadow once') - dungeon = DungeonList.find(self.config.DungeonDaily_StagnantShadow) - self.dungeon_run(dungeon=dungeon, wave_limit=1) - # Cavern_of_Corrosion - if KEYWORDS_DAILY_QUEST.Clear_Cavern_of_Corrosion_1_times in self.daily_quests \ - and self.config.DungeonDaily_CavernOfCorrosion != 'do_not_achieve' \ - and not final.is_Cavern_of_Corrosion \ - and not ran_cavern_of_corrosion: - logger.info('Run Cavern_of_Corrosion once') - dungeon = DungeonList.find(self.config.DungeonDaily_CavernOfCorrosion) - self.dungeon_run(dungeon=dungeon, wave_limit=1) - # Check daily if self.achieved_daily_quest: self.config.task_call('DailyQuest') diff --git a/tasks/dungeon/weekly.py b/tasks/dungeon/weekly.py index 039078ae9..01b754433 100644 --- a/tasks/dungeon/weekly.py +++ b/tasks/dungeon/weekly.py @@ -54,7 +54,7 @@ class WeeklyDungeon(Dungeon): planner = self.planner.get_weekly() if planner is not None: dungeon = planner - logger.info('DungeonWeekly', dungeon) + logger.attr('DungeonWeekly', dungeon) # UI switches self.dungeon_tab_goto(KEYWORDS_DUNGEON_TAB.Survival_Index) From d6968fdf64fd6c62116497e14d81526040e52a99 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Mon, 27 May 2024 22:03:11 +0800 Subject: [PATCH 27/37] Del: Remove do_not_use option of dungeon support cuz no effect --- module/config/argument/args.json | 3 +-- module/config/argument/override.yaml | 3 +++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/module/config/argument/args.json b/module/config/argument/args.json index b74308ba1..8f5549050 100644 --- a/module/config/argument/args.json +++ b/module/config/argument/args.json @@ -635,9 +635,8 @@ "type": "select", "value": "when_daily", "option": [ - "always_use", "when_daily", - "do_not_use" + "always_use" ] }, "Character": { diff --git a/module/config/argument/override.yaml b/module/config/argument/override.yaml index b38bca2a5..29fb499e7 100644 --- a/module/config/argument/override.yaml +++ b/module/config/argument/override.yaml @@ -30,6 +30,9 @@ Dungeon: value: true option: [ true, ] option_bold: [ true, ] + DungeonSupport: + Use: + option: [ when_daily, always_use ] DailyQuest: Scheduler: Enable: From 6c8b28312c19b68c6b75d8a9f5dccd57cd32ec43 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Mon, 27 May 2024 22:03:49 +0800 Subject: [PATCH 28/37] Fix: Handle character switch popup during rogue tp --- tasks/rogue/entry/entry.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tasks/rogue/entry/entry.py b/tasks/rogue/entry/entry.py index acba8caf1..12856df89 100644 --- a/tasks/rogue/entry/entry.py +++ b/tasks/rogue/entry/entry.py @@ -323,8 +323,13 @@ class RogueEntry(RouteBase, RogueRewardHandler, RoguePathHandler, DungeonUI): if self.ui_page_appear(page_rogue): break + # Additional if self.appear_then_click(REWARD_CLOSE, interval=2): continue + # Popup that confirm character switch + if self.handle_popup_confirm(): + continue + # Click if self.appear(page_guide.check_button, interval=2): buttons = TELEPORT.match_multi_template(self.device.image) if len(buttons): @@ -355,11 +360,11 @@ class RogueEntry(RouteBase, RogueRewardHandler, RoguePathHandler, DungeonUI): if self.config.RogueDebug_DebugMode: # Always run return - + if self.config.stored.SimulatedUniverseFarm.is_expired(): # Expired, reset farming counter self.config.stored.SimulatedUniverseFarm.set(0) - + if self.config.stored.SimulatedUniverse.is_expired(): # Expired, do rogue pass From 8f1707a5c7f70a27ec9f8fd76a03c1f6caefbfed Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Mon, 27 May 2024 23:09:46 +0800 Subject: [PATCH 29/37] Add: Planning daily quests to do normal quests less often --- tasks/assignment/assignment.py | 10 +++++- tasks/daily/daily_quest.py | 66 +++++++++++++++++++++++++++++++++- tasks/dungeon/dungeon.py | 8 ++++- 3 files changed, 81 insertions(+), 3 deletions(-) diff --git a/tasks/assignment/assignment.py b/tasks/assignment/assignment.py index c75e157d8..8a76ded7d 100644 --- a/tasks/assignment/assignment.py +++ b/tasks/assignment/assignment.py @@ -1,5 +1,7 @@ -from datetime import datetime +from datetime import datetime, timedelta +from module.config.stored.classes import now +from module.config.utils import get_server_next_update from module.logger import logger from tasks.assignment.claim import AssignmentClaim from tasks.assignment.keywords import (KEYWORDS_ASSIGNMENT_GROUP, @@ -76,6 +78,12 @@ class Assignment(AssignmentClaim, SynthesizeUI): # ValueError: min() arg is an empty sequence logger.error('Empty dispatched list, delay 2 hours instead') self.config.task_delay(minute=120) + # Check future daily + if now() > get_server_next_update(self.config.Scheduler_ServerUpdate) - timedelta(minutes=110) \ + and KEYWORDS_DAILY_QUEST.Dispatch_1_assignments in quests: + logger.error( + "Assigment is scheduled tomorrow but today's assignment daily haven't been finished yet") + self.config.task_call('DailyQuest') def _check_inlist(self, assignments: list[AssignmentEntry], duration: int): """ diff --git a/tasks/daily/daily_quest.py b/tasks/daily/daily_quest.py index d10953647..c186101c9 100644 --- a/tasks/daily/daily_quest.py +++ b/tasks/daily/daily_quest.py @@ -1,8 +1,11 @@ +from datetime import timedelta + import cv2 import numpy as np from module.base.timer import Timer from module.base.utils import crop +from module.config.utils import DEFAULT_TIME, get_server_next_update from module.logger import logger from module.ocr.ocr import Ocr, OcrResultButton from module.ocr.utils import split_and_pair_buttons @@ -165,6 +168,7 @@ class DailyQuestUI(DungeonUI, RouteLoader): """ self.claimed_point_reward will be set if claimed any point reward """ + def get_active(): for b in [ ACTIVE_POINTS_1_UNLOCK, @@ -244,7 +248,7 @@ class DailyQuestUI(DungeonUI, RouteLoader): int: Number of quests done """ logger.hr('Recognize quests', level=1) - quests = self.daily_quests_recognition() + quests = self.config.stored.DailyQuest.load_quests() done = 0 logger.hr('Do quests', level=1) @@ -313,6 +317,62 @@ class DailyQuestUI(DungeonUI, RouteLoader): return done + def check_future_achieve(self): + """ + Returns: + bool: True if daily activity will full, skip doing normal quests + False if daily activity will not full, do normal quests + """ + point = self.config.stored.DailyActivity.value + if point >= self.config.stored.DailyActivity.FIXED_TOTAL: + logger.warning('DailyActivity full, no need to check future') + return True + quests = self.config.stored.DailyQuest.load_quests() + if not len(quests): + logger.warning('DailyQuest empty, cannot check future') + return True + + # Get task schedule + assignment = self.config.cross_get('Assignment.Scheduler.NextRun', default=DEFAULT_TIME) + dungeon = self.config.cross_get('Dungeon.Scheduler.NextRun', default=DEFAULT_TIME) + reset = get_server_next_update(self.config.Scheduler_ServerUpdate) + logger.info(f'Assignment next run: {assignment}') + logger.info(f'Dungeon next run: {dungeon}') + logger.info(f'Daily reset: {reset}') + + # Calculate quests to be done in the future + future = 0 + if KEYWORDS_DAILY_QUEST.Obtain_victory_in_combat_with_Support_Characters_1_times in quests: + # 10min in advance to do quests + if dungeon < reset - timedelta(minutes=10): + logger.info('Daily support can be achieved in the future') + future += 200 + else: + logger.info('Daily support cannot achieved, dungeon task is scheduled tomorrow') + if KEYWORDS_DAILY_QUEST.Consume_120_Trailblaze_Power in quests: + # 12h10min in advance, waiting for stamina + if dungeon < reset - timedelta(hours=12, minutes=10): + logger.info('Stamina consume can be achieved in the future') + future += 200 + else: + logger.info('Stamina consume cannot achieved, dungeon task is scheduled tomorrow') + if KEYWORDS_DAILY_QUEST.Dispatch_1_assignments in quests: + # 10min in advance to do quests + if assignment < reset - timedelta(minutes=10): + logger.info('Assignment can be achieved in the future') + future += 100 + else: + logger.info('Assignment cannot achieved, assignment task is scheduled tomorrow') + + # Check + logger.attr('Future daily activity', future) + if point + future >= self.config.stored.DailyActivity.FIXED_TOTAL: + logger.info('Daily activity will full, skip doing normal quests') + return True + else: + logger.info('Daily activity will not full, do normal quests') + return False + def run(self): self.config.update_battle_pass_quests() self.claimed_point_reward = False @@ -322,6 +382,10 @@ class DailyQuestUI(DungeonUI, RouteLoader): got = self.get_daily_rewards() if got: break + self.daily_quests_recognition() + future = self.check_future_achieve() + if future: + break done = self.do_daily_quests() if not done: logger.info('No more quests able to do') diff --git a/tasks/dungeon/dungeon.py b/tasks/dungeon/dungeon.py index dd4455971..db2c86851 100644 --- a/tasks/dungeon/dungeon.py +++ b/tasks/dungeon/dungeon.py @@ -133,7 +133,7 @@ class Dungeon(DungeonStamina, DungeonEvent, Combat): # Support quest if support_character is not None: self.called_daily_support = True - if KEYWORDS_DAILY_QUEST.Obtain_victory_in_combat_with_Support_Characters_1_times: + if KEYWORDS_DAILY_QUEST.Obtain_victory_in_combat_with_Support_Characters_1_times in self.daily_quests: logger.info('Achieve daily quest Obtain_victory_in_combat_with_Support_Characters_1_times') self.achieved_daily_quest = True # Stamina quest @@ -305,6 +305,12 @@ class Dungeon(DungeonStamina, DungeonEvent, Combat): # Check daily if self.achieved_daily_quest: self.config.task_call('DailyQuest') + else: + # Check future daily + if KEYWORDS_DAILY_QUEST.Obtain_victory_in_combat_with_Support_Characters_1_times in self.daily_quests: + logger.error("Dungeon ran but support daily haven't been finished yet") + self.config.task_call('DailyQuest') + # Delay tasks self.dungeon_stamina_delay(dungeon) From 57a9519dc24ae4665d52742b154240efe9dc0451 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Mon, 27 May 2024 23:17:15 +0800 Subject: [PATCH 30/37] Fix: Handle random ocr error on data update --- tasks/item/data_update.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tasks/item/data_update.py b/tasks/item/data_update.py index bd87efdb6..635ea96ea 100644 --- a/tasks/item/data_update.py +++ b/tasks/item/data_update.py @@ -8,13 +8,20 @@ from tasks.base.ui import UI from tasks.item.assets.assets_item_data import OCR_DATA +class DataDigit(Digit): + def after_process(self, result): + result = re.sub(r'[l|]', '1', result) + result = re.sub(r'[oO]', '0', result) + return super().after_process(result) + + class DataUpdate(UI): def _get_data(self): """ Page: in: page_item """ - ocr = Digit(OCR_DATA) + ocr = DataDigit(OCR_DATA) timeout = Timer(2, count=6).start() credit, jade = 0, 0 From 1daf645da57d2e8cbf0d10c13d5ddb1c5e043de9 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Mon, 27 May 2024 23:32:11 +0800 Subject: [PATCH 31/37] Fix: Allow a total of 6 double calyx --- tasks/dungeon/event.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tasks/dungeon/event.py b/tasks/dungeon/event.py index 291baec5e..3b1101f9c 100644 --- a/tasks/dungeon/event.py +++ b/tasks/dungeon/event.py @@ -83,7 +83,10 @@ class DungeonEvent(UI): def _get_double_event_remain(self, button) -> int: ocr = DoubleEventOcr(button) remain, _, total = ocr.ocr_single_line(self.device.image) - if total not in [3, 12]: + # 3 is double relic + # 12 is double calyx + # 6 is double calyx on beginner account + if total not in [3, 6, 12]: logger.warning(f'Invalid double event remain') remain = 0 return remain From 4a5508859ec08610a25491b5eeeba51256dc877a Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Mon, 27 May 2024 23:42:30 +0800 Subject: [PATCH 32/37] Fix: Default dungeon wasn't converted to 2.0 --- config/template.json | 4 ++-- module/config/argument/args.json | 4 ++-- module/config/argument/argument.yaml | 4 ++-- module/config/config_generated.py | 4 ++-- module/config/config_updater.py | 8 ++++---- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/config/template.json b/config/template.json index 6397786b0..4a3b35a87 100644 --- a/config/template.json +++ b/config/template.json @@ -98,8 +98,8 @@ "Item_Shards_of_Desires": {} }, "Dungeon": { - "Name": "Calyx_Golden_Treasures", - "NameAtDoubleCalyx": "Calyx_Golden_Treasures", + "Name": "Calyx_Golden_Treasures_Jarilo_VI", + "NameAtDoubleCalyx": "Calyx_Golden_Treasures_Jarilo_VI", "NameAtDoubleRelic": "Cavern_of_Corrosion_Path_of_Providence", "Team": 1 }, diff --git a/module/config/argument/args.json b/module/config/argument/args.json index 8f5549050..8d1a000a5 100644 --- a/module/config/argument/args.json +++ b/module/config/argument/args.json @@ -518,7 +518,7 @@ "Dungeon": { "Name": { "type": "select", - "value": "Calyx_Golden_Treasures", + "value": "Calyx_Golden_Treasures_Jarilo_VI", "option": [ "Calyx_Golden_Memories_Jarilo_VI", "Calyx_Golden_Memories_The_Xianzhou_Luofu", @@ -573,7 +573,7 @@ }, "NameAtDoubleCalyx": { "type": "select", - "value": "Calyx_Golden_Treasures", + "value": "Calyx_Golden_Treasures_Jarilo_VI", "option": [ "Calyx_Golden_Memories_Jarilo_VI", "Calyx_Golden_Memories_The_Xianzhou_Luofu", diff --git a/module/config/argument/argument.yaml b/module/config/argument/argument.yaml index 4de2f6541..39430f202 100644 --- a/module/config/argument/argument.yaml +++ b/module/config/argument/argument.yaml @@ -99,10 +99,10 @@ CloudStorage: Dungeon: Name: # Dungeon names will be injected in config updater - value: Calyx_Golden_Treasures + value: Calyx_Golden_Treasures_Jarilo_VI option: [ ] NameAtDoubleCalyx: - value: Calyx_Golden_Treasures + value: Calyx_Golden_Treasures_Jarilo_VI option: [ ] NameAtDoubleRelic: value: Cavern_of_Corrosion_Path_of_Providence diff --git a/module/config/config_generated.py b/module/config/config_generated.py index 1de5b0cea..833c8c6ac 100644 --- a/module/config/config_generated.py +++ b/module/config/config_generated.py @@ -46,8 +46,8 @@ class GeneratedConfig: CloudStorage_CloudRemainFree = {} # Group `Dungeon` - Dungeon_Name = 'Calyx_Golden_Treasures' # Calyx_Golden_Memories_Jarilo_VI, Calyx_Golden_Memories_The_Xianzhou_Luofu, Calyx_Golden_Memories_Penacony, Calyx_Golden_Aether_Jarilo_VI, Calyx_Golden_Aether_The_Xianzhou_Luofu, Calyx_Golden_Aether_Penacony, Calyx_Golden_Treasures_Jarilo_VI, Calyx_Golden_Treasures_The_Xianzhou_Luofu, Calyx_Golden_Treasures_Penacony, Calyx_Crimson_Destruction_Herta_StorageZone, Calyx_Crimson_Destruction_Luofu_ScalegorgeWaterscape, Calyx_Crimson_Preservation_Herta_SupplyZone, Calyx_Crimson_Preservation_Penacony_ClockStudiosThemePark, Calyx_Crimson_The_Hunt_Jarilo_OutlyingSnowPlains, Calyx_Crimson_The_Hunt_Penacony_SoulGladScorchsandAuditionVenue, Calyx_Crimson_Abundance_Jarilo_BackwaterPass, Calyx_Crimson_Abundance_Luofu_FyxestrollGarden, Calyx_Crimson_Erudition_Jarilo_RivetTown, Calyx_Crimson_Harmony_Jarilo_RobotSettlement, Calyx_Crimson_Harmony_Penacony_TheReverieDreamscape, Calyx_Crimson_Nihility_Jarilo_GreatMine, Calyx_Crimson_Nihility_Luofu_AlchemyCommission, Stagnant_Shadow_Spike, Stagnant_Shadow_Perdition, Stagnant_Shadow_Duty, Stagnant_Shadow_Blaze, Stagnant_Shadow_Scorch, Stagnant_Shadow_Ire, Stagnant_Shadow_Rime, Stagnant_Shadow_Icicle, Stagnant_Shadow_Nectar, Stagnant_Shadow_Fulmination, Stagnant_Shadow_Doom, Stagnant_Shadow_Gust, Stagnant_Shadow_Celestial, Stagnant_Shadow_Quanta, Stagnant_Shadow_Abomination, Stagnant_Shadow_Roast, Stagnant_Shadow_Mirage, Stagnant_Shadow_Puppetry, Cavern_of_Corrosion_Path_of_Gelid_Wind, Cavern_of_Corrosion_Path_of_Jabbing_Punch, Cavern_of_Corrosion_Path_of_Drifting, Cavern_of_Corrosion_Path_of_Providence, Cavern_of_Corrosion_Path_of_Holy_Hymn, Cavern_of_Corrosion_Path_of_Conflagration, Cavern_of_Corrosion_Path_of_Elixir_Seekers, Cavern_of_Corrosion_Path_of_Darkness, Cavern_of_Corrosion_Path_of_Dreamdive - Dungeon_NameAtDoubleCalyx = 'Calyx_Golden_Treasures' # Calyx_Golden_Memories_Jarilo_VI, Calyx_Golden_Memories_The_Xianzhou_Luofu, Calyx_Golden_Memories_Penacony, Calyx_Golden_Aether_Jarilo_VI, Calyx_Golden_Aether_The_Xianzhou_Luofu, Calyx_Golden_Aether_Penacony, Calyx_Golden_Treasures_Jarilo_VI, Calyx_Golden_Treasures_The_Xianzhou_Luofu, Calyx_Golden_Treasures_Penacony, Calyx_Crimson_Destruction_Herta_StorageZone, Calyx_Crimson_Destruction_Luofu_ScalegorgeWaterscape, Calyx_Crimson_Preservation_Herta_SupplyZone, Calyx_Crimson_Preservation_Penacony_ClockStudiosThemePark, Calyx_Crimson_The_Hunt_Jarilo_OutlyingSnowPlains, Calyx_Crimson_The_Hunt_Penacony_SoulGladScorchsandAuditionVenue, Calyx_Crimson_Abundance_Jarilo_BackwaterPass, Calyx_Crimson_Abundance_Luofu_FyxestrollGarden, Calyx_Crimson_Erudition_Jarilo_RivetTown, Calyx_Crimson_Harmony_Jarilo_RobotSettlement, Calyx_Crimson_Harmony_Penacony_TheReverieDreamscape, Calyx_Crimson_Nihility_Jarilo_GreatMine, Calyx_Crimson_Nihility_Luofu_AlchemyCommission + Dungeon_Name = 'Calyx_Golden_Treasures_Jarilo_VI' # Calyx_Golden_Memories_Jarilo_VI, Calyx_Golden_Memories_The_Xianzhou_Luofu, Calyx_Golden_Memories_Penacony, Calyx_Golden_Aether_Jarilo_VI, Calyx_Golden_Aether_The_Xianzhou_Luofu, Calyx_Golden_Aether_Penacony, Calyx_Golden_Treasures_Jarilo_VI, Calyx_Golden_Treasures_The_Xianzhou_Luofu, Calyx_Golden_Treasures_Penacony, Calyx_Crimson_Destruction_Herta_StorageZone, Calyx_Crimson_Destruction_Luofu_ScalegorgeWaterscape, Calyx_Crimson_Preservation_Herta_SupplyZone, Calyx_Crimson_Preservation_Penacony_ClockStudiosThemePark, Calyx_Crimson_The_Hunt_Jarilo_OutlyingSnowPlains, Calyx_Crimson_The_Hunt_Penacony_SoulGladScorchsandAuditionVenue, Calyx_Crimson_Abundance_Jarilo_BackwaterPass, Calyx_Crimson_Abundance_Luofu_FyxestrollGarden, Calyx_Crimson_Erudition_Jarilo_RivetTown, Calyx_Crimson_Harmony_Jarilo_RobotSettlement, Calyx_Crimson_Harmony_Penacony_TheReverieDreamscape, Calyx_Crimson_Nihility_Jarilo_GreatMine, Calyx_Crimson_Nihility_Luofu_AlchemyCommission, Stagnant_Shadow_Spike, Stagnant_Shadow_Perdition, Stagnant_Shadow_Duty, Stagnant_Shadow_Blaze, Stagnant_Shadow_Scorch, Stagnant_Shadow_Ire, Stagnant_Shadow_Rime, Stagnant_Shadow_Icicle, Stagnant_Shadow_Nectar, Stagnant_Shadow_Fulmination, Stagnant_Shadow_Doom, Stagnant_Shadow_Gust, Stagnant_Shadow_Celestial, Stagnant_Shadow_Quanta, Stagnant_Shadow_Abomination, Stagnant_Shadow_Roast, Stagnant_Shadow_Mirage, Stagnant_Shadow_Puppetry, Cavern_of_Corrosion_Path_of_Gelid_Wind, Cavern_of_Corrosion_Path_of_Jabbing_Punch, Cavern_of_Corrosion_Path_of_Drifting, Cavern_of_Corrosion_Path_of_Providence, Cavern_of_Corrosion_Path_of_Holy_Hymn, Cavern_of_Corrosion_Path_of_Conflagration, Cavern_of_Corrosion_Path_of_Elixir_Seekers, Cavern_of_Corrosion_Path_of_Darkness, Cavern_of_Corrosion_Path_of_Dreamdive + Dungeon_NameAtDoubleCalyx = 'Calyx_Golden_Treasures_Jarilo_VI' # Calyx_Golden_Memories_Jarilo_VI, Calyx_Golden_Memories_The_Xianzhou_Luofu, Calyx_Golden_Memories_Penacony, Calyx_Golden_Aether_Jarilo_VI, Calyx_Golden_Aether_The_Xianzhou_Luofu, Calyx_Golden_Aether_Penacony, Calyx_Golden_Treasures_Jarilo_VI, Calyx_Golden_Treasures_The_Xianzhou_Luofu, Calyx_Golden_Treasures_Penacony, Calyx_Crimson_Destruction_Herta_StorageZone, Calyx_Crimson_Destruction_Luofu_ScalegorgeWaterscape, Calyx_Crimson_Preservation_Herta_SupplyZone, Calyx_Crimson_Preservation_Penacony_ClockStudiosThemePark, Calyx_Crimson_The_Hunt_Jarilo_OutlyingSnowPlains, Calyx_Crimson_The_Hunt_Penacony_SoulGladScorchsandAuditionVenue, Calyx_Crimson_Abundance_Jarilo_BackwaterPass, Calyx_Crimson_Abundance_Luofu_FyxestrollGarden, Calyx_Crimson_Erudition_Jarilo_RivetTown, Calyx_Crimson_Harmony_Jarilo_RobotSettlement, Calyx_Crimson_Harmony_Penacony_TheReverieDreamscape, Calyx_Crimson_Nihility_Jarilo_GreatMine, Calyx_Crimson_Nihility_Luofu_AlchemyCommission Dungeon_NameAtDoubleRelic = 'Cavern_of_Corrosion_Path_of_Providence' # Cavern_of_Corrosion_Path_of_Gelid_Wind, Cavern_of_Corrosion_Path_of_Jabbing_Punch, Cavern_of_Corrosion_Path_of_Drifting, Cavern_of_Corrosion_Path_of_Providence, Cavern_of_Corrosion_Path_of_Holy_Hymn, Cavern_of_Corrosion_Path_of_Conflagration, Cavern_of_Corrosion_Path_of_Elixir_Seekers, Cavern_of_Corrosion_Path_of_Darkness, Cavern_of_Corrosion_Path_of_Dreamdive Dungeon_Team = 1 # 1, 2, 3, 4, 5, 6, 7, 8, 9 diff --git a/module/config/config_updater.py b/module/config/config_updater.py index f611890d8..d49dddfd7 100644 --- a/module/config/config_updater.py +++ b/module/config/config_updater.py @@ -682,10 +682,10 @@ class ConfigGenerator: class ConfigUpdater: # source, target, (optional)convert_func redirection = [ - # ('Dungeon.Dungeon.Name', 'Dungeon.Dungeon.Name', convert_20_dungeon), - # ('Dungeon.Dungeon.NameAtDoubleCalyx', 'Dungeon.Dungeon.NameAtDoubleCalyx', convert_20_dungeon), - # ('Dungeon.DungeonDaily.CalyxGolden', 'Dungeon.DungeonDaily.CalyxGolden', convert_20_dungeon), - # ('Dungeon.DungeonDaily.CalyxCrimson', 'Dungeon.DungeonDaily.CalyxCrimson', convert_20_dungeon), + ('Dungeon.Dungeon.Name', 'Dungeon.Dungeon.Name', convert_20_dungeon), + ('Dungeon.Dungeon.NameAtDoubleCalyx', 'Dungeon.Dungeon.NameAtDoubleCalyx', convert_20_dungeon), + ('Dungeon.DungeonDaily.CalyxGolden', 'Dungeon.DungeonDaily.CalyxGolden', convert_20_dungeon), + ('Dungeon.DungeonDaily.CalyxCrimson', 'Dungeon.DungeonDaily.CalyxCrimson', convert_20_dungeon), ('Rogue.RogueWorld.SimulatedUniverseElite', 'Rogue.RogueWorld.SimulatedUniverseFarm', convert_rogue_farm), ] From ca18869f2fbfa4e2fdf4ddcef56218bb8a3f5a35 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Tue, 28 May 2024 00:51:16 +0800 Subject: [PATCH 33/37] Fix: Get obtain with Trailblaze_EXP drops --- assets/share/combat/obtain/OBTAIN_4.png | Bin 0 -> 15065 bytes .../obtain/OBTAIN_TRAILBLAZE_EXP.SEARCH.png | Bin 0 -> 24756 bytes .../combat/obtain/OBTAIN_TRAILBLAZE_EXP.png | Bin 0 -> 8022 bytes dev_tools/keywords/item.py | 4 +- tasks/combat/assets/assets_combat_obtain.py | 20 +++++++ tasks/combat/obtain.py | 54 ++++++++++++++---- tasks/planner/keywords/item_currency.py | 13 +++++ tasks/planner/scan.py | 2 +- 8 files changed, 78 insertions(+), 15 deletions(-) create mode 100644 assets/share/combat/obtain/OBTAIN_4.png create mode 100644 assets/share/combat/obtain/OBTAIN_TRAILBLAZE_EXP.SEARCH.png create mode 100644 assets/share/combat/obtain/OBTAIN_TRAILBLAZE_EXP.png diff --git a/assets/share/combat/obtain/OBTAIN_4.png b/assets/share/combat/obtain/OBTAIN_4.png new file mode 100644 index 0000000000000000000000000000000000000000..0d8a51d9a898106522853ff117c33b6821428012 GIT binary patch literal 15065 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 literal 0 HcmV?d00001 diff --git a/assets/share/combat/obtain/OBTAIN_TRAILBLAZE_EXP.png b/assets/share/combat/obtain/OBTAIN_TRAILBLAZE_EXP.png new file mode 100644 index 0000000000000000000000000000000000000000..ebc15a452ad1d10d0ea63b4641b9ad3b52cb3678 GIT binary patch literal 8022 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 Mr17*!!TjBS0m4P%N&o-= literal 0 HcmV?d00001 diff --git a/dev_tools/keywords/item.py b/dev_tools/keywords/item.py index 9a0662622..bd8e20dd1 100644 --- a/dev_tools/keywords/item.py +++ b/dev_tools/keywords/item.py @@ -71,8 +71,8 @@ class GenerateItemBase(GenerateKeyword): class GenerateItemCurrency(GenerateItemBase): output_file = './tasks/planner/keywords/item_currency.py' - # Leave 'Credit' only - whitelist = [2] + # Leave 'Credit' and `Trailblaze_EXP` + whitelist = [2, 22] def iter_keywords(self) -> t.Iterable[dict]: for data in self.iter_items(): diff --git a/tasks/combat/assets/assets_combat_obtain.py b/tasks/combat/assets/assets_combat_obtain.py index 3f0a01df7..10b0259a2 100644 --- a/tasks/combat/assets/assets_combat_obtain.py +++ b/tasks/combat/assets/assets_combat_obtain.py @@ -80,3 +80,23 @@ OBTAIN_3 = ButtonWrapper( button=(965, 414, 1029, 478), ), ) +OBTAIN_4 = ButtonWrapper( + name='OBTAIN_4', + share=Button( + file='./assets/share/combat/obtain/OBTAIN_4.png', + area=(1041, 414, 1105, 478), + search=(1021, 394, 1125, 498), + color=(76, 101, 109), + button=(1041, 414, 1105, 478), + ), +) +OBTAIN_TRAILBLAZE_EXP = ButtonWrapper( + name='OBTAIN_TRAILBLAZE_EXP', + share=Button( + file='./assets/share/combat/obtain/OBTAIN_TRAILBLAZE_EXP.png', + area=(827, 425, 860, 451), + search=(813, 349, 877, 589), + color=(167, 173, 194), + button=(827, 425, 860, 451), + ), +) diff --git a/tasks/combat/obtain.py b/tasks/combat/obtain.py index c1f61b3d3..ed58c6132 100644 --- a/tasks/combat/obtain.py +++ b/tasks/combat/obtain.py @@ -7,7 +7,7 @@ from module.ocr.ocr import Digit from tasks.combat.assets.assets_combat_obtain import * from tasks.combat.assets.assets_combat_prepare import COMBAT_PREPARE from tasks.dungeon.keywords import DungeonList -from tasks.planner.keywords import ITEM_CLASSES +from tasks.planner.keywords import ITEM_CLASSES, KEYWORDS_ITEM_CURRENCY from tasks.planner.model import ObtainedAmmount, PlannerMixin from tasks.planner.scan import OcrItemName @@ -93,7 +93,7 @@ class CombatObtain(PlannerMixin): continue @staticmethod - def _obtain_get_entry(dungeon: DungeonList, index: int = 1, prev: ObtainedAmmount = None): + def _obtain_get_entry(dungeon: DungeonList, index: int = 1, prev: ObtainedAmmount = None, start: int = 0): """ Args: dungeon: Current dungeon @@ -101,7 +101,7 @@ class CombatObtain(PlannerMixin): prev: Previous item checked Returns: - ButtonWrapper: Item entry, or None if no more check needed + int: Item entry index, or None if no more check needed """ if (index > 1 and prev is None) or (index <= 1 and prev is not None): raise ScriptError(f'_obtain_get_entry: index and prev must be set together, index={index}, prev={prev}') @@ -111,20 +111,29 @@ class CombatObtain(PlannerMixin): def may_obtain_one(): if prev is None: - return OBTAIN_1 + if start: + return 1 + start + else: + return 1 else: return None def may_obtain_multi(): if prev is None: - return OBTAIN_1 + if start: + return 1 + start + else: + return 1 # End at the item with the lowest rarity if prev.item.is_rarity_green: return None - if index == 2: - return OBTAIN_2 - if index == 3: - return OBTAIN_3 + # End at credict + if prev.item == KEYWORDS_ITEM_CURRENCY.Credit: + return None + if start: + return index + start + else: + return index if dungeon is None: return may_obtain_multi() @@ -185,18 +194,38 @@ class CombatObtain(PlannerMixin): index = 1 prev = None items = [] + dic_entry = { + 1: OBTAIN_1, + 2: OBTAIN_2, + 3: OBTAIN_3, + 4: OBTAIN_4, + } self._find_may_obtain() + trailblaze_exp = False for _ in range(5): - entry = self._obtain_get_entry(dungeon, index=index, prev=prev) - if entry is None: + if not trailblaze_exp and self.appear(OBTAIN_TRAILBLAZE_EXP): + trailblaze_exp = True + logger.attr('trailblaze_exp', trailblaze_exp) + + entry_index = self._obtain_get_entry(dungeon, index=index, prev=prev, start=int(trailblaze_exp)) + if entry_index is None: logger.info('Obtain get end') break + try: + entry = dic_entry[entry_index] + except KeyError: + logger.error(f'No obtain entry for {entry_index}') + break self._obtain_enter(entry) item = self._obtain_parse() - if item is not None: + if item.item == KEYWORDS_ITEM_CURRENCY.Trailblaze_EXP: + logger.warning('Trailblaze_EXP is in obtain list, OBTAIN_TRAILBLAZE_EXP may need to verify') + index += 1 + prev = item + elif item is not None: items.append(item) index += 1 prev = item @@ -258,6 +287,7 @@ class CombatObtain(PlannerMixin): OBTAIN_1.load_offset(MAY_OBTAIN) OBTAIN_2.load_offset(MAY_OBTAIN) OBTAIN_3.load_offset(MAY_OBTAIN) + OBTAIN_4.load_offset(MAY_OBTAIN) return True diff --git a/tasks/planner/keywords/item_currency.py b/tasks/planner/keywords/item_currency.py index 9ba85a4b7..07ac9a841 100644 --- a/tasks/planner/keywords/item_currency.py +++ b/tasks/planner/keywords/item_currency.py @@ -16,3 +16,16 @@ Credit = ItemCurrency( item_group=0, dungeon_id=-1, ) +Trailblaze_EXP = ItemCurrency( + id=2, + name='Trailblaze_EXP', + cn='里程', + cht='里程', + en='Trailblaze EXP', + jp='マイレージ', + es='EXP trazacaminos', + rarity='Rare', + item_id=22, + item_group=0, + dungeon_id=-1, +) diff --git a/tasks/planner/scan.py b/tasks/planner/scan.py index 80d064340..c8ae60745 100644 --- a/tasks/planner/scan.py +++ b/tasks/planner/scan.py @@ -22,7 +22,7 @@ class OcrItemName(Ocr): result = result.replace('念火之心', '忿火之心') result = re.sub('工造机$', '工造机杼', result) result = re.sub('工造轮', '工造迴轮', result) - result = re.sub('月狂牙', '月狂獠牙', result) + result = re.sub('月狂[療撩]?牙', '月狂獠牙', result) return result From bad6a5f9bcdc0104d053e71ea59eddb14b204b28 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Tue, 28 May 2024 03:56:30 +0800 Subject: [PATCH 34/37] Add: Predict is_approaching_total with combat_wave_done --- tasks/combat/combat.py | 2 +- tasks/combat/obtain.py | 8 ++++---- tasks/planner/model.py | 21 +++++++++++++-------- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/tasks/combat/combat.py b/tasks/combat/combat.py index a89f52f1d..019d113ff 100644 --- a/tasks/combat/combat.py +++ b/tasks/combat/combat.py @@ -125,7 +125,7 @@ class Combat(CombatInteract, CombatPrepare, CombatState, CombatTeam, CombatSuppo self.interval_reset(COMBAT_PREPARE) self.map_A_timer.reset() if self.appear(COMBAT_PREPARE, interval=2): - if self.obtained_is_full(self.dungeon): + if self.obtained_is_full(self.dungeon, wave_done=self.combat_wave_done): # Update stamina so task can be delayed if both obtained_is_full and stamina exhausted self.combat_get_trailblaze_power() return False diff --git a/tasks/combat/obtain.py b/tasks/combat/obtain.py index ed58c6132..e144c31e3 100644 --- a/tasks/combat/obtain.py +++ b/tasks/combat/obtain.py @@ -246,7 +246,7 @@ class CombatObtain(PlannerMixin): self.planner_write() return items - def obtained_is_full(self, dungeon: DungeonList | None) -> bool: + def obtained_is_full(self, dungeon: DungeonList | None, wave_done=0) -> bool: if dungeon is None: self.obtain_frequent_check = False return False @@ -270,9 +270,9 @@ class CombatObtain(PlannerMixin): return True # obtain_frequent_check - # approaching = row.is_approaching_total() - # logger.attr('is_approaching_total', approaching) - # self.obtain_frequent_check = approaching + approaching = row.is_approaching_total(wave_done) + logger.attr('is_approaching_total', approaching) + self.obtain_frequent_check = approaching return False def _find_may_obtain(self, skip_first_screenshot=True): diff --git a/tasks/planner/model.py b/tasks/planner/model.py index 750c5eb7f..355513af6 100644 --- a/tasks/planner/model.py +++ b/tasks/planner/model.py @@ -147,32 +147,37 @@ class StoredPlannerProxy(BaseModelWithFallback): else: self.value.blue += self.synthesize.purple * 3 - def is_approaching_total(self): + def is_approaching_total(self, wave_done: int = 0): """ + Args: + wave_done: + Returns: bool: True if the future value may >= total after next combat """ + wave_done = max(wave_done, 0) + # Items with a static drop rate will have `AVG * (wave_done + 1) if self.item.dungeon.is_Calyx_Golden_Treasures: - return self.value + 24000 >= self.total + return self.value + 24000 * (wave_done + 12) >= self.total if self.item.dungeon.is_Calyx_Golden_Memories: # purple, blue, green = 5, 1, 0 value = self.value.equivalent_green() total = self.total.equivalent_green() - return value + 48 >= total - if self.item.dungeon.Calyx_Golden_Aether: + return value + 48 * (wave_done + 12) >= total + if self.item.dungeon.is_Calyx_Golden_Aether: # purple, blue, green = 1, 2, 2.5 value = self.value.equivalent_green() total = self.total.equivalent_green() - return value + 17.5 >= total + return value + 17.5 * (wave_done + 12) >= total if self.item.is_ItemAscension: - return self.value + 3 >= self.total + return self.value + 3 * (wave_done + 1) >= self.total if self.item.is_ItemTrace: # purple, blue, green = 0.155, 1, 1.25 value = self.value.equivalent_green() total = self.total.equivalent_green() - return value + 33.87 >= total + return value + 5.645 * (wave_done + 12) >= total if self.item.is_ItemWeekly: - return self.value + 3 >= self.total + return self.value + 3 * (wave_done + 1) >= self.total return False def update_progress(self): From 6114dcf062be6de67eb8f8ac02e130a8f47440a1 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Tue, 28 May 2024 04:12:36 +0800 Subject: [PATCH 35/37] Fix: Planner wasn't used under double rogue and support quest --- tasks/dungeon/dungeon.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/tasks/dungeon/dungeon.py b/tasks/dungeon/dungeon.py index db2c86851..f5e42d9db 100644 --- a/tasks/dungeon/dungeon.py +++ b/tasks/dungeon/dungeon.py @@ -251,14 +251,11 @@ class Dungeon(DungeonStamina, DungeonEvent, Combat): and self.config.stored.DungeonDouble.rogue > 0: logger.info('Going to use stamina in double rogue event') do_rogue = True - if do_rogue: - final = KEYWORDS_DUNGEON_LIST.Simulated_Universe_World_1 - else: - final = DungeonList.find(self.config.Dungeon_Name) - # Planner - planner = self.planner.get_dungeon() - if planner is not None: - final = planner + final = DungeonList.find(self.config.Dungeon_Name) + # Planner + planner = self.planner.get_dungeon() + if planner is not None: + final = planner # Check daily if self.achieved_daily_quest: @@ -269,11 +266,11 @@ class Dungeon(DungeonStamina, DungeonEvent, Combat): self.config.task_stop() # Use all stamina - if final.is_Simulated_Universe: + if do_rogue: # Use support if prioritize rogue if self.require_compulsory_support(): logger.info('Run dungeon with support once as stamina is rogue prioritized') - self.dungeon_run(dungeon=DungeonList.find(self.config.Dungeon_Name), wave_limit=1) + self.dungeon_run(dungeon=final, wave_limit=1) # Store immersifiers logger.info('Prioritize stamina for simulated universe, skip dungeon') amount = 0 From b58aa493d89546fd7bc13dd3600107f4bae21871 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Tue, 28 May 2024 21:18:33 +0800 Subject: [PATCH 36/37] Add: Get obtain from synthesize page --- .../share/item/synthesize/ENTRY_ITEM_FROM.png | Bin 0 -> 19594 bytes .../share/item/synthesize/ENTRY_ITEM_TO.png | Bin 0 -> 27390 bytes assets/share/item/synthesize/ITEM_NAME.png | Bin 0 -> 16399 bytes .../share/item/synthesize/SWITCH_RARITY.png | Bin 0 -> 9367 bytes .../item/synthesize/SYNTHESIZE_AMOUNT.png | Bin 0 -> 16656 bytes .../item/synthesize/SYNTHESIZE_MINUS.png | Bin 0 -> 5557 bytes .../share/item/synthesize/SYNTHESIZE_PLUS.png | Bin 0 -> 5652 bytes tasks/combat/obtain.py | 25 ++- tasks/item/assets/assets_item_synthesize.py | 75 +++++++ tasks/item/synthesize.py | 211 ++++++++++++++++++ tasks/planner/scan.py | 5 +- 11 files changed, 306 insertions(+), 10 deletions(-) create mode 100644 assets/share/item/synthesize/ENTRY_ITEM_FROM.png create mode 100644 assets/share/item/synthesize/ENTRY_ITEM_TO.png create mode 100644 assets/share/item/synthesize/ITEM_NAME.png create mode 100644 assets/share/item/synthesize/SWITCH_RARITY.png create mode 100644 assets/share/item/synthesize/SYNTHESIZE_AMOUNT.png create mode 100644 assets/share/item/synthesize/SYNTHESIZE_MINUS.png create mode 100644 assets/share/item/synthesize/SYNTHESIZE_PLUS.png create mode 100644 tasks/item/assets/assets_item_synthesize.py create mode 100644 tasks/item/synthesize.py diff --git a/assets/share/item/synthesize/ENTRY_ITEM_FROM.png b/assets/share/item/synthesize/ENTRY_ITEM_FROM.png new file mode 100644 index 0000000000000000000000000000000000000000..c233deab177fb1ae77151af669e8e85d2e400e6c GIT binary patch literal 19594 zcmeHujaSlVA2+LQZEIVzwWVdUTjmy(Ix;IvVJ{{IrR!(Q#J5duH6>z7R)FYscW*O8 zNk`xd>K2ltL~m0hO{8rm8Hn=@Qxqx{QdCkD1O%Snw)^=To^zgadyWT=o+Cf6>-v7b zpSSDs=TVq9UfcE>1Oj>ENMtwwfxHC1UH8W;>%f;nyOo3ZDM7^G^CF zg$4L4UnCq{fR5LR88p-Uyko@CVc;3oRMfsr-*{~PphSN?6M_W33k`V$WkpAJ^+>;2 z_fF*F7vFnJulOzfrTY-b^Yzq=dFTFvUVEXD3|R;HdN|4R{_X_fD;e4KpuEP{u9&u&(|Pq2;>Yk_fucU%{L%(%STWzK^_Tz9N!9gblqF{ z3Z&=-Ncm38`|Ccw3;FVkZ-G7Q?)?V&ss3Z%fEVvIyzot2>t4*dZ=^3l&KIA$y6NLD zA>WjLxB z;&ZYu>K}_bR|j+5i;%aX`b=ZV)6?qpU&~+kPmYWzjE9V!gq+sd5`#3a{)B(+51Q=? z_2AmaKGi>e{vdRR_iGzB&ikeOEd6Nm3d$Yyg#7qQo(24^OGA4O$*TCcTy&3r$(i-l z+4BuRu|e_Tr--vw1I#7=F5g)U?|-_lDPhk$g^Q;y-}&74n4H=Ml!X7I=i1ZFGx>WL zp&aL{KVEnPQvZGQy{Og=V#S$zc?&PByYl9#f%A}$^UVItZ5vA8_sOSM8EKgw*Z zk9WTS>E+f=LLeQ_JQIFlZ#~-m3Ir1V)3>|+^T~$Sf8A1l?@ivXJ38)dUH8??;UB;L z{=Lw*K7D!W8}HYC_T+rxb>d#gqZh^hS-=D~Y=3)c!eFbMXyw~wzMT9@;2+b_^BLvze-^KaQMs1r?!0{f0r6Q1_UHRtixmS|@{nl@1{&n>#=G4(spZ(%D2W#2f{z~ktm;R0T?L*%me~E7K zqiwczT}%mSx_T<2{)2^o`23^VFX_WK-%kE9^8Gh^N~fY;`-k7vy+<}4Ax3H<{cB@t zQ3G#f@Q!|9yh`fPPQ7yboBH>-C6cR~u11$SdVZghYzetv@bu`b*WU+f*_*M!sp#n! z1vPs=E&u7}@m^u|&?`3!LTd0wNI#wbJTT@5;oJLiD*SpY-~CKc>(3wlqaq_c!+JV! z=3M)$6PJ7rbN}ED5T9+BG0a?AzVP}R-@kE59+|S!i|9r7s`He11(qMZ@zNl-{K1Kj zM)qzmPcPe4M*iZ|F3YamcS;^^z7}%r-$nfNeI)+`LDKD2^pAJD z_ognM`c$2La=md6vFun%{B7IZ(ep1kwx18AAC38pax<+ytt72VpVj-qo83*1pCdZ@0>;2LGP3lZhnkY|nV2n1_IhHb?J^${!amGqPY)BOG;{15MZu<%3rWbnnUsI+>rcS`*}ZR$8N^sAJNy_(vt z+WS+T@Lo2tBCWl@W$>I*`C!j@{iFJxxnkrnk68Ux=V*s!t>`8j4YJCvJ!E=PI(%-0>p+9&&d7j_AM zpNUQ1p8hkX${f7)2JWh2Gxt1qYYeHbuu2{+d}(*cyNVZdAD+0G)fwpP=?)S z+;SWXsNCL@_q(0Hjybr3?ACVqty5b*8 z74$DdP+_Td)y#TSH)>rCt>%Z$8=bzouufQ>I2Bdg;p- zetlv31?jijzHMGlTn~M*{6)cr;FpSCy8CkA>!^1=ef`xp;-J|-lJ9L@+HNBRji>La z=zr(oD^>5{x7$BbesI)B?NR)F-z&Pe^2j%AM`x6+uM|ASoGG{i&G0JUYGZ7H@7jAa zVD@-R%(<*{);;R_sW?Zvw0MyvWVRjBWVU9XPp{8B1DqkuRo?mL&dodDUi@M8@FI8f zxwqecb^h{H*Q2hm%afFKhBFmGys3&4oO6VR>GA7rTlF(4MfHWyxZt>}xd*>1+swU< zp1$c_@N+Onrqeu+xV|{bEQ}R z^ZA|sG(Wm?F>dj(uk7&McVSmzwq6K8Bm(XKyqNKPfT1|8xXp2X{`3^^#Zo49e5n|? zG$0Usw)qMuJ&Lmp%lJ0JJ6~P<=NG0-?r{9c{9RkGCD(sl|4YN_eNn>MLtmNUmG16FTJX>##lQm+cnV$^*@%{5RMB5jOrne`Pu-bD!(=u0zds})R z_O1{9DtJC#*;=nz`guR!LaWM8RGf5*Zb>>vKNwTXCN9v;Z(iE*&LORdJ=Y=LeKD3O zJvs-ozcuh3Na$EV_Wc!E)GS%sfXvGu|kZ-F}q>>@V_(4EcJx z@*DD4<$T2j@z?lWZ~pel^sgW4ZsgJ@KY#YK>i6;Adv)dcl9gACk<{G-A+nr$e?IA_ zu%D%-XH0(8j-(x!PKi6ugqS4c$b(t2)jw733|q8K{p-icx_{PHS4E-2hRVmO)}w3X z#Khx?()7>zs?l4|EgY33TYJn}11s@)$Jfig))bAO1Sbk>2*c!k?R*m17*95)S{3uL zp$+Gjj~Ng3n+EgknfHb%Bda3;i8WNIse5j_y1gwZv1%+;Z zW1Dp8pQ<09)ZMOoyYFG%NjKF{)o1s47skgK;`doS60id!SmE9{)!3p_|gW6V2TZhQsX#gYM@_@by779pWc_= zqRv)Ht=YgYRShVUlF5T28BM4IoqlI$Kzmn~f~<;&?>r&{aa5tvkJ38axX zhXFO4o5AT!M4ur{&|<#b;VwgM_P!nYECM+X-eYpw5w$2d27DvCHCown= z;!TsM=a>E?T2VW)S`ZOLXRbDIcnYDvfUaW$M7UpaB8j3>+da(|ORmk~9pqcAwm9#w zzl*8WVodxM5PW=1(KsHlT1^-tgX!TRvKBmmCw{twL0j6~j^s5BTwcbXO1M4t`g@S| zkkA%jmza%D3!mE8ogm4Hh?Wt?DiqA{_ayF!N4v7k9A(l@)D{mvO6h;UDM<8 z@z&t!P(70kJj@XqTRHe_xPYC26$(}mzTG?#3zY^90 z+9Sx9o7+A%3rI*iJQVG`C{3$K$0%e|;M1qWfC>+W^2E|irXxPzB;mAA0g0;hFM^uW z4Bva@RDrkRBx{t+f?@Hk z?bcT7en7c7tebQQ=oL<#7*zHXnETA7l-{sjf`!pyyc@eoWjJ9ziWnS^-;IlFZI|%- zO0ddMI&*)-cIS6=D@$uRzua#<-*M6HI!fs}NYiMv=`8Q#14sJ_-u%ObIU+jRWeIev zJ%7Kr?eVo6eJ?^D5AQbQ-kjchGZQ^FoN+f{J*xO!I0sKriNr{QXOJ(pN0A)@#q_4? zSOo=0$`J|^W$>mXW`-{#D5_Wut>t`6r$4SX5(ys6G7rjf6L@|*+ob+Towk~Sg-;he zvJw)x1-}TCWf@5U?-;c5i9@km%uex8f^Uj4t0f87MlskAj`7Ud(Ff}~M98{v`9YK1 zmYeJNj$3_k?f8dpcHEYoaDwnINnEUzGuaejP9hh;)t;khDLpz;jt|9xz{17q$PA@d ziAqG$gutTeNQonWk+~YR@f;ga=gd%wy+&f|%2Slo7Oi}bpkCA7fD4$;PPOK?km4zD zV><^QRX2Z{p;4m@%$?$TO(~%AtK%87GkTL|@ff97Mk3d1@v+4lzJkE{bII=;vX`~? z0=C{;w)QKlb|3HH^FrW-mmwEdcKKln5$U}%r_CKuF(*c4Y-b{YDA5WV=xJj$`QwNP z{V55}Tgqyx7Zj#NU-pG)8HdR0=A&JE4ljU#U)M!1W>xTckVu1YU&1hyC^m z*Ygk2hOQT0@Ps`6$1p89a|c0%j3cf$moTuMFUWYut8j>iI7^JKcUy^2TcUjQkt}Jh4Hg?9Y+Mhno z>xW_dD zlB!zOj#d#t2`uHc6dV*O`t9Jvs7NW+8lDBEVVhMYdM0v&mt8Jh35^KnEgj8Z1GGY( znK$`sTVf)~Z^YawK{Q0;R0u4}V)i5?F3oq2fU_tA?ia`%31z8avw&}k2~!~)>X0Oj zv`g|rNF`}ls*c6C9;%B8AD6Wya_|3rEI*s=Fp1d-OFV?UfSuVRWesMC#7ZxTBMDoM zFz^VfNQ8bTSd(Y1VZIN67_fB!xC(yP9>jn*Twf6Z7v4BAy9a48BMrIB)e#~7VrxkZ zz5yFGf7(&8)T&3x=e1+J5^Qg(5ScPQULA9UP@5yd)NntT7pwVVff{2wc&5!M-)58N+?l+dYp95L6X*33C!cfV@N#{k0XL3 zfuiRT6Fvy(YpNku;0pKA${GG}{iE50!GckRcYnOni=WIb9rOqVi9~5g23KF zCsS&@+LY`Vm7m?)(a8m(h~*^%c$HytDJ1Yo2WPLLqisU&Nl@Xj{(^{QM2d=MWXr?Idx4wA+;&T!XbX_>>?zSCF%G(tCkT+?cy}60(QC2#PG6*>gy zJ=?Zyc=MHe5XdH>fWrn$Va3A(DPRL|8wh2nsIm5;oJ6^&ydQ7k`aDIZS5Gcv_#-%* zo98Jy!-HR5C&6C+L7;ph0!#@FRete{6mp>6@*Br>NQj z6aY5-^tUhnJ-PE*cxJ&Z+w^j!SvyA6L8X)r0q~R-74*x&>9{447)2=d>+`D{qE|Ir z84Ah~(mV++US>p`@oUgmW9Vm-bed{=gf6Ll2fVK0x|*h80oX$DKnK)iQ6x2uPxlu& z+v^WVxubTb7IQiDICFJ%tr zLy$@D)&>_*c_XFjJIAOA@?L3{J)AaSp6&-xviX_+NND#1#h9U^9koLwQF8!{vND_2 zZ@7uDZv%;uvbRu@z`1TG``q}Vd1ZogOG~Y)*K;Md@rY* zz|@$lfiuPs!ycq=f!@<5&Wrox_2&-}86aFAHu{?!{b1i|Ua6+s1gQYlpTWdqi463Z zAdn_XRb}z|nxVLAXD;Q7fz1wK7bl`CcX_n0!-a7_i$$&7g02Gi1{1HL5vzjVXO>e? zAiQ<%J=Q+O1BVRK0FnLq)HivsCi7-fV8+lWCp2xF%=~DRtBK}Np%5k5 z%!fbifZ{~P+e#$(83Z2?hsb%{%?paR+m_A`X1gsEY8*?8EsW%=MeK2hHesCRb3+>ini|oYgL=mSwcV7C&HmHuK7sAblC-h9~6)uOW(95f*WadN!C#P2!W< zuH`LA(P7D}RTqCBS$Z2@hgT5D7kf7*JC-t}c9|>Ptu-dOyOY;`N4ueGcik?X-I#9{ zkr{?7eYxfb)VuUB{R2O-7M8Z1(dCUuS0*gY=c)Wa%+Mp?@Fh;YYiqvoGq=G#S(888 zGvfX&aIOE)2+L{Bw+41TNz}!1d6Yu7YW~jaw^4~82;5J@W@tFzyzzh+gY+JV0^crv z$6EXTvkias^L|bKGwU=jx-N&=Pm{gg6++TvB&Kx%Mo-3aD~I|8#m50>krs01sLKa9 zj+z9mea&)0iAUQ^Z{vy{kC$rR{SBcQ}HYs&hOkN$R zL*ET`MGAv`Ns7%L0yZ)Y%AdNuC%A44ya=IrhA=cI)AiCu`e2`H3cWJktP9cc@~63O zHQBkz{aF9&qa?>rsUFb{aC72uaDr+-2#<=}_T(_LymGGrV=RI&c)BAXN42{4gZSgW z@cU;o6KK05xq8U^IJP<{TlFVxW)Eb|A-YQmM~-(lDJQqgv&Z&ZMO|$P`NGbhS#dxi z8^qgw)-r3gP2C#6Lc7K^IkxTjruFXSKG&}rvui-QHUII3Jc}fM-qdNCC(lc*NZ8ZO zF+{l7m9O1*gWa0rwAY~>gF1H%nf|*bN>g=cM3auPmiI9hVv_A+2a>Iyp--uEdOYAo zNCVT{9}{kru5|8E@XQ1KRR9(~jK}rhgbb$kheP-7?S8le5PvsL9U);PJ^rSBy^q^S<4g`B@?Om^tGvZc96Q$Cre=1_JaM+VXwaiguJ#JuGa z<6vv}Hu-5fx|MW!3oK&lPMBrC1A$K*PWvX7quUFCG$xVVPihJaa83Kt<}fTTq@mJ` ziqKi# zjIUXpazBZ7&yc7S&3z1*d}%v$&17}1PSv5$J5{DO+FXfONsNj=FB-vH)%F6um2YJO zwI{H>@VZC=TTSapO;m&0Qhp;jJA8?^T%^Lt7wgzbMBlyf-BLGAN z>5pciiuZT-YnXAFGv-5weuN|;c7d1_+$~2*%{EFk>m;_Ea)XVBg0$G|4XcYJusx|Q zp_YA^SqZCa{=~Zna9mKd!x%JTN0CV=lM|gno(xjVNJSY1*f=ctNV)V zYl=zGDq%6!F67y`?xlE{VP57eVRhI(%-fHw0Wh(R}GRA zL9JkFGv&q-z^f)IoRu0a2JYwUBc(9PB_P@i6zh3WtZ~L9%Y84|PIk|msFMyL0BsF& zE3zp(GFfKuH|3ce`M!eXp1#~wVqfl8aU9+1YMs^lR z+w&c)yy;liSfzHgn3cQscC_mrU2+H{t^U3Zt@9us=v0Sb8OwGyOsu99BJ_f5^h7Ri z5WxAju?J|n_}$2BKw;Ruzlhi$A=dLu0_RS&z}68$jYLjJQFCoJE6z&*fed^o2we}BO_ z#B%>ub*QlhX5}aQkJPMA=t8U8&8<*JXRB`9F}Ky#mb^B}(lM+=pM0y%rjgl2jbRr@ z?&h1wj46qa&2=HnzD&$MCO<>65L)L?K&ew9nK$d^(`3hjlM@@;^!wwlpa#7&_QWT) zwkL{8(pkqj*$WtUS=`2be_`c%06Vj{7zsd2d|61?fG6MD6tI#R&&A^k2}r#+!63TaT=TN^aD^`N>kjE4>Ij!WiAXxn6RUtiwxq^xO=ab|X5co?RktvrLV zNSfh^TNt#}C|d!0AJ`jL&FJON&>c`obg1i*+`U-mzAJNW)!J0n+{c<xZ;$RW*UZ zLq#kk0mRqEY*W~s9R!O<&Zw{6Gw5( zIL%0?QkTYNH_ore@mPoGV+dNy(m7I?SxRv zgB+9d7hA9?7uWHY$KskNGIW%YwjMdrZyh^G4@VGUNGU z*Ik(vgMR+2+wRPNmVeIqU^p@>2M>E{IoLf{q6di8RMnk)%G(}D?%i0;C24QLe*8n^ zk`g`)(z7tKqJ6jmpnXlhtERQ=A5Me(DXsl{EhY-C0wwrla{P9fAj|88#Izy2@eIGO z6uZq|aIldTQHzR*66!a5v`y{r&PX&c5&C{RfP<&9+rYWr->qz`*MJ(~Ji|m=yDWD< z3ewqUB_`?edbDK_WAs?;B5KdKJEnk#ldSQ}1tx}xUQN|saX%(A9t*9VlR}9sgLxc) z2TQ@N(aM@PTqH>kW&NdRfs+a- z%zQrp66;-N-> z3(h??US8(#)G-F+P)2qw8wim-|IN9zV|D&cGWH8A=zrQ`)9Wz zf32otRYb1wxl12YYw6+a(ip6i6>83iHFG`DFx3E?`6w-YrEc+q#%I(?`ySJY~O~(q^;}~?e zk`GGFQFViAJh2(n`Mn5C9;LzdL;fns<{%i^4o!^=y9Y3<_i>qgU) zMy9Ed*StL({jDNBJH2*;!3+RKKHJ*^URT=kH}3pBv)j8QMa5rXr-*Q>nbZD;mZ{Br zt7kJS22Sh+iSX1TwV0)~MS{NR%JcjiWAFG&O-WJ;-D- zt7VYZdBHr~nA_APnQX4P{D@3WE-Ztt4a*jX2FbZPdj8W<7 zlOsYfN~gY+&7(w>6zA2J!LRLu2~H6G1%tM)A~U`1I7@8uHI8OW$cp zacV&^k`5IQm1QtFF%=$SHmI&Uw)t~uK4GBkAXq_lABA`G49-WK_5fV?9OjlljaSL% z7YBN6&3(Pj0&TU7_PkrtG1*4W5391`x$qd*e!Y^9Ov*NSEXZbap(sUpBu|jWzO3K~Gn*Qw?>PVlnQ?7G{_2t`%$+jP$j5+^kw5#~Q zHB#GHYgv$BoK_hGYMv1$f)Ho)H-y4^k#f(GIud!Z0}8*Ny7y#MhDpkK`rmyxb`KPZ z;44TIWa+1*ftgeF4O36%+HR+qNQo)0&o%ojk~O#OYk*75js4t*`xJdo$DGwZFP)-`T*o{64ho_gE( zcBAtT^r|D46>1wWOD?n?N?x1T6t-fKNLQbZfIRkEF*nKjz?f%VpTCl0qV{=G;z312 zJwRh2fMP!A)wb{1bhZzE&Dm!;x+WB@#N*wlK zhDi=Os#zf-urEK&yhUiONbzsu9K2>r8CyEvK4HyC^zBU;xa4frRm)cUb<`z)vL{G> zb*s!wb@TTdbIm1(qB*zE%q&mqoX=-2W6+C6+43dy@&=hr6v$g5%3Ldb zVb0`hwUFW;IsIpQ7i{9%9k zI#7KIPM;+#_rtD(lB=eS&x!+;o}y904MMo`ILzV^EUw(|D*#JVe$c)*#xZuOhGytv zJRJ?J_L0V92lN-4hw(nn=RcrNa93b`xvomx>asD}TFmwtWohyXHI>f$x6GFI50f3* z0~6GzO~;89%~MNy6Wze2=RLo+wK{i61hXH~I?aJ7hqy7{*bH6!L+7%Opj}ggv{d*93ZS7nvicU;MFLq6ma~0cl)@z&G z3wc`W z*(h^==>E?A+8PM)|m*|3X zj^!_oC}jU>RI3g3!HtRm!^Mfa+LOtYl^#~!)UQ`0EVTVu)gczQoYF+R&_ML}Ys5{cS5AX)Kz(~?FTsK+*4pQgph=B(5}{v%QX*#KKCqv} zf&{D^Ok9AYe&F^V4+ITuA{}YgR{$mo=e9D77r)!!knlM`wM`W5R9fBRunSPBRO%jY zPIf&YJJGV0(P)&-Wev03sOxmyt+9_a${u0-ks!uvUsuC$DV#kQM>e@Gb}0zAusz6Y zCzi@6BAih))d6+dgIUh!Xm|7c=>vVTr%o_d_4bRn>eM*W)s}BAJ-)f^Y+e(1KEg9%BW*GsD(+Dg`jc1*JQ$ZX; z+IjHd)Cuwu&1#m}73k+bx}Q4ffqB!pCd;o*nRPcbf34(0-=~)QbOw`~WbQt6q0?q? zPgNOAD>BQ^ntXTTM6Jo;I?y@40_(+N|Mao{RP{>Dwu8mmM)gXrcdSNDyAoOQ^jVl7 zf!H8UNUW*8X#Non)DW1+?$e;_7;UToGWnP;fY?E z?AhxD??uL7n*tQmm%zWTaB4KS+LdLX+}k8Mo?b(!PmRO1Nx`u8Q+dRw&+f$_QyEg^zLl{foexa7HsguS8G^%M~X9FWWPQD$lBKi))B* zjMKG_DVB6mAZgUCCA+^}YziEAu17byMw1!y1tlYELtqP=GF>hJEo_1bqk{XYMK>bp zhg6u|K~U+n(wM>u4jb6+pS_IWOCsUZJA*OdMn=35jL+11VTdKyix99z<#?laRCnqX9FsE*7AAjS&@}J&_491qYcs#sxX<$ zc7SO#BrHW;>L?KCx4;i?LlpCiyxfm<4#^I14j%t2w}ORB5Ur`@2o^t3z>B|9pa{L` z9fa8yqz3fQ{Ik2pXMyf{BalDGAm?Tu6JBzCikCf07y=xggicPfNe#k z)kdb8CiSP0+%kUGGl?}OoG2~IGC%Fzns1&A%(Z=oNDDusxXsRfwTYs_*^hy`e6}jq zNVBylC}9U{`sDqFaS(GnGVtIjqLrma!=xuci2%v*z{&B#!*9)}v2k^$*LpgDF&kIrU{-FD)e-yu+Qp?VM2p1?q4WOh#(Pm0dTw|&6 zCXuAz&R8oc&iDF?Pf5TgA9x~J(>kQk@=0+xIMPtR?)ASXiGssd{AW59UXmhglSjg_ zN5BaxKZ?L@^YGUnBO4Icr%o(I&e#Tt23(-)sB^$1tf4+1H$*kBJsv!eWg@zjn?m!) zK;lwk7oITp$BU>YObw&^6el!=r^#DB&*Ut5Q}zTm5`|)3YieSqsIBNEn4mG@VTXko zH%19cPl292+2T(}7w3f+viW_ev_3!o;JSU*Ja1{yXiNpHyPeHWv#7RQ{4V3$KT}{> zq>k4oW*6K!k6Ojm z{V*^;Wtg5_Ocfp9K6zVo$Liz3{eGSlmsY!9pV1TKYsh8f6c+GT`sqf7Fh?Y2WeHz3 zNuh)-uX_HK9l;Zrhhpp0m?JZLW83sRpncAFEdK8MPf969M&byA=?Uwl?xKd*mbPM` z^e3P*e!;C^(nN~t|}qz#0HD_gS4 zKVjq=F8e;cy?$nii!dm_1wQsi;R!e>^FqcSOs2L zH^r=`lP26>QLwMxya)P|1SPfnr7B7kf}kp;T;9U{?jC#^^fVbN+~B$1jHyujPCrS( zbZ>P2Qd9nQvh4@S(wBP}cQd7ZJi9m2PzuzB!ivE=hJFUA80fQN0MU9W4I+)U&Od_+b4U=JUd4kE(oL{>uL4n7w@mb&-d zzsSkHdjnyF!|Q9AVY^g&ZW8nr6lc#M8ELgJ@qs2fGf|W$)9HQAO+{{nz$Ep zg%zp-oJd@B0yoOyiwV0y$slGIxRjOWyFC5swwX(YFo$SyWW~&R6)5>qN0A9c5i;MJ zW}3bX8i*7J!r^r@c}Yk(2Ol6+JD&X) zX8^|A%P0;g`W(jzk?2QOQrtof85dx@ov`q7A2Qy=V4}JKEEH^v!+YfqC~V$Cg-AsR zPg2LAE$pX}L10+p_`u>ntEmgk;MstUlxGj+kO@(_(-RDX%!eUd>G8#>;$8=CD;2r;F&!Vc?m(k#Sf@7pB6>1c|I+Cy?2IcCs-?) zm0*d?A7CLPb_D^%uxk}K5~Wp(6~R-wPLt-OfdWf`^AXV>%sVG!`+&b0$;n{U@?a@( znNWUjQ+RP1qqsnks#Dx+c*GftsNu5w{u}GMrC|iN?E(2F*~(|B4SV4J;$noI$Gr^3 zZp3=U9U-nLhfGq98VbA+^d4Kw+2FcLGgYX-gHaw*aqx%-B4W8r*cVB?kpMo@jh#UP z0&{=bu9efyP+F95ie@(~oTaTXwB)XcevGm4|XmU?l#P}c zgC!~H5(&4cQ^Zo!!`sJN5#8D`+d&SXPHlT#=%dS^q5N^i$x=vts3^a1!++CVm$Ef$Gbf95FSX0`ajNaUGM*8LTo7z4PaF;JMV zxZg@~EZB7dHf{`*5Dtgmv`r7+h3QvX!Gto8m}t>1>|#Yu|N7-m0F zm^J$Smgc^lVh(c5)1ZxcSVt-kN-Nx)L6*0PQQdgsNtq*U@8Mc^;|OvrH6IXS$bR4l zhL34VfNen>eBb;7Fl()D2k&RVz?u5jBaQZhX)Yd6_=qEz3QY$wt=x7nyK`>WF8F<3 ze?%YLUvOiKh(#fS1zvZnqnLZZc>@HUC$jy6fm)=4d57d*$EH-5#1s=EdIUGJ%Tsgc zb3rNbYTV$8XLC^}dkn)W5Df9H2szb+FaXArT>Mf$m=T^Drx9U?!+XKhm+b)<=VMda zL&wSncYOI5sk$ikY^wz$EK9s*R@QQL%tN>pISUxP;Q|!B&>N2J{WGdqSmt2_(!wjl ze5ctQ!7?JE0%`CuKa);v_9y|8I7krI`1VG^B3Pq_dJl@~_7FJO8LDb96m=#CNvp}w z^H`(w<%98@M4Y!9DeYfgJK z4W}xz%CS|zL-CzauE!P|F3xr7a zg9-I&PHOECxMfwGKc!r-fio{I|LI+Akmns~CJK>tn#r9=F3)iRD+Vt-Tvx>O zP8dpnOad?dkiTMLa7H^%0%eLee?{uW;Z!5&-GT!p9ttYs1d-yB6YRnsU}#IN6RS&c z+hNH!a;6giE&UKJe{MFU?wl$2P|<&jLaoI|Wz-_&X9x)u99RlGI7!>30YKjsk1ZUE zU=@Hy$;{?UI2BGvT1LtH(?GLpj4o4_bRJc1qYQzpv58$l2|y-u2m3XCUt+qX<~ESq zYHlwh(jFQ>pDaCmyL2ko8`CAI3o%Kr0o|Ul3fl5XTpW?aST-qpLqtaHm|*z%b1o^VHl{dJ&4fvZ!Go&#bcD<+p-Cwefd5J8Hpr8x@E2 z1yw)K|BE$XDgJ_$r0fN55k@!jE_i3dlPkDODAr8gIpZuUU=I{L0{edwS)(1B-q@&~ z-yBSYgEynflah|@E7p#*Cy+J$_TDCmHd>K=M1*5dFZbWxLkAzlD@%NqpyEQmV&dKk zKvrq)%HyQrom@p)Z2{#_Kx18!#qY2ZY414t`z$DX|BQo%3C^WY*q_nKBI|Nh zVb&3NU5k3G`%z{QD8=v&{{Me>@qho}|5o7t-3qw#&2AqEBB!HMKrj|L%nYqeYjx=Wu+-Pa;l{s)z`7u{&X70q?iX!fTqLr(h zIS?0`TO6PwBBFBfy*}UH|N7s(?*BLrd2<|a#~1I{^L3t&^Ca%Mkq+}^p349LfccrO zmMH*mf&TLBpNnVdm&XEnXXuwpuXU~c0RSfU|NNZ+WPiO509-b7*VKIe+|4(@*WbH@poqc7{`#x8!l+wA%zKGr4yX!j5=Fk9LX5lYzJredgVYhTxn zheC!~-mc5NwQhW%bM`Ij0w5^K>J!W37l5}}8WID5TMy3yH2K*F&)qUO_wD{@$KP^?*U`#m(M7`g%g@K8p6 zoIX_lw6?muuEYGiEa_xc`*KXW$Ws?}%^;Bh2T@*@vZ z3gDWn+=CD@0Fc^2Nq#JT1~8aWv;+XOtzWk*`@(I|a}fa0`u0w&`U&He9~_n6uYUc( z*Y=(J>`Ml%$5({Dt6bAySblr+a{l%7CpXN$D@~qns6NN@jWv7WTKx~1hsw@US)Sk)fKw>`}4BV<=eua9z0`u=BYcQD_LY#q&9lZ z{i}h<<|pspbITWN-&WqrNNM`S^2zu!^>^oT6NgfF?4rS?0wL3)FKl}9&gv`Yi}N06 zWPO8~43y4e2a{k_%%QS-;HhV;=Bkxn{Ct z`U0)Q4#dJaQ~KD_l+rBHBGcAj=0Ja-IywZ#WL^Pesj#<*D0vF3d%jeDX`HJ9UCsw= zEmx=-G6kC^KOZ)6C~S8)d4J@@T>ByV>9-HMh2~C8JvS0>bM-uM-nG)0cw=#HQ^GUT z(9yP*vSko-fyx^s9cEyr=?`xaK-7+>Go;I?Kj(8+ndvbz}LBtb0u;~O5RH3z)MI4qzN(_ITJX%p*H5U8G^e+ z>LXpnVMlaG-us0l?hV2OVMIXjzT8EHPdB_|9y;v|d~#a%%ye%a+4*OojJ4klnbPG} zIP$Dev{?in3h?$Qf|(9)>GxBqx*xIqOPLu<@S4nUp%v2kR$bD0RscS zBxWloD}Qe#Jwku=guZN_YMVJuj4syx+O3zMkkFexogUtQA>A;YT$yR1W)WgRvcOb6 zo3PuI*z}&T^m@)~nR6vaDW|`&44wn;3d-@G=UC)OzBhW0Wacw1vvs_6c`JL1Vf)d+ z%LCy9?}K5!ftzf66UqU~;co&02ZFp_w`~?J$aR)?m3QJSU3p#e{d0-(+$H*-`q?sq zGPupWOXEv@@Q`sT9dX9vbVOUqWF#LzKTF6?tA~5>9YNc>JNLNq^9b8G=w{+)}wYdFElTv zy`)`mUbX#BUhOA3%K9N;-*A!+!K2MliDz;Hw*9!AWKYCE?ZMwQAxYWCZy9k<*y*`*q{XycH>FdTTr9Osy?0f&@L>p_j7bF&# z)HF5gseE3|wMaX7h*gfzJaYSB{4}sf_L8xwcu2~P&nTt5SZ`YO{3@4$beVXt z?RDGWW?hu7&A9Bm%jpK?4%2(CaMY*j=grl1lg+`_yMF~cv|G9EB$#o7rDQ;+$XCJc zJEIZ(ZvD0CC(jqHOo4kIA+vi)rctBC#hT*pE#=b6zx?j^soJ+`apHWL_6kVw$Uxg# zK*YGy94cQ|mQ@{AKW$!Jv@~)+hm%6Qr>E!in)IosyPf*=MKOdG&zue6X({FK|5`)_XWw0Gr~>!aMdbR zoygZG3%Uqf-%V$7|F)${RlvUCrt%13407c09qo@f#7KeiLQsUBE#4*{5$w%oVI=7= z=wO>0d>L;r3kppRMjW?KpKLH5v1~xZKUy=@;5q%)+S7 zfDZc3(>saDxdH;b%si;QS3{`_rM0E(!+nt!w2)2zinaNZMXLU=)$!yG6D47|qbj7F z_=ZqXj!^2o(<{(66r#FYatt4ui6Ar+kn_DLfvmg7k58WyM~<9&OaB;?)z-pKkEO}~ z5UIkWd9rl1dCN)H)DQp&6$Sv_Mgag68vTj`00Qp=02?m=0HsU-fZO-8L-$hv;Dz`z zttaNe(_1It`V!|5vY$Y&pFJ2G5t{cdpDbedMX=y)nbmU>c73@XD<`Omu>n~I#GY!! zChr*6Gx_|C@>#K4qbX{g-=CKd?D0XMwcS%cdon{Fxg!G%x2~HYFI}wb--;lh@ZHlL zkFx1{8+0D9g_ue>7~<-l$89+BbThFk`uSVNiM8lmv3HB_Vly1G4O`@w z8&*knj6QD$zEpx*7h4>OiZ8x@QwUjduPTv~Q^c5hhCYQFr`Mw^4=kO{5298K4mOtvAYAHqScD39Vr{*Xq%Exc{1j zyNgfWjQtc+{0*v&Ulws0^^1#k!Ltb^XP0x_27*;#UEe}D{A%vr)_Z-aL`Lu0?-hsN&2?lRQemcr=rn#|2t%{A9 zKA8*?<2N_F%&mCOV9x+)B9vP7tO}N;m1flp3(mK`ot2BWm$f31daqff3lYs89F|}cASXBYUeUZCpVLPZ zn{E3F^wrz_wUrj|aSwSQ)9vMy%l83i=_l;>cLS<{Y6-Dn9P*s3yX*J(^|DEcVpSU= zGV%%)92-71nOa0Qo+)__e8gv*l%Gf}& zpu$)84w0Ez;dnFyW{_FdNHE2z}4J%mIOdcu;xcHrZ zuQCRl$L7_j|0NOaPj*5UOUGX&a~px)wt^mF zEwQlT(Yl1#1grR;1%gkOaf;=!Bq!HD-9VtAVDyAd{>a`6`XP4_=HpTp7}QlP=>Fbv z@5@(gR6_cokfHRl;@rXCq|zSRikao{X2vZlW{6FjjlL}!JQ2=&C`hQ50d441gZA_`wXvvRn^w5^vo zEr$ogvN)f_zX)+rZOC*trkb^91!;H7O}uI^z=qiFSq1eCy&iur$!a!o*8}G0UKoH> zd*vZEO>>61oOEiH48Ovuj^>K!izXC#MoIF{WO(yqn4>MCIG%vI;q#%n?gnWaw?cNH z-9yI%!T8Y#8}>)m9)g$Kl~gr7WSvcV@uOwt+~^lG|Nq?dbJ!|!;mSAXntyqD*n;1>+-vi@c*lE2kf8zSLl}f#cM?X{v3R9v)xdvy!(nA6`9&Fi-F}k{!XCJNh)-vu1bn zeO@9q8n-g}dO(+d3hadik+01Xh-*o?>RYHwbcr0K?!sHACeZ}*R)*x6_WJ1|_;+wTP9a8HNsg+&xLN7+=zwi)qCf9mqz3WDguHzlW>q~Q< z!Jr-;&Rj+-?$5j@SL%I3hRX)NOX0;fY__NW7Qvx4oF?K~0fnAB_Mz7|P!k$qZ#?iZ z>K&Wpd2+u0aKyslVKCfO`RG?6;Xh6CijU z0G0oDwNpJlnv9M(_$lgB%!H7kRf~cxdUBGr0XR%SNYy{&EN%GfPL*IuqL3S@u=4GJZmYi%u>d&P`n zFAodY4leZ8T&xv?+|~VZy->eZG#Q`Sy6Q?+rMW$iz@>QY$35Bf>7l;rvE;IRT&B%563CcW?x6Rff? zdm3nY@4Z%?&GDP}x*UcJ!Imr4XY%+q zRccDdzGiMllBbo2P=|XdDZ(-QkyfR3uRBSK0in4$kU-+(yu4?F=EbsHAT@Y-ouko z)DwYVT054?kKJ<{x!0&x4=mW4YnqXT3=>e>1>8`Ra`Qp1PI=Iu|Fcn3!}9NajBi~N zadhwf!jYg8G3Za66=E?h(*lE2t`#(ciz((Sm64w~!6w{5Lo*a?ZoVe1Cqy_?*G8SV;;Pn!Mpqbm|Nr_x8V4??GhR#x<-q<*L*-fwM%#l^OA)Oe*D33%qCv%hYFb_;bF zEb^2ay_PyzyscZpy^aeYjSzxoGvkm=kdR1>J+6Q#ygg zRdks@=j2W{X}BMk^)$`=jFcAE=_{RC5Q#B_Ii&JGuYdhuLU~3~esbrwg;OTebs8(} z+V^lTY<~&R7FvfNovb%hu<-L&x|HByY-kOe)WNz7{}p3bzi*UlPjyLR+4jo#qn@T+ z@0;n6al1VYT3`2S-NRNO`0@rzN=otVRJ;I$rh}Q^gZmFZJUzU_lAY^Wylo+#o@g$X zRbKK?d}Yu+HIfqugn#^E&#*c!0WZ&-RVP;1@Sk$BnwX$7VFOh>=3Q7rc{qYAPOLyg zkgJd|r&!}j?oy0=Jtm`r!peD0J|En>-U^YFUfj6gs)p zc6TkB0vCwM4k*Wf*VUh$d9WZR6Mx+N>$CqAZ2G8coa~lHb;<-2j+ zYeMsnp0F%?#+w=7BX+wFlV8M84zf}X`=?A&a~%a=KE#6z`_zlHX{37%y?e>cWk=)V zTkR2J4o9oQLrR-0F$|ckSum|n-g5??4%?n<7_2NP@U(CfE+t%`R8nK*VR4915 zCPtvv=hZg_E5%Eg99dlewBdUbeImV3NA54yy+_7-Zk2O9d1Lmw{yE_S zA#L+pf7EwBt5Sj4d+Ml_1v4Y{2zq$+=jLQ9c-~C;pg|e6_BDJokA{meQeBE>XQGJG zmWWI2vieUMR##`Nt8FZHuM5LH6*##w?k=WJsFxZJeSR8jb_5)IanB!CQoi^=ETP;c z>)tn%Up>v1VipWtkyqUL=gf}R^M6n7 z$}O>=kuo;JuA=+W=1fElRhLntn3Ro)X-iwbK5Lzbl^n&AWgZER5zhvj(lMWL7#JId zY5$!CZrjEpP+MqYwcTY#+Ikc_`;4*bLUkd_%pWfcODSGbq_Abb6mR0=wPE`u7RAg-e5oX=xEu} z*l262VROW$cqBzbt0%n@629qO*v~yjD`8@>!CtS_FNT)e+~z7|i1Yc;uivvFwHRG} zf#-YO(+hOZ^8yk|(uZaoeU0idD162$#DY6Yp`VBBzP}8Bdr5M=n>xcy$go~f1BM}0 z>a0b@iWIi(BA3#3W3#!~plTSo-lmQ{mEHFUtBBe)^o{Vs9G0Fg^@p&=Fz}+|ozt#K zQ(tn=QJx*R9hC4JN8ybOApq5n(AI0m z2dhy=R!YdAZrC7^IEF1~dNqaQtvcz^2ve(2$dFPE4ksfRJC~%D)+ReKiXRv?if!n& zNuSzary26`AxX`%<?6?GsQ-x$38s z;j7B{5Qn3lz_0^D0&kuN7`gHge&C6$&3sd+YZvZiV=u&&QU-%b@la#l_iKGc=5yO@ zgTh4Hsf7-65wxuYDatt@9Fr~6Gu5)&N}!Awx072Ckqb0DmZ&woeEbIik+TWesMQ~` zY4no&@MEA$$iw;^H)yth<%D(RO%s>w|EGe-#HH27gc$T&nl3_JP0!=XIkEy|e@6vN z`l?#1sx={DSLHlQYJJ1oOnGnEKayA#N#`CB?keNgwrj-}*b8_~d=Lc>8yOBKUX^Y= zT;$I4n|K7-5f$L)t4hAYG9X#c!03@MlS?gq%Wb@Upc7_RV`;99k~sE&C^QMZbD8DS&tNBd7?jIWs)+zz60azS2#ez`I1i-i`N7~ z=B!0OUSS!B-5$>_ut+na`{fdA2X4%sV!_6Uwl;dp7DL~6JC*<4eDAenWdhA)q|N(a zzMr6F43h7`qGG%@3Sat4VWV#5#*a?_7(-ToNSnueMW$sp}n(rkTNBQP;2#DM2wGF_#jUhzs2qqaFIrIT{{4sY@_2rMutUo#PHCjzoZFSy zUrRGS5x4{+wWA%wPEA7~vx`_wMWTkrr`d7SiWu(r@B8wcyX#VePa{b2!a114(;b+=Rr#0+giVVhZWOp_F$8ug z60ufgPf1zGS4|CLnXX2fl)H*)ON3(WTd}80>c@ZA)MyREC&SW^2pW5xKm0nAA7{fj z6I{dwpB=vv_aXv$*Q=xi8d`)^mEMa;H4E!4RXfO2S6SItw1iO(4;y!Pbrks{w=`yi z3CBl6ykgnbb5)oe)K3EBZ!4m0c``K#+vb8ct-IyOC4r39*Vh@>;L&S*^C*y_dQZ7v z40<0B95i{B833rBKUZCW483Agrjw?D2DUA38|?8-`9)i33kg4#m0>Qtb58W8|Bk5I zX^N%8!3JM4p?NW0{Bp{jq=#Z%eK^jhlAOu2?WoL z8}~^b!WBT_r;2e>Z1S7WWP@#mbz8`KSSltB8hP=*qVe^)YAsGhY4gJ1mp136wakm} zFQJ5Shc3myFHM)go4uMJ5F*IemK4g zpv=tg9?aldNngi?STmky*;-k-4F{H#u9dpDsvLn46ud9(z*l=Jaw9G$hLJZ-FWu%N zR=aR_AdEyt3LC!rhdiZ`X89*6?i6Chg4yxS6CSL=fQJhjUgCVIf7 zT@ya_hNzhfnRr$mKG^>v4PKZ1#EIR z9WUOY9gpxvk|xjD=4EGjlZL7BCC`z~n|gLAfkcp2xKBI%%wO&R?VdnY$!)|)giS?Y zYpy-Q3@#tJPl8xA;hKXyrMS937?w&{BWvUpfdjJ<6me)1T}c7^bprhw>Y7_C;;pVR zN%0ht>Z{ng&Cb}>A3K^IChM_Y|5t{7FA=nlT@x#(O^p|Z474d1Rz!3WFMicfI8FvL3he05VUj^mM%RiD)ZXo0{mLiZxjZN)DX&SY)X?Q?#YgY9ho{pQ{=N^K{+lcn~ z@)xbH;Vx^?-^P%Y^wgo_?;E6;fr0JmfF0!AesD2QkXj(bCy^558{|`)&3py(04F)V z|68$rZ@=@Z4eHI5E@fnPOd7~3WA=<@nr2sfSHv_p)Q~3hIaFzfBc%3!IIRYxwI=-@ z1}H>KiZ!rQNQeJ~d91h#8aj2t1eGSs5Y{cr&TDx)t`L%FFJIW?Se6Z`UUrDOKmsQv z$w@z4J5;m}?aMp<(K<)QlbNrM+iv@mj!m_Khi88tYlOWF_1UAWIUE)Hp4yo!VWDMT zHq-kf%vV&Zbbg0*&+XzE8`UTy!*-C3DahZ|?Py9+n7twVGiL~4nsBUBC-as6`_7;E z(jYaq{oSUvgA1OPk~ePfUv&RbC&h2YW@d6S$e+2Gh%Dmb{B6M0IC=gG+n3YmXj2pKo@dXo9hye}CV{W34C3%(Kz&78-b2 z9vu}i;UoXnJcT0zymllr@=$B#tq#;4#hd=KM>yOwRvGzZzn6{+e>9D9eOcy%Fokp* zWYx7-r*fj-g+k*E1_vC##1qT#r5_on)y`IE=v`eIRr$!ne9<7~G!4ec2x{WsLfUPv z+gyxrb@v&kCd~0j!jM|fAgqRCj+)#qFrwYn|2)Tj4KHZnCXf1^XVhj7y|a3m?Z2ji z|E>1yxS^IKe$hl0*-o4?WmGCoac~v z^SYhF{fE_#q&7(pThS_EeH( zf|TLC{4FRGfsF$t52QDByHGP#T8Z4;dDUL^PuW%G@qY1kC~T^r1h&C;awBD4H9ppJ z-CeWot|p>c|MsF7?D+5Hzt&9QO#h)Dg&0t{o(sR6;_v>;LyXtc*|zSV7nV~{w@ zVkl2GFi=*0Oi$RhBWMdjTUhGlgBaC4dZ;j63H0df>~vKny!W-IU z<#fKlttm!UF$KQ@58l& znMkpMG(F*eJ^?+Kl$#)jnzYjv-{a#N(Sso6+>w-3rabT#UQ__zh;+JBC^%XM9PJFo zPjR1YK>6rjr1HE+spJ^SUQK=`^c59KJ4ECqH6<&r)MqeXmBQ8A;_a7Fn}{Q?bmzf$ z`JvvgaigS$p10BWRY}V|KvYv3z6~he(XX&Ypa;J^$b+Lmk{bnjZ=`MF7X4kDeXx`}VzjfpML+ z-++jkYe`_T<4Yr6e4Z|r|NQREwYT)I?`nXt1KH1nR0h*_<6`T|YavgfyA2HJIbzt2 zi>lsn>7fmyUK4Z28^l%D#>lk|M7(a@+<=BvgCiQM8Wwq+kf+{e*$%;iTFFKUzsg0u zPKh1i!?nfY+C^%g&|*MV)uk40J1}vA5`6%TkX=gkC;LwAmeYX-I;&K=r6rSdt@G9&TKk42 zc#gK-x6)}V&>S(fQypvWnXmAig?V!*F)y;GTM=919Mn$gS8fc)&45ASIQ8tF2xFfd z0Re|rcg@f?Fm^XpxPO9E8}t53vgB~_-giw!X!!D9UI$9s$ilmYd}KK??8fW<9gaI%LFC(w|+Q|753>(DKFE@Jhbo~Bl-qfS4kc6G~fgUt|n zrA~z{6JxtGkzucCY}Fnu9Q{;G{1L?zD9O~IXYqThxlqAAWH5P^%SPv>pVyo>%ayB$ zspjxuXt`#R%F$OQ`y=|m=r~TSNU-;tfh)G-nKKAevLkL+Ej-YJn=GK(fGJ4i=~!5= zYX`Wqk;!3K(Gzo1P7e9%_r^+U82O)To{3LAUmo;qc34-iB=qVPD?My;oJr5i4IKTP z(Q){wKjW&U*zJGzdi67EOJ*PeICQoiW=CDXRb+Qxtnux8;2FCXzPO7vTdL0m<48hQS4vR zSS)cY*tC>BQ742?MUhVzW%H<7AN_n@zQlcASn0f@&k(~ubM*S+RNm=S9__d%heoAW z%p9jKgAqWO;OKS$G;H;eeowlzS~I1koHz1K_pe`lHig!{axqU86c{XCSvO2wk63Td zJDn&Ng$4|iW`!MouTmT5AXBq*3NiCBpvEQTu$$ zdVZ@9WxSWx@@O;%nj^O7c=B18cj9Jvzj2i;Gb!|BIW-ydU~<0z?PcRsmIWjUOLw?o zhDsU4^p`Xcl6$Fn_BG+y5n>1en@F0Df2#d7rYBtr8$gJ{w4DT&LPHl<)B2xoG2p%G9>6_`?rYLLI^Bs9|L^}IdBF~7B1@`L zFk99)vC!65K^czO!F5=hPBkQCP2v`1x^;nu{j~#?Q)TiVX)MQheV^8CZY+ha9o^-4 zD<-Ox_WyR`sU$?lZeku-8KoXd6Nn(T&fC?zX^DMhEh1Q9QKfdgbEh?z5>5Fn+B83t zoy*Fa_NS7C<*Ed0%Jl?z^6X9{=4;rm|KweRHVuxq10RJ!L`jR&;%2GcC%}>J0jjg3 zn#fr)3d1-h^~Lor~i8S%`92Y7I4 zMz&G!nKe(KwAQSap>QtaP;Is2lrEfSj-1ZqK#AASNRq&YPfpwSCaElbGeWF%Z*4dE zx+h4~D<_^o$c5cLFPj=frH3c9uB5PvhJ|ub0^P{t;uyi|fxTV$)`WwT9|9*Uk`CBv zL5DiWN}Ed|tfj8p@jtcYk+IZ8)2c|Mv^WxHVFoE_5^(GPxwBYSEj{;0fgPNd8T6vq z@1@PRGb*+cKj^;{narDe{rjGv4;xy$TUqJ4JbT?K_M5B0X^HMV#}$S0Q(msZ9Y?0! z#iD9G`ThcG*Rq|Wxl!1VyM6|9{S0M6=jK5=U3Vwh`aaq!>&ycWO&O~lXu(Z<&0{Li z@u4#JRd(RMkg%2bPJz(F60@O)E6SFUOSv7ip?-V+DeE<8!+lxseu@q~G9sVGG;Yr+ z%F4)RIOn4`j0}dESrm>8r|w;lX0U)9-A+eHApjM@0U=L6Vs%P+@&vvW7et;EV8PRDv4d3h?|4BenfNkfv0iiU`l9J> zP`9n>Z}UO<=6-Pq`Og0}&5Wh^LH*SmGI~IuX7-8U<|k@vL1hPPMF)GuO}M1^517j; z)MzzcN1C2ZeT=rP7A}mUiUc*yIckLl&0Wc-LQ?2A*a;GB9huWGRa5GWr1P#FWw^&A|KqQl2k zn{7H5{#{PlEuV_ED4pro(Rms#*s<2G9OX)?JRLve_moRkS?vW_0pP{dgr`2jS2f>do~)b~7kcrpLT#n-njI z%oHOZ$lI*L`;!;J(ZrR{$-r?K`Zd-0g}SRfFjEliJg%F@O0Y(k85kth*RwhyU-yfBe*_DYg$mybXZHPWR?D+CH5DS{lQNi z0qkhmRTt}n?%_!MKkPy7 zHfEOW714#2WD%+TlYXtphMQ$>J1B|)oUA>I4BX6*;Zstrd;OatTH%4R*MBq*qB^AN zLv+%mB6<6`T(^7e%}l^%+?ahqfQs#L#`*uGA4hW?9jbLI!N#cP(j{Vyt-!tn9sG~J zF54iroRU|St0iKBM5gVy@+kp(2nLy0db;ZndDpx%8FJM5&dXd2xm^`2VOyv2>Sgt9 zv|b=CzCmzrva#0!ZinW&7r&UE;}edVnHMshZ^QjDGgc1@3R?YIH{7!CoWZ57t;HX< zk&+V`WIH0RRfM`PEx6XcK5MwC==_8~V);_JwSToKLh)(F6-nNk>?u&vtm;8Q>{q{y zX9&oYJEON1)cNJNsVh&MIKY0Kfy8>89?(v^IJP~Q`5rPjfOSx4xQ!Dui$-g0bNbSB z=sfn{0v3!w>m|VSL~?JbCgjF>`PX}RoK=tqj*K7kV!Ow<+&F6@??a(kVXtSBu>MXR z#KtD&qwk<6!*|S%h-w0G98hb0OAKCUqB=u_a}CXyS#7 zq2UP%URJ__q)?Dne8IJpzb`#P^*r`|6()~6ZqxfVhn-Ch!GAuiXbwJY`Bt&zn_kOv zQ#AsJ$LZIZpCsb(GQsvK76F{hQma*nL(tj#Z(XCg+~|4u$uq<8!UWFG&b2Q#T3XA$ zfRp;%ty0W)3^%{Q!*35KLPbJGCGKVJ6Qr-`B*7`^CGKNm3_Fxthulbp(Fj+ z75X`@fwG@(X$tI2#CV6i=-){5Wca|S{VL$KftE|jD&%;Kek8~Wn);3x-zLK2B3kpz z(H;#73XOhfBuNzddSh#LK-%~8M>68XuNB+A`*m|jl|0)&w}Z@RTW70#gMhUf8yVnj zC!0v8@UVkzin_qDI%L-svYXG+=7HTP){Hs_^KdW0P@Cf6R}S2h16!8$`coG6v`lT- zM1EfUpIV?~@e^+OX9>xL#a|~8@g=G4f;fSdka-Rv<=izeLC#k&* z4&GVKfz-HTd4RcjSpsfXXUTs$V|>*P+u^qD2k43n2@w4jOizNZ2NB;jjx%^{2kC1G zeId>GG$|{q%RW+-@3>EyUCWhK3SAxo&8YAGW+{m|YLgv8{e6$Q8945qM_ZiF1^1Zr z>%>>kHkS|~-JXcH)jsY#vLowMjgt=Co=O2nHiVI(_@5ecJ~TAr>8TTqf)NO&M#Ivb ztx*v~;yewVhf; z72wUoBu6f%8mqL+Gg&v-+_+gbEHQB9>Qyz$_)wrH#%l_>E$(pIlb@6P6`I9o`vB>M zdjKA8ZOMw37#JY%T`!ZgRmcXmO?%OKn@*GW$#e#d$cPDl*{l>s={M>iw#R5F%wE49 z?6}6Ry^w1m9=iLJ8?+ZiIBA<(_qJ+n%i%7~3;vbSPReb_xk{H->_LWGp?{MkWsnJh zP6KjWGQOlXboR}gWh~4JqZywbpkU=wi;HXPRe~eJsC%hN@kj>NWJjr^h6>-sOXvPA zV18%#n*kML)1FmfAeC85Uvcl46nEw^h_eD}n}>zSJVNc9Z2CZU;-;s#j}P__q3E0m z3Emt0*Kc0uxo*?$5mGqyix}QL>I^&jZ5)m->%7XF2Q4>;!H{%SWG@XSM@Q3LMD&c$ z$qK8^%@JG>10#cP^8vDhTA|K2A3|;^g?B8sEhVdYZGULcSM;@_5FCy++)Hz9%M}dZ zkF18ptl<{Qq=CBA>szW*`#nPIvy{2&L&Y=y-SnS|iA|rY0D+ob6! z*lwp4e;qXYzFRP!o)HVsqSOzU5aCqU)Ui5v5JuZBp>zom7C_K(h+E`ZTKrJi-L&)S z%#Os!|C^rvhexfIxjko1Tm_NZ*q;` z>Lil+3(jA9Fgb9F*MuCeg4;@2ySkKWh^UIHyc}=t5Pn!%w1qxM+#20*ja)FcR{;LG z8zUvad)lK&TcfM2KBgha54O$}T$@1}g6TU+o+7L32B_ z(9??e;8-SS@#^wsu8_Iy<(eH&-y@PVewq{qp-il=zrI*wgMNCa0wWfJkX4jDnE58i2L**#Gz7^ZpbypDY_j~eJTrZyk{5&^30osb|g}Nol zfw!loHug2D_tgsAiRtK#v=#jibesFP-#&c6cK&FUvp*w^_p6h{o_)0!8YYhj8=Hv? z#8ml45Yk5$RxusBX!RqaI(?kpyzb_LwrO}|2Ui%E7$#!cR^{v9eG@w1CMDH0Ii{Qa zPP!h>iI%|b_W_RC04^DM?y#O>q5mSfPa5jjEZx`rJbtFa^qM1=%uvI0t`C71Gh2zi zLt8+ot7Ecra&sEoc;M+-)-qOTV1IAb(JFBM^dPN{35%l~l+s8{=YZ_2vcy?N1_qZk zde=dm(8!R}pl^S$I5re&WE|*)7K@w1@AHQ)kMv82js4sYue0$tVI}PWd>_4?aWl6> z+*GAZMx74NNJozG&VS*~O&Ljh+-XMesWMP!2Ga4kWl#JFJ)=|j`;Z+B7DEomh|9*Hu& zrlsgE8{vS~y%yt>?oxIC?hze70BK$DA@}eNjpbpwi1tsmBb;HrFxx4_3UW9`(yR*D`tm}1H_nJ`ZJg@9%+!H zv3}Z0Aw=e3<7x!qxG+~a@2hW9_F&I|hJIJh;Oa^SDst*czfUv%s5dWE<)7zpV6C17 z0<($E&TOXs`TM6QD(Qrtk=nia;hv_a@TbfrZ9q6arj*@B!rTs|HK=`*P;lu#t)5e5 zI;5#q2c2Y*UzuiVk!ITp*(%ZrBzgNA(~4@Sl@Y%A4ty0$#Ck&26JaJ1JgV(iu_?)_ ztWlGzs%1l&UK+-RcfxrwK1VCt>alkQfa z%>OpeGq|egzoRPPB(~Xdgm{MAEK@Pqqd#}3rV@!6; zH@wp~Vtd9R5|iWb`M~P}lg!iIdQx9hfL&B6`bAZhX>+^|R2%>mcKJ_&bl`am^he*_ zfK^TWKkc0RKhymm|3BBK&sCo;A%s$trYZG zNiJX*0eI0+G5~VPSF8KGK?Mp^;i;ZW6*H|2WnTcuN#tCY3M8s2G;--JA}FfM&@xbE zm(aZPY-l5?HRbXLAvk$a_zS=yk0=R7%oRKg;$qjD*F60Y=urIVyBpHBe*TQcpS&Y zr;r{uvY9iT<6UD2F${YT&Ds`??unAl79KVJj}%$OBo_%-3h%ir`p`?=?&luwr=2*i zdcJqr&-K;h5*fg0zXc>y&!~e}S19o<3j2TH&@0TWg0x&hOw?t)0m0_FK#jkC_s;T^ zHD=}Q*#bV}Qz!l^kgJ_4U;G){H5)xcwaptScWd5lVyR(AO$N&o4xEj<_ikb6<>Mf- z?8fU`BOi7L_BKbgoB{&q!$tO#eh+PC`vZST97rT;Ra<=>dmPcUp5P-bE8S1oH_F)T zv{Y*ng=M{n9-QP7TOT6(F}-{mi&dHl84Et^=)zIVz-la=v$i;pdeIY2lyBb72ogkZ zp_Se4;p&I)$UwAKI!Ee9V#j_~7ORe;%HXcXQZ8~*3VJnZX=xp5!+rv!k%21>0c}H{ zmjHf5f`4YTBGEq-caXeFr^J2yTZZ4IUuV52Q^6Y-1}V$lDO?xR>s5o-gVdtFQL-wS zL0caFlb2M(lF@~E!W6rY1RZ_<3W%`3AZ{i^u$8&D(r)s$@V0ua z=RUT0bG3d~%%q>C8-WfYQb)Am-10=LeEX`;umUTvIYzkQbMNj%`ZK$WD|slu#W-&R zVHD+DH2aNq&C63mRXZ>1*&@lURP_kKeQE6Y-gjnqLF~}=`q34NS<1y)^-~sm^P}6n zvjJzNk}-l{Wf%o6gyC%-M#80mEm9*jTXnrD=!K%1l<3_M8PmvCvczR>eYm3i$B~X~9<4*ZPCuG5yH zA11~uYjY-jeC&*)N6I?Xs_(tO%XYlJfC$C)JAJkXUY{5p9W*H}^7?nC&o|EqIYnH> z0FaWIOg9=)ei62fvs4Z3-j~Y$zBYRKV(s!9f{xMGa=p61Awl4p!kHrajdZ`=F|q5+!4VARcFaNi~$0~%IJB?Pfp zd2g#r-h1&)JKeD=8WPkkM%M>UB@zwpO;g-v=rIa0K3W))+r68rtTo%NaMPW+!szV< z6G=`MXuyR=CwzxeU}~C+Dcl3+YZAg~n2MjDO_zzDDMYXrJ`=K@NJCV(eSFt&eL|^~ z>!C*Kcr#A#SBru3iA+l;)S<44fJ#DcyOl%O%F6}78i5+ z0a43oQ2x!XY762iG9BwBxo#6%iCb?;`)7v8Y1xmGiKGfAAQ=NhLALqCT{PEFuPCW7N(uhxfjSlq=lWwHOL{ zzalPaoC*Zz)?xgxe>mdSrl{m`6%#y*QlixY23M8uivjkK$)%K^^$-==j#{lFfx1S< zm3||gjD*$GsiMY-kC%e>xXCW`rl_Uc!V6TlnrK^b@Z!P8g5a&4$K~;Xum;OAX_~FNUhbuzw&IYn|CTacnrd%iu+4w{W~@G+(4G=c5WeNuSPZ1 z?%v8|&>X)mh;d?joFJK0G96babln{yYcUoyyQ_%CwvPB+=Ui4bY^VPfuchdqw| z1+xnCr>7$*L!Z99wn{6uwzzR|@)IK=p`&0hb|7|`y0<{P#4f@Ffsc^`1(mPTb7G$8 zHV}~422$bh-c#dKV9&o}(j3%bDVNNZXeoQqjT_p-i{H|3gjJW;qS>GB_2x=Trm*7c zc@@J!Y)Imr6TDC!dC6^;xutV)@BRCsY4Qo4Xm&bm$)@7mqNKR4{ZEG(MK>&0PiV97 zn4XfKqLm$0?U<5JsXX zuxV~tiGr2-X|sL#fiw&aj6Q#F8+M>uC-j%KvYd@uOmBU7`Qd|Jtsyy;kdg;A3Gm-1 zhyMMS%XIRw!kK*u`?;ZGO@MRuJeDZtG?vm4pvKXbagSNpvj zQ}qE}ZFSDcBxWNu4!IK#$WC1Dr9CGU7H&+!kE|{_+wVcVWLNYApje=j+;eNV`Ab6f4wYh0m2b9_Uh$61h*mo)0O!6#sUJ)h?_Md^)ff2X*32s#m=u1Zo^V zhsX4A%;?@Op{Zs>irP^0j=x*CJL38D2lj+ch=ac>j~gwvba^amHN5@B9m$GKV)s zUrhl6y@5q8XU3^jXVWd^BCb!@^IvI9!rFc4Ju1ggu<5F zu0HprW>`U_xRMZJGk>7@OQKnSa`K^Ed=-_;%VhZTbBQzG7aJ%?txzn`CacZ*zLq1+qV<^;(H1I_%J z(S~gGJMo$AR-%^rc*uUUNJ7`+Ol3;zgFZ1yQ6Wkm=C>C<9xOy?$O)Yes|K;_c zWeGPAx`1kOWBMy+4tC5kw?5uK9{69WB`Mu-eI+REw3z%GpV)E3rw+3ADJJfF`dG{4 zms>T3wp$v=tWf6*CGxaVR)@1T_zfO9lX}C2OJot4zBVyMTOp0m&^7peCFSXO_ zXQWbEXrEK3jVdR6$~n0PCfPJ_P?Nb5{YYmE04-!%UjG`c0OZk*CyFUN=C z6V&Iv>8?hjtI7vZ7u{>vbD}J&3q3$WEon<(OR#0PjuT0FOkI$dqXI%1#3vFOGV34l z&28G-dQ-}?bBi2eoi$4TfCqv7-;}uw*>cT4K;5H$btZPyVmN)T8ns>FgzIaP+WYdE*%B$O_3a>8CGy5q~jOcKHST*wI#@!Hd(2YKCbd%y3?{UJ(*4WA=FHBC+y0?xj57mzbj^bYp4mHcoViH-;HAYUVy93o{ zCdJTbHl?)Wn1^1n?#Pi8%*S6yUo9r~{5PJLS{{V;nR6hh0&>Sh`&eHh$h{1Ta;W0{ zKab@vU%{b3IW=GuVrXaz+3-B2TaUTmAHN=v61|@Jk~10;U&6NGy9zTJK7G7l8o0gW z#yiP<17UVlo0AQkDM7>G94)Z%_M%hMpD>yug-asg(3}x_@1hwXxD1X8WCufZ6{drj z^0um+9sLu0h+ z={@7+UyCm#Ux%kk_~#$ph_&~!10d*EDO|p(=5Ku&m0y4rzn6{P&d*YF5{fQG?i74V zSyAVhYkPNJ688)P$~!*~g@=J9mF~-U&wpc1VeiO}ZoK(k5dCejm`FTr=CxFLji)+6 zowzvH>tDc8^3u6fi0<_Ec4>S7-vzqC2ZABBq$qTJw3_3xGr-8I*>!U*y_4+>R%vt~ zDD*(^P-(DAJ0IHS`rw-5bI`{bck0gR*W~p6o1{A~f5&sNqE0m$CaQr!`jF**i zZ`-)KSmPEuO|%nJ5Ah^|wT-$`fv=y>M9gHnsNqYmRXIWB$rh$kK@ijE->b}{5OxGmJ< z$&P=s{k2f1-lw^R#15=3O7caiJvZYnAyPtm22)$0M$caT2Dx_Z2?rT!Gsd$4%?JD- zTz{`{iMt6}#X@VR7hHj#uWbsagY@AU%7tmS6LHdrRFMuW>(`<$)NffH7# zV#i#3z`3=+vvu&;+hb*8_t`Y|a=X8ijI@k_)^Gke;I*@n9gQS;`iIwI0Ho_*q-8Cz zx3}gL(-&(9{#sl@|KL?$8x`MB|Dv5+YmKbsp9D#El60BtTZKAh{D}n?v~PHyNOe?9 zbCyx}0Z#_1Ppa5(GJ1d#roGRBG4z>-RU9AQPqF>6lYp5Dq-LG?FK@?Ylhq&XNS~_ma!FTJsmG|iDyPrq1C%PkR)=aY!$q~X0 zfp%SyXIYSu)!nMyid!MNFhZc_Lucfv$(E2I=Xr=DEucSl z-wY#>6zc?qLkGM|7l2)&RlwcAIFR*Hgj%wvv^jDzjjV_G1)|>(0cl=T{5sNp#Mc_cAKm91(auABLbw-P2C#$KyvT2t7r`BytO9rRS8W@w2nJT*j@L>Wg+o{5 zRK!KP_-kYc>A;`4=~&>qlAA~iFUU%RbGu?}G<*`x^p7jtfRAa|yZM`5%;8^N zW-3R~xYR$Q=>w&aqE8caoqHUkrQmK8ks5kfP$m4h)+pLGGpLYw9YVzw{Vx)%sNJ^H zlNH(Mguov)*BdBT&&LXYZAG3vWGZ4ISkR9~ICQS`iTQ<#w)dShN^bGHKTy?G8odNd zM{V)?`KW6Mn?53o@pEc=Nfi33VQ-$}brvc4lb=KA5m3Di<$2Iy2*-8hm|1fRhcENv zf%}`@hXXWfkeG(VY_DnWw8VR}G%2}|w9&oZ4!euf?<6B^hW<{nvIbf%j-WPfNRK{& zLYCJkHJ3|;A@tIm8}hEbggi557FYVKfa(yFXgxVlIm}nX;yT??SuwT9ZzmWTjCdt? zD=8{5FgM3i4*WPL{cbR-l06q1Q}y&#!g}X@jnf$|Gv*7A)lR5g3cl&N{4hR# zsXeQJ?LIX4ZvF}pNLFeG<&P;w45))L>>~~Wh}dJwL{@1wfx?K4r)QMCeWOQ6C_~L6 z`0Y{yPi2;{xjY08heK%ED>|8!h&`PM6US~x)C2=A=8`GMP#x1dyvmF-DG!r;pZGOg zD9tUY=+Olw+(;Yc+VIW$Oqd<#Wn+H%lI!(C=SSOkijXV<0thoY0|YL;sLOCaKx|vz z-tPDOhsSf(AI2|OnhHCaTuNrDG=x)`gM8Y)sex3~RBA2CCX-CM-G4KV9;3>)5i_OA zU4MOL6cqZbucX`siIgytK6H}UOSuY9AY0bgzBLqe%3qiL^M%sK&s(cHr^@^f%hjI* z15A|4p7HuVXmstj=0#{soB>^snpUsEN9(V(KOQ5WlA5`y{DUmNKMU=4!%a9n&fCXs zb(E2n=^x#@ucp?Bd%XGxz+|uas0pH`mTnk(?~v18w%pW}O9jsamh5;r`gvqnNL&&X z1eXIpVIx(QJwq+|&77Dy1_80sk7}r1N!PFPFB`Z>W+mb&3A=OW7GyVt)Q zc?fzN?4MCu;%_mL*T;o0Grm99XIr%+Jb1Aa8>4p0KXa#5mI+$ z-w*5R5ndb>6!WEg;LxFQ1yF$>Zp>^^IEX>MLm=D4Z~Hsz3PPFYM^zmY#LQ(JfS z!Zm2BxS#|J9YLFroyq$wv;Op&Hn7m~uioL$D%pI_8R;_`nY(?16b&^)rg-gyIqpn+ z&gA7w9V}Z%)t#XvVWT`6?wKoj?3K`Rl4B_s0EqM|vjKai1c>b+b{r-89!`V*`tiJI zl93Y$_>PKV10tzE!fy%Tlz-NAWg$Ox6}h6Zk5m}K_m{a`+!JuFb>qoFoIO>G-mVkD7?IY6qj@FJI(6LSYc8GN=o}+}7A4HGw68D0E0M zpxANeS1VoKWNHg!0-ZGO$EZy#-G6uzvU{lN_ zCQe_D;#k{DhzeCKL)3)V`_h9YLDfIMe-!vffqxYEM}dD7_(y?%6!=Gh|33w~`}chQ Z)!$Mha$N{W{J(#79eTr@diBRa1e0{(F!uz-9E|KoIhgub&qH5JAa-ggd_Fh#S&4UO@X>qbrr~{GI?5&X*nje*8iH z@izeXe~zEH(eW(dsQszaV%g^R+y5RuYMztu>%+x5@0%|Zpo#aLwU_0_i}9!^*3uGh zgxAE#X6*Dul7NrJPFzTx%ZCAUz|Zt`N99s%&D*=1AMZbHK6UWi@j2o*Z=ntVVC6t$ zL~O`sqn&*fNWgwTz_99{C%UHho;KXQ-~c$63Ybzj!4TQ^8VvZZdhh!w!1ptN&yecB zjsnmCfL~GOZ!`iP-hTpC-v8wr z;6vFB1=WLJ%J)5fPSO$PPq=Zh_2Nmwe-h!pNn4e!$2|A^dzuvA^L=c zhL_RoK}_D&TNxip?0#T#hYpt{=;lH1AU=fMRfEK1PhJnrRQ!=8ZcHpxDH#5l>Fe#w z@ljg{X*e?RRMzy}&vygxdEo`d!qbiLKaM>=_O#2~brF8ks{05YoVmas`$Rh{r&e^vvMu`L z`;PTRs~k`vAj+(c}WWO}7f$dL6y^kRZSLVLsf0-BhG)7RUu?bx;j2X86wkHIUL=} zVBOqU;W+*Z?4{=vXBB^2f6Y*H4Rd|q%4?RjtZM)HO8l;`S{Z zFzhtkpBI%E-%`|~Kr?7LA4`tSh!x(+<>_&WyD3|((~)c9t2ROp_kGeHH4Cc8V{-cVR-u1A4iTokPCW?{35X?we5Lr+*>)b|8&pcoYPP#?v>sP zRdJu0j=D9pPI<_Grk{GCFT=^At(Plvh-r6eYD-+1wD8_ z!!Pq%_Dtrz^bpVT+41~(iMfS7Vs5zp1C0l-W3->WKk=^0X!hy3TX)sY*v=OFTg#kW z`zPy}t!YbsQoda!q0U18&0LOs+NSn|9?F>O^W64!Xy>&fw&2T=sb}BRpI^7OHz7}! zk;VeYRCLhU=%Oh6^7YVkHE?<6oY$kFJ(IfRXzK1h&emi)bdfM-qRewPZ zxcsiDxRu(c%MngzoFeMXo6Vs^*JzLUi`(Z#{uYmEe*O7w-RJ7jx`+p>Qwpu7jpFAM zArj%L>Mr1hzao5B22eyFVpaO?-CvI2`)jbs@wH^|(*X>|`10Ql*VC}*ZlZtr2DIMu zw0qiL_gS!a!%p^bmn72M$^yzhm2Z8%T{rj3!NzSkh8>BZP$n%1Jxfe;W+BtRjPszi z!lD^=bNJ`;m18)m)udS|H-yDx$j2xcJNZY=kDea~G~6|ooO)}^sB0fJ3pS&2;#`RK zySP$9%h&}9xpg8udR_FX%xS$z7W#J+{&Iu^yxA1R_8(aOdm=^BL#m;5E_R~MEZnvu zHzgVqExc*UV!}d@e&ZFbtyM=5V9g|1bKQX1tj9=7)>39TJ^*@2^xMtZuS&F{m}vUl z`Hy{V<841^8F2*mVWxTH|C0?jY+vi5+Tc01UF9i`XnR8Wu+-# zLxJN#RU}yA49X46Vgq)k%`2RcOfL>`$w9w7WW$=N*UuV^^xk0n^nh8f+3@+>=aW)$685ehM@b9kob3MC? zf7)^k88Q4FHO@clJjH>$YXY=+&eI%h4FE(b0sxPn0sz~4f^h)=2)zOTEItGPbl(F2 z6391h9k&4h4gEVNH}6GIeosW!c{w~dw-b&-m0r@oT(vK``u4@^E1Y=W_KRV5Q=lm+fBB z0u4`w!jdu-X5~q7eq(5GpUz?#77eyEnL$Hbw?*$-;BX)YnyirqWp);&8J10W%FS70 z{K@elKtYef`y42}IKlpihC)niKse3alicHWmRKM7q7^oOc zX2G`Q*{jd+^p6vDEd??$Q>_{!T+QwBWW4Xt@Ze!w7YgAdbdo2TK|~iZ1Y# zetv8u-|wm%EDtz)jnwTJJ4&fA^_`HKJAV(}c!s!rR|`SWmdn3wlKf-NxXd+CH>s_% zPGTg#JlPlx*2)6n(>I+!7<5o`_(*>GEIy%T_0X$Rsqw=``yr5*g2nG#H8ttOqVg9w zJ^9Hf^Q;Mp%F2ZwNUmvI&GtKx)PBI0!$;JhFx2A3siioaJSICx z)eeqZf=3kIgMN4)+!Oh-xu_@s9b^LV zAm)wzD|nV+NC37NT;5*z(j73EYv?bGgP=R;-8DGiU9 z0yF*J@+mP}-bCo%jjo?sCaC7)`&`vJ6O~Tw1dAI$)Bjj({5mo#U^1HOVg&w=y%pq%Rd~$k>6S!;m-j8 ze(1q3m|Ecx6~0B}PJ=THq{tX=ov3B?W4jI!izL;{x6xn=Ts<1SzHz#C*Ab3L%=Uy7 zu6FDRvE`F!Yw^*;@w%F64bD91NPJW{>J#Snj5KIgHaLxvewhk`I%Qg7gJ(p+=wi7`*DyumOz}KH!#Z_x>u4%i6(emJQ%IhS zm$krXul{pVZueOpr!RbWTZJ&0o?nP=-L5B%*L!wVx2|r}g91VLIj9F7NUcS-_IMS3Pi6vI>31IBCd3s7{R z=#cnoE!>U@NR!r?y=P$%vlU>W!>Cz?DqFmmZ2Yw3fj$u#!~MCRehM#&M{hc*9JRvY zYRz0~&S(eO48DD(DT+a#sMgU@l^AJ>aSp1{sW(>{BGuOl>!9#>vd^FfA^s{p{X}B+ zbFU349q>y^?#(puQnR=}ujK(XFsJZ^vBSX|4{NPP6!HyHyVa8;B)jC3fN(NR0)IPM zeLxl8yZmFIoTx3T7*r0zfMG+K5ZFlm3>5YQG_ZEZWKn*%p%r)za0tLZ>oJuu&wE6@ zIjWO=gT0^OE-ciOELE$-?M{JQBdtP!uFR!a!A13{b z*Jm2b2vSN6gJB`}$hw(_QMJIS{FF`4`iIA~zMN5=gavWF_^OvzaEb&Q6GwwUX#29j zvYxqK2~@-WxTp!Bboh8}w2=XSD<^J~ZnSRcMDQ=gzp@K7nXy!>pI@*=tuYLL%d187 z6T_A!Pxik{0Lthehc^}pau&p8p>4!y9qY!OC($;VbJv?BCIV-C5`&Xvb=I6(!?(SS zHl7%*9jMqM@2#xa(n6g2`aPzg^dl*#jq1GB`+J`vV|h!$E!+%R4RedG)ffLsBPk05 zDHp5n`a&1-$qInHJX0~KtrQLU10-&jkACAJzN^YG9=az!C?vzXg07mETJ{!v!3JMj ztc21C)!Bx;{H}6n8I_=~IeXQ0qcU2GVVo35x@{7DS1r}Wnf}QKG|Gn4F-K=V~kDw0sgD_hE|Y8sc!~|gQ7Db#*i8DU$rBy^;Mu0ktTJL zaLca}AUVkIcbG~0WOFo08?`4R*e0|09n9LBKnmqsUrxufv}+L5%>>{0rPpBY4rw<# zVXK+8_p~5f1`+M%-Mnb|p&3{?5Pv`z=(6`h3Wh6Y(cXa@rk~rYb{=Ooq_|cy3J0mk z3WQ8~1J{_w?qTh?Vy47z1G!DQ7gy55n(s57?VJu zU9?{R*cz1)c8@e?Y@q^-**X7tA=|IM4n#7%hructA4jF&SSt( z$*1}f0!B;nb}WRy^nSFySI2qDIG1st>t4)7nz39|?Oz}b z2D*!T!_NC`6Q6ZThuF5+5&`lLfAYw`KBxLK592|=P}4aIg3I;!YZ`&oNio7?6gBrP2SIBw=vezi1nP02spB_wX0}!gZ0#C^zh?-k9mI{ zql@w0|{HiRxZ=CE7?ZmdCE?c*&l5yggDORQ(+)G{<0X3-6mJR`90N` z8vZ-Gb9%vT7o89>Z!Eh*uh{+Qj!;pUTpx*x?(UGVxwiiDazz98sa(TyLyDUqx%;;O) zl{H8kxa%o~lJ40GQ8pFv={gf(C|Z4QdXw{UBqq_vcNl}dXa{}?J`Y3~2g|L16);!% zD060o>9P)ysFNY|<$n=!29T2ISABy}I2`Qe^yr~TD8%*7q@{61$oSQbxoAD7x>3@$ zwEeZy@$Ql2`7#jxAL>BLx~~&VOA}K=Y1X%5h#X^{cop2z_%zIE*=y|Qb&(LuvgiVJ zYl6@}IzO&yt{jW&1dEaiY^z4!iSvZhP3kJXX3Kz+vXi|Z0(GxYS&2BgT`-#S| z7S^uZ7MEd&WVKu?lIQNyc7|O9Pr1UeTZ38?;+2LO!CmEah>eQsL^Weeq)$&tLA7Pi zbP%}HnKilbw?xavVyWTE1dTmlhWdKmjYl_PKe=2n*5cVkrib}Gpq{8)YKM@rQ;hpo zx4L2i`?k}wkWJI{k=($|NI2ZsM7o8(LZSSOqAHN+DYVmK!a!LlwTj&1fz*`iOl4&D-DEX;Z}kAuO2weQY>F(5W1dOszCPqQwECec&FFOViqjp?z>f3$^$Q0% zMW9~S?E~sF?`aZr+z>xUvtHZ_HE5*Cl=>HOJqJXv_nf@rW6NVE{EE~1k_#qg95k{k@)D6RO}YOK7@!-G!MSy7PwTc3f<4cOq^zQ!W-^(zN5H#veD+-)Tre z?QFIsx+rCDzObE;7L#xd;A4w(LcirsDCK_k7A!!3z{0uwLD$Wm5ozs!&&?Fy;@q7T zQtWq+$_O@a=jXD4((QIK&guNGS^+Q{R8BS*i%zD=hzX0zN8I|XWo{D83i*z+QE}*`9qHC^-iUl%U%qr^E77NT+nYND+)LNI2 zX(2qB6F2p8;doJKvW#9BF)5GI;^|l#pCheyJL;CZ5sb~zNIV}-pl)GoYf+3 zpqN>U8)@DQm3{6lXh<+ads`(encCl@&5ZVIBz|Nz|mlibeter zY6qN4=@MeY?baezf=C(&rArlA5e5YF@5E+M=2+0q zA}kFAeeD)?Sc-Te+&9|~kpNb~r+3sCI$J(4v~W^+(O`WTK)9en>*U)DT5xHU$H z)Od9NL%@{b`xbo4cDXSu%OqOm(?;Ak^KDRRwbi*u z9F}TaVQ+w;-=-N(z=h08YtY4?3o$my-itPRE)puAU*r}^dA^1eJC9dNu7td@zWj>X z^-<>awabUdLCO&1%N?vOZQ~bdEJH8 zvRahoS)0}n8a=XXU}tF(%Lek>gL5OtII(;1K7Gax9*={#lyA#h(UI0&qnlHhrLov4 zMTwl4ffr({-;j~~ioHH+tqg(vYLL^+VeL&fiS)Jp?zT-x&|@`SQ%III;HJWjxYH@l zycP~>N>Emtrk_id^QYnoNLnTHA4maN5Qom2gr4{r{Vjuvyy35s$y(oE?whw$gUb6YFa5}; zUC+HjJ)in%J!IVeWGT6$uB28v9(xE}Dy}aV52d=ddMyqNJ-eu$ey9`_<^EX^4@;x< zrO%deKvE2M#H3xxe``jhWm`(%CVmU2)#$kjbIw?KWQmv)KCwO6x&o)IWHa=auzO*H zXW3H7t+dd+*>oplG`BjB5InMqsMY5%`7Sv2%51aRecNdA9VOO8eK3RQwwX_gTEV!jmoYFkWYPUCzwe~b z1Y9CsZg*6VKW{6@A~$u4&ElpR7_?#RE}JkJOXXL`Z<|u8iz@)g2h^(=x~TD`L}%C? zjp0wF!Bw_RmDle<9bVzgJcw<65F5KL?1<`8&5Yjc0m7m^5j~ZV4M}{`WvC1k?=#ku z2v4O<2X# zYiL1d|0-I&KE#%C9;_?ZR&yC#q={BK)?mIr$jFx6P!&%cy(@)9$*}bF9kMzNhu*80 z_Hlm>ia5C8(h4MbpDF9v&rMxw)OMLC{JRW?KwOvPpPhl`M4#rAwuV1C(FNp$T90h4 zCcCBSZtgfy+OG4vmzQ*Y&54mL=R``in${~=AAV@ECW?>ft4B-Pkqnt-@dg_=#a5Tp zV#g}@Yb3MwX}#q$4%1{({3rS)FUjPzt*Q9(2LwfS)B3X3%uZ#BOZZHmp2PkrC;#0U zCBs<0sgg;1BqO((RRa;!pb~F=rj%;jI+8Wn%#;Tm0iNyet1u?_;5Hp zTtIU!f|T+ix%^Y0i)7JkwATA+-@he@Fi>vy;$ig zf>K_p#(S9%L3Edg_<7pDUY-r&?RuI(aMG)08$8+pnG%bkL*2&A?{D-ot{8^o#JsW!{ zG?QkHDE@kPF@&hh3^h$UBl6BV{#HtBI&AO!ao_DPJ`Mfc^|VkqO|VVnJf@M%)@L{> zFFFPx$pbb>*ptftzY8l`+QMn1U5pg}j(7fohTwLUGrR`h2Rp%yAl`h~k_dChZ*q1T zQ#t;v-b(0qeb-VFtX$nKRC!4%0-lKyLc#-Z*xT0Cy99jt^-XtO;n1k&p{Jb#zt~t` z9Z~#D4fu-BgtqeKL&Ia*CU?|Dti4eQ59Erfy`ggHPf#ychsd8&Vdyi1Dv&*#tD;Zt z-Wb;aNr_N_4(c0(T3k#rciZbNzTK{5w2dt_iueJ`^kTJjE9FEi98l#l_qN01fpXYt zt*CXUy?J)U-ZMe3)%pHzj>7KrY-?lmNI5dDJ3MxSuB9B`l#U)q&S|7LqE<3fc76%+ zJ|lLSVXMDA#Asge;@KKZcg1c9S2V4a#6|20FUNIzvzOX2F^dqb+^C*zqiwoT(A?SL z3LI{CIGR6~<+}3CKq-4%{8oyJIBg53=29BI+s5Nh@9>PSDD>&9Z`dy^B3N5(Z*3>E z@}?o^*r_jH$8>HGA;Ay}Jo@_V0n%D5BnuciVG*%3y&Ca|9nsdC+1WlKZe1&LPzQf+ zT31Dspy!hb#4F3iSb2*U$09``t2OC=<(?nrFAVruq&Hags(_9`l*CbYH1lu2c;g*h zWiXPU?rejV_`V4GsspAayD;M9@@Ml$68Nce#%RjEyLOfq#yJyN23$nf1zWMDwQ>3< z0oR<)c_b75ailKmROs=4c^S(4U@4V~T1-dsvkzdK$XHbq9&+?=H^O%sN4Y2Y$A^@msMC-) zE;5qT)RgC;g%hY8n-f!-{FMRdkt2>jGu-}IPZKBz?Ck<#M+`mteM5aL>ym2j=y5Y z3ZKTTzgh-S^h4$!_Fy%J%Mn}F7?%z4xLV=sDpzdOfgxRAv}dzB-5oXAJO#q7yP@QG z-5ecfOJ3aW>HPE|;Bh!FCR#4NAK8DfE(`tda365e_OY&kUEcNv-^Py^9W zyYlOE2i}Rl=G=K7Bo*&#M+mLJ9XCrpr3e-J?0USkM!@9lA5=e-Tk_S~TB9ezO*x;e zzu{CjBuV_uUG1G-1*~RUQg2uKOYaD@@;?CMN;)`eciU2-Va)@!jB%TIT5=l3!qq-{ zG4gX_!@RYp2Z9Q4fi0XR;6)?PaGY!+KdZ-5bKjhaPv~%*DCzC1pC}YC)I%hXoP65k z0Bm~mQhO*YvuAIEJ$#*M{D)W~SOR1(kn572xev-N(|=~dTsGY;mUD7`P&f0*XeWq; zZ^9ecaS>3osN*b0Pf*?bWakKndl_b;DUi*usFQEjbp z{L>Do`B(|VsUs+KmLf%9tra^S{|;5=&nULNhEfJxBlAG5%7W8 z5Rg};k0m!0fv}9mHZJQncp=P6Q#JeITWYd`RTV8%u(c>UDms`M^}wERX)v-?K+$F9 zlj8La+kFC^7fz29wUrmgOr<;M-G7?+$#|J@VC0QwBy1xBsao3dgL}Nc+;j*GFR3g8 zO{hU?G93*#5t%#9_Rh)*3Xjr8i(;lEG2RDlXUQHHaW+oWxtsz-mj=4Hb{(DCMA6xSW0c@bC(z;dq{`2tP zP?O6K8+UA^K?6VIN=2Df-c;4^>En7r7`g;E@M-W&(;EIGszZ)rNor-;Jn^4+t@s{F zazah8QRdZ#g~NtJjSI#p(wZ9|yS^Z#=enB*7p@?-;qMu{HmlwyRyP+mQuAwe+p<#H z^tm~cpnS#Pa@%DD_9Na)tb#c8qdq+$x4vPsEG~SEF1OwR!Wi-2hV!R@t;^Z#YMHK& z1bSq+v;nysX%WdxM~DSdmk74*GX|WGDQ*?^&8pF1FQ1&p>`Vz8PQ6Is4WI{zZmR}B zUbdP3wt(9CDeyR#-*S9+GkmAtH=1L(@evr?^R{KJwP^wP+%oU#EkUiENP_3>eYH#+F*u`2|RM9 z&fwVD=lVv2wTk3gp2zq4zAkyQv$os^?`jPutxF%Yg9cuM&%T{}hw=%GxP$@rUwNf; zM5P#xU7B=GPlHik1pD4$sgrY-^DYh#fSL#merrf?M!?n-`lzO>B0`+vEAxL}gy`dHB`lIrqhGkSbDxm>fQZm_S zxRKp~y>yqPHCe{D7cfjvjvaFg7ApG>N2q+>yS%6=QjVN7d0ljgcG-GRTX47m3-r#d zWNnjBWFbbLl-Rr0B^kBda))eGtB9ZlcmS8KU+<$QQ#aXeoL)i;-K9%hQ9(eRuNA7r zS((KAI;myF3hEp4&JOKdpo^%UrzSW$IVC3iOQU&?+IN+VK;#4&t(9p;^VBm37u1hk)tE1R zIeM}@FSmPL8f*aIvR)$ zUFWMF4SwtlDMC^y`bF^-z45V|D~kuSFmtM`-qpJW>iF)tQkj|h>C-eMN(tE)=fc9-#M8?Q-g!(eVtyie!UW! z5J|$*qPiTov@mFQ<=!Lv?wVZG6;1tlb6`dx@_)rj(Eek}F^M z%w|Tb`VUb^JZodi0>9Es^%iFftdJC9WI>sOpF=~f=b*%zOu>1TI$(W!9la)f;OKU3 zTWEnYnAGeCDy07%jJ}A1GxcQ%$=1G+FIni959Xd#6b;xjPr!?d79P_c@V(;-lnj@N zx!W4HXP-}xR(cxkCGKq($Ih`C+2MOjdSSI%QA8?#M!=%a2)H@-l!&c1`#CcZa*Iuq zevTJk*IyF@M)x@AZ_3UM5?ZDrv-lhucS38gjWl~ACTl+}`%JP?JjXS;q+1%1~Go#q7oo5u#u`WQ1dZM(*Wg&@TG*HekXzZo#90l>T(0b>I^z^An;P|Uy9zs`FWz#=bGS79tCOX*id=+W56FxpqgI`0 zn5*MFIQph@G`OtfJ_O`x6I4-auYkV__56l_Ie6>C3A3Il*l*3C#pCsx+jl$98m9HZ z-iD@^OXF|rebeReO)_W_o`Z|+rC!Q56R_emN{X_7>o64D8_bSQYp%6v=H9j|NQtoY zQuW=`>Y>5^Cyk55IqjTPznyE`yY?_x66T~QL#odS3JmV6o)3pXhZ@dgb>@&jK|pKx z;+^{4dGvzFhJ{>I3-dVJpL=EQ*W4;R%Ep*wTnLs>k*`LPNWSkyT{4nwaQGsh`xO+z z=5Udh#Y%`K>k5va1bY#|I5t_z_b27uJ*KY@kr9O`yi4VLVJXkbaiX$SM9IQb&x%jFxgzEBp(a?pqh9WSR zu$_dwXVIKhG;lsX!JG+}bJ`!qnhZ3TwG2`- zxAjlglA~5OsmanhL!yJep)YF75M!0WoOGO%Q@*dscoIY#bwuZ(uzWJNSbnL;gP}p~pj3 zO&PmC+yt7x)?^SjgCR>mnNjVpppG@~);bld)|g!q1cjRu^-VA|<1GfQJ3Cq~U8vs~ zEBNQ~6@t=uFQ~_N0{@AQQpemL@u33KM-*^<@eMvH|7b3>)tQ|G%cghj|G~Wr=~-{0 z*&8+_O}ush-iHoVU0t2K)})0wW{t55{Y~COT@0<4AujU z<-jgQ!;?J2nu5Th*^S7-PQ#srxH!^TMs_QT*SFd}=ZJ%(g)JM#c9pvj0~*TjJ%v?- zeLJFrROadX_sY^M8&*_K`fcZz z`uX=A;vx{tWuD7tp%C!o$O=TX_r7#}~Tk8)mS7rD<0ZyMOBw&t*OvV~D=C;lOtv)?C0HgtcFZ_}I zX#gb%EUX*r?gNHPQ%A*s;rAy?`GJ>vfXoYqXL+tS0M6hipo=`sUx0UI*H0_$ZN}_* ze6RMhAKYGVAa%Hh_mmm$yGsLvU!_g~u~?%$Kyz1m zPloBhl_S{n?i^8Wrk6sU zot~I=r~1;@+un11mLaEgWmYKM#uq$$0g>B%bduA|%n)xt$DT?U!Kc&?AXx*Rlr4uV zWPx}04}1^bN`zlyUzf@&u>7hmdGf%)gNx@}3-Fhw5_Na3OhA4mMs4iA<&wjffxxWm zndQxe(VRdR{}2o)iQ2VSU&WtEJ;UvYMR1bj`X{ee*fHQ4^N_y?LY`{J!(zy#fgKj01=O?m*GqR zNF{6~Usu=z^kfuG0YKw#ar+NxVrK390Py#_C-Rjy_zrzOQr0Y#^I5X7S&YYJ|KHaS zoo&_@(ceG)_~d~C@i#Y)-)+_$-dj`2d*a<+Sra1FpH;32Qh$lsKcNMP?ROT>`y$>L zC$RU%SN_8;GMdl+a!ndO-ODeQnY8z`i_z8BkBd&tJv(yeblcaUBA!xx?^g~+XI2%1 zj%d8Jzs^b_e?4_Mft}E3sFL>N;~n0gJh1EaAEZwtw7-M)2VWI-c>LzQPydN(DV@i) z?@Rhkf5U=-$b=-f$Nefo?k`4M>|ey+bbfAoLbl_W+l;NMb+W3Pq^)|$QHka}7dro0 zp2Ww87x*1xM85bwO-V7dHM2GNaBksDwJ46?Mj$#y<;(fgsUIw=&xMO_wT8KBR;Ae9 zDwAE3k&?+f=XhS|uv4nhS)tC?)3*=EoJ+ZEe9+j(h-{=-WL2a)AmWu{CQD24>l~lv z|MP}B--cNqi zGtK<_#(wWYES0U|OAlX#Wu5EEK4#EWq*yrkJxJV!$0LrHh}Rug&NM6D_ah_o>)lJl%MzI$Z-w7Nz_#)m@{f+b z`X-vLnQomPl}<(71w+AlgK*TryJ)a5+R-|)_!jt+ZEW| z6UlWkXz|!0jYNk9{7hP zlxmomu6r5eq-)vL@rR>6+AgkMm&rw~MQzhfay@B2S?;+0>L0<~-Cr+`mJOG6E+lCT z=J@2fG>tTp*)L;}H*?xd64eqr-jLor>e~0l{0+10t+lQ-+?rucEHfUmr(LA^4cYiw zO4wu{%GS*8s{K%&UEUg!?MFE>c_dkGK#pPMPf}T6FC19NTG+p+vEj0DZo_Y*PqO=@ zsN|4Vu-2oXU|4sEZ$KlhXyR&1X=`cAoQ;QsM*%c=@#B&v6?sW$`ITHgD*X-&b<0-7kDsgtMlu&O=u39ItM>E>C`Vetc7L(`kxM)0wED zsLUuncHXuYtBaGoNtl4I9$UG+&w0=1Ju`doPfk4f#OuQ=y*G0&l23Kt%Y6;|l@IA2 z)juR4cuzVg71AuWDzW8uWz^$hR{zm&{JBT(ORz3=%bH0I{go8g$4?Q7f)sC=&2`uE zKl^3q`wT1Xbv#pSEAoh({AHBV{GDp6;J{$!#i6q4d)prPq?P2-7xh|X@7kack1}sx zkgwZ9HufdLp1pCS%Jmp)2+j;I?tz|JXGJTD>>6yci#lNVP;0(*Ne^0 z^>*cjYTr}6m-6UZ>|4={k9srFr}QnA#Vuw^{LLi~uf}A@THI`U|MLBvsyeKR_UqYP z>-3FlV_K00EYCQLTd?*k0u~?z_^acu>(8v2TmL;YUe-4fIHIVLmXnrK2om&qQ>{R} zRCl4y!d*cjRd5-S9}HU1lO`pgz|jeOKM)~vs<8icVFI%cN)K_Sde+%61jgg3hX z^9seCJHK`2;L~G|@F|s+*ve1C*syymzfKcwHXJ*XXeAb^q~ZX=JqYt!9*FGn?E3hI zV>xLH0Y}=6*IipiUM)~7+z8J_ICNQa%F%UABeJ9+!dPg;nozXlQLS-$ z+CpQELYR#Y{$?b@e_-k9Sn>&12^?WIY7A=_YSEUL9D$7B+qg-mc?Uy$M=J<~j{<%m zwU<&xngKJ}k0GS&#jMbpfcx@7UvA8NK2Irr6ftG_yP&saw55lViKt`n(~RH>1DXU_ znPQ>eJDmbN{r8K)TuDbs!=}2OD7wm&8>~Y$*(UE@?ggEdt?8$!QzZ{d@^WwM>HN$b zg)`0AnLZA89Pl0neR+Cf!AoX!PR!1=acZ)|a$~^yX{IarT%BpT4o1f?Du6R#RAC39 zJ!Ez*+Gt~f*Uf2K{q!FQmUr_Yl($3{>Fo;=b^fwrC$Z;njLndZMX}HWOJ<`E z;mKhY>?RUto{uFwkErHWJ#6;-wdvgK+}CnXm@Xs`pT|wx`4K-cVAyRqc;?obm&v)( z(h`Cv@T(7cQzuG3mK^T;7G=Eyr$NzF3TJZLw9l43ynJvgv5$a(m#ziT(WMod9cMbE z8++k8E5+>cUUDQIOUF?<@Y0zV*w?w1YyB*@j*{;dOkL_DE?b!yv$lP1ApgqlZS$0y z5y%_>!p{M~<7fcb+S#>p006rL0Q1fOp!pU6#2~Mo+HL{B@mAx%Z`=(dEsTwPdT0|V zy<9UizMTW5lINeixv%(S7Je&QHM;H8*+aD#7E%BqHG@}t&&>DK+85c&Tu(9oU?z3x zh{_(VQpvDHY=fRtxII75&M zk=<4PX@3a(A@Ki$02d*8bN5d!;W6PO4Q_|(yHwsdriO!wt^M{D4kxQkIWB|~${U7j z_`pM6WQ;lC8rDCJFV&dn15ygSoEKc*-8XcQ@oi2bv`rpk9$sjCqLy)~Ps;u!x_+no zht_OH61+u13Bj5>ue;HoYzDKgnc7XRFSUVQsBC2YM7t6(YA)4RfAlcONcY8OnJevE z{Y^Do3SIl*>+A{7%Tg97ZxC_tLk~Ei0JT3_zGQQhGzukLQz{N-wU1@H+YANG2}zoG z$)HLLG(Gqfket!baQ04PGGA`0^<0&cneoE6L?Aapa|X+K-*Z zM8xtFVMq8#rkGFEQ-Z64HbIUG@HR(ur zu9B8RuI)mawfkj2fBam7UMXBD@@ZHp^2^esin=W@vvW*)w~`E<*wa?byoedPJ$oS0 zu&}=+nY*xZKA*iL9IDW~>!U_zQvcaOjn5P+9;dxeZC`` z@3XlJNmIA6MczZCQS%7v&cI!MZyi<4SYFyh31y-7Pd%PqAwfG!Z8Qv`rOGa83u>i&EZ$+)X& zmk8Q~>gub6%0|e;ML$Snm!#9qT4bMp{_#WM=3_PWK2WVCQZ3qQuy(9~)0Y>p0Pm25 zPBi7~soo#cx^Ji9O{<4OY5BU?pGCt(BBq%eW%3>S0KhpsH)KHL6F#KiUbau1R^Hg= z`UImCg-YYHt@4L=Mxhkk8YY>tPG@S@QCTz$hbv4#%vI!su4Ob;R)z(G!7}Y4ZVp5T zC1K9=2F-D89Cpxfe6=Q7Z)=*Pz!wS`33oIBo4`;5Yw-hdZ3A>>?HGc7*W)42ZuR6+ z+fcd0`s7(_uY$T-H~JlXRI2H7Fe!Wi{E0IXNN!v`N~l@~Ut4 zXJ1~&JWd{2?I7klT*vy<<-aL*h1ONK#9fGMD@SkVYi(c^+-GM|s3f@%2O)6k+5^99 zIU8k1oOcP!sol5pna^ohO^lWZdhX*>D5Bqcj=DOwsGzJdF@2>^gWCN=U9ntmjzq+B zHV<1oTCK{@31`P)y38xQ<|}EPi^QFFt#99cqty}K%qEXK%1-M_3I6?@>SB?iOt3?* zhK=0f{d?Y^|KjiSRXL*ERqzP6n+0;MAfnMIT2KX1Vx7ue>T(j6XtjjY@T&&VAtA|6j53ig8xTa~rG~Ju9jlT&c+Z0Y!T3~=s|h$kt{8=y z7tOXU<|=;=`6t+mhfVu58;s#__m9w--#O3>(_SP7RZAl&DPQ#Gw)@pJvIoVS0{czH z#2RW=rn|57`DI-uN^>4;=ToZ5W?qbye| zv}q%bmZUcm(-$>+P=QZ42fktI&yHpu3h$9Se=J`1UmhC9^tK>*l+j$4a-Fk)@ zX_PiNR#aUdEW@1qDQs!}ug`3i3Jw&ClSI&>C>+sAqE^N_n(Mri*Wnbx{UXgBuZZF# zs*eY)ofnq6kDs1I6C$ZPPW4-}k$ggq;S;gK+Ldd|_v^6!f!fFE@Tfis2-am=n7gtP zPk&J=RFzPABB|lCbFqK;xw}JSF;NS#o%@M_ z73*MXZ@-pDis-*kiCNz4jo@xvkc zHx=!XUp3QzzT*=V3IorGqEK0-vU>O(yWIu+M_x@%?k8tN)&{Pcam`T+tF*x4Fdtou z377%fNNEQDhBSrw@VmgUnkR)T@UySur6V4+u*_iy427<&*t;l^&n}VP36+TSzcUMrH6LKTRADVx||I`Wo^=x7u zN_2`GM`P-1=i1V1cta_S9>^FccdPz0+f4O1Rj@13LT_Vw02kY1=nwx@)dGWjpRpY5 z8I4ONJX&+YPpB$OhZc5K;5L)AAbO0SR&O#glp7{rlOY5jcQ4ZyRWaD_kWkLb@e=gyIwwccIb-vkL`(2$kunghF?t)2=-=QR3KA*XHId41@6<^9>s8y zxs#n8`C$uNDDNa&!d#|D-u7nxqxr--uBVfxdU3eBd$FE)@HsHpir5o(#$uF`v$Lx{ z%wGl3Qz4BIdXvbPFMk~rh7igX0%fIccV02t-P(--NqSQ=4S8hPltdrGnX-@XR$@;^!Uw8*)r9$fSvr zb4>XU(vmq_nE55hSi{<9KMA#qkkQIO*#$3}x#=gD;bKYhQqFvt_kRiw#*buvUbsos zi|j(+oX4sXg>{0-TI1|obnlW7RZw{Gky^|RqlY;*tDone9qwD~!6cW;?=Z>o+|`*P zB`OXzFV9&OK%TK^ELv2;(Vq``G_6HXjCb_me_>lvZ>Vz)|F1vGVP1;NJQ7*B;L+0B z&g?Mb{0reXb%=-TH#EeSY`{@huZD>#v8G1#w{jY3mL^InZJ3^_R*ZsN+g-3mMxY%I zV+wu&PIr}mI8kk7A-5G1d!vSAo$5bttX^<^P&TUcnQ zTx*g;fVTGKOACD=fd&QRw=J;iP8y&<;P^BXRF-2Z3a3z|pfU&>qqU)W zRc1uc#iiNafi9J#;+jC;62xQFe{+B)morPox0?()e244dUhuJ?M(&`3USy@-yiju2 zD0FOoj@Z~hfrR5_!C)Kxv7eGEn|+rQ~uu^(GAkMkf|a`?pkQ>rut5x9luM6%*myVSaBL7vBZh7f%3w=merU} zVc{LYgMMPN4xSCev$p!EY*{eu$5rzmXqs#hYK}S7=5dVAAqh4}tE;~ahFs;?yes@S zO~K#3Hnj?y`>Iwa6O$edHSD&qwl*!x*ImUHCST)zfpI2MeI%ep-~^fW6|4fXRowN3 zUk3U}0~=xe)QumOi%J7I>U<V%q2d3Oj> zGTlX8TV-*Jfo?#JSFMb85%YsP&3;H`-OXk-c%WMfYqhF1kKe|Lx}ng{CtAnnVddpgYx4NnA&0W~0)(gS>bhPbP5Jw7)L1Q0^$^pWjPvn0 z!NS>36peXR8$BHNJ;l*63t}o2QlC(-7}(@!N3`>T(w((FkyKmmEUALQU_ToWSTzvq zUwJ=%1-stvQrv}%5vMs`DD43oftOq>oL)Cg~G8rLnp(cSCsa~Sbl{L7Zs8vG!fl`zKe~a<_q?r;@u`~vy z=ygp%ruc1E=h$@~!P!u$@vt^>F6jbJF7nC4Vq`hFme=ZHQ zyH`z=WT+q_EW@A-lvI%R6NdChVTuB6To2%jPgvk^m^$CA47J^X|Fk~@{t);>;17X6 i1pW^KY{CYC4`{yK*p5GO*6p7UjBnol8>8?1^nU=ZH~~Ze literal 0 HcmV?d00001 diff --git a/assets/share/item/synthesize/SYNTHESIZE_AMOUNT.png b/assets/share/item/synthesize/SYNTHESIZE_AMOUNT.png new file mode 100644 index 0000000000000000000000000000000000000000..264f7004febf6dcd64287f83f9c3ac3756874908 GIT binary patch literal 16656 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 bXo@>noLthYde962_|5H#Yr};<{``LcCw*@? literal 0 HcmV?d00001 diff --git a/assets/share/item/synthesize/SYNTHESIZE_MINUS.png b/assets/share/item/synthesize/SYNTHESIZE_MINUS.png new file mode 100644 index 0000000000000000000000000000000000000000..359d68b1eeabb4794d8f6dcf6175410861e6981b GIT binary patch literal 5557 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 ZW&mY9N*J!;n)~-|6Qj%L%M2VM{sV|}r#k=u literal 0 HcmV?d00001 diff --git a/tasks/combat/obtain.py b/tasks/combat/obtain.py index e144c31e3..50f941b87 100644 --- a/tasks/combat/obtain.py +++ b/tasks/combat/obtain.py @@ -30,10 +30,11 @@ class CombatObtain(PlannerMixin): # True to exit and reenter to get obtained items obtain_frequent_check = False - def _obtain_enter(self, entry, skip_first_screenshot=True): + def _obtain_enter(self, entry, appear_button, skip_first_screenshot=True): """ Args: entry: Item entry + appear_button: skip_first_screenshot: Pages: @@ -41,7 +42,7 @@ class CombatObtain(PlannerMixin): out: ITEM_CLOSE """ logger.info(f'Obtain enter {entry}') - self.interval_clear(COMBAT_PREPARE) + self.interval_clear(appear_button) while 1: if skip_first_screenshot: skip_first_screenshot = False @@ -50,9 +51,9 @@ class CombatObtain(PlannerMixin): if self.appear(ITEM_CLOSE): break - if self.appear(COMBAT_PREPARE, interval=2): + if self.appear(appear_button, interval=2): self.device.click(entry) - self.interval_reset(COMBAT_PREPARE) + self.interval_reset(appear_button) continue # Wait animation @@ -70,9 +71,10 @@ class CombatObtain(PlannerMixin): logger.warning('Wait obtain item timeout') break - def _obtain_close(self, skip_first_screenshot=True): + def _obtain_close(self, check_button, skip_first_screenshot=True): """ Args: + check_button: skip_first_screenshot: Pages: @@ -87,8 +89,13 @@ class CombatObtain(PlannerMixin): else: self.device.screenshot() - if not self.appear(ITEM_CLOSE) and self.appear(COMBAT_PREPARE) and self.appear(MAY_OBTAIN): - break + if not self.appear(ITEM_CLOSE): + if callable(check_button): + if check_button(): + break + else: + if self.appear(check_button): + break if self.appear_then_click(ITEM_CLOSE, interval=2): continue @@ -219,7 +226,7 @@ class CombatObtain(PlannerMixin): logger.error(f'No obtain entry for {entry_index}') break - self._obtain_enter(entry) + self._obtain_enter(entry, appear_button=COMBAT_PREPARE) item = self._obtain_parse() if item.item == KEYWORDS_ITEM_CURRENCY.Trailblaze_EXP: logger.warning('Trailblaze_EXP is in obtain list, OBTAIN_TRAILBLAZE_EXP may need to verify') @@ -229,7 +236,7 @@ class CombatObtain(PlannerMixin): items.append(item) index += 1 prev = item - self._obtain_close() + self._obtain_close(check_button=MAY_OBTAIN) logger.hr('Obtained Result') for item in items: diff --git a/tasks/item/assets/assets_item_synthesize.py b/tasks/item/assets/assets_item_synthesize.py new file mode 100644 index 000000000..863e4ec0a --- /dev/null +++ b/tasks/item/assets/assets_item_synthesize.py @@ -0,0 +1,75 @@ +from module.base.button import Button, ButtonWrapper + +# This file was auto-generated, do not modify it manually. To generate: +# ``` python -m dev_tools.button_extract ``` + +ENTRY_ITEM_FROM = ButtonWrapper( + name='ENTRY_ITEM_FROM', + share=Button( + file='./assets/share/item/synthesize/ENTRY_ITEM_FROM.png', + area=(825, 408, 905, 488), + search=(805, 388, 925, 508), + color=(70, 105, 114), + button=(825, 408, 905, 488), + ), +) +ENTRY_ITEM_TO = ButtonWrapper( + name='ENTRY_ITEM_TO', + share=Button( + file='./assets/share/item/synthesize/ENTRY_ITEM_TO.png', + area=(815, 193, 915, 293), + search=(795, 173, 935, 313), + color=(157, 143, 120), + button=(815, 193, 915, 293), + ), +) +ITEM_NAME = ButtonWrapper( + name='ITEM_NAME', + share=Button( + file='./assets/share/item/synthesize/ITEM_NAME.png', + area=(657, 95, 1057, 118), + search=(637, 75, 1077, 138), + color=(92, 100, 111), + button=(657, 95, 1057, 118), + ), +) +SWITCH_RARITY = ButtonWrapper( + name='SWITCH_RARITY', + share=Button( + file='./assets/share/item/synthesize/SWITCH_RARITY.png', + area=(1180, 262, 1216, 298), + search=(1160, 242, 1236, 318), + color=(89, 96, 124), + button=(1180, 262, 1216, 298), + ), +) +SYNTHESIZE_AMOUNT = ButtonWrapper( + name='SYNTHESIZE_AMOUNT', + share=Button( + file='./assets/share/item/synthesize/SYNTHESIZE_AMOUNT.png', + area=(683, 548, 1034, 568), + search=(663, 528, 1054, 588), + color=(122, 132, 147), + button=(683, 548, 1034, 568), + ), +) +SYNTHESIZE_MINUS = ButtonWrapper( + name='SYNTHESIZE_MINUS', + share=Button( + file='./assets/share/item/synthesize/SYNTHESIZE_MINUS.png', + area=(575, 569, 605, 589), + search=(555, 549, 625, 609), + color=(235, 235, 235), + button=(575, 569, 605, 589), + ), +) +SYNTHESIZE_PLUS = ButtonWrapper( + name='SYNTHESIZE_PLUS', + share=Button( + file='./assets/share/item/synthesize/SYNTHESIZE_PLUS.png', + area=(1125, 567, 1155, 589), + search=(1105, 547, 1175, 609), + color=(228, 228, 228), + button=(1125, 567, 1155, 589), + ), +) diff --git a/tasks/item/synthesize.py b/tasks/item/synthesize.py new file mode 100644 index 000000000..7867c0e04 --- /dev/null +++ b/tasks/item/synthesize.py @@ -0,0 +1,211 @@ +import cv2 +import numpy as np + +from module.base.timer import Timer +from module.base.utils import color_similarity_2d, crop, image_size +from module.logger import logger +from module.ocr.ocr import Ocr +from tasks.base.page import page_synthesize +from tasks.combat.obtain import CombatObtain +from tasks.item.assets.assets_item_synthesize import * +from tasks.planner.keywords import ITEM_CLASSES +from tasks.planner.model import ObtainedAmmount +from tasks.planner.scan import OcrItemName + +RARITY_COLOR = { + 'green': (68, 127, 124), + 'blue': (76, 124, 191), + 'purple': (141, 97, 203), +} + + +def image_color_count(image, color, threshold=221): + mask = color_similarity_2d(image, color=color) + cv2.inRange(mask, threshold, 255, dst=mask) + sum_ = cv2.countNonZero(mask) + return sum_ + + +class WhiteStrip(Ocr): + def pre_process(self, image): + mask = color_similarity_2d(image, color=(255, 255, 255)) + mask = cv2.inRange(mask, 180, 255, dst=mask) + + mask = np.mean(mask, axis=0) + point = np.array(cv2.findNonZero(mask))[:, 0, 1] + x1, x2 = point.min(), point.max() + + _, y = image_size(image) + image = crop(image, (x1 - 5, 0, x2 + 5, y), copy=False) + return image + + +class SynthesizeItemName(OcrItemName, WhiteStrip): + pass + + +class Synthesize(CombatObtain): + def item_get_rarity(self, button) -> str | None: + """ + Args: + button: + + Returns: + str: Rarity color or None if no match + + Pages: + in: page_synthesize + """ + image = self.image_crop(button) + image = cv2.GaussianBlur(image, (3, 3), 0) + x2, y2 = image_size(image) + y1 = y2 - int(y2 // 4) + image = crop(image, (0, y1, x2, y2)) + # self.device.image_show(image) + # print(image.shape) + + # Must contain 30% target color at icon bottom + minimum = x2 * (y2 - y1) * 0.3 + for rarity, color in RARITY_COLOR.items(): + count = image_color_count(image, color=color, threshold=221) + # print(rarity, count, minimum) + if count > minimum: + return rarity + + return None + + def item_get_rarity_retry(self, button, skip_first_screenshot=True) -> str | None: + timeout = Timer(1, count=3).start() + while 1: + if skip_first_screenshot: + skip_first_screenshot = False + else: + self.device.screenshot() + + # End + current = self.item_get_rarity(button) + logger.attr('SynthesizeRarity', current) + if current is not None: + return current + if timeout.reached(): + logger.warning(f'item_get_rarity_retry timeout') + return None + + def synthesize_rarity_set(self, rarity: str, skip_first_screenshot=True) -> bool: + """ + Args: + rarity: "green" or "blue" + note that rarity is the one you consume to synthesize + skip_first_screenshot: + + Returns: + bool: If switched + + Pages: + in: page_synthesize + """ + logger.info(f'item_synthesize_rarity_set: {rarity}') + + switched = False + self.interval_clear(page_synthesize.check_button) + while 1: + if skip_first_screenshot: + skip_first_screenshot = False + else: + self.device.screenshot() + + # End + current = self.item_get_rarity(ENTRY_ITEM_FROM) + logger.attr('SynthesizeRarity', current) + if current is not None and current == rarity: + break + + # Click + if self.ui_page_appear(page_synthesize, interval=2): + self.device.click(SWITCH_RARITY) + switched = True + continue + + return switched + + def synthesize_rarity_reset(self, skip_first_screenshot=True): + """ + Reset rarity switch, so current item will pin on the first row + + Returns: + bool: If success + """ + + current = self.item_get_rarity_retry(ENTRY_ITEM_FROM) + if current == 'blue': + r1, r2 = 'green', 'blue' + elif current == 'green': + r1, r2 = 'blue', 'green' + else: + logger.error(f'item_synthesize_rarity_reset: Unknown current rarity {current}') + return False + + self.synthesize_rarity_set(r1, skip_first_screenshot=skip_first_screenshot) + self.synthesize_rarity_set(r2, skip_first_screenshot=True) + return True + + def synthesize_obtain_get(self) -> list[ObtainedAmmount]: + """ + Update item amount from synthesize page + """ + logger.hr('Synthesize obtain get', level=2) + items = [] + + def obtain_end(): + return self.item_get_rarity(ENTRY_ITEM_FROM) is not None + + # Purple + self.synthesize_rarity_set('blue') + self._obtain_enter(ENTRY_ITEM_TO, appear_button=page_synthesize.check_button) + item = self._obtain_parse() + if item is not None: + items.append(item) + self._obtain_close(check_button=obtain_end) + # Blue + self._obtain_enter(ENTRY_ITEM_FROM, appear_button=page_synthesize.check_button) + item = self._obtain_parse() + if item is not None: + items.append(item) + self._obtain_close(check_button=obtain_end) + # Green + self.synthesize_rarity_set('green') + self._obtain_enter(ENTRY_ITEM_FROM, appear_button=page_synthesize.check_button) + item = self._obtain_parse() + if item is not None: + items.append(item) + self._obtain_close(check_button=obtain_end) + + logger.hr('Obtained Result') + for item in items: + # Pretend everything is full + # item.value += 1000 + logger.info(f'Obtained item: {item.item.name}, {item.value}') + """ + <<< OBTAIN GET RESULT >>> + ItemAmount: Arrow_of_the_Starchaser, 15 + ItemAmount: Arrow_of_the_Demon_Slayer, 68 + ItemAmount: Arrow_of_the_Beast_Hunter, 85 + """ + self.planner.load_obtained_amount(items) + self.planner_write() + return items + + def synthesize_get_item(self): + ocr = SynthesizeItemName(ITEM_NAME) + item = ocr.matched_single_line(self.device.image, keyword_classes=ITEM_CLASSES) + if item is None: + logger.warning('synthesize_get_item: Unknown item name') + return None + + return item + + +if __name__ == '__main__': + self = Synthesize('src') + self.device.screenshot() + self.synthesize_obtain_get() diff --git a/tasks/planner/scan.py b/tasks/planner/scan.py index c8ae60745..5580c177b 100644 --- a/tasks/planner/scan.py +++ b/tasks/planner/scan.py @@ -21,8 +21,11 @@ class OcrItemName(Ocr): def after_process(self, result): result = result.replace('念火之心', '忿火之心') result = re.sub('工造机$', '工造机杼', result) - result = re.sub('工造轮', '工造迴轮', result) + result = re.sub('工造迥?轮', '工造迴轮', result) result = re.sub('月狂[療撩]?牙', '月狂獠牙', result) + # Error words on blank background + result = re.sub('^[國東]', '', result) + result = re.sub('時$', '', result) return result From e26e733f3d24dfcd62e24f3c83ca9e1777a02c58 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Tue, 28 May 2024 22:02:21 +0800 Subject: [PATCH 37/37] Doc: Add help text of character planner --- config/template.json | 1 + module/config/argument/args.json | 6 ++++++ module/config/argument/stored.json | 11 +++++++++++ module/config/config_generated.py | 1 + module/config/config_updater.py | 2 +- module/config/i18n/en-US.json | 12 ++++++++---- module/config/i18n/es-ES.json | 12 ++++++++---- module/config/i18n/ja-JP.json | 4 ++++ module/config/i18n/zh-CN.json | 8 ++++++-- module/config/i18n/zh-TW.json | 8 ++++++-- module/config/stored/stored_generated.py | 1 + 11 files changed, 53 insertions(+), 13 deletions(-) diff --git a/config/template.json b/config/template.json index 4a3b35a87..f15b5a042 100644 --- a/config/template.json +++ b/config/template.json @@ -48,6 +48,7 @@ }, "Planner": { "Item_Credit": {}, + "Item_Trailblaze_EXP": {}, "Item_Traveler_Guide": {}, "Item_Refined_Aether": {}, "Item_Lost_Crystal": {}, diff --git a/module/config/argument/args.json b/module/config/argument/args.json index 8d1a000a5..e7b4eb3d5 100644 --- a/module/config/argument/args.json +++ b/module/config/argument/args.json @@ -226,6 +226,12 @@ "display": "display", "stored": "StoredPlanner" }, + "Item_Trailblaze_EXP": { + "type": "planner", + "value": {}, + "display": "display", + "stored": "StoredPlanner" + }, "Item_Traveler_Guide": { "type": "planner", "value": {}, diff --git a/module/config/argument/stored.json b/module/config/argument/stored.json index b07c15952..ced444313 100644 --- a/module/config/argument/stored.json +++ b/module/config/argument/stored.json @@ -148,6 +148,17 @@ "order": 0, "color": "#777777" }, + "Item_Trailblaze_EXP": { + "name": "Item_Trailblaze_EXP", + "path": "Dungeon.Planner.Item_Trailblaze_EXP", + "i18n": "Planner.Item_Trailblaze_EXP.name", + "stored": "StoredPlanner", + "attrs": { + "time": "2020-01-01 00:00:00" + }, + "order": 0, + "color": "#777777" + }, "Item_Traveler_Guide": { "name": "Item_Traveler_Guide", "path": "Dungeon.Planner.Item_Traveler_Guide", diff --git a/module/config/config_generated.py b/module/config/config_generated.py index 833c8c6ac..3dd679b0f 100644 --- a/module/config/config_generated.py +++ b/module/config/config_generated.py @@ -67,6 +67,7 @@ class GeneratedConfig: # Group `Planner` Planner_Item_Credit = {} + Planner_Item_Trailblaze_EXP = {} Planner_Item_Traveler_Guide = {} Planner_Item_Refined_Aether = {} Planner_Item_Lost_Crystal = {} diff --git a/module/config/config_updater.py b/module/config/config_updater.py index d49dddfd7..9c6b92904 100644 --- a/module/config/config_updater.py +++ b/module/config/config_updater.py @@ -517,7 +517,7 @@ class ConfigGenerator: else: continue if res := re.search(r'[::](.*)[((]', i18n): - i18n = res.group(1) + i18n = res.group(1).strip() elif item.is_ItemAscension or (item.is_ItemTrace and item.is_group_base): dungeon = item.group_base.dungeon.name i18n = deep_get(new, keys=['Dungeon', 'Name', dungeon], default='Unknown_Dungeon_Come_From') diff --git a/module/config/i18n/en-US.json b/module/config/i18n/en-US.json index 4da4bdc81..d8391e12a 100644 --- a/module/config/i18n/en-US.json +++ b/module/config/i18n/en-US.json @@ -461,18 +461,22 @@ "Planner": { "_info": { "name": "Character Planner Progress", - "help": "" + "help": "Character planner is prioritized. After completed, \"Dungeon Settings\" will be executed." }, "Item_Credit": { "name": "Credit", "help": "" }, + "Item_Trailblaze_EXP": { + "name": "Trailblaze EXP", + "help": "" + }, "Item_Traveler_Guide": { - "name": " Character EXP ", + "name": "Character EXP", "help": "" }, "Item_Refined_Aether": { - "name": " Light Cone EXP ", + "name": "Light Cone EXP", "help": "" }, "Item_Lost_Crystal": { @@ -1197,7 +1201,7 @@ "PlannerScan": { "_info": { "name": "Scan Character Planner Results", - "help": "Tools need to stop the scheduler and then run independently\nBefore use, set planner goal in the in-game planner, calculate the results, and start scanning from the result page. Detailed usage see: https://github.com/LmeSzinc/StarRailCopilot/wiki/planner_cn" + "help": "Tools need to stop the scheduler and then run independently\nBefore use, set planner goal in the in-game planner, calculate the results, and start scanning from the result page. Detailed usage see: https://github.com/LmeSzinc/StarRailCopilot/wiki/Planner_cn" }, "ResultAdd": { "name": "Accumulate multiple scan results", diff --git a/module/config/i18n/es-ES.json b/module/config/i18n/es-ES.json index 679495977..5d6bfec11 100644 --- a/module/config/i18n/es-ES.json +++ b/module/config/i18n/es-ES.json @@ -461,18 +461,22 @@ "Planner": { "_info": { "name": "Progreso del planificador de personajes", - "help": "" + "help": "Se prioriza el planificador de personajes. Una vez completado, se ejecutará la \"Ajustes de Mazmorra\"." }, "Item_Credit": { "name": "Crédito", "help": "" }, + "Item_Trailblaze_EXP": { + "name": "EXP trazacaminos", + "help": "" + }, "Item_Traveler_Guide": { - "name": " EXP de personaje ", + "name": "EXP de personaje", "help": "" }, "Item_Refined_Aether": { - "name": " EXP de conos de luz ", + "name": "EXP de conos de luz", "help": "" }, "Item_Lost_Crystal": { @@ -1197,7 +1201,7 @@ "PlannerScan": { "_info": { "name": "Escanear resultados del planificador de caracteres", - "help": "Las herramientas deben detener el programador y luego ejecutarse de forma independiente\nAntes de usarlo, establezca el objetivo del planificador en el planificador del juego, calcule los resultados y comience a escanear desde la página de resultados. Para uso detallado, consulte: https://github .com/LmeSzinc/StarRailCopilot/wiki/planner_cn" + "help": "Las herramientas deben detener el programador y luego ejecutarse de forma independiente\nAntes de usarlo, establezca el objetivo del planificador en el planificador del juego, calcule los resultados y comience a escanear desde la página de resultados. Para uso detallado, consulte: https://github .com/LmeSzinc/StarRailCopilot/wiki/Planner_cn" }, "ResultAdd": { "name": "Acumular múltiples resultados de escaneo", diff --git a/module/config/i18n/ja-JP.json b/module/config/i18n/ja-JP.json index dd3516a70..53515b104 100644 --- a/module/config/i18n/ja-JP.json +++ b/module/config/i18n/ja-JP.json @@ -467,6 +467,10 @@ "name": "信用ポイント", "help": "" }, + "Item_Trailblaze_EXP": { + "name": "マイレージ", + "help": "" + }, "Item_Traveler_Guide": { "name": "役割経験", "help": "" diff --git a/module/config/i18n/zh-CN.json b/module/config/i18n/zh-CN.json index d773499be..264ebf75e 100644 --- a/module/config/i18n/zh-CN.json +++ b/module/config/i18n/zh-CN.json @@ -461,12 +461,16 @@ "Planner": { "_info": { "name": "养成规划进度", - "help": "" + "help": "优先执行养成规划,养成规划完成后,执行 \"每日副本设置\"" }, "Item_Credit": { "name": "信用点", "help": "" }, + "Item_Trailblaze_EXP": { + "name": "里程", + "help": "" + }, "Item_Traveler_Guide": { "name": "角色经验", "help": "" @@ -1197,7 +1201,7 @@ "PlannerScan": { "_info": { "name": "识别角色养成规划计算结果", - "help": "工具需要停止调度器再单独运行\n使用前需要在游戏内养成计算器中设定养成目标,并计算出结果,在计算结果页面启动养成规划识别。详细使用教程见:\nhttps://github.com/LmeSzinc/StarRailCopilot/wiki/planner_cn" + "help": "工具需要停止调度器再单独运行\n使用前需要在游戏内养成计算器中设定养成目标,并计算出结果,在计算结果页面启动养成规划识别。详细使用教程见:\nhttps://github.com/LmeSzinc/StarRailCopilot/wiki/Planner_cn" }, "ResultAdd": { "name": "累加多次扫描结果", diff --git a/module/config/i18n/zh-TW.json b/module/config/i18n/zh-TW.json index 15c6b9190..b76fb1e88 100644 --- a/module/config/i18n/zh-TW.json +++ b/module/config/i18n/zh-TW.json @@ -461,12 +461,16 @@ "Planner": { "_info": { "name": "養成規劃進度", - "help": "" + "help": "優先執行養成規劃,養成規劃完成後,執行 \"每日副本設定\"" }, "Item_Credit": { "name": "信用點", "help": "" }, + "Item_Trailblaze_EXP": { + "name": "里程", + "help": "" + }, "Item_Traveler_Guide": { "name": "角色經驗", "help": "" @@ -1197,7 +1201,7 @@ "PlannerScan": { "_info": { "name": "辨識角色養成規劃計算結果", - "help": "工具需要停止調度器再單獨運行\n使用前需要在遊戲內養成計算器中設定養成目標,併計算出結果,在計算結果頁面啟動養成規劃識別。詳細使用教程 請參閱:\nhttps://github.com/LmeSzinc/StarRailCopilot/wiki/planner_cn" + "help": "工具需要停止調度器再單獨運行\n使用前需要在遊戲內養成計算器中設定養成目標,併計算出結果,在計算結果頁面啟動養成規劃識別。詳細使用教程 請參閱:\nhttps://github.com/LmeSzinc/StarRailCopilot/wiki/Planner_cn" }, "ResultAdd": { "name": "累加多次掃描結果", diff --git a/module/config/stored/stored_generated.py b/module/config/stored/stored_generated.py index a3a99b164..e7748b3e5 100644 --- a/module/config/stored/stored_generated.py +++ b/module/config/stored/stored_generated.py @@ -34,6 +34,7 @@ class StoredGenerated: CloudRemainPaid = StoredInt("Alas.CloudStorage.CloudRemainPaid") CloudRemainFree = StoredInt("Alas.CloudStorage.CloudRemainFree") Item_Credit = StoredPlanner("Dungeon.Planner.Item_Credit") + Item_Trailblaze_EXP = StoredPlanner("Dungeon.Planner.Item_Trailblaze_EXP") Item_Traveler_Guide = StoredPlanner("Dungeon.Planner.Item_Traveler_Guide") Item_Refined_Aether = StoredPlanner("Dungeon.Planner.Item_Refined_Aether") Item_Lost_Crystal = StoredPlanner("Dungeon.Planner.Item_Lost_Crystal")