diff --git a/assets/cn/combat_ui/PAUSE_Devil.png b/assets/cn/combat_ui/PAUSE_Devil.png new file mode 100644 index 000000000..1b59f42ff Binary files /dev/null and b/assets/cn/combat_ui/PAUSE_Devil.png differ diff --git a/assets/cn/retire/DOCK_FIRST_NPC.png b/assets/cn/retire/DOCK_FIRST_NPC.png new file mode 100644 index 000000000..db691f5af Binary files /dev/null and b/assets/cn/retire/DOCK_FIRST_NPC.png differ diff --git a/deploy/adb.py b/deploy/adb.py index 90b4e8210..c728d8f96 100644 --- a/deploy/adb.py +++ b/deploy/adb.py @@ -46,7 +46,7 @@ class AdbManager(DeployConfig): logger.hr('ADB Connect', 1) emulator.brute_force_connect() - if self.InstallUiautomator2: + if False: logger.hr('Uiautomator2 Init', 1) try: import adbutils diff --git a/deploy/pip.py b/deploy/pip.py index 3c598f718..f6c912343 100644 --- a/deploy/pip.py +++ b/deploy/pip.py @@ -1,4 +1,6 @@ import sys +import typing as t +from dataclasses import dataclass from urllib.parse import urlparse from deploy.config import DeployConfig @@ -6,15 +8,49 @@ from deploy.logger import logger from deploy.utils import * +@dataclass +class DataDependency: + name: str + version: str + + def __post_init__(self): + # uvicorn[standard] -> uvicorn + self.name = re.sub(r'\[.*\]', '', self.name) + # opencv_python -> opencv-python + self.name = self.name.replace('_', '-').strip() + # PyYaml -> pyyaml + self.name = self.name.lower() + self.version = self.version.strip() + self.version = re.sub(r'\.0$', '', self.version) + + @cached_property + def pretty_name(self): + return f'{self.name}=={self.version}' + + def __str__(self): + return self.pretty_name + + __repr__ = __str__ + + def __eq__(self, other): + return str(self) == str(other) + + def __hash__(self): + return hash(str(self)) + + class PipManager(DeployConfig): @cached_property - def python(self): - exe = self.filepath("PythonExecutable") - if os.path.exists(exe): - return exe + def python(self) -> str: + # No need to read PythonExecutable + # since you run this code with python, current python is the python + + # exe = self.filepath(self.PythonExecutable) + # if os.path.exists(exe): + # return exe current = sys.executable.replace("\\", "/") - logger.warning(f'PythonExecutable: {exe} does not exist, use current python instead: {current}') + # logger.warning(f'PythonExecutable: {exe} does not exist, use current python instead: {current}') return current @cached_property @@ -28,6 +64,58 @@ class PipManager(DeployConfig): def pip(self): return f'"{self.python}" -m pip' + @cached_property + def python_site_packages(self) -> str: + import site + paths = site.getsitepackages() + # site-packages should be site-packages folder + for path in paths: + if path.endswith('site-packages'): + return path + # Otherwise pick first + return paths[0] + + @cached_property + def set_installed_dependency(self) -> t.Set[DataDependency]: + data = [] + regex = re.compile(r'(.*)-(.*).dist-info') + try: + for name in os.listdir(self.python_site_packages): + res = regex.search(name) + if res: + dep = DataDependency(name=res.group(1), version=res.group(2)) + data.append(dep) + except FileNotFoundError: + logger.info(f'Directory not found: {self.python_site_packages}') + return set(data) + + @cached_property + def set_required_dependency(self) -> t.Set[DataDependency]: + data = [] + regex = re.compile('(.*)==(.*)[ ]*#') + file = self.requirements_file + try: + with open(file, 'r', encoding='utf-8') as f: + for line in f.readlines(): + res = regex.search(line) + if res: + dep = DataDependency(name=res.group(1), version=res.group(2)) + data.append(dep) + except FileNotFoundError: + logger.info(f'File not found: {file}') + return set(data) + + @cached_property + def set_dependency_to_install(self) -> t.Set[DataDependency]: + """ + A poor dependency comparison, but much much faster than `pip install` and `pip list` + """ + data = [] + for dep in self.set_required_dependency: + if dep not in self.set_installed_dependency: + data.append(dep) + return set(data) + def pip_install(self): logger.hr('Update Dependencies', 0) @@ -35,6 +123,12 @@ class PipManager(DeployConfig): logger.info('InstallDependencies is disabled, skip') return + if not len(self.set_dependency_to_install): + logger.info('All dependencies installed') + return + else: + logger.info(f'Dependencies to install: {self.set_dependency_to_install}') + logger.hr('Check Python', 1) self.execute(f'"{self.python}" --version') diff --git a/module/awaken/awaken.py b/module/awaken/awaken.py index ae7829b2a..692ee3e4f 100644 --- a/module/awaken/awaken.py +++ b/module/awaken/awaken.py @@ -3,7 +3,7 @@ from module.base.timer import Timer from module.exception import ScriptError from module.logger import logger from module.ocr.ocr import Digit -from module.retire.dock import CARD_GRIDS, DOCK_EMPTY, Dock, SHIP_DETAIL_CHECK +from module.retire.dock import DOCK_EMPTY, Dock from module.ui.assets import BACK_ARROW from module.ui.page import page_dock, page_main @@ -346,8 +346,11 @@ class Awaken(Dock): break # page_dock -> SHIP_DETAIL_CHECK - self.ship_info_enter( - CARD_GRIDS[(0, 0)], check_button=SHIP_DETAIL_CHECK, long_click=False) + entered = self.dock_enter_first() + if not entered: + logger.info('awaken_run finished, no ships to awaken') + result = 'finish' + break # is_in_awaken result = self.awaken_ship(use_array) diff --git a/module/combat/combat.py b/module/combat/combat.py index 97a6d74a7..660177c61 100644 --- a/module/combat/combat.py +++ b/module/combat/combat.py @@ -108,6 +108,9 @@ class Combat(Level, HPBalancer, Retirement, SubmarineCall, CombatAuto, CombatMan return PAUSE_Pharaoh if PAUSE_Nurse.match_luma(self.device.image, offset=(10, 10)): return PAUSE_Nurse + # PAUSE_Devil is in red + if PAUSE_Devil.match_template_color(self.device.image, offset=(10, 10)): + return PAUSE_Devil return False def handle_combat_quit(self, offset=(20, 20), interval=3): @@ -141,6 +144,7 @@ class Combat(Level, HPBalancer, Retirement, SubmarineCall, CombatAuto, CombatMan self.device.click(QUIT_Nurse) timer.reset() return True + # Battle UI PAUSE_Devil uses QUIT_New return False def ensure_combat_oil_loaded(self): diff --git a/module/combat_ui/assets.py b/module/combat_ui/assets.py index 9e076c42f..b123d1746 100644 --- a/module/combat_ui/assets.py +++ b/module/combat_ui/assets.py @@ -8,6 +8,7 @@ PAUSE = Button(area={'cn': (1158, 40, 1199, 58), 'en': (1155, 38, 1216, 51), 'jp PAUSE_Christmas = Button(area={'cn': (1234, 35, 1250, 56), 'en': (1234, 35, 1250, 56), 'jp': (1234, 35, 1250, 56), 'tw': (1234, 35, 1250, 56)}, color={'cn': (158, 181, 210), 'en': (158, 181, 210), 'jp': (158, 181, 210), 'tw': (158, 181, 210)}, button={'cn': (1234, 35, 1250, 56), 'en': (1234, 35, 1250, 56), 'jp': (1234, 35, 1250, 56), 'tw': (1234, 35, 1250, 56)}, file={'cn': './assets/cn/combat_ui/PAUSE_Christmas.png', 'en': './assets/cn/combat_ui/PAUSE_Christmas.png', 'jp': './assets/cn/combat_ui/PAUSE_Christmas.png', 'tw': './assets/cn/combat_ui/PAUSE_Christmas.png'}) PAUSE_Cyber = Button(area={'cn': (1231, 32, 1253, 59), 'en': (1231, 32, 1253, 59), 'jp': (1231, 32, 1253, 59), 'tw': (1231, 32, 1253, 59)}, color={'cn': (40, 140, 157), 'en': (40, 140, 157), 'jp': (40, 140, 157), 'tw': (40, 140, 157)}, button={'cn': (1231, 32, 1253, 59), 'en': (1231, 32, 1253, 59), 'jp': (1231, 32, 1253, 59), 'tw': (1231, 32, 1253, 59)}, file={'cn': './assets/cn/combat_ui/PAUSE_Cyber.png', 'en': './assets/cn/combat_ui/PAUSE_Cyber.png', 'jp': './assets/cn/combat_ui/PAUSE_Cyber.png', 'tw': './assets/cn/combat_ui/PAUSE_Cyber.png'}) PAUSE_DOUBLE_CHECK = Button(area={'cn': (1226, 35, 1231, 60), 'en': (1226, 35, 1231, 61), 'jp': (1226, 35, 1230, 60), 'tw': (1226, 35, 1231, 60)}, color={'cn': (96, 104, 136), 'en': (83, 98, 118), 'jp': (97, 102, 120), 'tw': (96, 104, 136)}, button={'cn': (1226, 35, 1231, 60), 'en': (1226, 35, 1231, 61), 'jp': (1226, 35, 1230, 60), 'tw': (1226, 35, 1231, 60)}, file={'cn': './assets/cn/combat_ui/PAUSE_DOUBLE_CHECK.png', 'en': './assets/en/combat_ui/PAUSE_DOUBLE_CHECK.png', 'jp': './assets/jp/combat_ui/PAUSE_DOUBLE_CHECK.png', 'tw': './assets/tw/combat_ui/PAUSE_DOUBLE_CHECK.png'}) +PAUSE_Devil = Button(area={'cn': (1233, 35, 1250, 57), 'en': (1233, 35, 1250, 57), 'jp': (1233, 35, 1250, 57), 'tw': (1233, 35, 1250, 57)}, color={'cn': (193, 98, 108), 'en': (193, 98, 108), 'jp': (193, 98, 108), 'tw': (193, 98, 108)}, button={'cn': (1233, 35, 1250, 57), 'en': (1233, 35, 1250, 57), 'jp': (1233, 35, 1250, 57), 'tw': (1233, 35, 1250, 57)}, file={'cn': './assets/cn/combat_ui/PAUSE_Devil.png', 'en': './assets/cn/combat_ui/PAUSE_Devil.png', 'jp': './assets/cn/combat_ui/PAUSE_Devil.png', 'tw': './assets/cn/combat_ui/PAUSE_Devil.png'}) PAUSE_HolyLight = Button(area={'cn': (1233, 35, 1250, 57), 'en': (1233, 35, 1250, 57), 'jp': (1233, 35, 1250, 57), 'tw': (1233, 35, 1250, 57)}, color={'cn': (54, 40, 27), 'en': (54, 40, 27), 'jp': (54, 40, 27), 'tw': (54, 40, 27)}, button={'cn': (1233, 35, 1250, 57), 'en': (1233, 35, 1250, 57), 'jp': (1233, 35, 1250, 57), 'tw': (1233, 35, 1250, 57)}, file={'cn': './assets/cn/combat_ui/PAUSE_HolyLight.png', 'en': './assets/cn/combat_ui/PAUSE_HolyLight.png', 'jp': './assets/cn/combat_ui/PAUSE_HolyLight.png', 'tw': './assets/cn/combat_ui/PAUSE_HolyLight.png'}) PAUSE_Iridescent_Fantasy = Button(area={'cn': (1232, 33, 1252, 57), 'en': (1232, 33, 1252, 57), 'jp': (1232, 33, 1252, 57), 'tw': (1232, 33, 1252, 57)}, color={'cn': (124, 139, 190), 'en': (124, 139, 190), 'jp': (124, 139, 190), 'tw': (124, 139, 190)}, button={'cn': (1232, 33, 1252, 57), 'en': (1232, 33, 1252, 57), 'jp': (1232, 33, 1252, 57), 'tw': (1232, 33, 1252, 57)}, file={'cn': './assets/cn/combat_ui/PAUSE_Iridescent_Fantasy.png', 'en': './assets/en/combat_ui/PAUSE_Iridescent_Fantasy.png', 'jp': './assets/jp/combat_ui/PAUSE_Iridescent_Fantasy.png', 'tw': './assets/tw/combat_ui/PAUSE_Iridescent_Fantasy.png'}) PAUSE_Neon = Button(area={'cn': (1228, 32, 1250, 59), 'en': (1228, 32, 1250, 59), 'jp': (1228, 32, 1250, 59), 'tw': (1228, 32, 1250, 59)}, color={'cn': (106, 137, 80), 'en': (106, 137, 80), 'jp': (106, 137, 80), 'tw': (106, 137, 80)}, button={'cn': (1228, 32, 1250, 59), 'en': (1228, 32, 1250, 59), 'jp': (1228, 32, 1250, 59), 'tw': (1228, 32, 1250, 59)}, file={'cn': './assets/cn/combat_ui/PAUSE_Neon.png', 'en': './assets/cn/combat_ui/PAUSE_Neon.png', 'jp': './assets/cn/combat_ui/PAUSE_Neon.png', 'tw': './assets/cn/combat_ui/PAUSE_Neon.png'}) diff --git a/module/equipment/equipment.py b/module/equipment/equipment.py index fcc5804ac..e3c774634 100644 --- a/module/equipment/equipment.py +++ b/module/equipment/equipment.py @@ -3,8 +3,7 @@ from module.base.decorator import cached_property from module.base.timer import Timer from module.equipment.assets import * from module.logger import logger -from module.retire.assets import DOCK_CHECK -from module.retire.assets import EQUIP_CONFIRM as RETIRE_EQUIP_CONFIRM +from module.retire.assets import DOCK_CHECK, EQUIP_CONFIRM as RETIRE_EQUIP_CONFIRM from module.storage.storage import StorageHandler from module.ui.assets import BACK_ARROW from module.ui.navbar import Navbar @@ -40,6 +39,9 @@ class Equipment(StorageHandler): if self.appear(RETIRE_EQUIP_CONFIRM, offset=(30, 30)): logger.info('RETIRE_EQUIP_CONFIRM popup in _ship_view_swipe()') return False + # Popup when enhancing a NPC ship + if self.handle_popup_confirm('SHIP_VIEW_SWIPE'): + continue swipe_count += 1 self.device.screenshot() diff --git a/module/event_hospital/hospital.py b/module/event_hospital/hospital.py index c01251eef..85c121722 100644 --- a/module/event_hospital/hospital.py +++ b/module/event_hospital/hospital.py @@ -6,7 +6,7 @@ from module.event_hospital.clue import HospitalClue from module.event_hospital.combat import HospitalCombat from module.exception import OilExhausted, ScriptEnd from module.logger import logger -from module.ui.page import page_hospital +from module.ui.page import page_hospital, page_campaign_menu from module.ui.switch import Switch @@ -384,9 +384,17 @@ class Hospital(HospitalClue, HospitalCombat): self.config.task_stop() def run(self): - self.ui_ensure(page_hospital) + # Check if event available + if self.event_time_limit_triggered(): + self.config.task_stop() + self.ui_ensure(page_campaign_menu) + if self.is_event_entrance_available(): + self.ui_goto(page_hospital) + + # Receive rewards self.daily_reward_receive() + # Run self.clue_enter() delay = True try: diff --git a/module/exercise/combat.py b/module/exercise/combat.py index 51d7898d1..c362e0b9e 100644 --- a/module/exercise/combat.py +++ b/module/exercise/combat.py @@ -53,6 +53,13 @@ class ExerciseCombat(HpDaemon, OpponentChoose, ExerciseEquipment, Combat): while 1: self.device.screenshot() + # End + if self._in_exercise() or self.appear(BATTLE_PREPARATION, offset=(20, 20)): + logger.hr('Combat end') + if not end: + logger.warning('Combat ended without end conditions detected') + break + p = self.is_combat_executing() if p: if end: @@ -108,13 +115,6 @@ class ExerciseCombat(HpDaemon, OpponentChoose, ExerciseEquipment, Combat): show_hp_timer.reset() self._show_hp() - # End - if self._in_exercise() or self.appear(BATTLE_PREPARATION, offset=(20, 20)): - logger.hr('Combat end') - if not end: - logger.warning('Combat ended without end conditions detected') - break - return success def _choose_opponent(self, index, skip_first_screenshot=True): diff --git a/module/exercise/hp_daemon.py b/module/exercise/hp_daemon.py index 3ab53d96a..9b2222bdc 100644 --- a/module/exercise/hp_daemon.py +++ b/module/exercise/hp_daemon.py @@ -2,7 +2,7 @@ from module.base.base import ModuleBase from module.base.timer import Timer from module.base.utils import color_bar_percentage from module.combat_ui.assets import (PAUSE, PAUSE_Christmas, PAUSE_Cyber, PAUSE_HolyLight, PAUSE_Iridescent_Fantasy, - PAUSE_Neon, PAUSE_New, PAUSE_Nurse, PAUSE_Pharaoh) + PAUSE_Neon, PAUSE_New, PAUSE_Nurse, PAUSE_Pharaoh, PAUSE_Devil) from module.exercise.assets import * from module.logger import logger @@ -71,6 +71,7 @@ class HpDaemon(ModuleBase): PAUSE_HolyLight, PAUSE_Pharaoh, PAUSE_Nurse, + PAUSE_Devil, ]: self.attacker_hp = self._calculate_hp(image, area=ATTACKER_HP_AREA_New.area, reverse=True) self.defender_hp = self._calculate_hp(image, area=DEFENDER_HP_AREA_New.area, reverse=True) diff --git a/module/freebies/mail_white.py b/module/freebies/mail_white.py index 7a4f495fc..61e188224 100644 --- a/module/freebies/mail_white.py +++ b/module/freebies/mail_white.py @@ -79,6 +79,8 @@ class MailWhite(UI): continue if self.ui_main_appear_then_click(page_mail, offset=(30, 30), interval=3): continue + if self._handle_mail_reward(): + continue def _mail_quit(self, skip_first_screenshot=True): """ @@ -197,6 +199,8 @@ class MailWhite(UI): if self.handle_popup_confirm('MAIL_CLAIM'): deleted = True continue + if self._handle_mail_reward(): + continue # info_bar appears if mail success to delete and no mail deleted return True diff --git a/module/retire/assets.py b/module/retire/assets.py index cbdb30b3c..8e3ecf56d 100644 --- a/module/retire/assets.py +++ b/module/retire/assets.py @@ -11,6 +11,7 @@ DOCK_CHECK = Button(area={'cn': (121, 14, 175, 39), 'en': (121, 17, 189, 39), 'j DOCK_EMPTY = Button(area={'cn': (95, 347, 388, 378), 'en': (95, 318, 264, 339), 'jp': (96, 347, 252, 376), 'tw': (94, 347, 390, 379)}, color={'cn': (160, 154, 159), 'en': (106, 99, 106), 'jp': (159, 152, 156), 'tw': (163, 157, 162)}, button={'cn': (95, 347, 388, 378), 'en': (95, 318, 264, 339), 'jp': (96, 347, 252, 376), 'tw': (94, 347, 390, 379)}, file={'cn': './assets/cn/retire/DOCK_EMPTY.png', 'en': './assets/en/retire/DOCK_EMPTY.png', 'jp': './assets/jp/retire/DOCK_EMPTY.png', 'tw': './assets/tw/retire/DOCK_EMPTY.png'}) DOCK_FILTER = Button(area={'cn': (1099, 5, 1193, 48), 'en': (1098, 4, 1194, 49), 'jp': (1101, 6, 1192, 46), 'tw': (1099, 6, 1193, 47)}, color={'cn': (70, 87, 127), 'en': (73, 90, 128), 'jp': (67, 84, 125), 'tw': (78, 96, 137)}, button={'cn': (1099, 5, 1193, 48), 'en': (1098, 4, 1194, 49), 'jp': (1101, 6, 1192, 46), 'tw': (1099, 6, 1193, 47)}, file={'cn': './assets/cn/retire/DOCK_FILTER.png', 'en': './assets/en/retire/DOCK_FILTER.png', 'jp': './assets/jp/retire/DOCK_FILTER.png', 'tw': './assets/tw/retire/DOCK_FILTER.png'}) DOCK_FILTER_CONFIRM = Button(area={'cn': (714, 613, 886, 671), 'en': (718, 618, 883, 666), 'jp': (717, 618, 885, 668), 'tw': (715, 630, 884, 680)}, color={'cn': (86, 133, 192), 'en': (108, 148, 201), 'jp': (83, 128, 188), 'tw': (83, 130, 190)}, button={'cn': (714, 613, 886, 671), 'en': (718, 618, 883, 666), 'jp': (717, 618, 885, 668), 'tw': (715, 630, 884, 680)}, file={'cn': './assets/cn/retire/DOCK_FILTER_CONFIRM.png', 'en': './assets/en/retire/DOCK_FILTER_CONFIRM.png', 'jp': './assets/jp/retire/DOCK_FILTER_CONFIRM.png', 'tw': './assets/tw/retire/DOCK_FILTER_CONFIRM.png'}) +DOCK_FIRST_NPC = Button(area={'cn': (96, 111, 123, 123), 'en': (96, 111, 123, 123), 'jp': (96, 111, 123, 123), 'tw': (96, 111, 123, 123)}, color={'cn': (184, 150, 150), 'en': (184, 150, 150), 'jp': (184, 150, 150), 'tw': (184, 150, 150)}, button={'cn': (96, 111, 123, 123), 'en': (96, 111, 123, 123), 'jp': (96, 111, 123, 123), 'tw': (96, 111, 123, 123)}, file={'cn': './assets/cn/retire/DOCK_FIRST_NPC.png', 'en': './assets/cn/retire/DOCK_FIRST_NPC.png', 'jp': './assets/cn/retire/DOCK_FIRST_NPC.png', 'tw': './assets/cn/retire/DOCK_FIRST_NPC.png'}) DOCK_SCROLL = Button(area={'cn': (1239, 76, 1248, 641), 'en': (1239, 76, 1248, 641), 'jp': (1237, 78, 1250, 628), 'tw': (1239, 76, 1248, 641)}, color={'cn': (47, 46, 37), 'en': (47, 46, 37), 'jp': (180, 156, 66), 'tw': (47, 46, 37)}, button={'cn': (1239, 76, 1248, 641), 'en': (1239, 76, 1248, 641), 'jp': (1237, 78, 1250, 628), 'tw': (1239, 76, 1248, 641)}, file={'cn': './assets/cn/retire/DOCK_SCROLL.png', 'en': './assets/en/retire/DOCK_SCROLL.png', 'jp': './assets/jp/retire/DOCK_SCROLL.png', 'tw': './assets/tw/retire/DOCK_SCROLL.png'}) DOCK_SELECTED = Button(area={'cn': (582, 662, 647, 685), 'en': (702, 660, 751, 686), 'jp': (603, 662, 655, 685), 'tw': (582, 662, 647, 685)}, color={'cn': (75, 75, 83), 'en': (84, 85, 93), 'jp': (84, 83, 92), 'tw': (75, 75, 83)}, button={'cn': (582, 662, 647, 685), 'en': (702, 660, 751, 686), 'jp': (603, 662, 655, 685), 'tw': (582, 662, 647, 685)}, file={'cn': './assets/cn/retire/DOCK_SELECTED.png', 'en': './assets/en/retire/DOCK_SELECTED.png', 'jp': './assets/jp/retire/DOCK_SELECTED.png', 'tw': './assets/tw/retire/DOCK_SELECTED.png'}) EMPTY_ENHANCE_SLOT_PLUS = Button(area={'cn': (737, 402, 773, 437), 'en': (737, 402, 773, 437), 'jp': (737, 402, 773, 437), 'tw': (737, 402, 773, 437)}, color={'cn': (46, 46, 46), 'en': (46, 46, 46), 'jp': (46, 46, 46), 'tw': (46, 46, 46)}, button={'cn': (737, 402, 773, 437), 'en': (737, 402, 773, 437), 'jp': (737, 402, 773, 437), 'tw': (737, 402, 773, 437)}, file={'cn': './assets/cn/retire/EMPTY_ENHANCE_SLOT_PLUS.png', 'en': './assets/en/retire/EMPTY_ENHANCE_SLOT_PLUS.png', 'jp': './assets/jp/retire/EMPTY_ENHANCE_SLOT_PLUS.png', 'tw': './assets/tw/retire/EMPTY_ENHANCE_SLOT_PLUS.png'}) diff --git a/module/retire/dock.py b/module/retire/dock.py index de99d0794..27029538b 100644 --- a/module/retire/dock.py +++ b/module/retire/dock.py @@ -1,6 +1,6 @@ import module.config.server as server -from module.base.button import ButtonGrid +from module.base.button import ButtonGrid, get_color, color_similar from module.base.decorator import cached_property from module.base.timer import Timer from module.equipment.equipment import Equipment @@ -35,10 +35,23 @@ OCR_DOCK_SELECTED = DigitCounter(DOCK_SELECTED, threshold=64, name='OCR_DOCK_SEL class Dock(Equipment): - def handle_dock_cards_loading(self): - # Poor implementation. - self.device.sleep((1, 1.5)) - self.device.screenshot() + def handle_dock_cards_loading(self, skip_first_screenshot=True): + # Poor implementation + # confirm_timer method cannot be used + timeout = Timer(1.2, count=1).start() + while 1: + if skip_first_screenshot: + skip_first_screenshot = False + else: + self.device.screenshot() + + # Quick exit if dock is empty + if self.appear(DOCK_EMPTY): + logger.info('Dock empty') + break + # Otherwise we just wait 1.2s + if timeout.reached(): + break def dock_favourite_set(self, enable=False, wait_loading=True): """ @@ -244,3 +257,57 @@ class Dock(Equipment): continue if self.handle_popup_confirm('DOCK_SELECT_CONFIRM'): continue + + def dock_enter_first(self, non_npc=True, skip_first_screenshot=True): + """ + Enter first ship in dock + + Args: + non_npc: True to enter the second ship if first ship is NPC + skip_first_screenshot: + + Returns: + bool: True if success to enter + False if dock empty + False if non_npc and only one NPC in dock + + Pages: + in: page_dock + out: SHIP_DETAIL_CHECK + """ + logger.info('Dock enter first') + self.interval_clear(DOCK_CHECK, interval=3) + + while 1: + if skip_first_screenshot: + skip_first_screenshot = False + else: + self.device.screenshot() + + # End + if self.appear(SHIP_DETAIL_CHECK, offset=(20, 20)): + return True + if self.appear(DOCK_EMPTY, offset=(20, 20)): + logger.info('Dock empty') + return False + + # Click + if self.appear(DOCK_CHECK, offset=(20, 20), interval=3): + if non_npc: + # Check NPC + if DOCK_FIRST_NPC.match_luma(self.device.image, offset=(20, 20)): + logger.info('First ship is NPC, select second') + button = CARD_GRIDS[(1, 0)] + # Check if there's second ship + color = get_color(self.device.image, button.area) + if color_similar(color, (34, 34, 42)): + logger.info('Second ship empty, dock empty') + return False + else: + button = CARD_GRIDS[(0, 0)] + else: + button = CARD_GRIDS[(0, 0)] + self.device.click(button) + continue + if self.handle_game_tips(): + continue diff --git a/module/retire/enhancement.py b/module/retire/enhancement.py index be0a3702d..a1c9b0131 100644 --- a/module/retire/enhancement.py +++ b/module/retire/enhancement.py @@ -54,9 +54,7 @@ class Enhancement(Dock): if self.appear(DOCK_EMPTY, offset=(30, 30)): return False - self.ship_info_enter( - CARD_GRIDS[(0, 0)], check_button=SHIP_DETAIL_CHECK, long_click=False) - return True + return self.dock_enter_first() def _enhance_quit(self): """ diff --git a/module/tactical/tactical_class.py b/module/tactical/tactical_class.py index 08e299672..241abcd95 100644 --- a/module/tactical/tactical_class.py +++ b/module/tactical/tactical_class.py @@ -422,11 +422,11 @@ class RewardTacticalClass(Dock): # Tactical page, has empty position if self.appear_then_click(ADD_NEW_STUDENT, offset=(800, 20), interval=1): self.interval_reset([TACTICAL_CHECK, RAPID_TRAINING]) - self.interval_clear([POPUP_CONFIRM, POPUP_CANCEL, GET_MISSION]) + self.interval_clear([POPUP_CONFIRM, POPUP_CANCEL, GET_MISSION, DOCK_CHECK, SKILL_CONFIRM]) continue if self.handle_rapid_training(): self.interval_reset(TACTICAL_CHECK) - self.interval_clear([POPUP_CONFIRM, POPUP_CANCEL, GET_MISSION]) + self.interval_clear([POPUP_CONFIRM, POPUP_CANCEL, GET_MISSION, DOCK_CHECK, SKILL_CONFIRM]) continue # Get finish time @@ -496,6 +496,7 @@ class RewardTacticalClass(Dock): # so we need click BACK_ARROW to clear selected state logger.info('Having pre-selected ship in dock, re-enter') self.device.click(BACK_ARROW) + self.interval_reset([BOOK_EMPTY_POPUP, DOCK_CHECK], interval=3) continue # If not enable or can not fina a suitable ship if self.config.AddNewStudent_Enable: @@ -508,7 +509,7 @@ class RewardTacticalClass(Dock): logger.info('Not going to learn skill but in dock, close it') study_finished = True self.device.click(BACK_ARROW) - self.interval_reset([BOOK_EMPTY_POPUP]) + self.interval_reset([BOOK_EMPTY_POPUP, DOCK_CHECK], interval=3) continue if self.appear(SKILL_CONFIRM, offset=(20, 20), interval=3): # If not enable or can not find a skill @@ -522,7 +523,7 @@ class RewardTacticalClass(Dock): logger.info('Not going to learn skill but having SKILL_CONFIRM, close it') study_finished = True self.device.click(BACK_ARROW) - self.interval_reset([BOOK_EMPTY_POPUP]) + self.interval_reset([BOOK_EMPTY_POPUP, SKILL_CONFIRM], interval=3) continue if self.appear(TACTICAL_META, offset=(200, 20), interval=3): # If meta's skill page, it's inappropriate