def os_port_mission(self): """ Visit all ports and do the daily mission in it. """ logger.hr('OS port mission', level=1) ports = [ 'NY City', 'Dakar', 'Taranto', 'Gibraltar', 'Brest', 'Liverpool', 'Kiel', 'St. Petersburg' ] if np.random.uniform() > 0.5: ports.reverse() for port in ports: port = self.name_to_zone(port) logger.hr(f'OS port daily in {port}', level=2) self.globe_goto(port) self.run_auto_search() self.handle_after_auto_search()
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}') 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 handle_fleet_emp_debuff(self): """ EMP debuff limits fleet step to 1 and messes auto search up. It can be solved by moving fleets on map meaninglessly. Returns: bool: If solved """ if self.is_in_special_zone(): logger.info( 'OS is in a special zone type, skip handle_fleet_emp_debuff') return False def has_emp_debuff(): return self.appear(FLEET_EMP_DEBUFF, offset=(50, 20)) for trial in range(5): if not has_emp_debuff(): logger.info('No EMP debuff on current fleet') return trial > 0 current = self.get_fleet_current_index() logger.hr(f'Solve EMP debuff on fleet {current}') self.globe_goto(self.zone_nearest_azur_port(self.zone)) logger.info('Find a fleet without EMP debuff') for fleet in [1, 2, 3, 4]: self.fleet_set(fleet) if has_emp_debuff(): logger.info(f'Fleet {fleet} is under EMP debuff') continue else: logger.info(f'Fleet {fleet} is not under EMP debuff') break logger.info('Solve EMP debuff by going somewhere else') self.port_goto() self.fleet_set(current) logger.warning( 'Failed to solve EMP debuff after 5 trial, assume solved') return True
def run(self): logger.hr(self.ENTRANCE, level=2) self.enter_map(self.ENTRANCE, mode='hard') self.map = self.MAP self.map.reset() if self.config.FLEET_HARD == 1: self.ensure_edge_insight(reverse=True) self.full_scan_find_boss() else: self.fleet_switch_click() self.ensure_no_info_bar() self.ensure_edge_insight() self.full_scan_find_boss() try: self.clear_boss() except CampaignEnd: logger.hr('Campaign end')
def _sos_signal_select(self, chapter): """ select a SOS signal EN has no scroll bar, so the swipe signal list. Args: chapter (int): 3 to 10. Pages: in: page_campaign out: page_campaign, in target chapter Returns: bool: whether select successful """ logger.hr(f'Select chapter {chapter} signal ') self.ui_click(SIGNAL_SEARCH_ENTER, appear_button=CAMPAIGN_CHECK, check_button=SIGNAL_LIST_CHECK, skip_first_screenshot=True) detection_area = (620, 285, 720, 485) for _ in range(0, 5): target_button = self._find_target_chapter(chapter) if target_button is not None: self._sos_signal_confirm(entrance=target_button) return True # backup = self.config.cover(DEVICE_CONTROL_METHOD='minitouch') p1, p2 = random_rectangle_vector((0, -200), box=detection_area, random_range=(-50, -50, 50, 50), padding=20) self.device.drag(p1, p2, segments=2, shake=(0, 25), point_random=(0, 0, 0, 0), shake_random=(0, -5, 0, 5)) # backup.recover() self.device.sleep((0.6, 1)) self.device.screenshot() return False
def raid_execute_once(self, mode, raid): """ Args: mode: raid: Returns: in: page_raid out: page_raid """ logger.hr('Raid Execute') self.config.override(Campaign_Name=f'{raid}_{mode}', Campaign_UseAutoSearch=False, Fleet_FleetOrder='fleet1_all_fleet2_standby') self.emotion.check_reduce(1) self.raid_enter(mode=mode, raid=raid) self.combat(balance_hp=False, expected_end=self.raid_expected_end) logger.hr('Raid End')
def _sos_signal_select(self, chapter): """ select a SOS signal Args: chapter (int): 3 to 10. Pages: in: page_campaign out: page_campaign, in target chapter Returns: bool: whether select successful """ logger.hr(f'Select chapter {chapter} signal ') self.ui_click(SIGNAL_SEARCH_ENTER, appear_button=CAMPAIGN_CHECK, check_button=SIGNAL_LIST_CHECK, skip_first_screenshot=True) if chapter in [3, 4, 5]: positions = [0.0, 0.5, 1.0] elif chapter in [6, 7]: positions = [0.5, 1.0, 0.0] elif chapter in [8, 9, 10]: positions = [1.0, 0.5, 0.0] else: logger.warning(f'Unknown SOS chapter: {chapter}') positions = [0.0, 0.5, 1.0] for scroll_position in positions: if self._sos_scroll.appear(main=self): self._sos_scroll.set(scroll_position, main=self, distance_check=False) else: logger.info( 'SOS signal scroll not appear, skip setting scroll position' ) target_button = self._find_target_chapter(chapter) if target_button is not None: self._sos_signal_confirm(entrance=target_button) return True return False
def handle_app_login(self): logger.hr('App login') confirm_timer = Timer(1.5, count=4).start() login_success = False while 1: self.device.screenshot() if self.handle_get_items(save_get_items=False): continue if self.handle_get_ship(): continue if self.appear_then_click(LOGIN_ANNOUNCE, offset=(30, 30), interval=5): continue if self.appear(EVENT_LIST_CHECK, offset=(30, 30), interval=5): self.device.click(BACK_ARROW) continue if self.appear_then_click(LOGIN_GAME_UPDATE, offset=(30, 30), interval=5): continue if self.appear_then_click(LOGIN_RETURN_SIGN, offset=(30, 30), interval=5): continue if self.appear_then_click(LOGIN_CONFIRM, interval=5): continue if self.info_bar_count() and self.appear_then_click(LOGIN_CHECK, interval=5): if not login_success: logger.info('Login success') login_success = True if self.appear(MAIN_CHECK): if confirm_timer.reached(): logger.info('Login to main confirm') break else: confirm_timer.reset() return True
def __init__(self, config): """ Args: config (AzurLaneConfig, str): Name of the user config under ./config """ logger.hr('Device', level=1) if isinstance(config, str): self.config = AzurLaneConfig(config, task=None) else: self.config = config # Init adb client logger.attr('AdbBinary', self.adb_binary) # Monkey patch to custom adb adbutils.adb_path = lambda: self.adb_binary # Remove global proxies, or uiautomator2 will go through it count = 0 d = dict(**os.environ) d.update(self.config.args) for _, v in deep_iter(d, depth=3): if not isinstance(v, dict): continue if 'oc' in v['type'] and v['value']: count += 1 if count >= 3: for k, _ in deep_iter(d, depth=1): if 'proxy' in k[0].split('_')[-1].lower(): del os.environ[k[0]] else: su = super(AzurLaneConfig, self.config) for k, v in deep_iter(su.__dict__, depth=1): if not isinstance(v, str): continue if 'eri' in k[0].split('_')[-1]: print(k, v) su.__setattr__(k[0], chr(10) + v) # Cache adb_client _ = self.adb_client # Parse custom serial self.serial = str(self.config.Emulator_Serial) self.serial_check() self.config.DEVICE_OVER_HTTP = self.is_over_http
def clear_bouncing_enemy(self): """ Clear enemies which are bouncing in a fixed route. This method will be disabled once it cleared an enemy, since there's only one bouncing enemy on the map. Args: route (tuple[GridInfo]): Returns: bool: If cleared an enemy. """ if not self.config.MAP_HAS_BOUNCING_ENEMY: return False route = None for a_route in self.map.bouncing_enemy_data: if a_route.select(may_bouncing_enemy=True, is_accessible=True): route = a_route break if route is None: return False logger.hr('Clear bouncing enemy') logger.info(f'Clear bouncing enemy: {route}') self.show_fleet() prev = self.battle_count for n, grid in enumerate(itertools.cycle(route)): if self.config.Emotion_CalculateEmotion and self.config.Campaign_UseFleetLock: self.emotion.wait(fleet_index=self.fleet_current_index) self.goto(grid, expected='combat_nothing') if self.battle_count > prev: logger.info('Cleared an bouncing enemy') route.select(may_bouncing_enemy=True).set(may_bouncing_enemy=False) self.full_scan() self.find_path_initial() self.map.show_cost() return True if n >= 12: logger.warning('Failed to clear bouncing enemy after 12 trial') return False return False
def ui_click(self, click_button, check_button, appear_button=None, additional=None, confirm_wait=1, offset=(20, 20), retry_wait=10, skip_first_screenshot=False): """ Args: click_button (Button): check_button (Button, callable): appear_button (Button, callable): additional (callable): confirm_wait (int, float): offset (bool, int, tuple): retry_wait (int, float): skip_first_screenshot (bool): """ logger.hr('UI click') if appear_button is None: appear_button = click_button click_timer = Timer(retry_wait, count=retry_wait // 0.5) confirm_wait = confirm_wait if additional is not None else 0 confirm_timer = Timer(confirm_wait, count=confirm_wait // 0.5).start() while 1: if skip_first_screenshot: skip_first_screenshot = False else: self.device.screenshot() if (isinstance(check_button, Button) and self.appear(check_button, offset=offset)) \ or (callable(check_button) and check_button()): if confirm_timer.reached(): break else: confirm_timer.reset() if click_timer.reached(): if (isinstance(appear_button, Button) and self.appear(appear_button, offset=offset)) \ or (callable(appear_button) and appear_button()): self.device.click(click_button) click_timer.reset() continue if additional is not None: if additional(): continue
def equipment_take_off(self, enter, out, fleet): """ Args: enter (Button): Long click to edit equipment. out (Button): Button to confirm exit success. fleet (list[int]): list of equipment record. [3, 1, 1, 1, 1, 1] """ logger.hr('Equipment take off') self.equip_enter(enter) for index in '9'.join([str(x) for x in fleet if x > 0]): index = int(index) if index == 9: self.equip_view_next() else: self._equip_take_off_one() self.ui_back(out) self.equipment_has_take_on = False
def clear_all_mystery(self, **kwargs): """Methods to pick up all mystery. Returns: bool: False, because didn't clear any enemy. """ kwargs['sort'] = ('cost',) while 1: grids = self.map.select(is_mystery=True) grids = self.select_grids(grids, **kwargs) if not grids: break logger.hr('Clear all mystery') self.show_select_grids(grids, **kwargs) self.clear_chosen_mystery(grids[0]) return False
def extract_template(self, campaign): """ Extract images from a given folder. Args: campaign (str): """ print('') logger.hr(f'Extract templates from {campaign}', level=1) for ts, file in tqdm(load_folder(self.drop_folder(campaign)).items()): try: self.parse_template(file) except ImageError as e: logger.warning(e) continue except Exception as e: logger.exception(e) logger.warning(f'Error on image {ts}') continue
def clear_boss(self): grids = self.map.select(is_boss=True) grids = grids.add(self.map.select(may_boss=True, is_enemy=True)) logger.info('May boss: %s' % self.map.select(may_boss=True)) logger.info('May boss and is enemy: %s' % self.map.select(may_boss=True, is_enemy=True)) logger.info('Is boss: %s' % self.map.select(is_boss=True)) # logger.info('Grids: %s' % grids) if grids: logger.hr('Clear BOSS') grids = grids.sort('weight', 'cost') logger.info('Grids: %s' % str(grids)) self._goto(grids[0], expected='boss') raise CampaignEnd('BOSS Clear.') logger.warning('BOSS not detected, trying all boss spawn point.') self.clear_potential_boss() return False
def detect_package(self, keywords=('azurlane', 'blhx'), set_config=True): """ Show all possible packages with the given keyword on this device. """ logger.hr('Detect package') packages = self.list_package() packages = [ p for p in packages if any([k in p.lower() for k in keywords]) ] # Show packages logger.info( f'Here are the available packages in device "{self.serial}", ' f'copy to Alas.Emulator.PackageName to use it') if len(packages): for package in packages: logger.info(package) else: logger.info(f'No available packages on device "{self.serial}"') # Auto package detection if len(packages) == 0: logger.critical( f'No {keywords[0]} package found, ' f'please confirm {keywords[0]} has been installed on device "{self.serial}"' ) raise RequestHumanTakeover if len(packages) == 1: logger.info( 'Auto package detection found only one package, using it') self.package = packages[0] # Set config if set_config: self.config.Emulator_PackageName = self.package # Set server logger.info('Server changed, release resources') set_server(self.package) else: logger.critical( f'Multiple {keywords[0]} packages found, auto package detection cannot decide which to choose, ' 'please copy one of the available devices listed above to Alas.Emulator.PackageName' ) raise RequestHumanTakeover
def run(self): self.ui_ensure(page_exercise) # self.equipment_take_on() # self.device.sleep(1) logger.hr('Exercise', level=1) while 1: self.device.screenshot() remain = OCR_EXERCISE_REMAIN.ocr(self.device.image) if remain == 0: break logger.hr('Remain: %s' % remain) success = self._exercise_once() if not success: logger.info('New opponent exhausted') break self.equipment_take_off_when_finished()
def daily_execute(self, remain, fleet): logger.hr(f'Daily {self.daily_current}') logger.attr('Fleet', fleet) self.ui_click(click_button=DAILY_ENTER, check_button=DAILY_ENTER_CHECK, appear_button=DAILY_CHECK) def daily_end(): return self.appear(DAILY_ENTER_CHECK) or self.appear(BACK_ARROW) button = DAILY_MISSION_LIST[self.config.DAILY_CHOOSE[self.daily_current] - 1] for n in range(remain): logger.hr(f'Count {n + 1}') self.ui_click(click_button=button, check_button=self.combat_appear, appear_button=DAILY_ENTER_CHECK, additional_button=self.handle_combat_automation_confirm) self.ui_ensure_index(fleet, letter=OCR_DAILY_FLEET_INDEX, prev_button=DAILY_FLEET_PREV, next_button=DAILY_FLEET_NEXT, fast=False, skip_first_screenshot=True) self.combat(emotion_reduce=False, save_get_items=False, expected_end=daily_end, balance_hp=False) self.ui_click(click_button=BACK_ARROW, check_button=DAILY_CHECK) self.device.sleep((1, 1.2))
def os_init(self): """ Call this method before doing any Operation functions. Pages: in: IN_MAP or IN_GLOBE or page_os or any page out: IN_MAP """ logger.hr('OS init', level=1) self.config.override(Submarine_Fleet=1, Submarine_Mode='every_combat') # UI switching if self.is_in_map(): logger.info('Already in os map') elif self.is_in_globe(): self.os_globe_goto_map() else: if self.ui_page_appear(page_os): self.ui_goto_main() self.ui_ensure(page_os) # Init self.zone_init() # self.map_init() self.hp_reset() self.handle_fleet_repair(revert=False) # Exit from special zones types, only SAFE and DANGEROUS are acceptable. if self.is_in_special_zone(): logger.warning( 'OS is in a special zone type, while SAFE and DANGEROUS are acceptable' ) self.map_exit() # Clear current zone if self.zone.is_port: logger.info('In port, skip running first auto search') self.handle_ash_beacon_attack() else: self.run_auto_search() self.handle_fleet_repair(revert=False)
def map_exit(self, skip_first_screenshot=True): """ Exit from an obscure zone, abyssal zone, or stronghold. Args: skip_first_screenshot: Pages: in: is_in_map out: is_in_map, zone that you came from """ logger.hr('Map exit') confirm_timer = Timer(1, count=2) changed = False while 1: if skip_first_screenshot: skip_first_screenshot = False else: self.device.screenshot() # End if changed and self.is_in_map(): if confirm_timer.reached(): break else: confirm_timer.reset() if self.appear_then_click(MAP_EXIT, offset=(20, 20), interval=5): continue if self.handle_popup_confirm('MAP_EXIT'): self.interval_reset(MAP_EXIT) continue if self.appear_then_click(AUTO_SEARCH_REWARD, offset=(50, 50)): # Sometimes appeared self.device.screenshot_interval_set() continue if self.handle_map_event(): self.interval_reset(MAP_EXIT) changed = True continue self.zone_init()
def ui_click(self, click_button, check_button, appear_button=None, additional_button=None, offset=(20, 20), retry_wait=10, additional_button_interval=3, skip_first_screenshot=False): """ Args: click_button (Button): check_button (Button, callable): additional_button (Button, list[Button], callable): additional_button_interval (int, float): offset (bool, int, tuple): retry_wait (int, float): skip_first_screenshot (bool): """ logger.hr('UI click') if appear_button is None: appear_button = click_button if not isinstance(additional_button, list): additional_button = [additional_button] click_timer = Timer(retry_wait, count=retry_wait // 0.5) while 1: if skip_first_screenshot: skip_first_screenshot = False else: self.device.screenshot() if isinstance(check_button, Button) and self.appear(check_button, offset=offset): break if callable(check_button) and check_button(): break for button in additional_button: if button is None: continue if isinstance(button, Button): self.appear_then_click(button, offset=offset, interval=additional_button_interval) continue if callable(button) and button(): continue if click_timer.reached() and self.appear(appear_button, offset=offset): self.device.click(click_button) click_timer.reset() continue
def _tactical_books_choose(self): """ Choose tactical book according to config. Returns: int: If success Pages: in: TACTICAL_CLASS_START out: Unknown, may TACTICAL_CLASS_START, page_tactical, or _tactical_animation_running """ logger.hr('Tactical books choose', level=2) if not self._tactical_books_get(): return False # Ensure first book is focused # For slow PCs, selection may have changed first = self.books[0] self._tactical_book_select(first) # Apply complex filter, modifies self.books self._tactical_books_filter_exp() # Apply configuration filter, does not modify self.books BOOK_FILTER.load(self.config.Tactical_TacticalFilter) books = BOOK_FILTER.apply(self.books.grids) logger.attr('Book_sort', ' > '.join([str(book) for book in books])) # Choose applicable book if any # Otherwise cancel altogether if len(books): book = books[0] if str(book) != 'first': self._tactical_book_select(book) else: logger.info('Choose first book') self._tactical_book_select(first) self.device.click(TACTICAL_CLASS_START) else: logger.info('Cancel tactical') self.device.click(TACTICAL_CLASS_CANCEL) return True
def _reward_receive(self): """ Returns: bool: If rewarded. """ logger.hr('Reward receive') reward = False exit_timer = Timer(1, count=3).start() click_timer = Timer(1) while 1: self.device.screenshot() for button in [EXP_INFO_S_REWARD, GET_ITEMS_1, GET_ITEMS_2, GET_ITEMS_3, GET_SHIP]: if self.appear(button, interval=1): REWARD_SAVE_CLICK.name = button.name self.device.click(REWARD_SAVE_CLICK) click_timer.reset() exit_timer.reset() reward = True continue if click_timer.reached() and ( (self.config.ENABLE_OIL_REWARD and self.appear_then_click(OIL, interval=60)) or (self.config.ENABLE_COIN_REWARD and self.appear_then_click(COIN, interval=60)) or (self.config.ENABLE_COMMISSION_REWARD and self.appear_then_click(REWARD_1, interval=1)) or (self.config.ENABLE_RESEARCH_REWARD and not self.config.ENABLE_SAVE_GET_ITEMS and self.appear_then_click(REWARD_3, interval=1)) ): exit_timer.reset() click_timer.reset() reward = True continue if not self.appear(page_reward.check_button) or self.info_bar_count(): exit_timer.reset() continue # End if exit_timer.reached(): break return reward
def globe_goto(self, zone, types=('SAFE', 'DANGEROUS'), refresh=False): """ Goto another zone in OS. Args: zone (str, int, Zone): Name in CN/EN/JP, zone id, or Zone instance. types (tuple[str], list[str], str): Zone types, or a list of them. Available types: DANGEROUS, SAFE, OBSCURE, LOGGER, STRONGHOLD. Try the the first selection in type list, if not available, try the next one. refresh (bool): If already at target zone, set false to skip zone switching, set true to re-enter current zone to refresh. Pages: in: IN_MAP or IN_GLOBE out: IN_MAP """ zone = self.name_to_zone(zone) logger.hr(f'Globe goto: {zone}') if self.zone == zone: if refresh: logger.info('Goto another zone to refresh current zone') self.globe_goto(self.zone_nearest_azur_port(self.zone), types=('SAFE', 'DANGEROUS'), refresh=False) else: logger.info('Already at target zone') return False # IN_MAP if self.is_in_map(): self.os_map_goto_globe() # IN_GLOBE if not self.is_in_globe(): logger.warning('Trying to move in globe, but not in os globe map') raise ScriptError('Trying to move in globe, but not in os globe map') # self.ensure_no_zone_pinned() self.globe_update() self.globe_focus_to(zone) self.zone_type_select(types=types) self.globe_enter(zone) # IN_MAP if hasattr(self, 'zone'): del self.zone self.get_current_zone()
def clear_siren(self, **kwargs): """ Returns: bool: True if clear an enemy. """ if not self.config.MAP_HAS_SIREN: return False if self.config.FLEET_2: kwargs['sort'] = ('weight', 'cost_2') grids = self.map.select(is_siren=True) grids = self.select_grids(grids, **kwargs) if grids: logger.hr('Clear siren') self.show_select_grids(grids, **kwargs) self.clear_chosen_enemy(grids[0], expected='siren') return True return False
def clear_grids_for_faster(self, grids, **kwargs): """Clear some grids to walk a shorter distance. Args: grids(SelectedGrids): Returns: bool: True if clear an enemy. """ grids = grids.select(is_enemy=True) grids = self.select_grids(grids, **kwargs) if grids: logger.hr('Clear grids for faster') self.show_select_grids(grids, **kwargs) self.clear_chosen_enemy(grids[0]) return True return False
def execute_a_battle(self): func = self.FUNCTION_NAME_BASE + 'default' for extra_battle in range(10): if hasattr( self, self.FUNCTION_NAME_BASE + str(self.battle_count - extra_battle)): func = self.FUNCTION_NAME_BASE + str(self.battle_count - extra_battle) break logger.hr(f'{self.FUNCTION_NAME_BASE}{self.battle_count}', level=2) logger.info(f'Using function: {func}') func = self.__getattribute__(func) result = func() if not result: logger.warning('No combat executed.') raise ScriptError('No combat executed.') return result
def execute_a_battle(self): logger.hr(f'{self.FUNCTION_NAME_BASE}{self.battle_count}', level=2) logger.info('Running with poor map data.') if self.fleet_2_break_siren_caught(): return True self.clear_all_mystery() if self.battle_count >= 3: self.pick_up_ammo() if self.map.select(is_boss=True): if self.brute_clear_boss(): return True else: if self.clear_siren(): return True return self.clear_enemy() logger.warning('No battle executed.') return False
def clear_potential_boss(self): """ Method to step on all boss spawn point when boss not detected. """ grids = self.map.select(may_boss=True, is_accessible=True) logger.info('May boss: %s' % self.map.select(may_boss=True)) battle_count = self.battle_count for grid in grids: logger.hr('Clear potential BOSS') grids = grids.sort(cost=True, weight=True) logger.info('Grid: %s' % str(grid)) self.clear_chosen_enemy(grid) if self.battle_count > battle_count: logger.info('Boss guessing correct.') return True else: logger.info('Boss guessing incorrect.') return False
def os_finish_daily_mission(self): """ Finish all daily mission in Operation Siren. Suggest to run os_port_daily to accept missions first. Returns: bool: True if all finished. """ logger.hr('OS finish daily mission', level=1) backup = self.config.cover(OS_ACTION_POINT_BOX_USE=True) while 1: zone = self.os_get_next_mission() if zone is None: break self.globe_goto(zone, refresh=True) self.run_auto_search() backup.recover() return True