Add: Switching dungeon tabs
BIN
assets/share/dungeon/ui/DAILY_TRAINING_CHECK.SEARCH.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
assets/share/dungeon/ui/DAILY_TRAINING_CHECK.png
Normal file
After Width: | Height: | Size: 8.5 KiB |
BIN
assets/share/dungeon/ui/DAILY_TRAINING_CLICK.SEARCH.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
assets/share/dungeon/ui/DAILY_TRAINING_CLICK.png
Normal file
After Width: | Height: | Size: 8.4 KiB |
BIN
assets/share/dungeon/ui/DAILY_TRAINING_LOADED.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
BIN
assets/share/dungeon/ui/OCR_DUNGEON_LIST.png
Normal file
After Width: | Height: | Size: 187 KiB |
BIN
assets/share/dungeon/ui/OCR_DUNGEON_NAV.png
Normal file
After Width: | Height: | Size: 49 KiB |
BIN
assets/share/dungeon/ui/OPERATION_BRIEFING_CHECK.SEARCH.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
assets/share/dungeon/ui/OPERATION_BRIEFING_CHECK.png
Normal file
After Width: | Height: | Size: 8.6 KiB |
BIN
assets/share/dungeon/ui/OPERATION_BRIEFING_CLICK.SEARCH.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
assets/share/dungeon/ui/OPERATION_BRIEFING_CLICK.png
Normal file
After Width: | Height: | Size: 8.5 KiB |
BIN
assets/share/dungeon/ui/SURVIVAL_INDEX_CHECK.SEARCH.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
assets/share/dungeon/ui/SURVIVAL_INDEX_CHECK.png
Normal file
After Width: | Height: | Size: 8.4 KiB |
BIN
assets/share/dungeon/ui/SURVIVAL_INDEX_CLICK.SEARCH.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
assets/share/dungeon/ui/SURVIVAL_INDEX_CLICK.png
Normal file
After Width: | Height: | Size: 8.2 KiB |
BIN
assets/share/dungeon/ui/SURVIVAL_INDEX_LOADED.SEARCH.png
Normal file
After Width: | Height: | Size: 7.3 KiB |
BIN
assets/share/dungeon/ui/SURVIVAL_INDEX_LOADED.png
Normal file
After Width: | Height: | Size: 6.6 KiB |
@ -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
|
||||
|
||||
|
167
module/ui/switch.py
Normal file
@ -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
|
@ -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)
|
||||
|
105
tasks/dungeon/assets/assets_dungeon_ui.py
Normal file
@ -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),
|
||||
),
|
||||
)
|
94
tasks/dungeon/ui.py
Normal file
@ -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
|