diff --git a/assets/share/dungeon/ui/DAILY_TRAINING_CHECK.SEARCH.png b/assets/share/dungeon/ui/DAILY_TRAINING_CHECK.SEARCH.png new file mode 100644 index 000000000..3b77dea4c Binary files /dev/null and b/assets/share/dungeon/ui/DAILY_TRAINING_CHECK.SEARCH.png differ diff --git a/assets/share/dungeon/ui/DAILY_TRAINING_CHECK.png b/assets/share/dungeon/ui/DAILY_TRAINING_CHECK.png new file mode 100644 index 000000000..47e9c5f35 Binary files /dev/null and b/assets/share/dungeon/ui/DAILY_TRAINING_CHECK.png differ diff --git a/assets/share/dungeon/ui/DAILY_TRAINING_CLICK.SEARCH.png b/assets/share/dungeon/ui/DAILY_TRAINING_CLICK.SEARCH.png new file mode 100644 index 000000000..3b77dea4c Binary files /dev/null and b/assets/share/dungeon/ui/DAILY_TRAINING_CLICK.SEARCH.png differ diff --git a/assets/share/dungeon/ui/DAILY_TRAINING_CLICK.png b/assets/share/dungeon/ui/DAILY_TRAINING_CLICK.png new file mode 100644 index 000000000..a105b9cc7 Binary files /dev/null and b/assets/share/dungeon/ui/DAILY_TRAINING_CLICK.png differ diff --git a/assets/share/dungeon/ui/DAILY_TRAINING_LOADED.png b/assets/share/dungeon/ui/DAILY_TRAINING_LOADED.png new file mode 100644 index 000000000..83844582e Binary files /dev/null and b/assets/share/dungeon/ui/DAILY_TRAINING_LOADED.png differ diff --git a/assets/share/dungeon/ui/OCR_DUNGEON_LIST.png b/assets/share/dungeon/ui/OCR_DUNGEON_LIST.png new file mode 100644 index 000000000..a65736ca0 Binary files /dev/null and b/assets/share/dungeon/ui/OCR_DUNGEON_LIST.png differ diff --git a/assets/share/dungeon/ui/OCR_DUNGEON_NAV.png b/assets/share/dungeon/ui/OCR_DUNGEON_NAV.png new file mode 100644 index 000000000..db9f26053 Binary files /dev/null and b/assets/share/dungeon/ui/OCR_DUNGEON_NAV.png differ diff --git a/assets/share/dungeon/ui/OPERATION_BRIEFING_CHECK.SEARCH.png b/assets/share/dungeon/ui/OPERATION_BRIEFING_CHECK.SEARCH.png new file mode 100644 index 000000000..3b77dea4c Binary files /dev/null and b/assets/share/dungeon/ui/OPERATION_BRIEFING_CHECK.SEARCH.png differ diff --git a/assets/share/dungeon/ui/OPERATION_BRIEFING_CHECK.png b/assets/share/dungeon/ui/OPERATION_BRIEFING_CHECK.png new file mode 100644 index 000000000..a366cedea Binary files /dev/null and b/assets/share/dungeon/ui/OPERATION_BRIEFING_CHECK.png differ diff --git a/assets/share/dungeon/ui/OPERATION_BRIEFING_CLICK.SEARCH.png b/assets/share/dungeon/ui/OPERATION_BRIEFING_CLICK.SEARCH.png new file mode 100644 index 000000000..3b77dea4c Binary files /dev/null and b/assets/share/dungeon/ui/OPERATION_BRIEFING_CLICK.SEARCH.png differ diff --git a/assets/share/dungeon/ui/OPERATION_BRIEFING_CLICK.png b/assets/share/dungeon/ui/OPERATION_BRIEFING_CLICK.png new file mode 100644 index 000000000..76f9a3f0f Binary files /dev/null and b/assets/share/dungeon/ui/OPERATION_BRIEFING_CLICK.png differ diff --git a/assets/share/dungeon/ui/SURVIVAL_INDEX_CHECK.SEARCH.png b/assets/share/dungeon/ui/SURVIVAL_INDEX_CHECK.SEARCH.png new file mode 100644 index 000000000..3b77dea4c Binary files /dev/null and b/assets/share/dungeon/ui/SURVIVAL_INDEX_CHECK.SEARCH.png differ diff --git a/assets/share/dungeon/ui/SURVIVAL_INDEX_CHECK.png b/assets/share/dungeon/ui/SURVIVAL_INDEX_CHECK.png new file mode 100644 index 000000000..7088024da Binary files /dev/null and b/assets/share/dungeon/ui/SURVIVAL_INDEX_CHECK.png differ diff --git a/assets/share/dungeon/ui/SURVIVAL_INDEX_CLICK.SEARCH.png b/assets/share/dungeon/ui/SURVIVAL_INDEX_CLICK.SEARCH.png new file mode 100644 index 000000000..3b77dea4c Binary files /dev/null and b/assets/share/dungeon/ui/SURVIVAL_INDEX_CLICK.SEARCH.png differ diff --git a/assets/share/dungeon/ui/SURVIVAL_INDEX_CLICK.png b/assets/share/dungeon/ui/SURVIVAL_INDEX_CLICK.png new file mode 100644 index 000000000..0276295b3 Binary files /dev/null and b/assets/share/dungeon/ui/SURVIVAL_INDEX_CLICK.png differ diff --git a/assets/share/dungeon/ui/SURVIVAL_INDEX_LOADED.SEARCH.png b/assets/share/dungeon/ui/SURVIVAL_INDEX_LOADED.SEARCH.png new file mode 100644 index 000000000..d24689d6f Binary files /dev/null and b/assets/share/dungeon/ui/SURVIVAL_INDEX_LOADED.SEARCH.png differ diff --git a/assets/share/dungeon/ui/SURVIVAL_INDEX_LOADED.png b/assets/share/dungeon/ui/SURVIVAL_INDEX_LOADED.png new file mode 100644 index 000000000..f6d446b77 Binary files /dev/null and b/assets/share/dungeon/ui/SURVIVAL_INDEX_LOADED.png differ diff --git a/dev_tools/button_extract.py b/dev_tools/button_extract.py index c362cd682..c632abcc0 100644 --- a/dev_tools/button_extract.py +++ b/dev_tools/button_extract.py @@ -183,7 +183,7 @@ def iter_assets(): for path, frames in deep_iter(data, depth=3): print(path, frames) first = frames[1] - search = DataAssets.area_to_search(first.area) + search = first.search if first.search else DataAssets.area_to_search(first.area) for frame in frames.values(): frame.search = search diff --git a/module/ui/switch.py b/module/ui/switch.py new file mode 100644 index 000000000..b7337c9fb --- /dev/null +++ b/module/ui/switch.py @@ -0,0 +1,167 @@ +from module.base.base import ModuleBase +from module.base.button import Button +from module.base.timer import Timer +from module.exception import ScriptError +from module.logger import logger + + +class Switch: + """ + A wrapper to handle switches in game, switch among states with reties. + + Examples: + # Definitions + submarine_hunt = Switch('Submarine_hunt', offset=120) + submarine_hunt.add_state('on', check_button=SUBMARINE_HUNT_ON) + submarine_hunt.add_state('off', check_button=SUBMARINE_HUNT_OFF) + + # Change state to ON + submarine_view.set('on', main=self) + """ + + def __init__(self, name='Switch', is_selector=False): + """ + Args: + name (str): + is_selector (bool): True if this is a multi choice, click to choose one of the switches. + For example: | [Daily] | Urgent | -> click -> | Daily | [Urgent] | + False if this is a switch, click the switch itself, and it changed in the same position. + For example: | [ON] | -> click -> | [OFF] | + """ + self.name = name + self.is_choice = is_selector + self.state_list = [] + + def add_state(self, state, check_button, click_button=None): + """ + Args: + state (str): + check_button (Button): + click_button (Button): + """ + self.state_list.append({ + 'state': state, + 'check_button': check_button, + 'click_button': click_button if click_button is not None else check_button, + }) + + def appear(self, main): + """ + Args: + main (ModuleBase): + + Returns: + bool + """ + for data in self.state_list: + if main.appear(data['check_button']): + return True + + return False + + def get(self, main): + """ + Args: + main (ModuleBase): + + Returns: + str: state name or 'unknown'. + """ + for data in self.state_list: + if main.appear(data['check_button']): + return data['state'] + + return 'unknown' + + def click(self, state, main): + """ + Args: + state (str): + main (ModuleBase): + """ + button = self.get_data(state)['click_button'] + main.device.click(button) + + def get_data(self, state): + """ + Args: + state (str): + + Returns: + dict: Dictionary in add_state + + Raises: + ScriptError: If state invalid + """ + for row in self.state_list: + if row['state'] == state: + return row + + logger.warning(f'Switch {self.name} received an invalid state {state}') + raise ScriptError(f'Switch {self.name} received an invalid state {state}') + + def handle_additional(self, main): + """ + Args: + main (ModuleBase): + + Returns: + bool: If handled + """ + return False + + def set(self, state, main, skip_first_screenshot=True): + """ + Args: + state: + main (ModuleBase): + skip_first_screenshot (bool): + + Returns: + bool: + """ + logger.info(f'{self.name} set to {state}') + self.get_data(state) + + counter = 0 + changed = False + warning_show_timer = Timer(5, count=10).start() + click_timer = Timer(1, count=3) + while 1: + if skip_first_screenshot: + skip_first_screenshot = False + else: + main.device.screenshot() + + # Detect + current = self.get(main=main) + logger.attr(self.name, current) + + # Handle additional popups + if self.handle_additional(main=main): + continue + + # End + if current == state: + return changed + + # Warning + if current == 'unknown': + if warning_show_timer.reached(): + logger.warning(f'Unknown {self.name} switch') + warning_show_timer.reset() + if counter >= 1: + logger.warning(f'{self.name} switch {state} asset has evaluated to unknown too many times, ' + f'asset should be re-verified') + return False + counter += 1 + continue + + # Click + if click_timer.reached(): + click_state = state if self.is_choice else current + self.click(click_state, main=main) + click_timer.reset() + changed = True + + return changed diff --git a/tasks/base/page.py b/tasks/base/page.py index f680e77dc..08da9df44 100644 --- a/tasks/base/page.py +++ b/tasks/base/page.py @@ -76,6 +76,11 @@ page_menu = Page(MENU_CHECK) page_menu.link(CLOSE, destination=page_main) page_main.link(MAIN_GOTO_MENU, destination=page_menu) +# Character +page_character = Page(CHARACTER_CHECK) +page_character.link(CLOSE, destination=page_main) +page_main.link(MAIN_GOTO_CHARACTER, destination=page_character) + # Team page_team = Page(TEAM_CHECK) page_team.link(CLOSE, destination=page_main) diff --git a/tasks/dungeon/assets/assets_dungeon_ui.py b/tasks/dungeon/assets/assets_dungeon_ui.py new file mode 100644 index 000000000..1ca2e166f --- /dev/null +++ b/tasks/dungeon/assets/assets_dungeon_ui.py @@ -0,0 +1,105 @@ +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 ``` + +DAILY_TRAINING_CHECK = ButtonWrapper( + name='DAILY_TRAINING_CHECK', + share=Button( + file='./assets/share/dungeon/ui/DAILY_TRAINING_CHECK.png', + area=(239, 55, 279, 94), + search=(109, 45, 491, 104), + color=(129, 129, 129), + button=(239, 55, 279, 94), + ), +) +DAILY_TRAINING_CLICK = ButtonWrapper( + name='DAILY_TRAINING_CLICK', + share=Button( + file='./assets/share/dungeon/ui/DAILY_TRAINING_CLICK.png', + area=(230, 55, 270, 94), + search=(109, 45, 491, 104), + color=(98, 96, 96), + button=(230, 55, 270, 94), + ), +) +DAILY_TRAINING_LOADED = ButtonWrapper( + name='DAILY_TRAINING_LOADED', + share=Button( + file='./assets/share/dungeon/ui/DAILY_TRAINING_LOADED.png', + area=(1139, 520, 1156, 539), + search=(1119, 500, 1176, 559), + color=(63, 56, 50), + button=(1139, 520, 1156, 539), + ), +) +OCR_DUNGEON_LIST = ButtonWrapper( + name='OCR_DUNGEON_LIST', + share=Button( + file='./assets/share/dungeon/ui/OCR_DUNGEON_LIST.png', + area=(432, 128, 1169, 625), + search=(412, 108, 1189, 645), + color=(221, 222, 225), + button=(432, 128, 1169, 625), + ), +) +OCR_DUNGEON_NAV = ButtonWrapper( + name='OCR_DUNGEON_NAV', + share=Button( + file='./assets/share/dungeon/ui/OCR_DUNGEON_NAV.png', + area=(108, 132, 428, 613), + search=(88, 112, 448, 633), + color=(178, 177, 177), + button=(108, 132, 428, 613), + ), +) +OPERATION_BRIEFING_CHECK = ButtonWrapper( + name='OPERATION_BRIEFING_CHECK', + share=Button( + file='./assets/share/dungeon/ui/OPERATION_BRIEFING_CHECK.png', + area=(147, 53, 191, 96), + search=(109, 45, 491, 104), + color=(151, 151, 151), + button=(147, 53, 191, 96), + ), +) +OPERATION_BRIEFING_CLICK = ButtonWrapper( + name='OPERATION_BRIEFING_CLICK', + share=Button( + file='./assets/share/dungeon/ui/OPERATION_BRIEFING_CLICK.png', + area=(138, 53, 182, 96), + search=(109, 45, 491, 104), + color=(83, 81, 81), + button=(138, 53, 182, 96), + ), +) +SURVIVAL_INDEX_CHECK = ButtonWrapper( + name='SURVIVAL_INDEX_CHECK', + share=Button( + file='./assets/share/dungeon/ui/SURVIVAL_INDEX_CHECK.png', + area=(330, 55, 368, 93), + search=(109, 45, 491, 104), + color=(136, 136, 136), + button=(330, 55, 368, 93), + ), +) +SURVIVAL_INDEX_CLICK = ButtonWrapper( + name='SURVIVAL_INDEX_CLICK', + share=Button( + file='./assets/share/dungeon/ui/SURVIVAL_INDEX_CLICK.png', + area=(421, 56, 459, 93), + search=(109, 45, 491, 104), + color=(95, 93, 93), + button=(421, 56, 459, 93), + ), +) +SURVIVAL_INDEX_LOADED = ButtonWrapper( + name='SURVIVAL_INDEX_LOADED', + share=Button( + file='./assets/share/dungeon/ui/SURVIVAL_INDEX_LOADED.png', + area=(451, 244, 481, 265), + search=(446, 239, 486, 269), + color=(143, 150, 203), + button=(451, 244, 481, 265), + ), +) diff --git a/tasks/dungeon/ui.py b/tasks/dungeon/ui.py new file mode 100644 index 000000000..dce21e63b --- /dev/null +++ b/tasks/dungeon/ui.py @@ -0,0 +1,94 @@ +import numpy as np + +from module.base.timer import Timer +from module.base.utils import get_color +from module.logger import logger +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.keywords import DungeonTab, KEYWORDS_DUNGEON_TAB + + +class DungeonTabSwitch(Switch): + 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 +) + + +class DungeonUI(UI): + def dungeon_tab_goto(self, state: DungeonTab): + """ + Args: + state: + + 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) + """ + self.ui_ensure(page_guide) + SWITCH_DUNGEON_TAB.set(state, main=self) + 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_loaded() + + def _dungeon_wait_daily_training_loaded(self, skip_first_screenshot=True): + 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') + break + color = get_color(self.device.image, DAILY_TRAINING_LOADED.area) + if np.mean(color) < 128: + logger.info('Daily training loaded') + break + + def _dungeon_wait_survival_loaded(self, skip_first_screenshot=True): + 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') + break + if self.appear(SURVIVAL_INDEX_LOADED): + logger.info('Survival index loaded') + break