import sys from collections import deque from module.base.timer import Timer from module.device.app_control import AppControl from module.device.control import Control from module.device.screenshot import Screenshot from module.exception import ( EmulatorNotRunningError, GameNotRunningError, GameStuckError, GameTooManyClickError, RequestHumanTakeover ) from module.logger import logger if sys.platform == 'win32': from module.device.platform.platform_windows import PlatformWindows as Platform else: from module.device.platform.platform_base import PlatformBase as Platform class Device(Screenshot, Control, AppControl, Platform): _screen_size_checked = False detect_record = set() click_record = deque(maxlen=15) stuck_timer = Timer(60, count=60).start() stuck_timer_long = Timer(180, count=180).start() stuck_long_wait_list = ['BATTLE_STATUS_S', 'PAUSE', 'LOGIN_CHECK'] def __init__(self, *args, **kwargs): for _ in range(2): try: super().__init__(*args, **kwargs) break except EmulatorNotRunningError: # Try to start emulator if self.emulator_instance is not None: self.emulator_start() else: logger.critical( f'No emulator with serial "{self.config.Emulator_Serial}" found, ' f'please set a correct serial' ) raise self.screenshot_interval_set() # Auto-select the fastest screenshot method if not self.config.is_template_config and self.config.Emulator_ScreenshotMethod == 'auto': self.run_simple_screenshot_benchmark() def run_simple_screenshot_benchmark(self): """ Perform a screenshot method benchmark, test 3 times on each method. The fastest one will be set into config. """ logger.info('run_simple_screenshot_benchmark') # Check resolution first self.resolution_check_uiautomator2() # Perform benchmark from module.daemon.benchmark import Benchmark bench = Benchmark(config=self.config, device=self) method = bench.run_simple_screenshot_benchmark() # Set self.config.Emulator_ScreenshotMethod = method def screenshot(self): """ Returns: np.ndarray: """ self.stuck_record_check() try: super().screenshot() except RequestHumanTakeover: if not self.ascreencap_available: logger.error('aScreenCap unavailable on current device, fallback to auto') self.run_simple_screenshot_benchmark() super().screenshot() else: raise return self.image def release_during_wait(self): # Scrcpy server is still sending video stream, # stop it during wait if self.config.Emulator_ScreenshotMethod == 'scrcpy': self._scrcpy_server_stop() def stuck_record_add(self, button): self.detect_record.add(str(button)) def stuck_record_clear(self): self.detect_record = set() self.stuck_timer.reset() self.stuck_timer_long.reset() def stuck_record_check(self): """ Raises: GameStuckError: """ reached = self.stuck_timer.reached() reached_long = self.stuck_timer_long.reached() if not reached: return False if not reached_long: for button in self.stuck_long_wait_list: if button in self.detect_record: return False logger.warning('Wait too long') logger.warning(f'Waiting for {self.detect_record}') self.stuck_record_clear() if self.app_is_running(): raise GameStuckError(f'Wait too long') else: raise GameNotRunningError('Game died') def handle_control_check(self, button): self.stuck_record_clear() self.click_record_add(button) self.click_record_check() def click_record_add(self, button): self.click_record.append(str(button)) def click_record_clear(self): self.click_record.clear() def click_record_remove(self, button): """ Remove a button from `click_record` Args: button (Button): Returns: int: Number of button removed """ removed = 0 for _ in range(self.click_record.maxlen): try: self.click_record.remove(str(button)) removed += 1 except ValueError: # Value not in queue break return removed def click_record_check(self): """ Raises: GameTooManyClickError: """ count = {} for key in self.click_record: count[key] = count.get(key, 0) + 1 count = sorted(count.items(), key=lambda item: item[1]) if count[0][1] >= 12: logger.warning(f'Too many click for a button: {count[0][0]}') logger.warning(f'History click: {[str(prev) for prev in self.click_record]}') self.click_record_clear() raise GameTooManyClickError(f'Too many click for a button: {count[0][0]}') if len(count) >= 2 and count[0][1] >= 6 and count[1][1] >= 6: logger.warning(f'Too many click between 2 buttons: {count[0][0]}, {count[1][0]}') logger.warning(f'History click: {[str(prev) for prev in self.click_record]}') self.click_record_clear() raise GameTooManyClickError(f'Too many click between 2 buttons: {count[0][0]}, {count[1][0]}') def disable_stuck_detection(self): """ Disable stuck detection and its handler. Usually uses in semi auto and debugging. """ logger.info('Disable stuck detection') def empty_function(*arg, **kwargs): return False self.click_record_check = empty_function self.stuck_record_check = empty_function def app_start(self): super().app_start() self.stuck_record_clear() self.click_record_clear() def app_stop(self): super().app_stop() self.stuck_record_clear() self.click_record_clear()