Fix: Dungeon list swiping in 2.5
Before Width: | Height: | Size: 6.7 KiB |
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.0 KiB |
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 5.9 KiB |
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 69 KiB |
Before Width: | Height: | Size: 154 KiB After Width: | Height: | Size: 154 KiB |
BIN
assets/share/dungeon/ui_list/OCR_DUNGEON_NAME.png
Normal file
After Width: | Height: | Size: 42 KiB |
BIN
assets/share/dungeon/ui_list/OCR_DUNGEON_NAME_ROGUE.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
assets/share/dungeon/ui_list/OCR_DUNGEON_TELEPORT.png
Normal file
After Width: | Height: | Size: 46 KiB |
@ -122,16 +122,27 @@ class GenerateDungeonList(GenerateKeyword):
|
|||||||
dungeon['name'] = 'Divergent_Universe_' + dungeon['name']
|
dungeon['name'] = 'Divergent_Universe_' + dungeon['name']
|
||||||
if 100 < dungeon['dungeon_id'] < 200:
|
if 100 < dungeon['dungeon_id'] < 200:
|
||||||
dungeon['name'] = 'Simulated_Universe_' + dungeon['name']
|
dungeon['name'] = 'Simulated_Universe_' + dungeon['name']
|
||||||
# Reverse Divergent_Universe
|
|
||||||
start = 0
|
# Reverse dungeon list, latest at top
|
||||||
end = 0
|
def reverse_on_name(d, prefix):
|
||||||
for index, dungeon in enumerate(dungeons):
|
start = 0
|
||||||
if dungeon['name'].startswith('Divergent_Universe'):
|
end = 0
|
||||||
if start == 0:
|
for index, dungeon in enumerate(d):
|
||||||
start = index
|
if dungeon['name'].startswith(prefix):
|
||||||
end = index + 1
|
if start == 0:
|
||||||
if start > 0 and end > 0:
|
start = index
|
||||||
dungeons = dungeons[:start] + dungeons[start:end][::-1] + dungeons[end:]
|
end = index + 1
|
||||||
|
if start > 0 and end > 0:
|
||||||
|
d = d[:start] + d[start:end][::-1] + d[end:]
|
||||||
|
return d
|
||||||
|
|
||||||
|
dungeons = reverse_on_name(dungeons, 'Divergent_Universe')
|
||||||
|
dungeons = reverse_on_name(dungeons, 'Cavern_of_Corrosion')
|
||||||
|
dungeons = reverse_on_name(dungeons, 'Echo_of_War')
|
||||||
|
|
||||||
|
# Reverse Calyx_Golden, sort by world
|
||||||
|
# Poor sort
|
||||||
|
dungeons[0:3], dungeons[6:9] = dungeons[6:9], dungeons[0:3]
|
||||||
|
|
||||||
# Re-sort ID
|
# Re-sort ID
|
||||||
self.keyword_index = 0
|
self.keyword_index = 0
|
||||||
|
@ -21,7 +21,7 @@ from tasks.daily.synthesize import SynthesizeMaterialUI
|
|||||||
from tasks.daily.use_technique import UseTechniqueUI
|
from tasks.daily.use_technique import UseTechniqueUI
|
||||||
from tasks.dungeon.assets.assets_dungeon_ui import DAILY_TRAINING_CHECK
|
from tasks.dungeon.assets.assets_dungeon_ui import DAILY_TRAINING_CHECK
|
||||||
from tasks.dungeon.keywords import KEYWORDS_DUNGEON_TAB
|
from tasks.dungeon.keywords import KEYWORDS_DUNGEON_TAB
|
||||||
from tasks.dungeon.ui import DungeonUI
|
from tasks.dungeon.ui.ui import DungeonUI
|
||||||
from tasks.item.consumable_usage import ConsumableUsageUI
|
from tasks.item.consumable_usage import ConsumableUsageUI
|
||||||
from tasks.item.relics import RelicsUI
|
from tasks.item.relics import RelicsUI
|
||||||
from tasks.map.route.loader import RouteLoader
|
from tasks.map.route.loader import RouteLoader
|
||||||
|
@ -3,36 +3,6 @@ from module.base.button import Button, ButtonWrapper
|
|||||||
# This file was auto-generated, do not modify it manually. To generate:
|
# This file was auto-generated, do not modify it manually. To generate:
|
||||||
# ``` python -m dev_tools.button_extract ```
|
# ``` python -m dev_tools.button_extract ```
|
||||||
|
|
||||||
CALYX_WORLD_1 = ButtonWrapper(
|
|
||||||
name='CALYX_WORLD_1',
|
|
||||||
share=Button(
|
|
||||||
file='./assets/share/dungeon/ui/CALYX_WORLD_1.png',
|
|
||||||
area=(490, 185, 540, 230),
|
|
||||||
search=(470, 165, 560, 250),
|
|
||||||
color=(197, 196, 196),
|
|
||||||
button=(490, 185, 540, 230),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
CALYX_WORLD_2 = ButtonWrapper(
|
|
||||||
name='CALYX_WORLD_2',
|
|
||||||
share=Button(
|
|
||||||
file='./assets/share/dungeon/ui/CALYX_WORLD_2.png',
|
|
||||||
area=(590, 185, 640, 230),
|
|
||||||
search=(570, 165, 660, 250),
|
|
||||||
color=(199, 198, 198),
|
|
||||||
button=(590, 185, 640, 230),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
CALYX_WORLD_3 = ButtonWrapper(
|
|
||||||
name='CALYX_WORLD_3',
|
|
||||||
share=Button(
|
|
||||||
file='./assets/share/dungeon/ui/CALYX_WORLD_3.png',
|
|
||||||
area=(689, 186, 739, 231),
|
|
||||||
search=(669, 166, 759, 251),
|
|
||||||
color=(158, 158, 158),
|
|
||||||
button=(689, 186, 739, 231),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
DAILY_TRAINING_CHECK = ButtonWrapper(
|
DAILY_TRAINING_CHECK = ButtonWrapper(
|
||||||
name='DAILY_TRAINING_CHECK',
|
name='DAILY_TRAINING_CHECK',
|
||||||
share=Button(
|
share=Button(
|
||||||
@ -73,16 +43,6 @@ LIST_LOADED_CHECK = ButtonWrapper(
|
|||||||
button=(576, 606, 951, 664),
|
button=(576, 606, 951, 664),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
OCR_DUNGEON_LIST = ButtonWrapper(
|
|
||||||
name='OCR_DUNGEON_LIST',
|
|
||||||
share=Button(
|
|
||||||
file='./assets/share/dungeon/ui/OCR_DUNGEON_LIST.png',
|
|
||||||
area=(581, 176, 1165, 661),
|
|
||||||
search=(561, 156, 1185, 681),
|
|
||||||
color=(212, 214, 220),
|
|
||||||
button=(440, 176, 588, 656),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
OCR_DUNGEON_NAV = ButtonWrapper(
|
OCR_DUNGEON_NAV = ButtonWrapper(
|
||||||
name='OCR_DUNGEON_NAV',
|
name='OCR_DUNGEON_NAV',
|
||||||
share=Button(
|
share=Button(
|
||||||
|
65
tasks/dungeon/assets/assets_dungeon_ui_list.py
Normal file
@ -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 ```
|
||||||
|
|
||||||
|
LIST_ASCENDING = ButtonWrapper(
|
||||||
|
name='LIST_ASCENDING',
|
||||||
|
share=Button(
|
||||||
|
file='./assets/share/dungeon/ui_list/LIST_ASCENDING.png',
|
||||||
|
area=(1125, 643, 1143, 661),
|
||||||
|
search=(1105, 623, 1163, 681),
|
||||||
|
color=(195, 194, 196),
|
||||||
|
button=(1125, 643, 1143, 661),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
LIST_DESCENDING = ButtonWrapper(
|
||||||
|
name='LIST_DESCENDING',
|
||||||
|
share=Button(
|
||||||
|
file='./assets/share/dungeon/ui_list/LIST_DESCENDING.png',
|
||||||
|
area=(1125, 643, 1143, 661),
|
||||||
|
search=(1105, 623, 1163, 681),
|
||||||
|
color=(195, 194, 196),
|
||||||
|
button=(1125, 643, 1143, 661),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
OCR_DUNGEON_LIST = ButtonWrapper(
|
||||||
|
name='OCR_DUNGEON_LIST',
|
||||||
|
share=Button(
|
||||||
|
file='./assets/share/dungeon/ui_list/OCR_DUNGEON_LIST.png',
|
||||||
|
area=(581, 176, 1165, 661),
|
||||||
|
search=(561, 156, 1185, 681),
|
||||||
|
color=(212, 214, 220),
|
||||||
|
button=(440, 176, 588, 656),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
OCR_DUNGEON_NAME = ButtonWrapper(
|
||||||
|
name='OCR_DUNGEON_NAME',
|
||||||
|
share=Button(
|
||||||
|
file='./assets/share/dungeon/ui_list/OCR_DUNGEON_NAME.png',
|
||||||
|
area=(563, 172, 788, 624),
|
||||||
|
search=(543, 152, 808, 644),
|
||||||
|
color=(245, 243, 245),
|
||||||
|
button=(563, 172, 788, 624),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
OCR_DUNGEON_NAME_ROGUE = ButtonWrapper(
|
||||||
|
name='OCR_DUNGEON_NAME_ROGUE',
|
||||||
|
share=Button(
|
||||||
|
file='./assets/share/dungeon/ui_list/OCR_DUNGEON_NAME_ROGUE.png',
|
||||||
|
area=(563, 292, 788, 624),
|
||||||
|
search=(543, 272, 808, 644),
|
||||||
|
color=(249, 247, 249),
|
||||||
|
button=(563, 292, 788, 624),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
OCR_DUNGEON_TELEPORT = ButtonWrapper(
|
||||||
|
name='OCR_DUNGEON_TELEPORT',
|
||||||
|
share=Button(
|
||||||
|
file='./assets/share/dungeon/ui_list/OCR_DUNGEON_TELEPORT.png',
|
||||||
|
area=(1013, 172, 1163, 624),
|
||||||
|
search=(993, 152, 1183, 644),
|
||||||
|
color=(231, 234, 230),
|
||||||
|
button=(1013, 172, 1163, 624),
|
||||||
|
),
|
||||||
|
)
|
@ -213,7 +213,7 @@ class Dungeon(DungeonStamina, DungeonEvent, Combat):
|
|||||||
elif require and not self.support_once:
|
elif require and not self.support_once:
|
||||||
# Run with support all the way
|
# Run with support all the way
|
||||||
return self._dungeon_run(dungeon=dungeon, team=team, wave_limit=0,
|
return self._dungeon_run(dungeon=dungeon, team=team, wave_limit=0,
|
||||||
support_character=self.config.DungeonSupport_Character)
|
support_character=self.config.DungeonSupport_Character)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Normal run
|
# Normal run
|
||||||
@ -250,10 +250,10 @@ class Dungeon(DungeonStamina, DungeonEvent, Combat):
|
|||||||
if self.has_double_rogue_event():
|
if self.has_double_rogue_event():
|
||||||
rogue = self.get_double_rogue_remain()
|
rogue = self.get_double_rogue_remain()
|
||||||
if self.has_double_calyx_event():
|
if self.has_double_calyx_event():
|
||||||
self._dungeon_nav_goto(KEYWORDS_DUNGEON_NAV.Calyx_Golden)
|
self.dungeon_nav_goto(KEYWORDS_DUNGEON_NAV.Calyx_Golden)
|
||||||
calyx = self.get_double_event_remain()
|
calyx = self.get_double_event_remain()
|
||||||
if self.has_double_relic_event():
|
if self.has_double_relic_event():
|
||||||
self._dungeon_nav_goto(KEYWORDS_DUNGEON_NAV.Cavern_of_Corrosion)
|
self.dungeon_nav_goto(KEYWORDS_DUNGEON_NAV.Cavern_of_Corrosion)
|
||||||
relic = self.get_double_rogue_remain()
|
relic = self.get_double_rogue_remain()
|
||||||
with self.config.multi_set():
|
with self.config.multi_set():
|
||||||
self.config.stored.DungeonDouble.calyx = calyx
|
self.config.stored.DungeonDouble.calyx = calyx
|
||||||
|
@ -3,38 +3,38 @@ from .classes import DungeonList
|
|||||||
# This file was auto-generated, do not modify it manually. To generate:
|
# This file was auto-generated, do not modify it manually. To generate:
|
||||||
# ``` python -m dev_tools.keyword_extract ```
|
# ``` python -m dev_tools.keyword_extract ```
|
||||||
|
|
||||||
Calyx_Golden_Memories_Jarilo_VI = DungeonList(
|
Calyx_Golden_Memories_Penacony = DungeonList(
|
||||||
id=1,
|
id=1,
|
||||||
name='Calyx_Golden_Memories_Jarilo_VI',
|
name='Calyx_Golden_Memories_Penacony',
|
||||||
cn='回忆之蕾',
|
cn='回忆之蕾',
|
||||||
cht='回憶之蕾',
|
cht='回憶之蕾',
|
||||||
en='Bud of Memories',
|
en='Bud of Memories',
|
||||||
jp='回憶の蕾',
|
jp='回憶の蕾',
|
||||||
es='Flor de los recuerdos',
|
es='Flor de los recuerdos',
|
||||||
dungeon_id=1001,
|
dungeon_id=1014,
|
||||||
plane_id=2010101,
|
plane_id=2031301,
|
||||||
)
|
)
|
||||||
Calyx_Golden_Aether_Jarilo_VI = DungeonList(
|
Calyx_Golden_Aether_Penacony = DungeonList(
|
||||||
id=2,
|
id=2,
|
||||||
name='Calyx_Golden_Aether_Jarilo_VI',
|
name='Calyx_Golden_Aether_Penacony',
|
||||||
cn='以太之蕾',
|
cn='以太之蕾',
|
||||||
cht='乙太之蕾',
|
cht='乙太之蕾',
|
||||||
en='Bud of Aether',
|
en='Bud of Aether',
|
||||||
jp='エーテルの蕾',
|
jp='エーテルの蕾',
|
||||||
es='Flor de éter',
|
es='Flor de éter',
|
||||||
dungeon_id=1002,
|
dungeon_id=1015,
|
||||||
plane_id=2011101,
|
plane_id=2031201,
|
||||||
)
|
)
|
||||||
Calyx_Golden_Treasures_Jarilo_VI = DungeonList(
|
Calyx_Golden_Treasures_Penacony = DungeonList(
|
||||||
id=3,
|
id=3,
|
||||||
name='Calyx_Golden_Treasures_Jarilo_VI',
|
name='Calyx_Golden_Treasures_Penacony',
|
||||||
cn='藏珍之蕾',
|
cn='藏珍之蕾',
|
||||||
cht='藏珍之蕾',
|
cht='藏珍之蕾',
|
||||||
en='Bud of Treasures',
|
en='Bud of Treasures',
|
||||||
jp='秘蔵の蕾',
|
jp='秘蔵の蕾',
|
||||||
es='Flor de tesoros',
|
es='Flor de tesoros',
|
||||||
dungeon_id=1003,
|
dungeon_id=1016,
|
||||||
plane_id=2012101,
|
plane_id=2031101,
|
||||||
)
|
)
|
||||||
Calyx_Golden_Memories_The_Xianzhou_Luofu = DungeonList(
|
Calyx_Golden_Memories_The_Xianzhou_Luofu = DungeonList(
|
||||||
id=4,
|
id=4,
|
||||||
@ -69,38 +69,38 @@ Calyx_Golden_Treasures_The_Xianzhou_Luofu = DungeonList(
|
|||||||
dungeon_id=1013,
|
dungeon_id=1013,
|
||||||
plane_id=2022201,
|
plane_id=2022201,
|
||||||
)
|
)
|
||||||
Calyx_Golden_Memories_Penacony = DungeonList(
|
Calyx_Golden_Memories_Jarilo_VI = DungeonList(
|
||||||
id=7,
|
id=7,
|
||||||
name='Calyx_Golden_Memories_Penacony',
|
name='Calyx_Golden_Memories_Jarilo_VI',
|
||||||
cn='回忆之蕾',
|
cn='回忆之蕾',
|
||||||
cht='回憶之蕾',
|
cht='回憶之蕾',
|
||||||
en='Bud of Memories',
|
en='Bud of Memories',
|
||||||
jp='回憶の蕾',
|
jp='回憶の蕾',
|
||||||
es='Flor de los recuerdos',
|
es='Flor de los recuerdos',
|
||||||
dungeon_id=1014,
|
dungeon_id=1001,
|
||||||
plane_id=2031301,
|
plane_id=2010101,
|
||||||
)
|
)
|
||||||
Calyx_Golden_Aether_Penacony = DungeonList(
|
Calyx_Golden_Aether_Jarilo_VI = DungeonList(
|
||||||
id=8,
|
id=8,
|
||||||
name='Calyx_Golden_Aether_Penacony',
|
name='Calyx_Golden_Aether_Jarilo_VI',
|
||||||
cn='以太之蕾',
|
cn='以太之蕾',
|
||||||
cht='乙太之蕾',
|
cht='乙太之蕾',
|
||||||
en='Bud of Aether',
|
en='Bud of Aether',
|
||||||
jp='エーテルの蕾',
|
jp='エーテルの蕾',
|
||||||
es='Flor de éter',
|
es='Flor de éter',
|
||||||
dungeon_id=1015,
|
dungeon_id=1002,
|
||||||
plane_id=2031201,
|
plane_id=2011101,
|
||||||
)
|
)
|
||||||
Calyx_Golden_Treasures_Penacony = DungeonList(
|
Calyx_Golden_Treasures_Jarilo_VI = DungeonList(
|
||||||
id=9,
|
id=9,
|
||||||
name='Calyx_Golden_Treasures_Penacony',
|
name='Calyx_Golden_Treasures_Jarilo_VI',
|
||||||
cn='藏珍之蕾',
|
cn='藏珍之蕾',
|
||||||
cht='藏珍之蕾',
|
cht='藏珍之蕾',
|
||||||
en='Bud of Treasures',
|
en='Bud of Treasures',
|
||||||
jp='秘蔵の蕾',
|
jp='秘蔵の蕾',
|
||||||
es='Flor de tesoros',
|
es='Flor de tesoros',
|
||||||
dungeon_id=1016,
|
dungeon_id=1003,
|
||||||
plane_id=2031101,
|
plane_id=2012101,
|
||||||
)
|
)
|
||||||
Calyx_Crimson_Destruction_Herta_StorageZone = DungeonList(
|
Calyx_Crimson_Destruction_Herta_StorageZone = DungeonList(
|
||||||
id=10,
|
id=10,
|
||||||
@ -476,107 +476,8 @@ Stagnant_Shadow_Gloam = DungeonList(
|
|||||||
dungeon_id=1121,
|
dungeon_id=1121,
|
||||||
plane_id=2033201,
|
plane_id=2033201,
|
||||||
)
|
)
|
||||||
Cavern_of_Corrosion_Path_of_Gelid_Wind = DungeonList(
|
|
||||||
id=44,
|
|
||||||
name='Cavern_of_Corrosion_Path_of_Gelid_Wind',
|
|
||||||
cn='霜风之径',
|
|
||||||
cht='霜風之徑',
|
|
||||||
en='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(
|
|
||||||
id=45,
|
|
||||||
name='Cavern_of_Corrosion_Path_of_Jabbing_Punch',
|
|
||||||
cn='迅拳之径',
|
|
||||||
cht='迅拳之徑',
|
|
||||||
en='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(
|
|
||||||
id=46,
|
|
||||||
name='Cavern_of_Corrosion_Path_of_Drifting',
|
|
||||||
cn='漂泊之径',
|
|
||||||
cht='漂泊之徑',
|
|
||||||
en='Path of Drifting',
|
|
||||||
jp='漂泊の路',
|
|
||||||
es='Senda de la deriva',
|
|
||||||
dungeon_id=1203,
|
|
||||||
plane_id=2013201,
|
|
||||||
)
|
|
||||||
Cavern_of_Corrosion_Path_of_Providence = DungeonList(
|
|
||||||
id=47,
|
|
||||||
name='Cavern_of_Corrosion_Path_of_Providence',
|
|
||||||
cn='睿治之径',
|
|
||||||
cht='睿治之徑',
|
|
||||||
en='Path of Providence',
|
|
||||||
jp='睿治の路',
|
|
||||||
es='Senda de la providencia',
|
|
||||||
dungeon_id=1204,
|
|
||||||
plane_id=2013401,
|
|
||||||
)
|
|
||||||
Cavern_of_Corrosion_Path_of_Holy_Hymn = DungeonList(
|
|
||||||
id=48,
|
|
||||||
name='Cavern_of_Corrosion_Path_of_Holy_Hymn',
|
|
||||||
cn='圣颂之径',
|
|
||||||
cht='聖頌之徑',
|
|
||||||
en='Path of Holy Hymn',
|
|
||||||
jp='聖頌の路',
|
|
||||||
es='Senda del himno sagrado',
|
|
||||||
dungeon_id=1205,
|
|
||||||
plane_id=2021101,
|
|
||||||
)
|
|
||||||
Cavern_of_Corrosion_Path_of_Conflagration = DungeonList(
|
|
||||||
id=49,
|
|
||||||
name='Cavern_of_Corrosion_Path_of_Conflagration',
|
|
||||||
cn='野焰之径',
|
|
||||||
cht='野焰之徑',
|
|
||||||
en='Path of Conflagration',
|
|
||||||
jp='野焔の路',
|
|
||||||
es='Senda de la conflagración',
|
|
||||||
dungeon_id=1206,
|
|
||||||
plane_id=2021201,
|
|
||||||
)
|
|
||||||
Cavern_of_Corrosion_Path_of_Elixir_Seekers = DungeonList(
|
|
||||||
id=50,
|
|
||||||
name='Cavern_of_Corrosion_Path_of_Elixir_Seekers',
|
|
||||||
cn='药使之径',
|
|
||||||
cht='藥使之徑',
|
|
||||||
en='Path of Elixir Seekers',
|
|
||||||
jp='薬使の路',
|
|
||||||
es='Senda de los elixires',
|
|
||||||
dungeon_id=1207,
|
|
||||||
plane_id=2023101,
|
|
||||||
)
|
|
||||||
Cavern_of_Corrosion_Path_of_Darkness = DungeonList(
|
|
||||||
id=51,
|
|
||||||
name='Cavern_of_Corrosion_Path_of_Darkness',
|
|
||||||
cn='幽冥之径',
|
|
||||||
cht='幽冥之徑',
|
|
||||||
en='Path of Darkness',
|
|
||||||
jp='幽冥の路',
|
|
||||||
es='Senda de la oscuridad',
|
|
||||||
dungeon_id=1208,
|
|
||||||
plane_id=2022301,
|
|
||||||
)
|
|
||||||
Cavern_of_Corrosion_Path_of_Dreamdive = DungeonList(
|
|
||||||
id=52,
|
|
||||||
name='Cavern_of_Corrosion_Path_of_Dreamdive',
|
|
||||||
cn='梦潜之径',
|
|
||||||
cht='夢潛之徑',
|
|
||||||
en='Path of Dreamdive',
|
|
||||||
jp='夢潜の路',
|
|
||||||
es='Senda de los sueños',
|
|
||||||
dungeon_id=1209,
|
|
||||||
plane_id=2031101,
|
|
||||||
)
|
|
||||||
Cavern_of_Corrosion_Path_of_Cavalier = DungeonList(
|
Cavern_of_Corrosion_Path_of_Cavalier = DungeonList(
|
||||||
id=53,
|
id=44,
|
||||||
name='Cavern_of_Corrosion_Path_of_Cavalier',
|
name='Cavern_of_Corrosion_Path_of_Cavalier',
|
||||||
cn='勇骑之径',
|
cn='勇骑之径',
|
||||||
cht='勇騎之徑',
|
cht='勇騎之徑',
|
||||||
@ -586,52 +487,118 @@ Cavern_of_Corrosion_Path_of_Cavalier = DungeonList(
|
|||||||
dungeon_id=1210,
|
dungeon_id=1210,
|
||||||
plane_id=2033201,
|
plane_id=2033201,
|
||||||
)
|
)
|
||||||
Echo_of_War_Destruction_Beginning = DungeonList(
|
Cavern_of_Corrosion_Path_of_Dreamdive = DungeonList(
|
||||||
id=54,
|
id=45,
|
||||||
name='Echo_of_War_Destruction_Beginning',
|
name='Cavern_of_Corrosion_Path_of_Dreamdive',
|
||||||
cn='毁灭的开端•历战余响',
|
cn='梦潜之径',
|
||||||
cht='毀滅的開端•歷戰餘響',
|
cht='夢潛之徑',
|
||||||
en="Echo of War: Destruction's Beginning",
|
en='Path of Dreamdive',
|
||||||
jp='歴戦余韻・壊滅の始まり',
|
jp='夢潜の路',
|
||||||
es='El principio de la Destrucción',
|
es='Senda de los sueños',
|
||||||
dungeon_id=1301,
|
dungeon_id=1209,
|
||||||
plane_id=2000301,
|
plane_id=2031101,
|
||||||
)
|
)
|
||||||
Echo_of_War_End_of_the_Eternal_Freeze = DungeonList(
|
Cavern_of_Corrosion_Path_of_Darkness = DungeonList(
|
||||||
id=55,
|
id=46,
|
||||||
name='Echo_of_War_End_of_the_Eternal_Freeze',
|
name='Cavern_of_Corrosion_Path_of_Darkness',
|
||||||
cn='寒潮的落幕•历战余响',
|
cn='幽冥之径',
|
||||||
cht='寒潮的落幕•歷戰餘響',
|
cht='幽冥之徑',
|
||||||
en='Echo of War: End of the Eternal Freeze',
|
en='Path of Darkness',
|
||||||
jp='歴戦余韻・寒波の幕切れ',
|
jp='幽冥の路',
|
||||||
es='El fin del Hielo Eterno',
|
es='Senda de la oscuridad',
|
||||||
dungeon_id=1302,
|
dungeon_id=1208,
|
||||||
|
plane_id=2022301,
|
||||||
|
)
|
||||||
|
Cavern_of_Corrosion_Path_of_Elixir_Seekers = DungeonList(
|
||||||
|
id=47,
|
||||||
|
name='Cavern_of_Corrosion_Path_of_Elixir_Seekers',
|
||||||
|
cn='药使之径',
|
||||||
|
cht='藥使之徑',
|
||||||
|
en='Path of Elixir Seekers',
|
||||||
|
jp='薬使の路',
|
||||||
|
es='Senda de los elixires',
|
||||||
|
dungeon_id=1207,
|
||||||
|
plane_id=2023101,
|
||||||
|
)
|
||||||
|
Cavern_of_Corrosion_Path_of_Conflagration = DungeonList(
|
||||||
|
id=48,
|
||||||
|
name='Cavern_of_Corrosion_Path_of_Conflagration',
|
||||||
|
cn='野焰之径',
|
||||||
|
cht='野焰之徑',
|
||||||
|
en='Path of Conflagration',
|
||||||
|
jp='野焔の路',
|
||||||
|
es='Senda de la conflagración',
|
||||||
|
dungeon_id=1206,
|
||||||
|
plane_id=2021201,
|
||||||
|
)
|
||||||
|
Cavern_of_Corrosion_Path_of_Holy_Hymn = DungeonList(
|
||||||
|
id=49,
|
||||||
|
name='Cavern_of_Corrosion_Path_of_Holy_Hymn',
|
||||||
|
cn='圣颂之径',
|
||||||
|
cht='聖頌之徑',
|
||||||
|
en='Path of Holy Hymn',
|
||||||
|
jp='聖頌の路',
|
||||||
|
es='Senda del himno sagrado',
|
||||||
|
dungeon_id=1205,
|
||||||
|
plane_id=2021101,
|
||||||
|
)
|
||||||
|
Cavern_of_Corrosion_Path_of_Providence = DungeonList(
|
||||||
|
id=50,
|
||||||
|
name='Cavern_of_Corrosion_Path_of_Providence',
|
||||||
|
cn='睿治之径',
|
||||||
|
cht='睿治之徑',
|
||||||
|
en='Path of Providence',
|
||||||
|
jp='睿治の路',
|
||||||
|
es='Senda de la providencia',
|
||||||
|
dungeon_id=1204,
|
||||||
plane_id=2013401,
|
plane_id=2013401,
|
||||||
)
|
)
|
||||||
Echo_of_War_Divine_Seed = DungeonList(
|
Cavern_of_Corrosion_Path_of_Drifting = DungeonList(
|
||||||
id=56,
|
id=51,
|
||||||
name='Echo_of_War_Divine_Seed',
|
name='Cavern_of_Corrosion_Path_of_Drifting',
|
||||||
cn='不死的神实•历战余响',
|
cn='漂泊之径',
|
||||||
cht='不死的神實•歷戰餘響',
|
cht='漂泊之徑',
|
||||||
en='Echo of War: Divine Seed',
|
en='Path of Drifting',
|
||||||
jp='歴戦余韻・不死の神実',
|
jp='漂泊の路',
|
||||||
es='Semilla divina',
|
es='Senda de la deriva',
|
||||||
dungeon_id=1303,
|
dungeon_id=1203,
|
||||||
plane_id=2023201,
|
plane_id=2013201,
|
||||||
)
|
)
|
||||||
Echo_of_War_Borehole_Planet_Old_Crater = DungeonList(
|
Cavern_of_Corrosion_Path_of_Jabbing_Punch = DungeonList(
|
||||||
id=57,
|
id=52,
|
||||||
name='Echo_of_War_Borehole_Planet_Old_Crater',
|
name='Cavern_of_Corrosion_Path_of_Jabbing_Punch',
|
||||||
cn='蛀星的旧靥•历战余响',
|
cn='迅拳之径',
|
||||||
cht='蛀星的舊靨•歷戰餘響',
|
cht='迅拳之徑',
|
||||||
en="Echo of War: Borehole Planet's Old Crater",
|
en='Path of Jabbing Punch',
|
||||||
jp='歴戦余韻・星を蝕む往日の面影',
|
jp='迅拳の路',
|
||||||
es='Cráter del planeta devorado',
|
es='Senda de los puños rápidos',
|
||||||
dungeon_id=1304,
|
dungeon_id=1202,
|
||||||
plane_id=2000401,
|
plane_id=2013101,
|
||||||
|
)
|
||||||
|
Cavern_of_Corrosion_Path_of_Gelid_Wind = DungeonList(
|
||||||
|
id=53,
|
||||||
|
name='Cavern_of_Corrosion_Path_of_Gelid_Wind',
|
||||||
|
cn='霜风之径',
|
||||||
|
cht='霜風之徑',
|
||||||
|
en='Path of Gelid Wind',
|
||||||
|
jp='霜風の路',
|
||||||
|
es='Senda del viento gélido',
|
||||||
|
dungeon_id=1201,
|
||||||
|
plane_id=2000201,
|
||||||
|
)
|
||||||
|
Echo_of_War_Inner_Beast_Battlefield = DungeonList(
|
||||||
|
id=54,
|
||||||
|
name='Echo_of_War_Inner_Beast_Battlefield',
|
||||||
|
cn='心兽的战场•历战余响',
|
||||||
|
cht='心獸的戰場•歷戰餘響',
|
||||||
|
en="Echo of War: Inner Beast's Battlefield",
|
||||||
|
jp='歴戦余韻・心獣の戦場',
|
||||||
|
es='Campo de batalla de la bestia interior',
|
||||||
|
dungeon_id=1306,
|
||||||
|
plane_id=2024201,
|
||||||
)
|
)
|
||||||
Echo_of_War_Salutations_of_Ashen_Dreams = DungeonList(
|
Echo_of_War_Salutations_of_Ashen_Dreams = DungeonList(
|
||||||
id=58,
|
id=55,
|
||||||
name='Echo_of_War_Salutations_of_Ashen_Dreams',
|
name='Echo_of_War_Salutations_of_Ashen_Dreams',
|
||||||
cn='尘梦的赞礼•历战余响',
|
cn='尘梦的赞礼•历战余响',
|
||||||
cht='塵夢的讚禮•歷戰餘響',
|
cht='塵夢的讚禮•歷戰餘響',
|
||||||
@ -641,16 +608,49 @@ Echo_of_War_Salutations_of_Ashen_Dreams = DungeonList(
|
|||||||
dungeon_id=1305,
|
dungeon_id=1305,
|
||||||
plane_id=2033201,
|
plane_id=2033201,
|
||||||
)
|
)
|
||||||
Echo_of_War_Inner_Beast_Battlefield = DungeonList(
|
Echo_of_War_Borehole_Planet_Old_Crater = DungeonList(
|
||||||
|
id=56,
|
||||||
|
name='Echo_of_War_Borehole_Planet_Old_Crater',
|
||||||
|
cn='蛀星的旧靥•历战余响',
|
||||||
|
cht='蛀星的舊靨•歷戰餘響',
|
||||||
|
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_Divine_Seed = DungeonList(
|
||||||
|
id=57,
|
||||||
|
name='Echo_of_War_Divine_Seed',
|
||||||
|
cn='不死的神实•历战余响',
|
||||||
|
cht='不死的神實•歷戰餘響',
|
||||||
|
en='Echo of War: Divine Seed',
|
||||||
|
jp='歴戦余韻・不死の神実',
|
||||||
|
es='Semilla divina',
|
||||||
|
dungeon_id=1303,
|
||||||
|
plane_id=2023201,
|
||||||
|
)
|
||||||
|
Echo_of_War_End_of_the_Eternal_Freeze = DungeonList(
|
||||||
|
id=58,
|
||||||
|
name='Echo_of_War_End_of_the_Eternal_Freeze',
|
||||||
|
cn='寒潮的落幕•历战余响',
|
||||||
|
cht='寒潮的落幕•歷戰餘響',
|
||||||
|
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_Destruction_Beginning = DungeonList(
|
||||||
id=59,
|
id=59,
|
||||||
name='Echo_of_War_Inner_Beast_Battlefield',
|
name='Echo_of_War_Destruction_Beginning',
|
||||||
cn='心兽的战场•历战余响',
|
cn='毁灭的开端•历战余响',
|
||||||
cht='心獸的戰場•歷戰餘響',
|
cht='毀滅的開端•歷戰餘響',
|
||||||
en="Echo of War: Inner Beast's Battlefield",
|
en="Echo of War: Destruction's Beginning",
|
||||||
jp='歴戦余韻・心獣の戦場',
|
jp='歴戦余韻・壊滅の始まり',
|
||||||
es='Campo de batalla de la bestia interior',
|
es='El principio de la Destrucción',
|
||||||
dungeon_id=1306,
|
dungeon_id=1301,
|
||||||
plane_id=2024201,
|
plane_id=2000301,
|
||||||
)
|
)
|
||||||
Simulated_Universe_World_1 = DungeonList(
|
Simulated_Universe_World_1 = DungeonList(
|
||||||
id=60,
|
id=60,
|
||||||
|
@ -6,7 +6,7 @@ from tasks.base.page import page_guide
|
|||||||
from tasks.combat.assets.assets_combat_stamina_status import ICON_SEARCH, IMMERSIFIER_ICON
|
from tasks.combat.assets.assets_combat_stamina_status import ICON_SEARCH, IMMERSIFIER_ICON
|
||||||
from tasks.dungeon.assets.assets_dungeon_stamina import *
|
from tasks.dungeon.assets.assets_dungeon_stamina import *
|
||||||
from tasks.dungeon.keywords import KEYWORDS_DUNGEON_TAB
|
from tasks.dungeon.keywords import KEYWORDS_DUNGEON_TAB
|
||||||
from tasks.dungeon.ui import DungeonUI
|
from tasks.dungeon.ui.ui import DungeonUI
|
||||||
|
|
||||||
|
|
||||||
class DungeonStamina(DungeonUI):
|
class DungeonStamina(DungeonUI):
|
||||||
|
@ -1,770 +0,0 @@
|
|||||||
import re
|
|
||||||
|
|
||||||
import cv2
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
from module.base.base import ModuleBase
|
|
||||||
from module.base.button import ClickButton
|
|
||||||
from module.base.decorator import run_once
|
|
||||||
from module.base.timer import Timer
|
|
||||||
from module.base.utils import get_color
|
|
||||||
from module.exception import ScriptError
|
|
||||||
from module.logger import logger
|
|
||||||
from module.ocr.ocr import Ocr, OcrResultButton
|
|
||||||
from module.ocr.utils import split_and_pair_button_attr, split_and_pair_buttons
|
|
||||||
from module.ui.draggable_list import DraggableList
|
|
||||||
from module.ui.switch import Switch
|
|
||||||
from tasks.base.page import page_guide
|
|
||||||
from tasks.combat.assets.assets_combat_interact import DUNGEON_COMBAT_INTERACT, DUNGEON_COMBAT_INTERACT_TEXT
|
|
||||||
from tasks.combat.assets.assets_combat_prepare import COMBAT_PREPARE
|
|
||||||
from tasks.dungeon.assets.assets_dungeon_ui import *
|
|
||||||
from tasks.dungeon.assets.assets_dungeon_ui_rogue import *
|
|
||||||
from tasks.dungeon.keywords import (
|
|
||||||
DungeonList,
|
|
||||||
DungeonNav,
|
|
||||||
DungeonTab,
|
|
||||||
KEYWORDS_DUNGEON_ENTRANCE,
|
|
||||||
KEYWORDS_DUNGEON_LIST,
|
|
||||||
KEYWORDS_DUNGEON_NAV,
|
|
||||||
KEYWORDS_DUNGEON_TAB
|
|
||||||
)
|
|
||||||
from tasks.dungeon.keywords.classes import DungeonEntrance
|
|
||||||
from tasks.dungeon.state import DungeonState
|
|
||||||
from tasks.map.interact.aim import inrange
|
|
||||||
from tasks.map.keywords import KEYWORDS_MAP_WORLD, MapPlane
|
|
||||||
|
|
||||||
|
|
||||||
class DungeonTabSwitch(Switch):
|
|
||||||
SEARCH_BUTTON = TAB_SEARCH
|
|
||||||
|
|
||||||
def add_state(self, state, check_button, click_button=None):
|
|
||||||
# Load search
|
|
||||||
if check_button is not None:
|
|
||||||
check_button.load_search(self.__class__.SEARCH_BUTTON.area)
|
|
||||||
if click_button is not None:
|
|
||||||
click_button.load_search(self.__class__.SEARCH_BUTTON.area)
|
|
||||||
return super().add_state(state, check_button, click_button)
|
|
||||||
|
|
||||||
def click(self, state, main):
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
state (str):
|
|
||||||
main (ModuleBase):
|
|
||||||
"""
|
|
||||||
button = self.get_data(state)['click_button']
|
|
||||||
_ = main.appear(button) # Search button to load offset
|
|
||||||
main.device.click(button)
|
|
||||||
|
|
||||||
|
|
||||||
SWITCH_DUNGEON_TAB = DungeonTabSwitch('DungeonTab', is_selector=True)
|
|
||||||
SWITCH_DUNGEON_TAB.add_state(
|
|
||||||
KEYWORDS_DUNGEON_TAB.Operation_Briefing,
|
|
||||||
check_button=OPERATION_BRIEFING_CHECK,
|
|
||||||
click_button=OPERATION_BRIEFING_CLICK
|
|
||||||
)
|
|
||||||
SWITCH_DUNGEON_TAB.add_state(
|
|
||||||
KEYWORDS_DUNGEON_TAB.Daily_Training,
|
|
||||||
check_button=DAILY_TRAINING_CHECK,
|
|
||||||
click_button=DAILY_TRAINING_CLICK
|
|
||||||
)
|
|
||||||
SWITCH_DUNGEON_TAB.add_state(
|
|
||||||
KEYWORDS_DUNGEON_TAB.Survival_Index,
|
|
||||||
check_button=SURVIVAL_INDEX_CHECK,
|
|
||||||
click_button=SURVIVAL_INDEX_CLICK
|
|
||||||
)
|
|
||||||
SWITCH_DUNGEON_TAB.add_state(
|
|
||||||
KEYWORDS_DUNGEON_TAB.Simulated_Universe,
|
|
||||||
check_button=SIMULATED_UNIVERSE_CHECK,
|
|
||||||
click_button=SIMULATED_UNIVERSE_CLICK
|
|
||||||
)
|
|
||||||
SWITCH_DUNGEON_TAB.add_state(
|
|
||||||
KEYWORDS_DUNGEON_TAB.Treasures_Lightward,
|
|
||||||
check_button=TREASURES_LIGHTWARD_CHECK,
|
|
||||||
click_button=TREASURES_LIGHTWARD_CLICK
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class OcrDungeonNav(Ocr):
|
|
||||||
def after_process(self, result):
|
|
||||||
result = super().after_process(result)
|
|
||||||
result = result.replace('#', '')
|
|
||||||
if self.lang == 'cn':
|
|
||||||
result = result.replace('萼喜', '萼')
|
|
||||||
result = result.replace('带', '滞') # 凝带虚影
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
class OcrDungeonList(Ocr):
|
|
||||||
def after_process(self, result):
|
|
||||||
# 乙太之蕾•雅利洛-Ⅵ
|
|
||||||
result = re.sub(r'-[VⅤ][IⅠ]', '-Ⅵ', result)
|
|
||||||
|
|
||||||
# 苏乐达™热砂海选会场
|
|
||||||
result = re.sub(r'(苏乐达|蘇樂達|SoulGlad|スラーダ|FelizAlma)[rtT]*M', r'\1', result)
|
|
||||||
|
|
||||||
result = super().after_process(result)
|
|
||||||
|
|
||||||
if self.lang == 'cn':
|
|
||||||
result = result.replace('翼', '巽') # 巽风之形
|
|
||||||
result = result.replace('皖A0', '50').replace('皖', '')
|
|
||||||
# 燔灼之形•凝滞虚影
|
|
||||||
result = result.replace('熠', '燔')
|
|
||||||
result = re.sub('^灼之形', '燔灼之形', result)
|
|
||||||
# 偃偶之形•凝滞虚影
|
|
||||||
result = re.sub('^偶之形', '偃偶之形', result)
|
|
||||||
# 嗔怒之形•凝滞虚影
|
|
||||||
result = re.sub('^怒之形', '嗔怒之形', result)
|
|
||||||
# 蛀星的旧·历战余响
|
|
||||||
result = re.sub(r'蛀星的旧.*?历战', '蛀星的旧靥•历战', result)
|
|
||||||
|
|
||||||
# 9支援仓段
|
|
||||||
for word in 'Q9α':
|
|
||||||
result = result.removeprefix(word)
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
class OcrDungeonListCalyxCrimson(OcrDungeonList):
|
|
||||||
def _match_result(self, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
Convert MapPlane object to their corresponding DungeonList object
|
|
||||||
"""
|
|
||||||
plane = super()._match_result(*args, **kwargs)
|
|
||||||
if plane is not None:
|
|
||||||
for dungeon in DungeonList.instances.values():
|
|
||||||
if dungeon.is_Calyx_Crimson and dungeon.plane == plane:
|
|
||||||
return dungeon
|
|
||||||
return plane
|
|
||||||
|
|
||||||
|
|
||||||
class OcrDungeonListLimitEntrance(OcrDungeonList):
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self.button = ClickButton((*self.button.area[:3], self.button.area[3] - 70))
|
|
||||||
|
|
||||||
|
|
||||||
class OcrDungeonListCalyxCrimsonLimitEntrance(OcrDungeonListCalyxCrimson, OcrDungeonListLimitEntrance):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class DraggableDungeonNav(DraggableList):
|
|
||||||
# 0.5 is the magic number to reach bottom in 1 swipe
|
|
||||||
# but relax we still have retires when magic doesn't work
|
|
||||||
drag_vector = (0.50, 0.52)
|
|
||||||
|
|
||||||
|
|
||||||
class DraggableDungeonList(DraggableList):
|
|
||||||
teleports: list[OcrResultButton] = []
|
|
||||||
navigates: list[OcrResultButton] = []
|
|
||||||
|
|
||||||
# use_plane: True to use map planes to predict dungeons only.
|
|
||||||
# Can only be True in Calyx Crimson
|
|
||||||
use_plane = False
|
|
||||||
# limit_entrance: True to ensure the teleport button is insight
|
|
||||||
limit_entrance = False
|
|
||||||
|
|
||||||
def load_rows(self, main: ModuleBase, allow_early_access=False):
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
main:
|
|
||||||
allow_early_access: True to allow dungeons that are in temporarily early access during events
|
|
||||||
"""
|
|
||||||
relative_area = (0, 0, 1280, 120)
|
|
||||||
if self.use_plane:
|
|
||||||
self.keyword_class = [MapPlane, DungeonEntrance]
|
|
||||||
if self.limit_entrance:
|
|
||||||
self.ocr_class = OcrDungeonListCalyxCrimsonLimitEntrance
|
|
||||||
else:
|
|
||||||
self.ocr_class = OcrDungeonListCalyxCrimson
|
|
||||||
else:
|
|
||||||
self.keyword_class = [DungeonList, DungeonEntrance]
|
|
||||||
if self.limit_entrance:
|
|
||||||
self.ocr_class = OcrDungeonListLimitEntrance
|
|
||||||
else:
|
|
||||||
self.ocr_class = OcrDungeonList
|
|
||||||
super().load_rows(main=main)
|
|
||||||
|
|
||||||
# Check early access dungeons
|
|
||||||
buttons = DUNGEON_LIST.cur_buttons.copy()
|
|
||||||
for name, button in split_and_pair_buttons(
|
|
||||||
DUNGEON_LIST.cur_buttons,
|
|
||||||
split_func=lambda x: x != KEYWORDS_DUNGEON_ENTRANCE.Enter,
|
|
||||||
relative_area=relative_area
|
|
||||||
):
|
|
||||||
logger.warning(f'Early access dungeon: {name}')
|
|
||||||
buttons.remove(name)
|
|
||||||
buttons.remove(button)
|
|
||||||
|
|
||||||
# Remove early access dungeons
|
|
||||||
if not allow_early_access:
|
|
||||||
DUNGEON_LIST.cur_buttons = buttons
|
|
||||||
# From super.load_rows(), re-calculate indexes
|
|
||||||
indexes = [self.keyword2index(row.matched_keyword)
|
|
||||||
for row in self.cur_buttons]
|
|
||||||
indexes = [index for index in indexes if index]
|
|
||||||
|
|
||||||
if not indexes:
|
|
||||||
logger.warning(f'No valid rows loaded into {self}')
|
|
||||||
return
|
|
||||||
|
|
||||||
self.cur_min = min(indexes)
|
|
||||||
self.cur_max = max(indexes)
|
|
||||||
logger.attr(self.name, f'{self.cur_min} - {self.cur_max}')
|
|
||||||
|
|
||||||
# Replace dungeon.button with teleport
|
|
||||||
self.teleports = list(split_and_pair_button_attr(
|
|
||||||
DUNGEON_LIST.cur_buttons,
|
|
||||||
split_func=lambda x: x != KEYWORDS_DUNGEON_ENTRANCE.Teleport and x != KEYWORDS_DUNGEON_ENTRANCE.Enter,
|
|
||||||
relative_area=relative_area
|
|
||||||
))
|
|
||||||
self.navigates = list(split_and_pair_button_attr(
|
|
||||||
DUNGEON_LIST.cur_buttons,
|
|
||||||
split_func=lambda x: x != KEYWORDS_DUNGEON_ENTRANCE.Navigate,
|
|
||||||
relative_area=relative_area
|
|
||||||
))
|
|
||||||
|
|
||||||
|
|
||||||
DUNGEON_NAV_LIST = DraggableDungeonNav(
|
|
||||||
'DungeonNavList', keyword_class=DungeonNav, ocr_class=OcrDungeonNav, search_button=OCR_DUNGEON_NAV)
|
|
||||||
DUNGEON_LIST = DraggableDungeonList(
|
|
||||||
'DungeonList', keyword_class=[DungeonList, DungeonEntrance, MapPlane],
|
|
||||||
ocr_class=OcrDungeonList, search_button=OCR_DUNGEON_LIST)
|
|
||||||
|
|
||||||
|
|
||||||
class DungeonUI(DungeonState):
|
|
||||||
def dungeon_tab_goto(self, state: DungeonTab):
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
state:
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
bool: If UI switched
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
self = DungeonUI('alas')
|
|
||||||
self.device.screenshot()
|
|
||||||
self.dungeon_tab_goto(KEYWORDS_DUNGEON_TAB.Operation_Briefing)
|
|
||||||
self.dungeon_tab_goto(KEYWORDS_DUNGEON_TAB.Daily_Training)
|
|
||||||
self.dungeon_tab_goto(KEYWORDS_DUNGEON_TAB.Survival_Index)
|
|
||||||
"""
|
|
||||||
logger.hr('Dungeon tab goto', level=2)
|
|
||||||
ui_switched = self.ui_ensure(page_guide)
|
|
||||||
tab_switched = SWITCH_DUNGEON_TAB.set(state, main=self)
|
|
||||||
|
|
||||||
if ui_switched or tab_switched:
|
|
||||||
if state == KEYWORDS_DUNGEON_TAB.Daily_Training:
|
|
||||||
logger.info(f'Tab goto {state}, wait until loaded')
|
|
||||||
self._dungeon_wait_daily_training_loaded()
|
|
||||||
elif state == KEYWORDS_DUNGEON_TAB.Survival_Index:
|
|
||||||
logger.info(f'Tab goto {state}, wait until loaded')
|
|
||||||
self._dungeon_wait_survival_index_loaded()
|
|
||||||
elif state == KEYWORDS_DUNGEON_TAB.Treasures_Lightward:
|
|
||||||
logger.info(f'Tab goto {state}, wait until loaded')
|
|
||||||
self._dungeon_wait_treasures_lightward_loaded()
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _dungeon_wait_daily_training_loaded(self, skip_first_screenshot=True):
|
|
||||||
"""
|
|
||||||
Returns:
|
|
||||||
bool: True if wait success, False if wait timeout.
|
|
||||||
|
|
||||||
Pages:
|
|
||||||
in: page_guide, Daily_Training
|
|
||||||
"""
|
|
||||||
timeout = Timer(2, count=4).start()
|
|
||||||
while 1:
|
|
||||||
if skip_first_screenshot:
|
|
||||||
skip_first_screenshot = False
|
|
||||||
else:
|
|
||||||
self.device.screenshot()
|
|
||||||
|
|
||||||
if timeout.reached():
|
|
||||||
logger.warning('Wait daily training loaded timeout')
|
|
||||||
return False
|
|
||||||
color = get_color(self.device.image, DAILY_TRAINING_LOADED.area)
|
|
||||||
if np.mean(color) < 128:
|
|
||||||
logger.info('Daily training loaded')
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _dungeon_wait_survival_index_loaded(self, skip_first_screenshot=True):
|
|
||||||
"""
|
|
||||||
Returns:
|
|
||||||
bool: True if wait success, False if wait timeout.
|
|
||||||
|
|
||||||
Pages:
|
|
||||||
in: page_guide, Survival_Index
|
|
||||||
"""
|
|
||||||
timeout = Timer(2, count=4).start()
|
|
||||||
while 1:
|
|
||||||
if skip_first_screenshot:
|
|
||||||
skip_first_screenshot = False
|
|
||||||
else:
|
|
||||||
self.device.screenshot()
|
|
||||||
|
|
||||||
if timeout.reached():
|
|
||||||
logger.warning('Wait survival index loaded timeout')
|
|
||||||
return False
|
|
||||||
if self.appear(SURVIVAL_INDEX_SU_LOADED):
|
|
||||||
logger.info('Survival index loaded, SURVIVAL_INDEX_SU_LOADED')
|
|
||||||
return True
|
|
||||||
if self.appear(SURVIVAL_INDEX_OE_LOADED):
|
|
||||||
logger.info('Survival index loaded, SURVIVAL_INDEX_OE_LOADED')
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _dungeon_survival_index_top_appear(self):
|
|
||||||
if self.appear(SURVIVAL_INDEX_SU_LOADED):
|
|
||||||
return True
|
|
||||||
if self.appear(SURVIVAL_INDEX_OE_LOADED):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _dungeon_wait_treasures_lightward_loaded(self, skip_first_screenshot=True):
|
|
||||||
"""
|
|
||||||
Returns:
|
|
||||||
bool: True if wait success, False if wait timeout.
|
|
||||||
|
|
||||||
Pages:
|
|
||||||
in: page_guide, Survival_Index
|
|
||||||
"""
|
|
||||||
timeout = Timer(2, count=4).start()
|
|
||||||
TREASURES_LIGHTWARD_LOADED.set_search_offset((5, 5))
|
|
||||||
TREASURES_LIGHTWARD_LOCKED.set_search_offset((5, 5))
|
|
||||||
while 1:
|
|
||||||
if skip_first_screenshot:
|
|
||||||
skip_first_screenshot = False
|
|
||||||
else:
|
|
||||||
self.device.screenshot()
|
|
||||||
|
|
||||||
if timeout.reached():
|
|
||||||
logger.warning('Wait treasures lightward loaded timeout')
|
|
||||||
return False
|
|
||||||
if self.appear(TREASURES_LIGHTWARD_LOADED):
|
|
||||||
logger.info('Treasures lightward loaded (event unlocked)')
|
|
||||||
return True
|
|
||||||
if self.appear(TREASURES_LIGHTWARD_LOCKED):
|
|
||||||
logger.info('Treasures lightward loaded (event locked)')
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _dungeon_list_button_has_content(self):
|
|
||||||
# Check if having any content
|
|
||||||
# List background: 254, guild border: 225
|
|
||||||
r, g, b = cv2.split(self.image_crop(LIST_LOADED_CHECK, copy=False))
|
|
||||||
minimum = cv2.min(cv2.min(r, g), b)
|
|
||||||
minimum = inrange(minimum, lower=0, upper=180)
|
|
||||||
if minimum.size > 100:
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _dungeon_wait_until_dungeon_list_loaded(self, skip_first_screenshot=True):
|
|
||||||
timeout = Timer(1, count=3).start()
|
|
||||||
while 1:
|
|
||||||
if skip_first_screenshot:
|
|
||||||
skip_first_screenshot = False
|
|
||||||
else:
|
|
||||||
self.device.screenshot()
|
|
||||||
|
|
||||||
# End
|
|
||||||
if timeout.reached():
|
|
||||||
logger.warning('Wait until dungeon list loaded timeout')
|
|
||||||
return False
|
|
||||||
|
|
||||||
if self._dungeon_list_button_has_content():
|
|
||||||
logger.info('Dungeon list loaded')
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _dungeon_wait_until_echo_or_war_stabled(self, skip_first_screenshot=True):
|
|
||||||
"""
|
|
||||||
Returns:
|
|
||||||
bool: True if wait success, False if wait timeout.
|
|
||||||
|
|
||||||
Pages:
|
|
||||||
in: page_guide, Survival_Index
|
|
||||||
"""
|
|
||||||
# Wait until Forgotten_Hall stabled
|
|
||||||
timeout = Timer(2, count=4).start()
|
|
||||||
while 1:
|
|
||||||
if skip_first_screenshot:
|
|
||||||
skip_first_screenshot = False
|
|
||||||
else:
|
|
||||||
self.device.screenshot()
|
|
||||||
|
|
||||||
# End
|
|
||||||
if timeout.reached():
|
|
||||||
logger.warning('Wait until Echo_of_War stabled timeout')
|
|
||||||
return False
|
|
||||||
|
|
||||||
DUNGEON_NAV_LIST.load_rows(main=self)
|
|
||||||
|
|
||||||
# End
|
|
||||||
button = DUNGEON_NAV_LIST.keyword2button(KEYWORDS_DUNGEON_NAV.Echo_of_War, show_warning=False)
|
|
||||||
if button:
|
|
||||||
# 513 is the top of the last row of DungeonNav
|
|
||||||
if button.area[1] > 513:
|
|
||||||
logger.info('DungeonNav row Echo_of_War stabled')
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
logger.info('No Echo_of_War in list skip waiting')
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _dungeon_nav_goto(self, nav: DungeonNav, skip_first_screenshot=True):
|
|
||||||
"""
|
|
||||||
Equivalent to `DUNGEON_NAV_LIST.select_row(dungeon.dungeon_nav, main=self)`
|
|
||||||
but with tricks to be faster
|
|
||||||
|
|
||||||
Args:
|
|
||||||
nav:
|
|
||||||
skip_first_screenshot:
|
|
||||||
"""
|
|
||||||
logger.hr('Dungeon nav goto', level=2)
|
|
||||||
logger.info(f'Dungeon nav goto {nav}')
|
|
||||||
|
|
||||||
# Wait rows
|
|
||||||
while 1:
|
|
||||||
if skip_first_screenshot:
|
|
||||||
skip_first_screenshot = False
|
|
||||||
else:
|
|
||||||
self.device.screenshot()
|
|
||||||
DUNGEON_NAV_LIST.load_rows(main=self)
|
|
||||||
if DUNGEON_NAV_LIST.cur_buttons:
|
|
||||||
break
|
|
||||||
|
|
||||||
# Wait first row selected
|
|
||||||
timeout = Timer(0.5, count=2).start()
|
|
||||||
skip_first_screenshot = True
|
|
||||||
while 1:
|
|
||||||
if skip_first_screenshot:
|
|
||||||
skip_first_screenshot = False
|
|
||||||
else:
|
|
||||||
self.device.screenshot()
|
|
||||||
if timeout.reached():
|
|
||||||
logger.info('DUNGEON_NAV_LIST not selected')
|
|
||||||
break
|
|
||||||
if button := DUNGEON_NAV_LIST.get_selected_row(main=self):
|
|
||||||
logger.info(f'DUNGEON_NAV_LIST selected at {button}')
|
|
||||||
break
|
|
||||||
|
|
||||||
# Check if it's at the first page.
|
|
||||||
if DUNGEON_NAV_LIST.keyword2button(KEYWORDS_DUNGEON_NAV.Simulated_Universe, show_warning=False) \
|
|
||||||
or DUNGEON_NAV_LIST.keyword2button(KEYWORDS_DUNGEON_NAV.Ornament_Extraction, show_warning=False):
|
|
||||||
# Going to use a faster method to navigate but can only start from list top
|
|
||||||
logger.info('DUNGEON_NAV_LIST at top')
|
|
||||||
# Update points if possible
|
|
||||||
# 2.3, No longer weekly points after Divergent Universe unlocked
|
|
||||||
# if DUNGEON_NAV_LIST.is_row_selected(button, main=self):
|
|
||||||
# self.dungeon_update_simuni()
|
|
||||||
# Treasures lightward is always at top
|
|
||||||
elif DUNGEON_NAV_LIST.keyword2button(KEYWORDS_DUNGEON_NAV.Forgotten_Hall, show_warning=False) \
|
|
||||||
or DUNGEON_NAV_LIST.keyword2button(KEYWORDS_DUNGEON_NAV.Pure_Fiction, show_warning=False):
|
|
||||||
logger.info('DUNGEON_NAV_LIST at top')
|
|
||||||
else:
|
|
||||||
# To start from any list states.
|
|
||||||
logger.info('DUNGEON_NAV_LIST not at top')
|
|
||||||
DUNGEON_NAV_LIST.select_row(nav, main=self)
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Check the first page
|
|
||||||
if nav in [
|
|
||||||
KEYWORDS_DUNGEON_NAV.Simulated_Universe,
|
|
||||||
KEYWORDS_DUNGEON_NAV.Divergent_Universe,
|
|
||||||
KEYWORDS_DUNGEON_NAV.Ornament_Extraction,
|
|
||||||
KEYWORDS_DUNGEON_NAV.Calyx_Golden,
|
|
||||||
KEYWORDS_DUNGEON_NAV.Calyx_Crimson,
|
|
||||||
KEYWORDS_DUNGEON_NAV.Stagnant_Shadow,
|
|
||||||
KEYWORDS_DUNGEON_NAV.Cavern_of_Corrosion,
|
|
||||||
KEYWORDS_DUNGEON_NAV.Forgotten_Hall,
|
|
||||||
KEYWORDS_DUNGEON_NAV.Pure_Fiction,
|
|
||||||
]:
|
|
||||||
button = DUNGEON_NAV_LIST.keyword2button(nav)
|
|
||||||
if button:
|
|
||||||
DUNGEON_NAV_LIST.select_row(nav, main=self, insight=False)
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Check the second page
|
|
||||||
while 1:
|
|
||||||
DUNGEON_NAV_LIST.drag_page('down', main=self)
|
|
||||||
# No skip_first_screenshot since drag_page is just called
|
|
||||||
if self._dungeon_wait_until_echo_or_war_stabled(skip_first_screenshot=False):
|
|
||||||
DUNGEON_NAV_LIST.select_row(nav, main=self, insight=False)
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _dungeon_world_set(self, dungeon: DungeonList, skip_first_screenshot=True):
|
|
||||||
"""
|
|
||||||
Switch worlds in Calyx_Golden
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
bool: True if success to set
|
|
||||||
"""
|
|
||||||
logger.hr('Dungeon world set', level=2)
|
|
||||||
if not dungeon.is_Calyx_Golden:
|
|
||||||
logger.warning(f'Dungeon {dungeon} is not Calyx Golden, no need to set world')
|
|
||||||
return False
|
|
||||||
if dungeon.world is None:
|
|
||||||
logger.error(f'Dungeon {dungeon} does not belongs to any world')
|
|
||||||
return False
|
|
||||||
dic_world_button = {
|
|
||||||
KEYWORDS_MAP_WORLD.Jarilo_VI: CALYX_WORLD_1,
|
|
||||||
KEYWORDS_MAP_WORLD.The_Xianzhou_Luofu: CALYX_WORLD_2,
|
|
||||||
KEYWORDS_MAP_WORLD.Penacony: CALYX_WORLD_3,
|
|
||||||
}
|
|
||||||
button = dic_world_button.get(dungeon.world)
|
|
||||||
if button is None:
|
|
||||||
logger.error(f'Dungeon {dungeon} with world {dungeon.world} has no corresponding world button')
|
|
||||||
return False
|
|
||||||
|
|
||||||
logger.info(f'Dungeon world set {dungeon.world}')
|
|
||||||
while 1:
|
|
||||||
if skip_first_screenshot:
|
|
||||||
skip_first_screenshot = False
|
|
||||||
else:
|
|
||||||
self.device.screenshot()
|
|
||||||
|
|
||||||
# End
|
|
||||||
if self.image_color_count(button, color=(18, 18, 18), threshold=180, count=50):
|
|
||||||
logger.info(f'Dungeon world at {dungeon.world}')
|
|
||||||
return True
|
|
||||||
# Click
|
|
||||||
if self.ui_page_appear(page_guide, interval=2):
|
|
||||||
self.device.click(button)
|
|
||||||
continue
|
|
||||||
|
|
||||||
def _dungeon_world_set_wrapper(self, dungeon: DungeonList, skip_first_screenshot=True):
|
|
||||||
"""
|
|
||||||
Switch worlds in Calyx_Golden with error handling
|
|
||||||
If world tab is not unlocked, fallback to Jarilo dungeons
|
|
||||||
"""
|
|
||||||
# Wait world tab
|
|
||||||
button = CALYX_WORLD_1
|
|
||||||
tab = False
|
|
||||||
timeout = Timer(0.6, count=3).start()
|
|
||||||
while 1:
|
|
||||||
if skip_first_screenshot:
|
|
||||||
skip_first_screenshot = False
|
|
||||||
else:
|
|
||||||
self.device.screenshot()
|
|
||||||
# End
|
|
||||||
if timeout.reached():
|
|
||||||
break
|
|
||||||
# Selected tab
|
|
||||||
if self.image_color_count(button, color=(18, 18, 18), threshold=180, count=50):
|
|
||||||
tab = True
|
|
||||||
break
|
|
||||||
# Unselected tab
|
|
||||||
if self.image_color_count(button, color=(134, 134, 134), threshold=180, count=50):
|
|
||||||
tab = True
|
|
||||||
break
|
|
||||||
|
|
||||||
logger.attr('WorldTab', tab)
|
|
||||||
if not tab:
|
|
||||||
logger.warning('World tab is not unlocked, fallback to Jarilo dungeons')
|
|
||||||
if dungeon.is_Calyx_Golden_Memories:
|
|
||||||
dungeon = KEYWORDS_DUNGEON_LIST.Calyx_Golden_Treasures_Jarilo_VI
|
|
||||||
if dungeon.is_Calyx_Golden_Aether:
|
|
||||||
dungeon = KEYWORDS_DUNGEON_LIST.Calyx_Golden_Aether_Jarilo_VI
|
|
||||||
if dungeon.is_Calyx_Golden_Treasures:
|
|
||||||
dungeon = KEYWORDS_DUNGEON_LIST.Calyx_Golden_Treasures_Jarilo_VI
|
|
||||||
|
|
||||||
self._dungeon_world_set(dungeon, skip_first_screenshot=skip_first_screenshot)
|
|
||||||
return dungeon
|
|
||||||
|
|
||||||
def _dungeon_insight(self, dungeon: DungeonList):
|
|
||||||
"""
|
|
||||||
Pages:
|
|
||||||
in: page_guide, Survival_Index, nav including dungeon
|
|
||||||
out: page_guide, Survival_Index, nav including dungeon, dungeon insight
|
|
||||||
"""
|
|
||||||
logger.hr('Dungeon insight', level=2)
|
|
||||||
DUNGEON_LIST.use_plane = bool(dungeon.is_Calyx_Crimson)
|
|
||||||
# Insight dungeon
|
|
||||||
DUNGEON_LIST.insight_row(dungeon, main=self)
|
|
||||||
self.device.click_record_clear()
|
|
||||||
# Check if dungeon unlocked
|
|
||||||
for entrance in DUNGEON_LIST.navigates:
|
|
||||||
entrance: OcrResultButton = entrance
|
|
||||||
logger.warning(f'Teleport {entrance.matched_keyword} is not unlocked')
|
|
||||||
if entrance == dungeon:
|
|
||||||
logger.error(f'Trying to enter dungeon {dungeon}, but teleport is not unlocked')
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Find teleport button
|
|
||||||
if dungeon not in [tp.matched_keyword for tp in DUNGEON_LIST.teleports]:
|
|
||||||
# Dungeon name is insight but teleport button is not
|
|
||||||
logger.info('Dungeon name is insight, swipe down a little bit to find the teleport button')
|
|
||||||
if dungeon.is_Forgotten_Hall:
|
|
||||||
DUNGEON_LIST.drag_vector = (-0.4, -0.2) # Keyword loaded is reversed
|
|
||||||
else:
|
|
||||||
DUNGEON_LIST.drag_vector = (0.2, 0.4)
|
|
||||||
DUNGEON_LIST.limit_entrance = True
|
|
||||||
DUNGEON_LIST.insight_row(dungeon, main=self)
|
|
||||||
self.device.click_record_clear()
|
|
||||||
DUNGEON_LIST.drag_vector = DraggableList.drag_vector
|
|
||||||
DUNGEON_LIST.limit_entrance = False
|
|
||||||
DUNGEON_LIST.load_rows(main=self)
|
|
||||||
# Check if dungeon unlocked
|
|
||||||
for entrance in DUNGEON_LIST.navigates:
|
|
||||||
if entrance == dungeon:
|
|
||||||
logger.error(f'Trying to enter dungeon {dungeon}, but teleport is not unlocked')
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _dungeon_enter(self, dungeon, enter_check_button=COMBAT_PREPARE, skip_first_screenshot=True):
|
|
||||||
"""
|
|
||||||
Pages:
|
|
||||||
in: page_guide, Survival_Index, nav including dungeon
|
|
||||||
out: COMBAT_PREPARE, FORGOTTEN_HALL_CHECK
|
|
||||||
"""
|
|
||||||
logger.hr('Dungeon enter', level=2)
|
|
||||||
DUNGEON_LIST.use_plane = bool(dungeon.is_Calyx_Crimson)
|
|
||||||
skip_first_load = skip_first_screenshot
|
|
||||||
|
|
||||||
@run_once
|
|
||||||
def screenshot_interval_set():
|
|
||||||
self.device.screenshot_interval_set('combat')
|
|
||||||
|
|
||||||
while 1:
|
|
||||||
if skip_first_screenshot:
|
|
||||||
skip_first_screenshot = False
|
|
||||||
else:
|
|
||||||
self.device.screenshot()
|
|
||||||
|
|
||||||
# End
|
|
||||||
if self.appear(enter_check_button):
|
|
||||||
logger.info(f'Arrive {enter_check_button.name}')
|
|
||||||
self.device.screenshot_interval_set()
|
|
||||||
break
|
|
||||||
|
|
||||||
# Additional
|
|
||||||
# Popup that confirm character switch
|
|
||||||
if self.handle_popup_confirm():
|
|
||||||
self.interval_reset(page_guide.check_button)
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Click teleport
|
|
||||||
if self.appear(page_guide.check_button, interval=1):
|
|
||||||
if skip_first_load:
|
|
||||||
skip_first_load = False
|
|
||||||
else:
|
|
||||||
DUNGEON_LIST.load_rows(main=self)
|
|
||||||
entrance = DUNGEON_LIST.keyword2button(dungeon)
|
|
||||||
if entrance is not None:
|
|
||||||
self.device.click(entrance)
|
|
||||||
screenshot_interval_set()
|
|
||||||
self.interval_reset(page_guide.check_button)
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
logger.warning(f'Cannot find dungeon entrance of {dungeon}')
|
|
||||||
continue
|
|
||||||
|
|
||||||
def get_dungeon_interact(self) -> DungeonList | None:
|
|
||||||
"""
|
|
||||||
Pages:
|
|
||||||
in: page_main
|
|
||||||
"""
|
|
||||||
if not self.appear(DUNGEON_COMBAT_INTERACT):
|
|
||||||
logger.info('No dungeon interact')
|
|
||||||
return None
|
|
||||||
|
|
||||||
self.acquire_lang_checked()
|
|
||||||
|
|
||||||
ocr = OcrDungeonList(DUNGEON_COMBAT_INTERACT_TEXT)
|
|
||||||
result = ocr.detect_and_ocr(self.device.image)
|
|
||||||
|
|
||||||
dungeon = None
|
|
||||||
# Special match names in English
|
|
||||||
# Second row must have at least 3 characters which is the shortest name "Ire"
|
|
||||||
# Stangnant Shadow: Shape of
|
|
||||||
# Quanta
|
|
||||||
if len(result) == 2 and len(result[1].ocr_text) >= 3:
|
|
||||||
first, second = result[0].ocr_text, result[1].ocr_text
|
|
||||||
if re.search(r'Stagnant\s*Shadow', first):
|
|
||||||
dungeon = DungeonList.find_dungeon_by_string(en=second, is_Stagnant_Shadow=True)
|
|
||||||
elif re.search(r'Cavern\s*of\s*Corrosion', first):
|
|
||||||
dungeon = DungeonList.find_dungeon_by_string(en=second, is_Cavern_of_Corrosion=True)
|
|
||||||
elif re.search(r'Echo\s*of\s*War', first):
|
|
||||||
dungeon = DungeonList.find_dungeon_by_string(en=second, is_Echo_of_War=True)
|
|
||||||
elif re.search(r'Calyx[\s(]+Golden', first):
|
|
||||||
dungeon = DungeonList.find_dungeon_by_string(en=second, is_Calyx_Golden=True, world=self.plane.world)
|
|
||||||
elif re.search(r'Calyx[\s(]+Crimson', first):
|
|
||||||
dungeon = DungeonList.find_dungeon_by_string(en=second, is_Calyx_Crimson=True, plane=self.plane)
|
|
||||||
if dungeon is not None:
|
|
||||||
logger.attr('DungeonInteract', dungeon)
|
|
||||||
return dungeon
|
|
||||||
|
|
||||||
# Join
|
|
||||||
result = ' '.join([row.ocr_text for row in result])
|
|
||||||
|
|
||||||
# Special match names in Chinese
|
|
||||||
# Only calyxes need spacial match
|
|
||||||
if res := re.search(r'(^.+之蕾)', result):
|
|
||||||
dungeon = DungeonList.find_dungeon_by_string(cn=res.group(1), is_Calyx_Crimson=True, plane=self.plane)
|
|
||||||
if dungeon is not None:
|
|
||||||
logger.attr('DungeonInteract', dungeon)
|
|
||||||
return dungeon
|
|
||||||
dungeon = DungeonList.find_dungeon_by_string(cn=res.group(1), is_Calyx_Golden=True, world=self.plane.world)
|
|
||||||
if dungeon is not None:
|
|
||||||
logger.attr('DungeonInteract', dungeon)
|
|
||||||
return dungeon
|
|
||||||
|
|
||||||
# Dungeons
|
|
||||||
try:
|
|
||||||
dungeon = DungeonList.find(result)
|
|
||||||
logger.attr('DungeonInteract', dungeon)
|
|
||||||
return dungeon
|
|
||||||
except ScriptError:
|
|
||||||
pass
|
|
||||||
# Simulated Universe returns Simulated_Universe_World_1
|
|
||||||
try:
|
|
||||||
dungeon = DungeonNav.find(result)
|
|
||||||
if dungeon == KEYWORDS_DUNGEON_NAV.Simulated_Universe:
|
|
||||||
dungeon = KEYWORDS_DUNGEON_LIST.Simulated_Universe_World_1
|
|
||||||
logger.attr('DungeonInteract', dungeon)
|
|
||||||
return dungeon
|
|
||||||
except ScriptError:
|
|
||||||
pass
|
|
||||||
# Unknown
|
|
||||||
logger.attr('DungeonInteract', None)
|
|
||||||
return None
|
|
||||||
|
|
||||||
def dungeon_goto(self, dungeon: DungeonList):
|
|
||||||
"""
|
|
||||||
Returns:
|
|
||||||
bool: If success
|
|
||||||
|
|
||||||
Pages:
|
|
||||||
in: page_guide, Survival_Index
|
|
||||||
out: COMBAT_PREPARE if success
|
|
||||||
page_guide if failed
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
from tasks.dungeon.keywords import KEYWORDS_DUNGEON_LIST
|
|
||||||
self = DungeonUI('src')
|
|
||||||
self.device.screenshot()
|
|
||||||
self.dungeon_tab_goto(KEYWORDS_DUNGEON_TAB.Survival_Index)
|
|
||||||
self.dungeon_goto(KEYWORDS_DUNGEON_LIST.Calyx_Crimson_Harmony)
|
|
||||||
"""
|
|
||||||
# Reset search button
|
|
||||||
DUNGEON_LIST.search_button = OCR_DUNGEON_LIST
|
|
||||||
|
|
||||||
if dungeon.is_Calyx_Crimson \
|
|
||||||
or dungeon.is_Stagnant_Shadow \
|
|
||||||
or dungeon.is_Cavern_of_Corrosion \
|
|
||||||
or dungeon.is_Echo_of_War \
|
|
||||||
or dungeon.is_Ornament_Extraction:
|
|
||||||
self._dungeon_nav_goto(dungeon.dungeon_nav)
|
|
||||||
self._dungeon_wait_until_dungeon_list_loaded()
|
|
||||||
self._dungeon_insight(dungeon)
|
|
||||||
self._dungeon_enter(dungeon)
|
|
||||||
return True
|
|
||||||
if dungeon.is_Calyx_Golden:
|
|
||||||
self._dungeon_nav_goto(dungeon.dungeon_nav)
|
|
||||||
self._dungeon_wait_until_dungeon_list_loaded()
|
|
||||||
dungeon = self._dungeon_world_set_wrapper(dungeon)
|
|
||||||
self._dungeon_wait_until_dungeon_list_loaded()
|
|
||||||
self._dungeon_insight(dungeon)
|
|
||||||
self._dungeon_enter(dungeon)
|
|
||||||
return True
|
|
||||||
|
|
||||||
logger.error(f'Goto dungeon {dungeon} is not supported')
|
|
||||||
return False
|
|
85
tasks/dungeon/ui/interact.py
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import re
|
||||||
|
|
||||||
|
from module.exception import ScriptError
|
||||||
|
from module.logger import logger
|
||||||
|
from tasks.base.ui import UI
|
||||||
|
from tasks.combat.assets.assets_combat_interact import DUNGEON_COMBAT_INTERACT, DUNGEON_COMBAT_INTERACT_TEXT
|
||||||
|
from tasks.dungeon.keywords import (
|
||||||
|
DungeonList,
|
||||||
|
DungeonNav,
|
||||||
|
KEYWORDS_DUNGEON_LIST,
|
||||||
|
KEYWORDS_DUNGEON_NAV
|
||||||
|
)
|
||||||
|
from tasks.dungeon.ui.llist import OcrDungeonName
|
||||||
|
|
||||||
|
|
||||||
|
class DungeonUIInteract(UI):
|
||||||
|
def get_dungeon_interact(self) -> DungeonList | None:
|
||||||
|
"""
|
||||||
|
Pages:
|
||||||
|
in: page_main
|
||||||
|
"""
|
||||||
|
if not self.appear(DUNGEON_COMBAT_INTERACT):
|
||||||
|
logger.info('No dungeon interact')
|
||||||
|
return None
|
||||||
|
|
||||||
|
self.acquire_lang_checked()
|
||||||
|
|
||||||
|
ocr = OcrDungeonName(DUNGEON_COMBAT_INTERACT_TEXT)
|
||||||
|
result = ocr.detect_and_ocr(self.device.image)
|
||||||
|
|
||||||
|
dungeon = None
|
||||||
|
# Special match names in English
|
||||||
|
# Second row must have at least 3 characters which is the shortest name "Ire"
|
||||||
|
# Stangnant Shadow: Shape of
|
||||||
|
# Quanta
|
||||||
|
if len(result) == 2 and len(result[1].ocr_text) >= 3:
|
||||||
|
first, second = result[0].ocr_text, result[1].ocr_text
|
||||||
|
if re.search(r'Stagnant\s*Shadow', first):
|
||||||
|
dungeon = DungeonList.find_dungeon_by_string(en=second, is_Stagnant_Shadow=True)
|
||||||
|
elif re.search(r'Cavern\s*of\s*Corrosion', first):
|
||||||
|
dungeon = DungeonList.find_dungeon_by_string(en=second, is_Cavern_of_Corrosion=True)
|
||||||
|
elif re.search(r'Echo\s*of\s*War', first):
|
||||||
|
dungeon = DungeonList.find_dungeon_by_string(en=second, is_Echo_of_War=True)
|
||||||
|
elif re.search(r'Calyx[\s(]+Golden', first):
|
||||||
|
dungeon = DungeonList.find_dungeon_by_string(en=second, is_Calyx_Golden=True, world=self.plane.world)
|
||||||
|
elif re.search(r'Calyx[\s(]+Crimson', first):
|
||||||
|
dungeon = DungeonList.find_dungeon_by_string(en=second, is_Calyx_Crimson=True, plane=self.plane)
|
||||||
|
if dungeon is not None:
|
||||||
|
logger.attr('DungeonInteract', dungeon)
|
||||||
|
return dungeon
|
||||||
|
|
||||||
|
# Join
|
||||||
|
result = ' '.join([row.ocr_text for row in result])
|
||||||
|
|
||||||
|
# Special match names in Chinese
|
||||||
|
# Only calyxes need spacial match
|
||||||
|
if res := re.search(r'(^.+之蕾)', result):
|
||||||
|
dungeon = DungeonList.find_dungeon_by_string(cn=res.group(1), is_Calyx_Crimson=True, plane=self.plane)
|
||||||
|
if dungeon is not None:
|
||||||
|
logger.attr('DungeonInteract', dungeon)
|
||||||
|
return dungeon
|
||||||
|
dungeon = DungeonList.find_dungeon_by_string(cn=res.group(1), is_Calyx_Golden=True, world=self.plane.world)
|
||||||
|
if dungeon is not None:
|
||||||
|
logger.attr('DungeonInteract', dungeon)
|
||||||
|
return dungeon
|
||||||
|
|
||||||
|
# Dungeons
|
||||||
|
try:
|
||||||
|
dungeon = DungeonList.find(result)
|
||||||
|
logger.attr('DungeonInteract', dungeon)
|
||||||
|
return dungeon
|
||||||
|
except ScriptError:
|
||||||
|
pass
|
||||||
|
# Simulated Universe returns Simulated_Universe_World_1
|
||||||
|
try:
|
||||||
|
dungeon = DungeonNav.find(result)
|
||||||
|
if dungeon == KEYWORDS_DUNGEON_NAV.Simulated_Universe:
|
||||||
|
dungeon = KEYWORDS_DUNGEON_LIST.Simulated_Universe_World_1
|
||||||
|
logger.attr('DungeonInteract', dungeon)
|
||||||
|
return dungeon
|
||||||
|
except ScriptError:
|
||||||
|
pass
|
||||||
|
# Unknown
|
||||||
|
logger.attr('DungeonInteract', None)
|
||||||
|
return None
|
385
tasks/dungeon/ui/llist.py
Normal file
@ -0,0 +1,385 @@
|
|||||||
|
import re
|
||||||
|
|
||||||
|
import cv2
|
||||||
|
from pponnxcr.predict_system import BoxedResult
|
||||||
|
|
||||||
|
from module.base.base import ModuleBase
|
||||||
|
from module.base.decorator import run_once
|
||||||
|
from module.base.timer import Timer
|
||||||
|
from module.base.utils import area_center, area_offset, crop, image_size
|
||||||
|
from module.logger import logger
|
||||||
|
from module.ocr.ocr import Ocr, OcrResultButton
|
||||||
|
from module.ocr.utils import split_and_pair_button_attr, split_and_pair_buttons
|
||||||
|
from module.ui.draggable_list import DraggableList
|
||||||
|
from module.ui.switch import Switch
|
||||||
|
from tasks.base.page import page_guide
|
||||||
|
from tasks.base.ui import UI
|
||||||
|
from tasks.combat.assets.assets_combat_prepare import COMBAT_PREPARE
|
||||||
|
from tasks.dungeon.assets.assets_dungeon_ui_list import *
|
||||||
|
from tasks.dungeon.keywords import (
|
||||||
|
DungeonList,
|
||||||
|
KEYWORDS_DUNGEON_ENTRANCE,
|
||||||
|
KEYWORDS_DUNGEON_LIST
|
||||||
|
)
|
||||||
|
from tasks.dungeon.keywords.classes import DungeonEntrance
|
||||||
|
from tasks.map.keywords import MapPlane
|
||||||
|
|
||||||
|
LIST_SORTING = Switch('DUNGEON_LIST_SORTING', is_selector=True)
|
||||||
|
LIST_SORTING.add_state('Ascending', check_button=LIST_ASCENDING)
|
||||||
|
LIST_SORTING.add_state('Descending', check_button=LIST_DESCENDING)
|
||||||
|
LIST_SORTING.Ascending = 'Ascending'
|
||||||
|
LIST_SORTING.Descending = 'Descending'
|
||||||
|
|
||||||
|
|
||||||
|
class OcrDungeonName(Ocr):
|
||||||
|
def after_process(self, result):
|
||||||
|
# 乙太之蕾•雅利洛-Ⅵ
|
||||||
|
result = re.sub(r'-[VⅤ][IⅠ]', '-Ⅵ', result)
|
||||||
|
|
||||||
|
# 苏乐达™热砂海选会场
|
||||||
|
result = re.sub(r'(苏乐达|蘇樂達|SoulGlad|スラーダ|FelizAlma)[rtT]*M', r'\1', result)
|
||||||
|
|
||||||
|
result = super().after_process(result)
|
||||||
|
|
||||||
|
if self.lang == 'cn':
|
||||||
|
result = result.replace('翼', '巽') # 巽风之形
|
||||||
|
result = result.replace('皖A0', '50').replace('皖', '')
|
||||||
|
# 燔灼之形•凝滞虚影
|
||||||
|
result = result.replace('熠', '燔')
|
||||||
|
result = re.sub('^灼之形', '燔灼之形', result)
|
||||||
|
# 偃偶之形•凝滞虚影
|
||||||
|
result = re.sub('^偶之形', '偃偶之形', result)
|
||||||
|
# 嗔怒之形•凝滞虚影
|
||||||
|
result = re.sub('^怒之形', '嗔怒之形', result)
|
||||||
|
# 蛀星的旧·历战余响
|
||||||
|
result = re.sub(r'蛀星的旧.*?历战', '蛀星的旧靥•历战', result)
|
||||||
|
|
||||||
|
# 9支援仓段
|
||||||
|
for word in 'Q9α':
|
||||||
|
result = result.removeprefix(word)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
class OcrDungeonList(OcrDungeonName):
|
||||||
|
# Keep __init__ parameter unused
|
||||||
|
def __init__(self, button: ButtonWrapper = None, lang=None, name=None):
|
||||||
|
super().__init__(button=button, lang=lang, name='OcrDungeonList')
|
||||||
|
self.limit_entrance = False
|
||||||
|
|
||||||
|
def detect_and_ocr(self, image, direct_ocr=False) -> list[BoxedResult]:
|
||||||
|
if self.button != OCR_DUNGEON_NAME:
|
||||||
|
return super().detect_and_ocr(image, direct_ocr=direct_ocr)
|
||||||
|
|
||||||
|
# Concat OCR_DUNGEON_NAME and OCR_DUNGEON_TELEPORT
|
||||||
|
# so they can be OCRed at one time
|
||||||
|
left = crop(image, OCR_DUNGEON_NAME.area, copy=False)
|
||||||
|
right = crop(image, OCR_DUNGEON_TELEPORT.area, copy=False)
|
||||||
|
lw, lh = image_size(left)
|
||||||
|
rw, rh = image_size(right)
|
||||||
|
if lh != rh:
|
||||||
|
logger.error('OCR_DUNGEON_NAME and OCR_DUNGEON_TELEPORT does not have same height, image cannot concat')
|
||||||
|
image = cv2.hconcat([left, right])
|
||||||
|
|
||||||
|
if self.limit_entrance:
|
||||||
|
w, h = image_size(image)
|
||||||
|
image = crop(image, (0, 0, w, h - 70), copy=False)
|
||||||
|
|
||||||
|
results = super().detect_and_ocr(image, direct_ocr=True)
|
||||||
|
|
||||||
|
# Move box
|
||||||
|
for result in results:
|
||||||
|
x, _ = area_center(result.box)
|
||||||
|
# Belongs to right image
|
||||||
|
if x >= lw:
|
||||||
|
result.box = area_offset(result.box, offset=(-lw, 0))
|
||||||
|
result.box = area_offset(result.box, offset=OCR_DUNGEON_TELEPORT.area[:2])
|
||||||
|
# Belongs to left image
|
||||||
|
else:
|
||||||
|
result.box = area_offset(result.box, offset=OCR_DUNGEON_NAME.area[:2])
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
class OcrDungeonListUsingPlane(OcrDungeonList):
|
||||||
|
def _match_result(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Convert MapPlane object to their corresponding DungeonList object
|
||||||
|
"""
|
||||||
|
plane = super()._match_result(*args, **kwargs)
|
||||||
|
if plane is not None:
|
||||||
|
for dungeon in DungeonList.instances.values():
|
||||||
|
if dungeon.is_Calyx_Golden and dungeon.plane == plane:
|
||||||
|
return dungeon
|
||||||
|
return plane
|
||||||
|
|
||||||
|
|
||||||
|
class OcrDungeonListLimitEntrance(OcrDungeonList):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.limit_entrance = True
|
||||||
|
|
||||||
|
|
||||||
|
class OcrDungeonListUsingPlaneLimitEntrance(OcrDungeonListUsingPlane, OcrDungeonListLimitEntrance):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class DraggableDungeonList(DraggableList):
|
||||||
|
teleports: list[OcrResultButton] = []
|
||||||
|
navigates: list[OcrResultButton] = []
|
||||||
|
|
||||||
|
# use_plane: True to use map planes to predict dungeons only.
|
||||||
|
# Can only be True in Calyx Crimson
|
||||||
|
use_plane = False
|
||||||
|
# limit_entrance: True to ensure the teleport button is insight
|
||||||
|
limit_entrance = False
|
||||||
|
|
||||||
|
def load_rows(self, main: ModuleBase, allow_early_access=False):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
main:
|
||||||
|
allow_early_access: True to allow dungeons that are in temporarily early access during events
|
||||||
|
"""
|
||||||
|
relative_area = (0, 0, 1280, 120)
|
||||||
|
if self.use_plane:
|
||||||
|
self.keyword_class = [MapPlane, DungeonEntrance]
|
||||||
|
if self.limit_entrance:
|
||||||
|
self.ocr_class = OcrDungeonListUsingPlaneLimitEntrance
|
||||||
|
else:
|
||||||
|
self.ocr_class = OcrDungeonListUsingPlane
|
||||||
|
else:
|
||||||
|
self.keyword_class = [DungeonList, DungeonEntrance]
|
||||||
|
if self.limit_entrance:
|
||||||
|
self.ocr_class = OcrDungeonListLimitEntrance
|
||||||
|
else:
|
||||||
|
self.ocr_class = OcrDungeonList
|
||||||
|
super().load_rows(main=main)
|
||||||
|
|
||||||
|
# Check early access dungeons
|
||||||
|
buttons = DUNGEON_LIST.cur_buttons.copy()
|
||||||
|
for name, button in split_and_pair_buttons(
|
||||||
|
DUNGEON_LIST.cur_buttons,
|
||||||
|
split_func=lambda x: x != KEYWORDS_DUNGEON_ENTRANCE.Enter,
|
||||||
|
relative_area=relative_area
|
||||||
|
):
|
||||||
|
logger.warning(f'Early access dungeon: {name}')
|
||||||
|
buttons.remove(name)
|
||||||
|
buttons.remove(button)
|
||||||
|
|
||||||
|
# Remove early access dungeons
|
||||||
|
if not allow_early_access:
|
||||||
|
DUNGEON_LIST.cur_buttons = buttons
|
||||||
|
# From super.load_rows(), re-calculate indexes
|
||||||
|
indexes = [self.keyword2index(row.matched_keyword)
|
||||||
|
for row in self.cur_buttons]
|
||||||
|
indexes = [index for index in indexes if index]
|
||||||
|
|
||||||
|
if not indexes:
|
||||||
|
logger.warning(f'No valid rows loaded into {self}')
|
||||||
|
return
|
||||||
|
|
||||||
|
self.cur_min = min(indexes)
|
||||||
|
self.cur_max = max(indexes)
|
||||||
|
logger.attr(self.name, f'{self.cur_min} - {self.cur_max}')
|
||||||
|
|
||||||
|
# Replace dungeon.button with teleport
|
||||||
|
self.teleports = list(split_and_pair_button_attr(
|
||||||
|
self.cur_buttons,
|
||||||
|
split_func=lambda x: x != KEYWORDS_DUNGEON_ENTRANCE.Teleport and x != KEYWORDS_DUNGEON_ENTRANCE.Enter,
|
||||||
|
relative_area=relative_area
|
||||||
|
))
|
||||||
|
self.navigates = list(split_and_pair_button_attr(
|
||||||
|
self.cur_buttons,
|
||||||
|
split_func=lambda x: x != KEYWORDS_DUNGEON_ENTRANCE.Navigate,
|
||||||
|
relative_area=relative_area
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
DUNGEON_LIST = DraggableDungeonList(
|
||||||
|
'DungeonList', keyword_class=[DungeonList, DungeonEntrance, MapPlane],
|
||||||
|
ocr_class=OcrDungeonList, search_button=OCR_DUNGEON_NAME)
|
||||||
|
|
||||||
|
|
||||||
|
class DungeonUIList(UI):
|
||||||
|
def _dungeon_list_reset(self):
|
||||||
|
"""
|
||||||
|
Reset list to top
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: If success
|
||||||
|
"""
|
||||||
|
logger.info('Dungeon list reset')
|
||||||
|
current = LIST_SORTING.get(main=self)
|
||||||
|
if current == LIST_SORTING.Descending:
|
||||||
|
another = LIST_SORTING.Ascending
|
||||||
|
elif current == LIST_SORTING.Ascending:
|
||||||
|
another = LIST_SORTING.Descending
|
||||||
|
else:
|
||||||
|
logger.warning('Unknown dungeon LIST_SORTING')
|
||||||
|
return False
|
||||||
|
|
||||||
|
LIST_SORTING.set(another, main=self)
|
||||||
|
LIST_SORTING.set(current, main=self)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _dungeon_insight_index(self, dungeon: DungeonList):
|
||||||
|
"""
|
||||||
|
Insight a dungeon using pre-defined dungeon indexes from DUNGEON_LIST
|
||||||
|
|
||||||
|
Pages:
|
||||||
|
in: page_guide, Survival_Index, nav including dungeon
|
||||||
|
out: page_guide, Survival_Index, nav including dungeon, dungeon insight
|
||||||
|
"""
|
||||||
|
logger.hr('Dungeon insight (index)', level=2)
|
||||||
|
if dungeon.is_Ornament_Extraction:
|
||||||
|
# Limit drag area in iOrnament_Extraction
|
||||||
|
DUNGEON_LIST.search_button = OCR_DUNGEON_NAME_ROGUE
|
||||||
|
elif dungeon.is_Echo_of_War:
|
||||||
|
DUNGEON_LIST.search_button = OCR_DUNGEON_LIST
|
||||||
|
else:
|
||||||
|
DUNGEON_LIST.search_button = OCR_DUNGEON_NAME
|
||||||
|
# Predict dungeon by plane name in calyxes where dungeons share the same names
|
||||||
|
DUNGEON_LIST.use_plane = bool(dungeon.is_Calyx)
|
||||||
|
DUNGEON_LIST.check_row_order = True
|
||||||
|
|
||||||
|
# Insight dungeon
|
||||||
|
DUNGEON_LIST.insight_row(dungeon, main=self)
|
||||||
|
self.device.click_record_clear()
|
||||||
|
# Check if dungeon unlocked
|
||||||
|
for entrance in DUNGEON_LIST.navigates:
|
||||||
|
entrance: OcrResultButton = entrance
|
||||||
|
logger.warning(f'Teleport {entrance.matched_keyword} is not unlocked')
|
||||||
|
if entrance == dungeon:
|
||||||
|
logger.error(f'Trying to enter dungeon {dungeon}, but teleport is not unlocked')
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Find teleport button
|
||||||
|
if dungeon not in [tp.matched_keyword for tp in DUNGEON_LIST.teleports]:
|
||||||
|
# Dungeon name is insight but teleport button is not
|
||||||
|
logger.info('Dungeon name is insight, swipe down a little bit to find the teleport button')
|
||||||
|
if dungeon.is_Forgotten_Hall:
|
||||||
|
DUNGEON_LIST.drag_vector = (-0.4, -0.2) # Keyword loaded is reversed
|
||||||
|
else:
|
||||||
|
DUNGEON_LIST.drag_vector = (0.2, 0.4)
|
||||||
|
DUNGEON_LIST.limit_entrance = True
|
||||||
|
DUNGEON_LIST.insight_row(dungeon, main=self)
|
||||||
|
self.device.click_record_clear()
|
||||||
|
DUNGEON_LIST.drag_vector = DraggableList.drag_vector
|
||||||
|
DUNGEON_LIST.limit_entrance = False
|
||||||
|
DUNGEON_LIST.load_rows(main=self)
|
||||||
|
# Check if dungeon unlocked
|
||||||
|
for entrance in DUNGEON_LIST.navigates:
|
||||||
|
if entrance.matched_keyword == dungeon:
|
||||||
|
logger.error(f'Trying to enter dungeon {dungeon}, but teleport is not unlocked')
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _dungeon_insight_sort(self, dungeon: DungeonList):
|
||||||
|
"""
|
||||||
|
Insight a dungeon using sorter and plain drag, reset list on error
|
||||||
|
"""
|
||||||
|
logger.hr('Dungeon insight (sort)', level=2)
|
||||||
|
logger.info(f'Dungeon insight: {dungeon}')
|
||||||
|
DUNGEON_LIST.search_button = OCR_DUNGEON_NAME
|
||||||
|
DUNGEON_LIST.use_plane = bool(dungeon.is_Calyx_Golden)
|
||||||
|
DUNGEON_LIST.check_row_order = False
|
||||||
|
|
||||||
|
for _ in range(3):
|
||||||
|
visited = set()
|
||||||
|
end_count = 0
|
||||||
|
self.device.click_record_clear()
|
||||||
|
while 1:
|
||||||
|
visited_count = len(visited)
|
||||||
|
# Load
|
||||||
|
DUNGEON_LIST.load_rows(main=self, allow_early_access=True)
|
||||||
|
for entrance in DUNGEON_LIST.teleports:
|
||||||
|
if entrance.matched_keyword == dungeon:
|
||||||
|
logger.info(f'Found dungeon {dungeon}')
|
||||||
|
return True
|
||||||
|
for entrance in DUNGEON_LIST.navigates:
|
||||||
|
if entrance.matched_keyword == dungeon:
|
||||||
|
logger.error(f'Trying to enter dungeon {dungeon}, but teleport is not unlocked')
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Check end
|
||||||
|
for entrance in DUNGEON_LIST.cur_buttons:
|
||||||
|
visited.add(entrance.matched_keyword.name)
|
||||||
|
if len(visited) <= visited_count:
|
||||||
|
logger.warning('No more rows loaded')
|
||||||
|
end_count += 1
|
||||||
|
if end_count >= 3:
|
||||||
|
logger.error('Dungeon list reached end but target dungeon not found')
|
||||||
|
break
|
||||||
|
|
||||||
|
# Drag down
|
||||||
|
DUNGEON_LIST.drag_page('down', main=self)
|
||||||
|
self.wait_until_stable(DUNGEON_LIST.search_button, timer=Timer(
|
||||||
|
0, count=0), timeout=Timer(1.5, count=5))
|
||||||
|
|
||||||
|
self._dungeon_list_reset()
|
||||||
|
|
||||||
|
logger.error('Failed to insight dungeon after 3 trial')
|
||||||
|
return False
|
||||||
|
|
||||||
|
def dungeon_insight(self, dungeon: DungeonList):
|
||||||
|
"""
|
||||||
|
Insight a dungeon
|
||||||
|
|
||||||
|
Pages:
|
||||||
|
in: page_guide, Survival_Index, nav including dungeon
|
||||||
|
out: page_guide, Survival_Index, nav including dungeon, dungeon insight
|
||||||
|
"""
|
||||||
|
if dungeon.is_Calyx_Crimson or dungeon.is_Stagnant_Shadow:
|
||||||
|
# Having dungeon sorting and early access
|
||||||
|
self._dungeon_insight_sort(dungeon)
|
||||||
|
else:
|
||||||
|
self._dungeon_insight_index(dungeon)
|
||||||
|
|
||||||
|
def _dungeon_enter(self, dungeon, enter_check_button=COMBAT_PREPARE, skip_first_screenshot=True):
|
||||||
|
"""
|
||||||
|
Pages:
|
||||||
|
in: page_guide, Survival_Index, nav including dungeon
|
||||||
|
out: COMBAT_PREPARE, FORGOTTEN_HALL_CHECK
|
||||||
|
"""
|
||||||
|
logger.hr('Dungeon enter', level=2)
|
||||||
|
DUNGEON_LIST.use_plane = bool(dungeon.is_Calyx_Crimson)
|
||||||
|
skip_first_load = skip_first_screenshot
|
||||||
|
|
||||||
|
@run_once
|
||||||
|
def screenshot_interval_set():
|
||||||
|
self.device.screenshot_interval_set('combat')
|
||||||
|
|
||||||
|
while 1:
|
||||||
|
if skip_first_screenshot:
|
||||||
|
skip_first_screenshot = False
|
||||||
|
else:
|
||||||
|
self.device.screenshot()
|
||||||
|
|
||||||
|
# End
|
||||||
|
if self.appear(enter_check_button):
|
||||||
|
logger.info(f'Arrive {enter_check_button.name}')
|
||||||
|
break
|
||||||
|
|
||||||
|
# Additional
|
||||||
|
# Popup that confirm character switch
|
||||||
|
if self.handle_popup_confirm():
|
||||||
|
self.interval_reset(page_guide.check_button)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Click teleport
|
||||||
|
if self.appear(page_guide.check_button, interval=1):
|
||||||
|
if skip_first_load:
|
||||||
|
skip_first_load = False
|
||||||
|
else:
|
||||||
|
DUNGEON_LIST.load_rows(main=self)
|
||||||
|
entrance = DUNGEON_LIST.keyword2button(dungeon)
|
||||||
|
if entrance is not None:
|
||||||
|
self.device.click(entrance)
|
||||||
|
screenshot_interval_set()
|
||||||
|
self.interval_reset(page_guide.check_button)
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
logger.warning(f'Cannot find dungeon entrance of {dungeon}')
|
||||||
|
continue
|
||||||
|
|
||||||
|
self.device.screenshot_interval_set()
|
351
tasks/dungeon/ui/nav.py
Normal file
@ -0,0 +1,351 @@
|
|||||||
|
import cv2
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from module.base.base import ModuleBase
|
||||||
|
from module.base.timer import Timer
|
||||||
|
from module.base.utils import get_color
|
||||||
|
from module.logger import logger
|
||||||
|
from module.ocr.ocr import Ocr
|
||||||
|
from module.ui.draggable_list import DraggableList
|
||||||
|
from module.ui.switch import Switch
|
||||||
|
from tasks.base.page import page_guide
|
||||||
|
from tasks.base.ui import UI
|
||||||
|
from tasks.dungeon.assets.assets_dungeon_ui import *
|
||||||
|
from tasks.dungeon.assets.assets_dungeon_ui_rogue import *
|
||||||
|
from tasks.dungeon.keywords import (
|
||||||
|
DungeonNav,
|
||||||
|
DungeonTab,
|
||||||
|
KEYWORDS_DUNGEON_NAV,
|
||||||
|
KEYWORDS_DUNGEON_TAB
|
||||||
|
)
|
||||||
|
from tasks.map.interact.aim import inrange
|
||||||
|
|
||||||
|
|
||||||
|
class DungeonTabSwitch(Switch):
|
||||||
|
SEARCH_BUTTON = TAB_SEARCH
|
||||||
|
|
||||||
|
def add_state(self, state, check_button, click_button=None):
|
||||||
|
# Load search
|
||||||
|
if check_button is not None:
|
||||||
|
check_button.load_search(self.__class__.SEARCH_BUTTON.area)
|
||||||
|
if click_button is not None:
|
||||||
|
click_button.load_search(self.__class__.SEARCH_BUTTON.area)
|
||||||
|
return super().add_state(state, check_button, click_button)
|
||||||
|
|
||||||
|
def click(self, state, main):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
state (str):
|
||||||
|
main (ModuleBase):
|
||||||
|
"""
|
||||||
|
button = self.get_data(state)['click_button']
|
||||||
|
_ = main.appear(button) # Search button to load offset
|
||||||
|
main.device.click(button)
|
||||||
|
|
||||||
|
|
||||||
|
SWITCH_DUNGEON_TAB = DungeonTabSwitch('DungeonTab', is_selector=True)
|
||||||
|
SWITCH_DUNGEON_TAB.add_state(
|
||||||
|
KEYWORDS_DUNGEON_TAB.Operation_Briefing,
|
||||||
|
check_button=OPERATION_BRIEFING_CHECK,
|
||||||
|
click_button=OPERATION_BRIEFING_CLICK
|
||||||
|
)
|
||||||
|
SWITCH_DUNGEON_TAB.add_state(
|
||||||
|
KEYWORDS_DUNGEON_TAB.Daily_Training,
|
||||||
|
check_button=DAILY_TRAINING_CHECK,
|
||||||
|
click_button=DAILY_TRAINING_CLICK
|
||||||
|
)
|
||||||
|
SWITCH_DUNGEON_TAB.add_state(
|
||||||
|
KEYWORDS_DUNGEON_TAB.Survival_Index,
|
||||||
|
check_button=SURVIVAL_INDEX_CHECK,
|
||||||
|
click_button=SURVIVAL_INDEX_CLICK
|
||||||
|
)
|
||||||
|
SWITCH_DUNGEON_TAB.add_state(
|
||||||
|
KEYWORDS_DUNGEON_TAB.Simulated_Universe,
|
||||||
|
check_button=SIMULATED_UNIVERSE_CHECK,
|
||||||
|
click_button=SIMULATED_UNIVERSE_CLICK
|
||||||
|
)
|
||||||
|
SWITCH_DUNGEON_TAB.add_state(
|
||||||
|
KEYWORDS_DUNGEON_TAB.Treasures_Lightward,
|
||||||
|
check_button=TREASURES_LIGHTWARD_CHECK,
|
||||||
|
click_button=TREASURES_LIGHTWARD_CLICK
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class OcrDungeonNav(Ocr):
|
||||||
|
def after_process(self, result):
|
||||||
|
result = super().after_process(result)
|
||||||
|
result = result.replace('#', '')
|
||||||
|
if self.lang == 'cn':
|
||||||
|
result = result.replace('萼喜', '萼')
|
||||||
|
result = result.replace('带', '滞') # 凝带虚影
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
class DraggableDungeonNav(DraggableList):
|
||||||
|
# 0.5 is the magic number to reach bottom in 1 swipe
|
||||||
|
# but relax we still have retires when magic doesn't work
|
||||||
|
drag_vector = (0.50, 0.52)
|
||||||
|
|
||||||
|
|
||||||
|
DUNGEON_NAV_LIST = DraggableDungeonNav(
|
||||||
|
'DungeonNavList', keyword_class=DungeonNav, ocr_class=OcrDungeonNav, search_button=OCR_DUNGEON_NAV)
|
||||||
|
|
||||||
|
|
||||||
|
class DungeonUINav(UI):
|
||||||
|
def dungeon_tab_goto(self, state: DungeonTab):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
state:
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: If UI switched
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
self = DungeonUI('alas')
|
||||||
|
self.device.screenshot()
|
||||||
|
self.dungeon_tab_goto(KEYWORDS_DUNGEON_TAB.Operation_Briefing)
|
||||||
|
self.dungeon_tab_goto(KEYWORDS_DUNGEON_TAB.Daily_Training)
|
||||||
|
self.dungeon_tab_goto(KEYWORDS_DUNGEON_TAB.Survival_Index)
|
||||||
|
"""
|
||||||
|
logger.hr('Dungeon tab goto', level=2)
|
||||||
|
ui_switched = self.ui_ensure(page_guide)
|
||||||
|
tab_switched = SWITCH_DUNGEON_TAB.set(state, main=self)
|
||||||
|
|
||||||
|
if ui_switched or tab_switched:
|
||||||
|
if state == KEYWORDS_DUNGEON_TAB.Daily_Training:
|
||||||
|
logger.info(f'Tab goto {state}, wait until loaded')
|
||||||
|
self._dungeon_wait_daily_training_loaded()
|
||||||
|
elif state == KEYWORDS_DUNGEON_TAB.Survival_Index:
|
||||||
|
logger.info(f'Tab goto {state}, wait until loaded')
|
||||||
|
self._dungeon_wait_survival_index_loaded()
|
||||||
|
elif state == KEYWORDS_DUNGEON_TAB.Treasures_Lightward:
|
||||||
|
logger.info(f'Tab goto {state}, wait until loaded')
|
||||||
|
self._dungeon_wait_treasures_lightward_loaded()
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _dungeon_wait_daily_training_loaded(self, skip_first_screenshot=True):
|
||||||
|
"""
|
||||||
|
Returns:
|
||||||
|
bool: True if wait success, False if wait timeout.
|
||||||
|
|
||||||
|
Pages:
|
||||||
|
in: page_guide, Daily_Training
|
||||||
|
"""
|
||||||
|
timeout = Timer(2, count=4).start()
|
||||||
|
while 1:
|
||||||
|
if skip_first_screenshot:
|
||||||
|
skip_first_screenshot = False
|
||||||
|
else:
|
||||||
|
self.device.screenshot()
|
||||||
|
|
||||||
|
if timeout.reached():
|
||||||
|
logger.warning('Wait daily training loaded timeout')
|
||||||
|
return False
|
||||||
|
color = get_color(self.device.image, DAILY_TRAINING_LOADED.area)
|
||||||
|
if np.mean(color) < 128:
|
||||||
|
logger.info('Daily training loaded')
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _dungeon_wait_survival_index_loaded(self, skip_first_screenshot=True):
|
||||||
|
"""
|
||||||
|
Returns:
|
||||||
|
bool: True if wait success, False if wait timeout.
|
||||||
|
|
||||||
|
Pages:
|
||||||
|
in: page_guide, Survival_Index
|
||||||
|
"""
|
||||||
|
timeout = Timer(2, count=4).start()
|
||||||
|
while 1:
|
||||||
|
if skip_first_screenshot:
|
||||||
|
skip_first_screenshot = False
|
||||||
|
else:
|
||||||
|
self.device.screenshot()
|
||||||
|
|
||||||
|
if timeout.reached():
|
||||||
|
logger.warning('Wait survival index loaded timeout')
|
||||||
|
return False
|
||||||
|
if self.appear(SURVIVAL_INDEX_SU_LOADED):
|
||||||
|
logger.info('Survival index loaded, SURVIVAL_INDEX_SU_LOADED')
|
||||||
|
return True
|
||||||
|
if self.appear(SURVIVAL_INDEX_OE_LOADED):
|
||||||
|
logger.info('Survival index loaded, SURVIVAL_INDEX_OE_LOADED')
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _dungeon_survival_index_top_appear(self):
|
||||||
|
if self.appear(SURVIVAL_INDEX_SU_LOADED):
|
||||||
|
return True
|
||||||
|
if self.appear(SURVIVAL_INDEX_OE_LOADED):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _dungeon_wait_treasures_lightward_loaded(self, skip_first_screenshot=True):
|
||||||
|
"""
|
||||||
|
Returns:
|
||||||
|
bool: True if wait success, False if wait timeout.
|
||||||
|
|
||||||
|
Pages:
|
||||||
|
in: page_guide, Survival_Index
|
||||||
|
"""
|
||||||
|
timeout = Timer(2, count=4).start()
|
||||||
|
TREASURES_LIGHTWARD_LOADED.set_search_offset((5, 5))
|
||||||
|
TREASURES_LIGHTWARD_LOCKED.set_search_offset((5, 5))
|
||||||
|
while 1:
|
||||||
|
if skip_first_screenshot:
|
||||||
|
skip_first_screenshot = False
|
||||||
|
else:
|
||||||
|
self.device.screenshot()
|
||||||
|
|
||||||
|
if timeout.reached():
|
||||||
|
logger.warning('Wait treasures lightward loaded timeout')
|
||||||
|
return False
|
||||||
|
if self.appear(TREASURES_LIGHTWARD_LOADED):
|
||||||
|
logger.info('Treasures lightward loaded (event unlocked)')
|
||||||
|
return True
|
||||||
|
if self.appear(TREASURES_LIGHTWARD_LOCKED):
|
||||||
|
logger.info('Treasures lightward loaded (event locked)')
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _dungeon_list_button_has_content(self):
|
||||||
|
# Check if having any content
|
||||||
|
# List background: 254, guild border: 225
|
||||||
|
r, g, b = cv2.split(self.image_crop(LIST_LOADED_CHECK, copy=False))
|
||||||
|
minimum = cv2.min(cv2.min(r, g), b)
|
||||||
|
minimum = inrange(minimum, lower=0, upper=180)
|
||||||
|
if minimum.size > 100:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _dungeon_wait_until_dungeon_list_loaded(self, skip_first_screenshot=True):
|
||||||
|
timeout = Timer(1, count=3).start()
|
||||||
|
while 1:
|
||||||
|
if skip_first_screenshot:
|
||||||
|
skip_first_screenshot = False
|
||||||
|
else:
|
||||||
|
self.device.screenshot()
|
||||||
|
|
||||||
|
# End
|
||||||
|
if timeout.reached():
|
||||||
|
logger.warning('Wait until dungeon list loaded timeout')
|
||||||
|
return False
|
||||||
|
|
||||||
|
if self._dungeon_list_button_has_content():
|
||||||
|
logger.info('Dungeon list loaded')
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _dungeon_wait_until_echo_or_war_stabled(self, skip_first_screenshot=True):
|
||||||
|
"""
|
||||||
|
Returns:
|
||||||
|
bool: True if wait success, False if wait timeout.
|
||||||
|
|
||||||
|
Pages:
|
||||||
|
in: page_guide, Survival_Index
|
||||||
|
"""
|
||||||
|
# Wait until Forgotten_Hall stabled
|
||||||
|
timeout = Timer(2, count=4).start()
|
||||||
|
while 1:
|
||||||
|
if skip_first_screenshot:
|
||||||
|
skip_first_screenshot = False
|
||||||
|
else:
|
||||||
|
self.device.screenshot()
|
||||||
|
|
||||||
|
# End
|
||||||
|
if timeout.reached():
|
||||||
|
logger.warning('Wait until Echo_of_War stabled timeout')
|
||||||
|
return False
|
||||||
|
|
||||||
|
DUNGEON_NAV_LIST.load_rows(main=self)
|
||||||
|
|
||||||
|
# End
|
||||||
|
button = DUNGEON_NAV_LIST.keyword2button(KEYWORDS_DUNGEON_NAV.Echo_of_War, show_warning=False)
|
||||||
|
if button:
|
||||||
|
# 513 is the top of the last row of DungeonNav
|
||||||
|
if button.area[1] > 513:
|
||||||
|
logger.info('DungeonNav row Echo_of_War stabled')
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
logger.info('No Echo_of_War in list skip waiting')
|
||||||
|
return False
|
||||||
|
|
||||||
|
def dungeon_nav_goto(self, nav: DungeonNav, skip_first_screenshot=True):
|
||||||
|
"""
|
||||||
|
Equivalent to `DUNGEON_NAV_LIST.select_row(dungeon.dungeon_nav, main=self)`
|
||||||
|
but with tricks to be faster
|
||||||
|
|
||||||
|
Args:
|
||||||
|
nav:
|
||||||
|
skip_first_screenshot:
|
||||||
|
"""
|
||||||
|
logger.hr('Dungeon nav goto', level=2)
|
||||||
|
logger.info(f'Dungeon nav goto {nav}')
|
||||||
|
|
||||||
|
# Wait rows
|
||||||
|
while 1:
|
||||||
|
if skip_first_screenshot:
|
||||||
|
skip_first_screenshot = False
|
||||||
|
else:
|
||||||
|
self.device.screenshot()
|
||||||
|
DUNGEON_NAV_LIST.load_rows(main=self)
|
||||||
|
if DUNGEON_NAV_LIST.cur_buttons:
|
||||||
|
break
|
||||||
|
|
||||||
|
# Wait first row selected
|
||||||
|
timeout = Timer(0.5, count=2).start()
|
||||||
|
skip_first_screenshot = True
|
||||||
|
while 1:
|
||||||
|
if skip_first_screenshot:
|
||||||
|
skip_first_screenshot = False
|
||||||
|
else:
|
||||||
|
self.device.screenshot()
|
||||||
|
if timeout.reached():
|
||||||
|
logger.info('DUNGEON_NAV_LIST not selected')
|
||||||
|
break
|
||||||
|
if button := DUNGEON_NAV_LIST.get_selected_row(main=self):
|
||||||
|
logger.info(f'DUNGEON_NAV_LIST selected at {button}')
|
||||||
|
break
|
||||||
|
|
||||||
|
# Check if it's at the first page.
|
||||||
|
if DUNGEON_NAV_LIST.keyword2button(KEYWORDS_DUNGEON_NAV.Simulated_Universe, show_warning=False) \
|
||||||
|
or DUNGEON_NAV_LIST.keyword2button(KEYWORDS_DUNGEON_NAV.Ornament_Extraction, show_warning=False):
|
||||||
|
# Going to use a faster method to navigate but can only start from list top
|
||||||
|
logger.info('DUNGEON_NAV_LIST at top')
|
||||||
|
# Update points if possible
|
||||||
|
# 2.3, No longer weekly points after Divergent Universe unlocked
|
||||||
|
# if DUNGEON_NAV_LIST.is_row_selected(button, main=self):
|
||||||
|
# self.dungeon_update_simuni()
|
||||||
|
# Treasures lightward is always at top
|
||||||
|
elif DUNGEON_NAV_LIST.keyword2button(KEYWORDS_DUNGEON_NAV.Forgotten_Hall, show_warning=False) \
|
||||||
|
or DUNGEON_NAV_LIST.keyword2button(KEYWORDS_DUNGEON_NAV.Pure_Fiction, show_warning=False):
|
||||||
|
logger.info('DUNGEON_NAV_LIST at top')
|
||||||
|
else:
|
||||||
|
# To start from any list states.
|
||||||
|
logger.info('DUNGEON_NAV_LIST not at top')
|
||||||
|
DUNGEON_NAV_LIST.select_row(nav, main=self)
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Check the first page
|
||||||
|
if nav in [
|
||||||
|
KEYWORDS_DUNGEON_NAV.Simulated_Universe,
|
||||||
|
KEYWORDS_DUNGEON_NAV.Divergent_Universe,
|
||||||
|
KEYWORDS_DUNGEON_NAV.Ornament_Extraction,
|
||||||
|
KEYWORDS_DUNGEON_NAV.Calyx_Golden,
|
||||||
|
KEYWORDS_DUNGEON_NAV.Calyx_Crimson,
|
||||||
|
KEYWORDS_DUNGEON_NAV.Stagnant_Shadow,
|
||||||
|
KEYWORDS_DUNGEON_NAV.Cavern_of_Corrosion,
|
||||||
|
KEYWORDS_DUNGEON_NAV.Forgotten_Hall,
|
||||||
|
KEYWORDS_DUNGEON_NAV.Pure_Fiction,
|
||||||
|
]:
|
||||||
|
button = DUNGEON_NAV_LIST.keyword2button(nav)
|
||||||
|
if button:
|
||||||
|
DUNGEON_NAV_LIST.select_row(nav, main=self, insight=False)
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Check the second page
|
||||||
|
while 1:
|
||||||
|
DUNGEON_NAV_LIST.drag_page('down', main=self)
|
||||||
|
# No skip_first_screenshot since drag_page is just called
|
||||||
|
if self._dungeon_wait_until_echo_or_war_stabled(skip_first_screenshot=False):
|
||||||
|
DUNGEON_NAV_LIST.select_row(nav, main=self, insight=False)
|
||||||
|
return True
|
41
tasks/dungeon/ui/ui.py
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
from module.logger import logger
|
||||||
|
from tasks.dungeon.keywords import DungeonList
|
||||||
|
from tasks.dungeon.ui.interact import DungeonUIInteract
|
||||||
|
from tasks.dungeon.ui.llist import DungeonUIList
|
||||||
|
from tasks.dungeon.ui.nav import DungeonUINav
|
||||||
|
from tasks.dungeon.ui.state import DungeonState
|
||||||
|
|
||||||
|
|
||||||
|
class DungeonUI(DungeonState, DungeonUINav, DungeonUIList, DungeonUIInteract):
|
||||||
|
def dungeon_goto(self, dungeon: DungeonList):
|
||||||
|
"""
|
||||||
|
Returns:
|
||||||
|
bool: If success
|
||||||
|
|
||||||
|
Pages:
|
||||||
|
in: page_guide, Survival_Index
|
||||||
|
out: COMBAT_PREPARE if success
|
||||||
|
page_guide if failed
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
from tasks.dungeon.keywords import KEYWORDS_DUNGEON_LIST
|
||||||
|
self = DungeonUI('src')
|
||||||
|
self.device.screenshot()
|
||||||
|
self.dungeon_tab_goto(KEYWORDS_DUNGEON_TAB.Survival_Index)
|
||||||
|
self.dungeon_goto(KEYWORDS_DUNGEON_LIST.Calyx_Crimson_Harmony)
|
||||||
|
"""
|
||||||
|
# Reset search button
|
||||||
|
if dungeon.is_Calyx_Golden \
|
||||||
|
or dungeon.is_Calyx_Crimson \
|
||||||
|
or dungeon.is_Stagnant_Shadow \
|
||||||
|
or dungeon.is_Cavern_of_Corrosion \
|
||||||
|
or dungeon.is_Echo_of_War \
|
||||||
|
or dungeon.is_Ornament_Extraction:
|
||||||
|
self.dungeon_nav_goto(dungeon.dungeon_nav)
|
||||||
|
self._dungeon_wait_until_dungeon_list_loaded()
|
||||||
|
self.dungeon_insight(dungeon)
|
||||||
|
self._dungeon_enter(dungeon)
|
||||||
|
return True
|
||||||
|
|
||||||
|
logger.error(f'Goto dungeon {dungeon} is not supported')
|
||||||
|
return False
|
@ -3,9 +3,11 @@ from module.base.utils import random_rectangle_vector
|
|||||||
from module.logger import logger
|
from module.logger import logger
|
||||||
from tasks.base.page import page_guide
|
from tasks.base.page import page_guide
|
||||||
from tasks.dungeon.assets.assets_dungeon_ui import *
|
from tasks.dungeon.assets.assets_dungeon_ui import *
|
||||||
|
from tasks.dungeon.assets.assets_dungeon_ui_list import OCR_DUNGEON_LIST
|
||||||
from tasks.dungeon.assets.assets_dungeon_ui_rogue import *
|
from tasks.dungeon.assets.assets_dungeon_ui_rogue import *
|
||||||
from tasks.dungeon.keywords import KEYWORDS_DUNGEON_NAV, KEYWORDS_DUNGEON_TAB
|
from tasks.dungeon.keywords import KEYWORDS_DUNGEON_NAV, KEYWORDS_DUNGEON_TAB
|
||||||
from tasks.dungeon.ui import DungeonUI, SWITCH_DUNGEON_TAB
|
from tasks.dungeon.ui.nav import SWITCH_DUNGEON_TAB
|
||||||
|
from tasks.dungeon.ui.ui import DungeonUI
|
||||||
from tasks.forgotten_hall.assets.assets_forgotten_hall_ui import TELEPORT
|
from tasks.forgotten_hall.assets.assets_forgotten_hall_ui import TELEPORT
|
||||||
|
|
||||||
|
|
||||||
@ -41,7 +43,7 @@ class DungeonRogueUI(DungeonUI):
|
|||||||
logger.info(f'Tab goto {state}, wait until loaded')
|
logger.info(f'Tab goto {state}, wait until loaded')
|
||||||
self._dungeon_wait_until_rogue_loaded()
|
self._dungeon_wait_until_rogue_loaded()
|
||||||
# Switch nav
|
# Switch nav
|
||||||
self._dungeon_nav_goto(KEYWORDS_DUNGEON_NAV.Simulated_Universe)
|
self.dungeon_nav_goto(KEYWORDS_DUNGEON_NAV.Simulated_Universe)
|
||||||
# No idea how to wait list loaded
|
# No idea how to wait list loaded
|
||||||
# List is not able to swipe without fully loaded
|
# List is not able to swipe without fully loaded
|
||||||
self.wait_until_stable(LIST_LOADED_CHECK)
|
self.wait_until_stable(LIST_LOADED_CHECK)
|
||||||
@ -59,7 +61,7 @@ class DungeonRogueUI(DungeonUI):
|
|||||||
if self.appear(SURVIVAL_INDEX_SU_LOADED):
|
if self.appear(SURVIVAL_INDEX_SU_LOADED):
|
||||||
logger.info('Already at nav Simulated_Universe')
|
logger.info('Already at nav Simulated_Universe')
|
||||||
else:
|
else:
|
||||||
self._dungeon_nav_goto(KEYWORDS_DUNGEON_NAV.Simulated_Universe)
|
self.dungeon_nav_goto(KEYWORDS_DUNGEON_NAV.Simulated_Universe)
|
||||||
|
|
||||||
def _dungeon_wait_until_rogue_loaded(self, skip_first_screenshot=True):
|
def _dungeon_wait_until_rogue_loaded(self, skip_first_screenshot=True):
|
||||||
"""
|
"""
|
@ -2,10 +2,9 @@ from module.config.utils import get_server_next_monday_update
|
|||||||
from module.logger import logger
|
from module.logger import logger
|
||||||
from module.ocr.ocr import DigitCounter
|
from module.ocr.ocr import DigitCounter
|
||||||
from tasks.daily.keywords import KEYWORDS_DAILY_QUEST
|
from tasks.daily.keywords import KEYWORDS_DAILY_QUEST
|
||||||
from tasks.dungeon.assets.assets_dungeon_ui import OCR_DUNGEON_LIST, OCR_WEEKLY_LIMIT
|
from tasks.dungeon.assets.assets_dungeon_ui import OCR_WEEKLY_LIMIT
|
||||||
from tasks.dungeon.dungeon import Dungeon
|
from tasks.dungeon.dungeon import Dungeon
|
||||||
from tasks.dungeon.keywords import DungeonList, KEYWORDS_DUNGEON_NAV, KEYWORDS_DUNGEON_TAB
|
from tasks.dungeon.keywords import DungeonList, KEYWORDS_DUNGEON_NAV, KEYWORDS_DUNGEON_TAB
|
||||||
from tasks.dungeon.ui import DUNGEON_LIST
|
|
||||||
|
|
||||||
|
|
||||||
class OcrWeeklyLimit(DigitCounter):
|
class OcrWeeklyLimit(DigitCounter):
|
||||||
@ -78,8 +77,7 @@ class WeeklyDungeon(Dungeon):
|
|||||||
# UI switches
|
# UI switches
|
||||||
self.dungeon_tab_goto(KEYWORDS_DUNGEON_TAB.Survival_Index)
|
self.dungeon_tab_goto(KEYWORDS_DUNGEON_TAB.Survival_Index)
|
||||||
# Equivalent to self.dungeon_goto(dungeon), but check limit remains
|
# Equivalent to self.dungeon_goto(dungeon), but check limit remains
|
||||||
DUNGEON_LIST.search_button = OCR_DUNGEON_LIST
|
self.dungeon_nav_goto(KEYWORDS_DUNGEON_NAV.Echo_of_War)
|
||||||
self._dungeon_nav_goto(KEYWORDS_DUNGEON_NAV.Echo_of_War)
|
|
||||||
self._dungeon_wait_until_dungeon_list_loaded()
|
self._dungeon_wait_until_dungeon_list_loaded()
|
||||||
monday = get_server_next_monday_update(self.config.Scheduler_ServerUpdate)
|
monday = get_server_next_monday_update(self.config.Scheduler_ServerUpdate)
|
||||||
|
|
||||||
@ -94,7 +92,7 @@ class WeeklyDungeon(Dungeon):
|
|||||||
self.config.task_delay(target=monday)
|
self.config.task_delay(target=monday)
|
||||||
self.config.task_stop()
|
self.config.task_stop()
|
||||||
|
|
||||||
self._dungeon_insight(dungeon)
|
self.dungeon_insight(dungeon)
|
||||||
self._dungeon_enter(dungeon)
|
self._dungeon_enter(dungeon)
|
||||||
|
|
||||||
# Combat
|
# Combat
|
||||||
|
@ -10,8 +10,8 @@ from module.ocr.keyword import Keyword
|
|||||||
from module.ocr.ocr import Ocr, OcrResultButton
|
from module.ocr.ocr import Ocr, OcrResultButton
|
||||||
from module.ui.draggable_list import DraggableList
|
from module.ui.draggable_list import DraggableList
|
||||||
from tasks.base.assets.assets_base_page import FORGOTTEN_HALL_CHECK, MAP_EXIT
|
from tasks.base.assets.assets_base_page import FORGOTTEN_HALL_CHECK, MAP_EXIT
|
||||||
|
from tasks.dungeon.ui.ui import DungeonUI
|
||||||
from tasks.dungeon.keywords import DungeonList, KEYWORDS_DUNGEON_LIST, KEYWORDS_DUNGEON_NAV, KEYWORDS_DUNGEON_TAB
|
from tasks.dungeon.keywords import DungeonList, KEYWORDS_DUNGEON_LIST, KEYWORDS_DUNGEON_NAV, KEYWORDS_DUNGEON_TAB
|
||||||
from tasks.dungeon.ui import DungeonUI
|
|
||||||
from tasks.forgotten_hall.assets.assets_forgotten_hall_nav import *
|
from tasks.forgotten_hall.assets.assets_forgotten_hall_nav import *
|
||||||
from tasks.forgotten_hall.assets.assets_forgotten_hall_ui import *
|
from tasks.forgotten_hall.assets.assets_forgotten_hall_ui import *
|
||||||
from tasks.forgotten_hall.keywords import ForgottenHallStage, KEYWORDS_FORGOTTEN_HALL_STAGE
|
from tasks.forgotten_hall.keywords import ForgottenHallStage, KEYWORDS_FORGOTTEN_HALL_STAGE
|
||||||
@ -187,7 +187,7 @@ class ForgottenHallUI(DungeonUI, ForgottenHallTeam):
|
|||||||
logger.info('Already in forgotten hall')
|
logger.info('Already in forgotten hall')
|
||||||
else:
|
else:
|
||||||
self.dungeon_tab_goto(KEYWORDS_DUNGEON_TAB.Treasures_Lightward)
|
self.dungeon_tab_goto(KEYWORDS_DUNGEON_TAB.Treasures_Lightward)
|
||||||
self._dungeon_nav_goto(KEYWORDS_DUNGEON_NAV.Forgotten_Hall)
|
self.dungeon_nav_goto(KEYWORDS_DUNGEON_NAV.Forgotten_Hall)
|
||||||
|
|
||||||
self.stage_choose(dungeon)
|
self.stage_choose(dungeon)
|
||||||
logger.info(f'Stage list select: {stage_keyword}')
|
logger.info(f'Stage list select: {stage_keyword}')
|
||||||
|
@ -6,7 +6,7 @@ from tasks.base.assets.assets_base_popup import POPUP_CANCEL
|
|||||||
from tasks.combat.assets.assets_combat_prepare import COMBAT_PREPARE
|
from tasks.combat.assets.assets_combat_prepare import COMBAT_PREPARE
|
||||||
from tasks.combat.assets.assets_combat_support import COMBAT_SUPPORT_LIST
|
from tasks.combat.assets.assets_combat_support import COMBAT_SUPPORT_LIST
|
||||||
from tasks.dungeon.dungeon import Dungeon
|
from tasks.dungeon.dungeon import Dungeon
|
||||||
from tasks.dungeon.state import DungeonState
|
from tasks.dungeon.ui.state import DungeonState
|
||||||
from tasks.map.route.loader import RouteLoader
|
from tasks.map.route.loader import RouteLoader
|
||||||
from tasks.map.route.route.daily import OrnamentExtraction__route
|
from tasks.map.route.route.daily import OrnamentExtraction__route
|
||||||
from tasks.ornament.assets.assets_ornament_combat import *
|
from tasks.ornament.assets.assets_ornament_combat import *
|
||||||
|
@ -14,8 +14,8 @@ from tasks.base.assets.assets_base_page import MAP_EXIT
|
|||||||
from tasks.base.page import page_guide, page_item, page_main, page_rogue
|
from tasks.base.page import page_guide, page_item, page_main, page_rogue
|
||||||
from tasks.dungeon.keywords import DungeonList
|
from tasks.dungeon.keywords import DungeonList
|
||||||
from tasks.dungeon.keywords.dungeon import Simulated_Universe_World_1
|
from tasks.dungeon.keywords.dungeon import Simulated_Universe_World_1
|
||||||
from tasks.dungeon.state import OcrSimUniPoint
|
from tasks.dungeon.ui.state import OcrSimUniPoint
|
||||||
from tasks.dungeon.ui_rogue import DungeonRogueUI
|
from tasks.dungeon.ui.ui_rogue import DungeonRogueUI
|
||||||
from tasks.forgotten_hall.assets.assets_forgotten_hall_ui import TELEPORT
|
from tasks.forgotten_hall.assets.assets_forgotten_hall_ui import TELEPORT
|
||||||
from tasks.rogue.assets.assets_rogue_entry import (
|
from tasks.rogue.assets.assets_rogue_entry import (
|
||||||
LEVEL_CONFIRM,
|
LEVEL_CONFIRM,
|
||||||
|
@ -4,7 +4,7 @@ from module.base.timer import Timer
|
|||||||
from module.logger import logger
|
from module.logger import logger
|
||||||
from tasks.base.assets.assets_base_popup import GET_REWARD
|
from tasks.base.assets.assets_base_popup import GET_REWARD
|
||||||
from tasks.combat.interact import CombatInteract
|
from tasks.combat.interact import CombatInteract
|
||||||
from tasks.dungeon.state import DungeonState
|
from tasks.dungeon.ui.state import DungeonState
|
||||||
from tasks.rogue.assets.assets_rogue_reward import REWARD_CLOSE, USE_IMMERSIFIER, USE_STAMINA
|
from tasks.rogue.assets.assets_rogue_reward import REWARD_CLOSE, USE_IMMERSIFIER, USE_STAMINA
|
||||||
from tasks.rogue.blessing.ui import RogueUI
|
from tasks.rogue.blessing.ui import RogueUI
|
||||||
|
|
||||||
|