def get_next(self): """ Returns: Function: Command to run """ self.get_next_task() if self.pending_task: AzurLaneConfig.is_hoarding_task = False logger.info( f'Pending tasks: {[f.command for f in self.pending_task]}') task = self.pending_task[0] logger.attr('Task', task) return task else: AzurLaneConfig.is_hoarding_task = True if self.waiting_task: logger.info('No task pending') task = copy.deepcopy(self.waiting_task[0]) task.next_run = (task.next_run + self.hoarding).replace(microsecond=0) logger.attr('Task', task) return task else: logger.critical('No task waiting or pending') logger.critical('Please enable at least one task') raise RequestHumanTakeover
def check_screen(self): """ Screen size must be 1280x720. Take a screenshot before call. """ # Check screen size width, height = self.image.size logger.attr('Screen_size', f'{width}x{height}') if not (width == 1280 and height == 720): logger.critical(f'Resolution not supported: {width}x{height}') logger.critical('Please set emulator resolution to 1280x720') raise RequestHumanTakeover # Check screen color # May get a pure black screenshot on some emulators. color = get_color(self.image, area=(0, 0, 1280, 720)) if sum(color) < 1: logger.critical( f'Received pure black screenshots from emulator, color: {color}' ) logger.critical( f'Screenshot method `{self.config.Emulator_ScreenshotMethod}` ' f'may not work on emulator `{self.serial}`') logger.critical('Please use other screenshot methods') raise RequestHumanTakeover
def retry_wrapper(self, *args, **kwargs): """ Args: self (Hermit): """ init = None for _ in range(RETRY_TRIES): try: if callable(init): self.sleep(RETRY_DELAY) init() return func(self, *args, **kwargs) # Can't handle except RequestHumanTakeover: break # When adb server was killed except ConnectionResetError as e: logger.error(e) def init(): self.adb_reconnect() # When unable to send requests except requests.exceptions.ConnectionError as e: logger.error(e) text = str(e) if 'Connection aborted' in text: # Hermit not installed or not running # ('Connection aborted.', RemoteDisconnected('Remote end closed connection without response')) def init(): self.adb_reconnect() self.hermit_init() else: # Lost connection, adb server was killed # HTTPConnectionPool(host='127.0.0.1', port=20269): # Max retries exceeded with url: /click?x=500&y=500 def init(): self.adb_reconnect() # AdbError except AdbError as e: if handle_adb_error(e): def init(): self.adb_reconnect() else: break # HermitError: {"code":-1,"msg":"error"} except HermitError as e: logger.error(e) def init(): self.adb_reconnect() self.hermit_init() # Unknown, probably a trucked image except Exception as e: logger.exception(e) def init(): pass logger.critical(f'Retry {func.__name__}() failed') raise RequestHumanTakeover
def ascreencap_init(self): logger.hr('aScreenCap init') self.__bytepointer = 0 arc = self.adb_shell(['getprop', 'ro.product.cpu.abi']) sdk = self.adb_shell(['getprop', 'ro.build.version.sdk']) logger.info(f'cpu_arc: {arc}, sdk_ver: {sdk}') if int(sdk) in range(21, 26): ver = "Android_5.x-7.x" elif int(sdk) in range(26, 28): ver = "Android_8.x" elif int(sdk) == 28: ver = "Android_9.x" else: ver = "0" filepath = os.path.join(self.config.ASCREENCAP_FILEPATH_LOCAL, ver, arc, 'ascreencap') if not os.path.exists(filepath): logger.critical('No suitable version of aScreenCap lib available for this device') logger.critical('Please use ADB or uiautomator2 for screenshots instead') raise RequestHumanTakeover logger.info(f'pushing {filepath}') self.adb_push(filepath, self.config.ASCREENCAP_FILEPATH_REMOTE) logger.info(f'chmod 0777 {self.config.ASCREENCAP_FILEPATH_REMOTE}') self.adb_shell(['chmod', '0777', self.config.ASCREENCAP_FILEPATH_REMOTE])
def ui_goto_archives_campaign(self, mode='ex'): """ Performs the operations needed to transition to target archive's campaign stage map """ # On first run regardless of current location # even in target stage map, start from page_archives # For subsequent runs when neither reward or # stop_triggers occur, no need perform operations result = True if self.first_run or not self.appear(WAR_ARCHIVES_CAMPAIGN_CHECK, offset=(20, 20)): result = self.ui_ensure(destination=page_archives) WAR_ARCHIVES_SWITCH.set(mode, main=self) entrance = self._search_archives_entrance( self.config.Campaign_Event) if entrance is not None: self.ui_click(entrance, appear_button=WAR_ARCHIVES_CHECK, check_button=WAR_ARCHIVES_CAMPAIGN_CHECK, skip_first_screenshot=True) else: logger.critical( 'Respective server may not yet support the chosen War Archives campaign, ' 'check back in the next app update') raise RequestHumanTakeover # Subsequent runs all set False if self.first_run: self.first_run = False return result
def check_screen_size(self): """ Screen size must be 1280x720. Take a screenshot before call. """ if self._screen_size_checked: return True orientated = False for _ in range(2): # Check screen size width, height = image_size(self.image) logger.attr('Screen_size', f'{width}x{height}') if width == 1280 and height == 720: self._screen_size_checked = True return True elif not orientated and (width == 720 and height == 1280): logger.info('Received orientated screenshot, handling') self.get_orientation() self.image = self._handle_orientated_image(self.image) orientated = True continue elif self.config.Emulator_Serial == 'wsa-0': self.display_resize_wsa(0) return False elif hasattr(self, 'app_is_running') and not self.app_is_running(): logger.warning( 'Received orientated screenshot, game not running') return True else: logger.critical(f'Resolution not supported: {width}x{height}') logger.critical('Please set emulator resolution to 1280x720') raise RequestHumanTakeover
def handle_app_login(self): """ Returns: bool: If login success Raises: RequestHumanTakeover: If login failed more than 3 """ for _ in range(3): self.device.stuck_record_clear() self.device.click_record_clear() try: self._handle_app_login() return True except (GameTooManyClickError, GameStuckError) as e: logger.warning(e) self.device.app_stop() self.device.app_start() continue logger.critical('Login failed more than 3') logger.critical( 'Azur Lane server may be under maintenance, or you may lost network connection' ) raise RequestHumanTakeover
def _assert_and_prepare_model_files(self): model_dir = self._model_dir model_files = [ 'label_cn.txt', '%s-%04d.params' % (self._model_file_prefix, self._model_epoch), '%s-symbol.json' % self._model_file_prefix, ] file_prepared = True for f in model_files: f = os.path.join(model_dir, f) if not os.path.exists(f): file_prepared = False logger.warning('can not find file %s', f) break if file_prepared: return # Disable auto downloading cnocr models when model not found. # get_model_file(model_dir) logger.warning(f'Ocr model not prepared: {model_dir}') logger.warning(f'Required files: {model_files}') logger.critical( 'Please check if required files of pre-trained OCR model exist') raise RequestHumanTakeover
def run_stronghold(self): """ All fleets take turns in attacking siren stronghold. Returns: bool: If success to clear. Pages: in: Siren logger (abyssal), boss appeared. out: If success, dangerous or safe zone. If failed, still in abyssal. """ logger.hr(f'Stronghold clear', level=1) fleets = self.parse_fleet_filter() for fleet in fleets: logger.hr(f'Turn: {fleet}', level=2) if not isinstance(fleet, BossFleet): self.os_order_execute(recon_scan=False, submarine_call=True) continue result = self.run_stronghold_one_fleet(fleet) if result: return True else: continue logger.critical('Unable to clear boss, fleets exhausted') return False
def hermit_send(self, url, **kwargs): """ Args: url (str): **kwargs: Returns: dict: Usually to be {"code":0,"msg":"ok"} """ result = self.hermit_session.get(f'{self._hermit_url}{url}', params=kwargs, timeout=3).text try: result = json.loads(result, encoding='utf-8') if result['code'] != 0: # {"code":-1,"msg":"error"} raise HermitError(result) except (json.decoder.JSONDecodeError, KeyError): e = HermitError(result) if 'GestureDescription$Builder' in result: logger.error(e) logger.critical('Hermit cannot run on current device, hermit requires Android>=7.0') raise RequestHumanTakeover if 'accessibilityservice' in result: # Attempt to invoke virtual method # 'boolean android.accessibilityservice.AccessibilityService.dispatchGesture( # android.accessibilityservice.GestureDescription, # android.accessibilityservice.AccessibilityService$GestureResultCallback, # android.os.Handler # )' on a null object reference logger.error('Unable to access accessibility service') raise e # Hermit only takes 2-4ms # Add a 50ms delay because game can't response quickly. self.sleep(0.05) return result
def app_start(self): logger.info(f'App start: {self.config.Emulator_PackageName}') try: self.device.app_start(self.config.Emulator_PackageName) except BaseError as e: logger.critical(e) raise RequestHumanTakeover
def _ascreencap_init(self): logger.hr('aScreenCap init') arc = self.adb_exec_out(['getprop', 'ro.product.cpu.abi' ]).decode('utf-8').strip() sdk = self.adb_exec_out(['getprop', 'ro.build.version.sdk' ]).decode('utf-8').strip() logger.info(f'cpu_arc: {arc}, sdk_ver: {sdk}') filepath = os.path.join(self.config.ASCREENCAP_FILEPATH_LOCAL, arc, 'ascreencap') if int(sdk) not in range(21, 26) or not os.path.exists(filepath): logger.critical( 'No suitable version of aScreenCap lib available for this device' ) logger.critical( 'Please use ADB or uiautomator2 for screenshots instead') raise RequestHumanTakeover logger.info(f'pushing {filepath}') self.adb_push([filepath, self.config.ASCREENCAP_FILEPATH_REMOTE]) logger.info(f'chmod 0777 {self.config.ASCREENCAP_FILEPATH_REMOTE}') self.adb_shell( ['chmod', '0777', self.config.ASCREENCAP_FILEPATH_REMOTE])
def shop_buy_select_execute(self, item): """ Args: item (Item): Returns: bool: """ # Search for appropriate select grid button for item select = self.shop_get_select(item) # Get displayed stock limit; varies between shops # If read 0, then warn and exit as cannot safely buy _, _, limit = OCR_SHOP_SELECT_STOCK.ocr(self.device.image) if not limit: logger.critical(f'{item.name}\'s stock count cannot be ' 'extracted. Advised to re-cut the asset ' 'OCR_SHOP_SELECT_STOCK') raise ScriptError # Click in intervals until plus/minus are onscreen click_timer = Timer(3, count=6) select_offset = (500, 400) while 1: if click_timer.reached(): self.device.click(select) click_timer.reset() # Scan for plus/minus locations; searching within # offset will update the click position automatically self.device.screenshot() if self.appear(SELECT_MINUS, offset=select_offset) and self.appear(SELECT_PLUS, offset=select_offset): break else: continue # Total number to purchase altogether total = int(self._currency // item.price) diff = limit - total if diff > 0: limit = total # Alias OCR_SHOP_SELECT_STOCK to adapt with # ui_ensure_index; prevent overbuying when # out of stock; item.price may still evaluate # incorrectly def shop_buy_select_ensure_index(image): current, remain, _ = OCR_SHOP_SELECT_STOCK.ocr(image) if not current: group_case = item.group.title() if len(item.group) > 2 else item.group.upper() logger.info(f'{group_case}(s) out of stock; exit to prevent overbuying') return limit return remain self.ui_ensure_index(limit, letter=shop_buy_select_ensure_index, prev_button=SELECT_MINUS, next_button=SELECT_PLUS, skip_first_screenshot=True) self.device.click(SHOP_BUY_CONFIRM_SELECT) return True
def os_auto_search_daemon(self, drop=None, skip_first_screenshot=True): """ Raises: CampaignEnd: If auto search ended RequestHumanTakeover: If there's no auto search option. Pages: in: AUTO_SEARCH_OS_MAP_OPTION_OFF out: AUTO_SEARCH_OS_MAP_OPTION_OFF and info_bar_count() >= 2, if no more objects to clear on this map. AUTO_SEARCH_REWARD if get auto search reward. """ logger.hr('OS auto search', level=2) self._auto_search_battle_count = 0 unlock_checked = True unlock_check_timer = Timer(5, count=10).start() self.ash_popup_canceled = False success = True died_timer = Timer(1.5, count=3) while 1: if skip_first_screenshot: skip_first_screenshot = False else: self.device.screenshot() if not unlock_checked and unlock_check_timer.reached(): logger.critical('Unable to use auto search in current zone') logger.critical('Please finish the story mode of OpSi to unlock auto search ' 'before using any OpSi functions') raise RequestHumanTakeover if self.is_in_map(): self.device.stuck_record_clear() if not success: if died_timer.reached(): logger.warning('Fleet died confirm') break else: died_timer.reset() else: died_timer.reset() if self.handle_os_auto_search_map_option(drop=drop, enable=success): unlock_checked = True continue if self.handle_retirement(): # Retire will interrupt auto search, need a retry self.ash_popup_canceled = True continue if self.combat_appear(): self._auto_search_battle_count += 1 logger.attr('battle_count', self._auto_search_battle_count) result = self.auto_search_combat(drop=drop) if not result: success = False logger.warning('Fleet died, stop auto search') continue if self.handle_map_event(): # Auto search can not handle siren searching device. continue
def boss_clear(self, has_fleet_step=True): """ All fleets take turns in attacking the boss. Args: has_fleet_step (bool): Returns: bool: If success to clear. Pages: in: Siren logger (abyssal), boss appeared. out: If success, dangerous or safe zone. If failed, still in abyssal. """ logger.hr(f'BOSS clear', level=1) fleets = self.parse_fleet_filter() with self.stat.new(genre=inflection.underscore( self.config.task.command), save=self.config.DropRecord_SaveOpsi, upload=self.config.DropRecord_UploadOpsi) as drop: for fleet in fleets: logger.hr(f'Turn: {fleet}', level=2) if not isinstance(fleet, BossFleet): self.os_order_execute(recon_scan=False, submarine_call=True) continue # Attack self.fleet_set(fleet.fleet_index) self.handle_os_map_fleet_lock(enable=False) self.boss_goto(location=(0, 0), has_fleet_step=has_fleet_step, drop=drop) # End self.predict_radar() if self.radar.select(is_question=True): logger.info('BOSS clear') if drop.count: drop.add(self.device.image) self.map_exit() return True # Standby self.boss_leave() if fleet.standby_loca != (0, 0): self.boss_goto(location=fleet.standby_loca, has_fleet_step=has_fleet_step, drop=drop) else: if drop.count: drop.add(self.device.image) break logger.critical('Unable to clear boss, fleets exhausted') return False
def app_stop(self): logger.info(f'App stop: {self.config.Emulator_PackageName}') try: self.device.app_stop(self.config.Emulator_PackageName) if self.config.Emulator_ControlMethod == "WSA": del self.device.__dict__['get_game_windows_id'] except BaseError as e: logger.critical(e) raise RequestHumanTakeover
def hermit_enable_accessibility(self): """ Turn on accessibility service for Hermit. Raises: RequestHumanTakeover: If failed and user should do it manually. """ logger.hr('Enable accessibility service') interval = Timer(0.3) timeout = Timer(10, count=10).start() while 1: h = self.dump_hierarchy_adb() interval.wait() interval.reset() def appear(xpath): return bool(HierarchyButton(h, xpath)) def appear_then_click(xpath): b = HierarchyButton(h, xpath) if b: point = random_rectangle_point(b.button) logger.info(f'Click {point2str(*point)} @ {b}') self.click_adb(*point) return True else: return False if appear_then_click( '//*[@text="Hermit" and @resource-id="android:id/title"]'): continue if appear_then_click( '//*[@class="android.widget.Switch" and @checked="false"]' ): continue if appear_then_click('//*[@resource-id="android:id/button1"]'): # Just plain click here # Can't use uiautomator once hermit has access to accessibility service, # or uiautomator will get the access. break if appear( '//*[@class="android.widget.Switch" and @checked="true"]'): raise HermitError( 'Accessibility service already enable but get error') # End if timeout.reached(): logger.critical( 'Unable to turn on accessibility service for Hermit') logger.critical( '\n\n' 'Please do this manually:\n' '1. Find "Hermit" in accessibility setting and click it\n' '2. Turn it ON and click OK\n' '3. Switch back to AzurLane\n') raise RequestHumanTakeover
def possible_reasons(*args): """ Show possible reasons Possible reason #1: <reason_1> Possible reason #2: <reason_2> """ for index, reason in enumerate(args): index += 1 logger.critical(f'Possible reason #{index}: {reason}')
def app_stop(self): if not self.config.Error_HandleError: logger.critical('No app stop/start, because HandleError disabled') logger.critical( 'Please enable Alas.Error.HandleError or manually login to AzurLane' ) raise RequestHumanTakeover super().app_stop() self.stuck_record_clear() self.click_record_clear()
def config(self): try: config = AzurLaneConfig(config_name=self.config_name) return config except RequestHumanTakeover: logger.critical('Request human takeover') exit(1) except Exception as e: logger.exception(e) exit(1)
def device(self): try: from module.device.device import Device device = Device(config=self.config) return device except RequestHumanTakeover: logger.critical('Request human takeover') exit(1) except Exception as e: logger.exception(e) exit(1)
def os_explore(self): for _ in range(2): try: self._os_explore() except OSExploreError: logger.info('Go back to NY, explore again') self.config.OpsiExplore_LastZone = 0 self.globe_goto(0) logger.critical('Failed to solve the locked zone') raise RequestHumanTakeover
def _api(self): method = self.config.DropRecord_API if method == 'default': return 'https://azurstats.lyoko.io/api/upload/' elif method == 'cn_gz_reverse_proxy': return 'https://service-rjfzwz8i-1301182309.gz.apigw.tencentcs.com/api/upload' elif method == 'cn_sh_reverse_proxy': return 'https://service-nlvjetab-1301182309.sh.apigw.tencentcs.com/api/upload' else: logger.critical('Invalid upload API, please check your settings') raise ScriptError('Invalid upload API')
def config(self): try: config = AzurLaneConfig(config_name=self.config_name) # Set server before loading any buttons. server.server = deep_get(config.data, keys='Alas.Emulator.Server', default='cn') return config except RequestHumanTakeover: logger.critical('Request human takeover') exit(1) except Exception as e: logger.exception(e) exit(1)
def ui_get_current_page(self, skip_first_screenshot=True): """ Args: skip_first_screenshot: Returns: Page: """ if not skip_first_screenshot or not hasattr( self.device, 'image') or self.device.image is None: self.device.screenshot() # Known pages for page in self.ui_pages: if page.check_button is None: continue if self.ui_page_appear(page=page): logger.attr('UI', page.name) self.ui_current = page return page # Unknown page but able to handle logger.info('Unknown ui page') if self.appear_then_click(GOTO_MAIN, offset=(20, 20)) or self.ui_additional(): logger.info('Goto page_main') self.ui_current = page_unknown self.ui_goto(page_main, skip_first_screenshot=True) # Unknown page, need manual switching if hasattr(self, 'ui_current'): logger.warning( f'Unrecognized ui_current, using previous: {self.ui_current}') else: logger.info('Unable to goto page_main') logger.attr('EMULATOR__SCREENSHOT_METHOD', self.config.Emulator_ScreenshotMethod) logger.attr('EMULATOR__CONTROL_METHOD', self.config.Emulator_ControlMethod) logger.attr('SERVER', self.config.SERVER) logger.warning('Starting from current page is not supported') logger.warning( f'Supported page: {[str(page) for page in self.ui_pages]}') logger.warning( f'Supported page: Any page with a "HOME" button on the upper-right' ) if not self.device.app_is_running(): raise GameNotRunningError('Game not running') else: logger.critical( 'Please switch to a supported page before starting Alas') raise RequestHumanTakeover
def run_process(config_name, func: str, q: queue.Queue, e: threading.Event = None) -> None: # Setup logger set_file_logger(name=config_name) set_func_logger(func=q.put) # Set server before loading any buttons. import module.config.server as server from module.config.config import AzurLaneConfig AzurLaneConfig.stop_event = e config = AzurLaneConfig(config_name=config_name) server.server = deep_get(config.data, keys="Alas.Emulator.Server", default="cn") try: # Run alas if func == "Alas": from alas import AzurLaneAutoScript if e is not None: AzurLaneAutoScript.stop_event = e AzurLaneAutoScript(config_name=config_name).loop() elif func == "Daemon": from module.daemon.daemon import AzurLaneDaemon AzurLaneDaemon(config=config_name, task="Daemon").run() elif func == "OpsiDaemon": from module.daemon.os_daemon import AzurLaneDaemon AzurLaneDaemon(config=config_name, task="OpsiDaemon").run() elif func == "AzurLaneUncensored": from module.daemon.uncensored import AzurLaneUncensored AzurLaneUncensored(config=config_name, task="AzurLaneUncensored").run() elif func == "Benchmark": from module.daemon.benchmark import Benchmark Benchmark(config=config_name, task="Benchmark").run() elif func == "GameManager": from module.daemon.game_manager import GameManager GameManager(config=config_name, task="GameManager").run() else: logger.critical("No function matched") logger.info(f"[{config_name}] exited. Reason: Finish\n") except Exception as e: logger.exception(e)
def run_process(config_name, func: str, q: queue.Queue, e: threading.Event = None) -> None: # Setup logger qh = QueueHandler(q) formatter = logging.Formatter( fmt='%(asctime)s.%(msecs)03d | %(levelname)s | %(message)s', datefmt='%H:%M:%S') webconsole = logging.StreamHandler(stream=qh) webconsole.setFormatter(formatter) logger.addHandler(webconsole) # Set server before loading any buttons. import module.config.server as server from module.config.config import AzurLaneConfig AzurLaneConfig.stop_event = e config = AzurLaneConfig(config_name=config_name) server.server = deep_get(config.data, keys='Alas.Emulator.Server', default='cn') try: # Run alas if func == 'Alas': from alas import AzurLaneAutoScript if e is not None: AzurLaneAutoScript.stop_event = e AzurLaneAutoScript(config_name=config_name).loop() elif func == 'Daemon': from module.daemon.daemon import AzurLaneDaemon AzurLaneDaemon(config=config_name, task='Daemon').run() elif func == 'OpsiDaemon': from module.daemon.os_daemon import AzurLaneDaemon AzurLaneDaemon(config=config_name, task='OpsiDaemon').run() elif func == 'AzurLaneUncensored': from module.daemon.uncensored import AzurLaneUncensored AzurLaneUncensored(config=config_name, task='AzurLaneUncensored').run() elif func == 'Benchmark': from module.daemon.benchmark import Benchmark Benchmark(config=config_name, task='Benchmark').run() elif func == 'GameManager': from module.daemon.game_manager import GameManager GameManager(config=config_name, task='GameManager').run() else: logger.critical("No function matched") logger.info(f"[{config_name}] exited. Reason: Finish") except Exception as e: logger.exception(e)
def app_start(self): logger.info(f'App start: {self.config.Emulator_PackageName}') try: if self.config.Emulator_WSA: self.adb_shell(['wm', 'size', '1280x720', '-d', '0']) self.adb_shell([ 'am', 'start', '--display', '0', self.config.Emulator_PackageName + '/com.manjuu.azurlane.MainActivity' ]) else: self.device.app_start(self.config.Emulator_PackageName) except BaseError as e: logger.critical(e) raise RequestHumanTakeover
def retry_wrapper(self, *args, **kwargs): """ Args: self (AScreenCap): """ init = None for _ in range(RETRY_TRIES): try: if callable(init): self.sleep(RETRY_DELAY) init() return func(self, *args, **kwargs) # Can't handle except RequestHumanTakeover: break # When adb server was killed except ConnectionResetError as e: logger.error(e) def init(): self.adb_disconnect(self.serial) self.adb_connect(self.serial) # When ascreencap is not installed except AscreencapError as e: logger.error(e) def init(): self.ascreencap_init() # AdbError except AdbError as e: if handle_adb_error(e): def init(): self.adb_disconnect(self.serial) self.adb_connect(self.serial) else: break # Unknown, probably a trucked image except Exception as e: logger.exception(e) def init(): pass logger.critical(f'Retry {func.__name__}() failed') raise RequestHumanTakeover
def load_campaign(self, name, folder='campaign_main'): """ Args: name (str): Name of .py file under module.campaign. folder (str): Name of the file folder under campaign. Returns: bool: If load. """ if hasattr(self, 'name') and name == self.name: return False self.name = name self.folder = folder if folder.startswith('campaign_'): self.stage = '-'.join(name.split('_')[1:3]) if folder.startswith('event') or folder.startswith('war_archives'): self.stage = name try: self.module = importlib.import_module('.' + name, f'campaign.{folder}') except ModuleNotFoundError: logger.warning(f'Map file not found: campaign.{folder}.{name}') folder = f'./campaign/{folder}' if not os.path.exists(folder): logger.warning(f'Folder not exists: {folder}') else: files = [f[:-3] for f in os.listdir(folder) if f[-3:] == '.py'] logger.warning(f'Existing files: {files}') logger.critical( f'Possible reason #1: This event ({folder}) does not have {name}' ) logger.critical( f'Possible reason #2: You are using an old Alas, ' 'please check for update, or make map files yourself using dev_tools/map_extractor.py' ) raise RequestHumanTakeover config = copy.deepcopy(self.config).merge(self.module.Config()) device = self.device self.campaign = self.module.Campaign(config=config, device=device) return True