def research_detect(self, image): """ Args: image (PIL.Image.Image): Screenshots """ projects = [] for name, series in zip(get_research_name(image), get_research_series(image)): project = ResearchProject(name=name, series=series) logger.attr('Project', project) projects.append(project) self.projects = projects
def triggered_bug(self): """ The game does not calculate emotion correctly, which is a bug in AzurLane. After a long run, we have to restart the game to update it. """ logger.attr('Emotion_bug', f'{self.total_reduced}/{self.BUG_THRESHOLD}') if self.total_reduced >= self.BUG_THRESHOLD: self.total_reduced = 0 return True return False
def daily_execute(self, remain, fleet): """ Args: remain (int): Remain daily challenge count. fleet (int): Index of fleet to use. Returns: bool: True if success, False if daily locked. """ logger.hr(f'Daily {self.daily_current}') logger.attr('Fleet', fleet) def daily_enter_check(): return self.appear(DAILY_ENTER_CHECK) def daily_end(): if self.appear(BATTLE_PREPARATION, interval=2): self.device.click(BACK_ARROW) return self.appear(DAILY_ENTER_CHECK) or self.appear(BACK_ARROW) self.ui_click(click_button=DAILY_ENTER, check_button=daily_enter_check, appear_button=DAILY_CHECK) if self.appear(DAILY_LOCKED): logger.info('Daily locked') self.ui_click(click_button=BACK_ARROW, check_button=DAILY_CHECK) self.device.sleep((1, 1.2)) return False 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=self.handle_combat_automation_confirm if not self.daily_auto_checked else None) self.daily_auto_checked = True 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)) return True
def _get_medals(self): """ Returns: np.array: [[x1, y1], [x2, y2]], location of the medal icon upper-left corner. """ left_column = self.image_crop((489, 256, 1120, 572)) medals = TEMPLATE_MEDAL_ICON.match_multi(left_column, similarity=0.5, threshold=5) medals = Points([(0., m.area[1]) for m in medals]).group(threshold=5) logger.attr('Medals_icon', len(medals)) return medals
def _opponent_sort(self, method="max_exp"): """ Args: method: EXERCISE_CHOOSE_MODE Returns: list[int]: List of opponent index, such as [2, 1, 0, 3]. Attack one by one. """ order = np.argsort([-x.get_priority(method) for x in self.opponents]) logger.attr('Order', str(order)) return order
def reward_loop(self): logger.hr('Reward loop') while 1: if self.config.triggered_app_restart(): self.app_restart() self.reward() logger.info('Reward loop wait') logger.attr('Reward_loop_wait', f'{self.config.REWARD_INTERVAL} min') self.device.sleep(self.config.REWARD_INTERVAL * 60)
def meow_train(self): """ Performs both retrieving a trained meowfficer and queuing meowfficer boxes for training Pages: in: page_meowfficer out: page_meowfficer """ logger.hr('Meowfficer train', level=1) # Retrieve capacity to determine whether able to collect current, remain, total = MEOWFFICER_CAPACITY.ocr(self.device.image) logger.attr('Meowfficer_capacity_remain', remain) # Read box count, utilized in other helper funcs self._box_count = MEOWFFICER_BOX_COUNT.ocr(self.device.image) logger.attr('MeowfficerTrain_Mode', self.config.MeowfficerTrain_Mode) collected = False if self.config.MeowfficerTrain_Mode == 'seamlessly': # Enter self.ui_click(MEOWFFICER_TRAIN_ENTER, check_button=MEOWFFICER_TRAIN_START, additional=self.meow_additional, retry_wait=3, confirm_wait=0, skip_first_screenshot=True) # Collect if remain > 0: collected = self.meow_collect(collect_all=True) # Queue self.meow_queue(ascending=False) # Exit self.meow_menu_close() else: # Enter self.ui_click(MEOWFFICER_TRAIN_ENTER, check_button=MEOWFFICER_TRAIN_START, additional=self.meow_additional, retry_wait=3, confirm_wait=0, skip_first_screenshot=True) # Collect if remain > 0: collected = self.meow_collect( collect_all=self.meow_is_sunday()) # Queue self.meow_queue(ascending=False) # Exit self.meow_menu_close() return collected
def get_remain(self, mode): """ Args: mode (str): easy, normal, hard Returns: int: """ ocr = raid_ocr(raid=self.config.RAID_NAME, mode=mode) remain, _, _ = ocr.ocr(self.device.image) logger.attr(f'{mode.capitalize()} Remain', remain) return remain
def zone_type_select(self, types=('SAFE', 'DANGEROUS')): """ Args: types (tuple[str], list[str], str): Zone types, or a list of them. Available types: DANGEROUS, SAFE, OBSCURE, ABYSSAL, STRONGHOLD, ARCHIVE. Try the the first selection in type list, if not available, try the next one. Do nothing if no selection satisfied input. Returns: bool: If success. Pages: in: is_zone_pinned out: is_zone_pinned """ if not self.zone_has_switch(): logger.info('Zone has no type to select, skip') return True if isinstance(types, str): types = [types] def get_button(selection_): for typ in types: typ = 'SELECT_' + typ for sele in selection_: if typ == sele.name: return sele return None pinned = self.get_zone_pinned_name() if pinned in types: logger.info(f'Already selected at {pinned}') return True for _ in range(3): self.zone_select_enter() selection = self.ensure_zone_select_expanded() logger.attr('Zone_selection', selection) button = get_button(selection) if button is None: logger.warning( 'No such zone type to select, fallback to default') types = ('SAFE', 'DANGEROUS') button = get_button(selection) self.zone_select_execute(button) if self.pinned_to_name(button) == self.get_zone_pinned_name(): return True logger.warning('Failed to select zone type after 3 trial') return False
def is_combat_loading(self): """ Returns: bool: """ left = color_bar_percentage(self.device.image, area=LOADING_BAR.area, prev_color=(99, 150, 255)) right = color_bar_percentage(self.device.image, area=LOADING_BAR.area, prev_color=(225, 225, 225), reverse=True) if 0.15 < left < 0.95 and right > 0.15 and left + right <= 1.2: logger.attr('Loading', f'{int(left * 100)}%({int(right * 100)}%)') return True return False
def combat_status(self, save_get_items=False, expected_end=None): """ Args: save_get_items (bool): expected_end (str): with_searching, no_searching, in_stage. """ logger.info('Combat status') logger.attr( 'expected_end', expected_end.__name__ if callable(expected_end) else expected_end) exp_info = False # This is for the white screen bug in game while 1: self.device.screenshot() # Combat status if not exp_info and self.handle_get_ship( save_get_items=save_get_items): continue if self.handle_get_items(save_get_items=save_get_items): continue if self.handle_battle_status(save_get_items=save_get_items): continue if self.handle_popup_confirm(): continue if self.handle_exp_info(): exp_info = True continue if self.handle_urgent_commission(save_get_items=save_get_items): continue if self.handle_story_skip(): continue # End if self.handle_in_stage(): break if expected_end is None: if self.handle_in_map_with_enemy_searching(): break if isinstance(expected_end, str): if expected_end == 'in_stage' and self.handle_in_stage(): break if expected_end == 'with_searching' and self.handle_in_map_with_enemy_searching( ): break if expected_end == 'no_searching' and self.handle_in_map_no_enemy_searching( ): break if expected_end == 'in_ui' and self.appear(BACK_ARROW, offset=(20, 20)): break if callable(expected_end): if expected_end(): break
def handle_guild(self): """ ALAS handler function for guild reward loop Returns: bool: If executed Pages: in: page_main out: page_main """ if not self.config.ENABLE_GUILD_LOGISTICS and not self.config.ENABLE_GUILD_OPERATIONS: return False now = datetime.now() guild_record = datetime.strptime(self.config.config.get(*GUILD_RECORD), self.config.TIME_FORMAT) update = guild_record + timedelta(seconds=self.guild_interval) attr = f'{GUILD_RECORD[0]}_{GUILD_RECORD[1]}' logger.attr(f'{attr}', f'Record time: {guild_record}') logger.attr(f'{attr}', f'Next update: {update}') if not now > update: return False if not self.appear(GUILD_RED_DOT, offset=(30, 30)): logger.info('Guild red dot not appears, skip current check') self.config.record_save(option=GUILD_RECORD) return False self.ui_ensure(page_guild) # Lobby self.guild_lobby() # Logistics if self.config.ENABLE_GUILD_LOGISTICS \ and not self.config.record_executed_since(option=RECORD_OPTION_LOGISTICS, since=RECORD_SINCE_LOGISTICS): # Save record when guild mission has been finished this week # If finished, only need to check guild logostics once a day # If not finished, check it in every guild reward if self.guild_logistics(): self.config.record_save(option=RECORD_OPTION_LOGISTICS) # Operation if self.config.ENABLE_GUILD_OPERATIONS \ and not self.config.record_executed_since(option=RECORD_OPTION_DISPATCH, since=RECORD_SINCE_DISPATCH): # Check guild operation 4 times a day self.guild_operations() self.guild_interval_reset() self.config.record_save(option=GUILD_RECORD) self.ui_goto_main() return True
def meow_choose(self, count) -> bool: """ Pages: in: page_meowfficer out: MEOWFFICER_BUY Args: count (int): 0 to 15. Returns: bool: If success. """ remain, bought, total = MEOWFFICER.ocr(self.device.image) logger.attr('Meowfficer_remain', remain) # Check buy status if total != BUY_MAX: logger.warning( f'Invalid meowfficer buy limit: {total}, revise to {BUY_MAX}') total = BUY_MAX bought = total - remain if bought > 0: if bought >= count: logger.info(f'Already bought {bought} today, stopped') return False else: count -= bought logger.info( f'Already bought {bought} today, only need to buy {count} more' ) # Check coins coins = MEOWFFICER_COINS.ocr(self.device.image) if (coins < BUY_PRIZE) and (remain < total): logger.info('Not enough coins to buy one, stopped') return False elif (count - int(remain == total)) * BUY_PRIZE > coins: count = coins // BUY_PRIZE + int(remain == total) logger.info(f'Current coins only enough to buy {count}') self.ui_click(MEOWFFICER_BUY_ENTER, check_button=MEOWFFICER_BUY, additional=self.meow_additional, retry_wait=3, confirm_wait=0, skip_first_screenshot=True) self.ui_ensure_index(count, letter=MEOWFFICER_CHOOSE, prev_button=MEOWFFICER_BUY_PREV, next_button=MEOWFFICER_BUY_NEXT, skip_first_screenshot=True) return True
def missing_get(self, battle_count, mystery_count=0, siren_count=0, carrier_count=0): try: missing = self.spawn_data[battle_count].copy() except IndexError: missing = self.spawn_data[-1].copy() may = {'enemy': 0, 'mystery': 0, 'siren': 0, 'boss': 0, 'carrier': 0} missing['enemy'] -= battle_count - siren_count missing['mystery'] -= mystery_count missing['siren'] -= siren_count missing['carrier'] = carrier_count - self.select(is_enemy=True, may_enemy=False).count for grid in self: for attr in ['enemy', 'mystery', 'siren', 'boss']: if grid.__getattribute__( 'is_' + attr) and grid.__getattribute__('may_' + attr): missing[attr] -= 1 for grid in self: if not grid.is_fleet and not grid.is_mystery and not grid.is_siren: continue cover = [(0, -1)] if grid.is_current_fleet: cover.append((0, -2)) for upper in cover: upper = tuple(np.array(grid.location) + upper) if upper in self: upper = self[upper] for attr in ['enemy', 'mystery', 'siren', 'boss']: if upper.__getattribute__( 'may_' + attr) and not upper.__getattribute__('is_' + attr): may[attr] += 1 if upper.may_carrier: may['carrier'] += 1 logger.attr( 'enemy_missing', ', '.join([ f'{k[:2].upper()}:{str(v).rjust(2)}' for k, v in missing.items() if k != 'battle' ])) logger.attr( 'enemy_may____', ', '.join([ f'{k[:2].upper()}:{str(v).rjust(2)}' for k, v in may.items() ])) return may, missing
def __init__(self, main_image, fleet_image, index): self.index = index self.power = self.get_power(image=main_image) self.level = self.get_level(image=fleet_image) self.priority = self.get_priority() # [OPPONENT_1] ( 8256) 120 120 120 | (12356) 100 80 80 level = [str(x).rjust(3, ' ') for x in self.level] power = ['(' + str(x).rjust(5, ' ') + ')' for x in self.power] logger.attr( 'OPPONENT_%s, %s' % (index, str(np.round(self.priority, 3)).ljust(5, '0')), ' '.join([power[0]] + level[:3] + ['|'] + [power[1]] + level[3:]))
def ocr(self, image): start_time = time.time() image_list = [self.pre_process(i) for i in image] result_list = self.cnocr.ocr_for_single_lines(image_list) result_list = [self.after_process(result) for result in result_list] if len(self.buttons) == 1: result_list = result_list[0] logger.attr(name='%s %ss' % (self.name, str(round(time.time() - start_time, 3)).ljust(5, '0')), text=str(result_list)) return result_list
def check_s3_enemy(self): if self.battle_count == 0: self.s3_enemy_count = 0 elif self.battle_count >= 5: self.withdraw() current = self.map.select(is_enemy=True, enemy_scale=2) \ .add(self.map.select(is_enemy=True, enemy_scale=1)) \ .count logger.attr('S2_enemy', current) if self.s3_enemy_count >= self.config.C122MediumLeveling_LargeEnemyTolerance and current == 0: self.withdraw()
def check_s3_enemy(self): if self.battle_count == 0: self.s3_enemy_count = 0 elif self.battle_count >= 5: self.withdraw() current = self.map.select(is_enemy=True, enemy_scale=2)\ .add(self.map.select(is_enemy=True, enemy_scale=1))\ .count logger.attr('S2_enemy', current) if self.s3_enemy_count >= self.config.C122_S3_TOLERANCE and current == 0: self.withdraw()
def research_sort_cheapest(self): """ Returns: list: A list of str and int, such as [2, 3, 0, 'reset'] """ FILTER.load(FILTER_STRING_CHEAPEST) priority = FILTER.apply(self.projects) priority = self._research_check_filter(priority) logger.attr( 'Cheapest_sort', ' > '.join([str(self.projects[index]) if isinstance(index, int) else index for index in priority])) return priority
def enhance_ships_order(self, favourite=None): """ Info: Target ships in order of specified type listing by ENHANCE_ORDER_STRING Pages: in: page_dock out: page_dock Args: favourite (bool): Returns: int: total enhanced """ if favourite is None: favourite = self.config.ENHANCE_FAVOURITE logger.hr('Enhancement by type') total = 0 ship_types = [s.strip().lower() for s in self.config.ENHANCE_ORDER_STRING.split('>')] enable_simple = True if ship_types == ['']: enable_simple = False ship_types = [None] logger.attr('Enhance Order', ship_types) for ship_type in ship_types: index = 0 # Helper variable only for _enhance_choose_simple logger.info(f'Favourite={favourite}, Ship Type={ship_type}') if not self._enhance_enter(favourite=favourite, ship_type=ship_type): logger.hr(f'Dock Empty by ship type {ship_type}') continue while 1: if enable_simple: choose_result, index = self._enhance_choose_simple(current_index=index) else: choose_result = self._enhance_choose() if not choose_result: break self._enhance_confirm() total += 10 if total >= self._retire_amount: break self.ui_back(DOCK_FILTER) self._enhance_quit() return total
def is_combat_loading(self): """ Returns: bool: """ similarity, location = TEMPLATE_COMBAT_LOADING.match_result( self.device.image.crop((0, 620, 1280, 720))) if similarity > 0.85: loading = (location[0] + 38 - LOADING_BAR.area[0]) / ( LOADING_BAR.area[2] - LOADING_BAR.area[0]) logger.attr('Loading', f'{int(loading * 100)}%') return True else: return False
def research_has_finished(self): """ Finished research should be auto-focused to the center, but sometimes didn't, due to an unknown game bug. This method will handle that. Returns: bool: True if a research finished """ index = get_research_finished(self.device.image) if index is not None: logger.attr('Research_finished', index) self._research_finished_index = index return True else: return False
def wait(self, fleet=(1, 2)): """ Args: fleet (int, tuple): """ self.update() recovered_time = self.recovered_time(fleet=fleet) while 1: if datetime.now() > recovered_time: break logger.attr('Emotion recovered', recovered_time) self.config.EMOTION_LIMIT_TRIGGERED = True sleep(60)
def research_detect(image): """ Args: image (np.ndarray): Screenshot Return: list[ResearchProject]: """ projects = [] for name, series in zip(get_research_name(image), get_research_series(image)): project = ResearchProject(name=name, series=series) logger.attr('Project', project) projects.append(project) return projects
def get_os_reset_remain(): """ Returns: int: number of days before next opsi reset """ from module.logger import logger next_reset = get_os_next_reset() now = datetime.now() logger.attr('OpsiNextReset', next_reset) remain = int((next_reset - now).total_seconds() // 86400) logger.attr('ResetRemain', remain) return remain
def ui_goto(self, destination, skip_first_screenshot=False): """ Args: destination (Page): skip_first_screenshot (bool): """ # Iter visited = [self.ui_current] visited = set(visited) while 1: new = visited.copy() for page in visited: for link in page.links.keys(): if link in visited: continue link.parent = page new.add(link) if len(new) == len(visited): break visited = new # Find path if destination.parent is None: return [] route = [destination] while 1: destination = destination.parent if destination is not None: route.append(destination) else: break route.reverse() if len(route) < 2: logger.warning('No page route found.') logger.attr('UI route', ' - '.join([p.name for p in route])) # Click for p1, p2 in zip(route[:-1], route[1:]): # self.ui_click(source=p1, destination=p2) self.ui_click( click_button=p1.links[p2], check_button=p2.check_button, offset=(20, 20), skip_first_screenshot=skip_first_screenshot) self.ui_current = p2 skip_first_screenshot = True # Reset for page in visited: page.parent = None
def get_current_zone(self): """ Returns: Zone: """ if not self.is_in_map(): logger.warning('Trying to get zone name, but not in OS map') raise ScriptError('Trying to get zone name, but not in OS map') name = self.get_zone_name() logger.info(f'Map name processed: {name}') self.zone = self.name_to_zone(name) logger.attr('Zone', self.zone) return self.zone
def check_screen_size(self): width, height = self.device.window_size() if height > width: width, height = height, width logger.attr('Screen_size', f'{width}x{height}') if width == 1280 and height == 720: return True else: logger.warning(f'Not supported screen size: {width}x{height}') logger.warning('Alas requires 1280x720') logger.hr('Script end') exit(1)
def set(self, status, main, skip_first_screenshot=True): """ Args: status (str): main (ModuleBase): skip_first_screenshot (bool): Returns: bool: """ self.get_data(status) counter = 0 changed = False warning_show_timer = Timer(5, count=10).start() click_timer = Timer(1, count=3) while 1: if skip_first_screenshot: skip_first_screenshot = False else: main.device.screenshot() # Detect current = self.get(main=main) logger.attr(self.name, current) # End if current == status: return changed # Warning if current == 'unknown': if warning_show_timer.reached(): logger.warning(f'Unknown {self.name} switch') warning_show_timer.reset() if counter >= 1: logger.warning(f'{self.name} switch {status} asset has evaluated to unknown too many times, ' f'asset should be re-verified') return False counter += 1 continue # Click if click_timer.reached(): click_status = status if self.is_choice else current main.device.click(self.get_data(click_status)['click_button']) click_timer.reset() changed = True return changed
def ui_get_current_page(self): self.device.screenshot() for page in self.ui_pages: if self.ui_page_appear(page=page): logger.attr('UI', page.name) self.ui_current = page return page logger.info('Unknown ui page') if self.appear_then_click(GOTO_MAIN, offset=(20, 20)): logger.info('Goto page_main') self.ui_current = page_unknown self.ui_goto(page_main, skip_first_screenshot=True) 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('DEVICE_SCREENSHOT_METHOD', self.config.DEVICE_SCREENSHOT_METHOD) logger.attr('DEVICE_CONTROL_METHOD', self.config.DEVICE_CONTROL_METHOD) 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: exit(1)