mirror of
https://github.com/LmeSzinc/StarRailCopilot.git
synced 2024-11-25 10:01:10 +00:00
352 lines
12 KiB
Python
352 lines
12 KiB
Python
|
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
|