Opt: Faster UI navigation to forgotten hall

This commit is contained in:
LmeSzinc 2023-07-06 22:41:16 +08:00
parent 3b11204fd9
commit 95c7b6dd53
3 changed files with 125 additions and 34 deletions

View File

@ -229,6 +229,7 @@ class DraggableList:
if skip_first_load_rows: if skip_first_load_rows:
skip_first_load_rows = False skip_first_load_rows = False
load_rows_interval.reset()
else: else:
if load_rows_interval.reached(): if load_rows_interval.reached():
self.load_rows(main=main) self.load_rows(main=main)
@ -240,7 +241,7 @@ class DraggableList:
# End # End
if self.is_row_selected(button, main=main): if self.is_row_selected(button, main=main):
logger.info('Row selected') logger.info(f'Row selected at {row}')
return True return True
# Click # Click

View File

@ -1,6 +1,8 @@
from dataclasses import dataclass from dataclasses import dataclass
from functools import cached_property
from typing import ClassVar from typing import ClassVar
from module.exception import ScriptError
from module.ocr.keyword import Keyword from module.ocr.keyword import Keyword
@ -18,46 +20,66 @@ class DungeonTab(Keyword):
class DungeonList(Keyword): class DungeonList(Keyword):
instances: ClassVar = {} instances: ClassVar = {}
@property @cached_property
def is_Calyx_Golden(self): def is_Calyx_Golden(self):
return 'Calyx_Golden' in self.name return 'Calyx_Golden' in self.name
@property @cached_property
def is_Calyx_Crimson(self): def is_Calyx_Crimson(self):
return 'Calyx_Crimson' in self.name return 'Calyx_Crimson' in self.name
@property @cached_property
def is_Stagnant_Shadow(self): def is_Stagnant_Shadow(self):
return 'Stagnant_Shadow' in self.name return 'Stagnant_Shadow' in self.name
@property @cached_property
def is_Cavern_of_Corrosion(self): def is_Cavern_of_Corrosion(self):
return 'Cavern_of_Corrosion' in self.name return 'Cavern_of_Corrosion' in self.name
@property @cached_property
def is_Echo_of_War(self): def is_Echo_of_War(self):
return 'Echo_of_War' in self.name return 'Echo_of_War' in self.name
@property @cached_property
def is_Simulated_Universe(self): def is_Simulated_Universe(self):
return 'Simulated_Universe' in self.name return 'Simulated_Universe' in self.name
@property @cached_property
def is_Forgotten_Hall(self): def is_Forgotten_Hall(self):
return 'Forgotten_Hall' or 'Last_Vestiges' in self.name return 'Forgotten_Hall' or 'Last_Vestiges' in self.name
@property @cached_property
def is_Last_Vestiges(self): def is_Last_Vestiges(self):
return 'Last_Vestiges' in self.name return 'Last_Vestiges' in self.name
@property @cached_property
def is_daily_dungeon(self): def is_daily_dungeon(self):
return self.is_Calyx_Golden or self.is_Calyx_Crimson or self.is_Stagnant_Shadow or self.is_Cavern_of_Corrosion return self.is_Calyx_Golden or self.is_Calyx_Crimson or self.is_Stagnant_Shadow or self.is_Cavern_of_Corrosion
@property @cached_property
def is_weekly_dungeon(self): def is_weekly_dungeon(self):
return self.is_Echo_of_War return self.is_Echo_of_War
@cached_property
def dungeon_nav(self) -> DungeonNav:
import tasks.dungeon.keywords.nav as KEYWORDS_DUNGEON_NAV
if self.is_Simulated_Universe:
return KEYWORDS_DUNGEON_NAV.Simulated_Universe
if self.is_Calyx_Golden:
return KEYWORDS_DUNGEON_NAV.Calyx_Golden
if self.is_Calyx_Crimson:
return KEYWORDS_DUNGEON_NAV.Calyx_Crimson
if self.is_Stagnant_Shadow:
return KEYWORDS_DUNGEON_NAV.Stagnant_Shadow
if self.is_Cavern_of_Corrosion:
return KEYWORDS_DUNGEON_NAV.Cavern_of_Corrosion
if self.is_Echo_of_War:
return KEYWORDS_DUNGEON_NAV.Echo_of_War
if self.is_Forgotten_Hall:
return KEYWORDS_DUNGEON_NAV.Forgotten_Hall
raise ScriptError(f'Cannot convert {self} to DungeonNav, please check keyword extractions')
@dataclass(repr=False) @dataclass(repr=False)
class DungeonEntrance(Keyword): class DungeonEntrance(Keyword):

View File

@ -71,12 +71,19 @@ class OcrDungeonList(Ocr):
result = result.replace('', '') # 巽风之形 result = result.replace('', '') # 巽风之形
return result return result
class OcrDungeonListLimitEntrance(OcrDungeonList): class OcrDungeonListLimitEntrance(OcrDungeonList):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.button = ClickButton((*self.button.area[:3], self.button.area[3] - 70)) self.button = ClickButton((*self.button.area[:3], self.button.area[3] - 70))
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): class DraggableDungeonList(DraggableList):
teleports: list[OcrResultButton] = [] teleports: list[OcrResultButton] = []
navigates: list[OcrResultButton] = [] navigates: list[OcrResultButton] = []
@ -96,7 +103,7 @@ class DraggableDungeonList(DraggableList):
)) ))
DUNGEON_NAV_LIST = DraggableList( DUNGEON_NAV_LIST = DraggableDungeonNav(
'DungeonNavList', keyword_class=DungeonNav, ocr_class=OcrDungeonNav, search_button=OCR_DUNGEON_NAV) 'DungeonNavList', keyword_class=DungeonNav, ocr_class=OcrDungeonNav, search_button=OCR_DUNGEON_NAV)
DUNGEON_LIST = DraggableDungeonList( DUNGEON_LIST = DraggableDungeonList(
'DungeonList', keyword_class=[DungeonList, DungeonEntrance], 'DungeonList', keyword_class=[DungeonList, DungeonEntrance],
@ -127,6 +134,13 @@ class DungeonUI(UI):
self._dungeon_wait_survival_loaded() self._dungeon_wait_survival_loaded()
def _dungeon_wait_daily_training_loaded(self, skip_first_screenshot=True): 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() timeout = Timer(2, count=4).start()
while 1: while 1:
if skip_first_screenshot: if skip_first_screenshot:
@ -136,13 +150,20 @@ class DungeonUI(UI):
if timeout.reached(): if timeout.reached():
logger.warning('Wait daily training loaded timeout') logger.warning('Wait daily training loaded timeout')
break return False
color = get_color(self.device.image, DAILY_TRAINING_LOADED.area) color = get_color(self.device.image, DAILY_TRAINING_LOADED.area)
if np.mean(color) < 128: if np.mean(color) < 128:
logger.info('Daily training loaded') logger.info('Daily training loaded')
break return True
def _dungeon_wait_survival_loaded(self, skip_first_screenshot=True): def _dungeon_wait_survival_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() timeout = Timer(2, count=4).start()
while 1: while 1:
if skip_first_screenshot: if skip_first_screenshot:
@ -152,10 +173,68 @@ class DungeonUI(UI):
if timeout.reached(): if timeout.reached():
logger.warning('Wait survival index loaded timeout') logger.warning('Wait survival index loaded timeout')
break return False
if self.appear(SURVIVAL_INDEX_LOADED): if self.appear(SURVIVAL_INDEX_LOADED):
logger.info('Survival index loaded') logger.info('Survival index loaded')
break return True
def _dungeon_wait_until_forgotten_hall_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 Forgotten_Hall stabled timeout')
return False
DUNGEON_NAV_LIST.load_rows(main=self)
# End
button = DUNGEON_NAV_LIST.keyword2button(KEYWORDS_DUNGEON_NAV.Forgotten_Hall)
if button:
# 513 is the top of the last row of DungeonNav
if button.area[1] > 513:
logger.info('DungeonNav row Forgotten_Hall stabled')
return True
def _dungeon_nav_select(self, dungeon: DungeonList):
"""
Equivalent to `DUNGEON_NAV_LIST.select_row(dungeon.dungeon_nav, main=self)`
but with tricks to be faster
"""
# Check the first page
DUNGEON_NAV_LIST.load_rows(main=self)
if dungeon.dungeon_nav in [
KEYWORDS_DUNGEON_NAV.Simulated_Universe,
KEYWORDS_DUNGEON_NAV.Calyx_Golden,
KEYWORDS_DUNGEON_NAV.Calyx_Crimson,
KEYWORDS_DUNGEON_NAV.Stagnant_Shadow,
KEYWORDS_DUNGEON_NAV.Cavern_of_Corrosion,
]:
button = DUNGEON_NAV_LIST.keyword2button(dungeon.dungeon_nav)
if button:
DUNGEON_NAV_LIST.select_row(dungeon.dungeon_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_forgotten_hall_stabled(skip_first_screenshot=False):
DUNGEON_NAV_LIST.select_row(dungeon.dungeon_nav, main=self, insight=False)
return True
def _dungeon_insight(self, dungeon: DungeonList): def _dungeon_insight(self, dungeon: DungeonList):
""" """
@ -259,28 +338,17 @@ class DungeonUI(UI):
# Reset search button # Reset search button
DUNGEON_LIST.search_button = OCR_DUNGEON_LIST DUNGEON_LIST.search_button = OCR_DUNGEON_LIST
if dungeon.is_Calyx_Golden: if dungeon.is_Calyx_Golden \
DUNGEON_NAV_LIST.select_row(KEYWORDS_DUNGEON_NAV.Calyx_Golden, main=self) or dungeon.is_Calyx_Crimson \
self._dungeon_insight(dungeon) or dungeon.is_Stagnant_Shadow \
self._dungeon_enter(dungeon) or dungeon.is_Stagnant_Shadow \
return True or dungeon.is_Cavern_of_Corrosion:
if dungeon.is_Calyx_Crimson: self._dungeon_nav_select(dungeon)
DUNGEON_NAV_LIST.select_row(KEYWORDS_DUNGEON_NAV.Calyx_Crimson, main=self)
self._dungeon_insight(dungeon)
self._dungeon_enter(dungeon)
return True
if dungeon.is_Stagnant_Shadow:
DUNGEON_NAV_LIST.select_row(KEYWORDS_DUNGEON_NAV.Stagnant_Shadow, main=self)
self._dungeon_insight(dungeon)
self._dungeon_enter(dungeon)
return True
if dungeon.is_Cavern_of_Corrosion:
DUNGEON_NAV_LIST.select_row(KEYWORDS_DUNGEON_NAV.Cavern_of_Corrosion, main=self)
self._dungeon_insight(dungeon) self._dungeon_insight(dungeon)
self._dungeon_enter(dungeon) self._dungeon_enter(dungeon)
return True return True
if dungeon.is_Forgotten_Hall: if dungeon.is_Forgotten_Hall:
DUNGEON_NAV_LIST.select_row(KEYWORDS_DUNGEON_NAV.Forgotten_Hall, main=self) self._dungeon_nav_select(dungeon)
self._dungeon_insight(dungeon) self._dungeon_insight(dungeon)
self._dungeon_enter(dungeon, enter_check_button=FORGOTTEN_HALL_CHECK) self._dungeon_enter(dungeon, enter_check_button=FORGOTTEN_HALL_CHECK)
return True return True