def meow_menu_close(self, skip_first_screenshot=True): """ Exit from any meowfficer menu popups Pages: in: MEOWFFICER_FORT_CHECK, MEOWFFICER_BUY, MEOWFFICER_TRAIN_START, etc out: page_meowfficer """ logger.hr('Meowfficer menu close') click_timer = Timer(3) while 1: if skip_first_screenshot: skip_first_screenshot = False else: self.device.screenshot() # End if self.appear(MEOWFFICER_CHECK, offset=(20, 20)) \ and MEOWFFICER_CHECK.match_appear_on(self.device.image): break else: if click_timer.reached(): # MEOWFFICER_CHECK is safe to click self.device.click(MEOWFFICER_CHECK) click_timer.reset() continue # Fort if self.appear(MEOWFFICER_FORT_CHECK, offset=(20, 20), interval=3): self.device.click(MEOWFFICER_CHECK) click_timer.reset() continue # Buy if self.appear(MEOWFFICER_BUY, offset=(20, 20), interval=3): self.device.click(MEOWFFICER_CHECK) click_timer.reset() continue # Train if self.appear(MEOWFFICER_TRAIN_FILL_QUEUE, offset=(20, 20), interval=3): self.device.click(MEOWFFICER_CHECK) click_timer.reset() continue if self.appear(MEOWFFICER_TRAIN_FINISH_ALL, offset=(20, 20), interval=3): self.device.click(MEOWFFICER_CHECK) click_timer.reset() continue # Popups if self.appear(MEOWFFICER_CONFIRM, offset=(40, 20), interval=3): self.device.click(MEOWFFICER_CHECK) click_timer.reset() continue if self.appear(MEOWFFICER_CANCEL, offset=(40, 20), interval=3): self.device.click(MEOWFFICER_CHECK) click_timer.reset() continue if self.appear_then_click(GET_ITEMS_1, offset=5, interval=3): click_timer.reset() continue
def ui_goto(self, destination, offset=(20, 20), confirm_wait=0, skip_first_screenshot=True): """ Args: destination (Page): offset: confirm_wait: skip_first_screenshot: """ # Reset connection for page in self.ui_pages: page.parent = None # Create connection visited = [destination] visited = set(visited) while 1: new = visited.copy() for page in visited: for link in self.ui_pages: if link in visited: continue if page in link.links: link.parent = page new.add(link) if len(new) == len(visited): break visited = new logger.hr(f'UI goto {destination}') confirm_timer = Timer(confirm_wait, count=int(confirm_wait // 0.5)).start() while 1: GOTO_MAIN.clear_offset() if skip_first_screenshot: skip_first_screenshot = False else: self.device.screenshot() # Destination page if self.appear(destination.check_button, offset=offset): if confirm_timer.reached(): break else: confirm_timer.reset() # Other pages clicked = False for page in visited: if page.parent is None or page.check_button is None: continue if self.appear(page.check_button, offset=offset, interval=5): self.device.click(page.links[page.parent]) confirm_timer.reset() clicked = True break if clicked: continue # Additional if self.ui_additional(): continue # Reset connection for page in self.ui_pages: page.parent = None
def handle_in_map_with_enemy_searching(self, drop=None): """ Args: drop (DropImage): Returns: bool: If handled. """ if not self.is_in_map(): return False timeout = Timer(self.MAP_ENEMY_SEARCHING_TIMEOUT_SECOND) appeared = False while 1: self.device.screenshot() if self.is_event_animation(): continue if self.is_in_map(): timeout.start() else: timeout.reset() # Stage might ends, # although here expects an enemy searching animation. if self.handle_in_stage(): return True if self.handle_auto_search_exit(drop=drop): continue # Popups if self.handle_vote_popup(): timeout.limit = 10 timeout.reset() continue if self.handle_story_skip(): self.ensure_no_story() timeout.limit = 10 timeout.reset() if self.handle_guild_popup_cancel(): timeout.limit = 10 timeout.reset() continue if self.handle_urgent_commission(drop=drop): timeout.limit = 10 timeout.reset() continue # End if self.enemy_searching_appear(): appeared = True else: if appeared: self.handle_enemy_flashing() self.device.sleep(0.3) logger.info('Enemy searching appeared.') break self.enemy_searching_color_initial() if timeout.reached(): logger.info('Enemy searching timeout.') break self.device.screenshot() return True
def _reward_mission(self): """ Returns: bool: If rewarded. """ if not self.config.ENABLE_MISSION_REWARD: return False logger.hr('Mission reward') if not self.appear(MISSION_NOTICE): logger.info('No mission reward') return False self.ui_goto(page_mission, skip_first_screenshot=True) reward = False exit_timer = Timer(2) click_timer = Timer(1) timeout = Timer(10) exit_timer.start() timeout.start() while 1: self.device.screenshot() for button in [GET_ITEMS_1, GET_ITEMS_2]: if self.appear_then_click(button, offset=(30, 30), interval=1): exit_timer.reset() timeout.reset() reward = True continue for button in [MISSION_MULTI, MISSION_SINGLE]: if not click_timer.reached(): continue if self.appear_then_click(button, interval=1): exit_timer.reset() click_timer.reset() timeout.reset() continue if not self.appear(MISSION_CHECK): if self.appear_then_click(GET_SHIP, interval=1): click_timer.reset() exit_timer.reset() timeout.reset() continue if self.handle_mission_popup_ack(): click_timer.reset() exit_timer.reset() timeout.reset() continue if self.story_skip(): click_timer.reset() exit_timer.reset() timeout.reset() continue # End if reward and exit_timer.reached(): break if timeout.reached(): logger.warning('Wait get items timeout.') break self.ui_goto(page_main, skip_first_screenshot=True) return reward
def research_receive(self, skip_first_screenshot=True): """ Args: skip_first_screenshot: Pages: in: page_research, stable, with project finished. out: page_research Returns: bool: True if success to receive rewards. False if project requirements are not satisfied. """ logger.hr('Research receive', level=2) def get_items(): for b in [GET_ITEMS_3, GET_ITEMS_2, GET_ITEMS_1]: if self.appear(b, offset=(5, 0)): return b return None with self.stat.new( genre='research', save=self.config.DropRecord_SaveResearch, upload=self.config.DropRecord_UploadResearch) as record: # Take screenshots of project list record.add(self.device.image) # Click finished project, to GET_ITEMS_* confirm_timer = Timer(1.5, count=5) record_button = None while 1: if skip_first_screenshot: skip_first_screenshot = False else: self.device.screenshot() if self.appear(RESEARCH_CHECK, interval=10): if self._research_has_finished_at( self._research_finished_index): self.device.click( RESEARCH_ENTRANCE[self._research_finished_index]) if self.appear(RESEARCH_STOP, offset=(20, 20)): logger.info( 'The research time is up, but requirements are not satisfied' ) self.research_project_started = None self.research_detail_quit() return False appear_button = get_items() if appear_button is not None: if appear_button == record_button: if confirm_timer.reached(): break else: logger.info(f'{appear_button} appeared') record_button = appear_button confirm_timer.reset() # Take screenshots of items if record: button = get_items() if button == GET_ITEMS_1 or button == GET_ITEMS_2: record.add(self.device.image) elif button == GET_ITEMS_3: self.device.sleep(1.5) self.device.screenshot() record.add(self.device.image) self.device.swipe((0, 250), box=ITEMS_3_SWIPE.area, random_range=(-10, -10, 10, 10), padding=0) self.device.sleep(2) self.device.screenshot() record.add(self.device.image) # Close GET_ITEMS_*, to project list self.ui_click(appear_button=get_items, click_button=GET_ITEMS_RESEARCH_SAVE, check_button=self._in_research, skip_first_screenshot=True) return True
def enter_map(self, button, mode='normal', skip_first_screenshot=True): """Enter a campaign. Args: button: Campaign to enter. mode (str): 'normal' or 'hard' or 'cd' skip_first_screenshot (bool): """ logger.hr('Enter map') campaign_timer = Timer(5) map_timer = Timer(5) fleet_timer = Timer(5) campaign_click = 0 map_click = 0 fleet_click = 0 checked_in_map = False self.stage_entrance = button with self.stat.new( genre=self.config.campaign_name, save=self.config.DropRecord_SaveCombat, upload=False ) as drop: while 1: if skip_first_screenshot: skip_first_screenshot = False else: self.device.screenshot() # Check errors if campaign_click > 5: logger.critical(f"Failed to enter {button}, too many click on {button}") logger.critical("Possible reason #1: You haven't reached the commander level to unlock this stage.") raise RequestHumanTakeover if fleet_click > 5: logger.critical(f"Failed to enter {button}, too many click on FLEET_PREPARATION") logger.critical("Possible reason #1: " "Your fleets haven't satisfied the stat restrictions of this stage.") logger.critical("Possible reason #2: " "This stage can only be farmed once a day, " "but it's the second time that you are entering") raise RequestHumanTakeover # Already in map if not checked_in_map and self.is_in_map(): logger.info('Already in map, skip enter_map.') return False else: checked_in_map = True # Map preparation if map_timer.reached() and self.handle_map_preparation(): self.map_get_info() self.handle_fast_forward() self.handle_auto_search() if self.triggered_map_stop(): self.enter_map_cancel() self.handle_map_stop() raise ScriptEnd(f'Reach condition: {self.config.StopCondition_MapAchievement}') self.device.click(MAP_PREPARATION) map_click += 1 map_timer.reset() campaign_timer.reset() continue # Fleet preparation if fleet_timer.reached() and self.appear(FLEET_PREPARATION, offset=(20, 20)): if mode == 'normal' or mode == 'hard': self.handle_2x_book_setting(mode='prep') self.fleet_preparation() self.handle_auto_submarine_call_disable() self.handle_auto_search_setting() self.map_fleet_checked = True self.device.click(FLEET_PREPARATION) fleet_click += 1 fleet_timer.reset() campaign_timer.reset() continue # Auto search continue if self.handle_auto_search_continue(): campaign_timer.reset() continue # Retire if self.handle_retirement(): campaign_timer.reset() map_timer.reset() fleet_timer.reset() continue # Use Data Key if self.handle_use_data_key(): continue # Emotion if self.handle_combat_low_emotion(): continue # Urgent commission if self.handle_urgent_commission(drop=drop): continue # 2X book popup if self.handle_2x_book_popup(): continue # Story skip if self.handle_story_skip(): campaign_timer.reset() continue # Enter campaign if campaign_timer.reached() and self.appear_then_click(button): campaign_click += 1 campaign_timer.reset() continue # End if self.map_is_auto_search: if self.is_auto_search_running(): break else: if self.handle_in_map_with_enemy_searching(): self.handle_map_after_combat_story() break return True
def _guild_operations_boss_preparation(self, skip_first_screenshot=True): """ Execute preperation sequence for guild raid boss Pages: in: GUILD_OPERATIONS_BOSS out: IN_BATTLE """ # Ensure in dispatch for Guild Raid Boss self.ui_click(GUILD_BOSS_ENTER, check_button=GUILD_DISPATCH_RECOMMEND_2, skip_first_screenshot=True) # If configured, auto recommend fleet composition if self.config.ENABLE_GUILD_OPERATIONS_BOSS_RECOMMEND: self.device.click(GUILD_DISPATCH_RECOMMEND_2) is_loading = False empty_timeout = Timer(3, count=6) dispatch_count = 0 while 1: if skip_first_screenshot: skip_first_screenshot = False else: self.device.screenshot() if self.appear(GUILD_DISPATCH_EMPTY_2): # Account for loading lag especially if using # guild support if not empty_timeout.started(): empty_timeout.reset() continue elif empty_timeout.reached(): logger.warning( 'Fleet composition is empty, cannot auto-battle Guild Raid Boss' ) return False if self.appear(GUILD_DISPATCH_FLEET, interval=3): # Button does not appear greyed out even # when empty fleet composition if not self.appear(GUILD_DISPATCH_EMPTY_2): if dispatch_count < 3: self.device.click(GUILD_DISPATCH_FLEET) dispatch_count += 1 else: logger.warning( 'Fleet cannot be dispatched for auto-battle Guild Raid Boss, verify composition manually' ) return False continue # Only print once when detected if not is_loading: if self.is_combat_loading(): is_loading = True continue if self.handle_combat_automation_confirm(): continue # End if self.is_combat_executing(): return True
def _guild_operations_ensure(self, skip_first_screenshot=True): """ Ensure guild operation is loaded After entering guild operation, background loaded first, then dispatch/boss """ logger.attr('Guild master/official', self.config.GuildOperation_SelectNewOperation) confirm_timer = Timer(1.5, count=3).start() click_count = 0 while 1: if skip_first_screenshot: skip_first_screenshot = False else: self.device.screenshot() # End if click_count > 5: # Info bar showing `none4302`. # Probably because guild operation has been started by another guild officer already. # Enter guild page again should fix the issue. logger.warning( 'Unable to start/join guild operation, ' 'probably because guild operation has been started by another guild officer already' ) raise GameBugError('Unable to start/join guild operation') if self._handle_guild_operations_start(): confirm_timer.reset() continue if self.appear(GUILD_OPERATIONS_JOIN, interval=3): if self.image_color_count(GUILD_OPERATIONS_MONTHLY_COUNT, color=(255, 93, 90), threshold=221, count=20): logger.info( 'Unable to join operation, no more monthly attempts left' ) self.device.click(GUILD_OPERATIONS_CLICK_SAFE_AREA) else: current, remain, total = GUILD_OPERATIONS_PROGRESS.ocr( self.device.image) threshold = total * self.config.GuildOperation_JoinThreshold if current <= threshold: logger.info( 'Joining Operation, current progress less than ' f'threshold ({threshold:.2f})') self.device.click(GUILD_OPERATIONS_JOIN) else: logger.info( 'Refrain from joining operation, current progress exceeds ' f'threshold ({threshold:.2f})') self.device.click(GUILD_OPERATIONS_CLICK_SAFE_AREA) confirm_timer.reset() continue if self.handle_popup_confirm('JOIN_OPERATION'): click_count += 1 confirm_timer.reset() continue if self.handle_popup_single('FLEET_UPDATED'): logger.info( 'Fleet composition altered, may still be dispatch-able. However ' 'fellow guild members have updated their support line up. ' 'Suggestion: Enable Boss Recommend') confirm_timer.reset() continue # End if self.appear(GUILD_BOSS_ENTER) or self.appear( GUILD_OPERATIONS_ACTIVE_CHECK, offset=(20, 20)): if not self.info_bar_count() and confirm_timer.reached(): break
def _shipyard_buy_confirm(self, text, skip_first_screenshot=True): """ Handles screen transitions to use/buy BPs Args: text (str): for handle_popup_confirm skip_first_screenshot (bool): """ success = False append = self._shipyard_get_append() button = globals()[f'SHIPYARD_CONFIRM_{append}'] ocr_timer = Timer(10, count=10).start() confirm_timer = Timer(1, count=2).start() self.interval_clear(button) while 1: if skip_first_screenshot: skip_first_screenshot = False else: self.device.screenshot() if ocr_timer.reached(): logger.warning( 'Failed to detect for normal exit routine, resort to OCR check' ) _, _, current = self._shipyard_get_total() if not current: logger.info( 'Confirm action has completed, setting flag for exit') self.interval_reset(button) success = True ocr_timer.reset() continue if self.appear_then_click(button, offset=(20, 20), interval=3): continue if self.handle_popup_confirm(text): self.interval_reset(button) ocr_timer.reset() confirm_timer.reset() continue if self.story_skip(): self.interval_reset(button) success = True ocr_timer.reset() confirm_timer.reset() continue if self.handle_info_bar(): self.interval_reset(button) success = True ocr_timer.reset() confirm_timer.reset() continue # End if success and \ self._shipyard_in_ui(): if confirm_timer.reached(): break else: confirm_timer.reset()
def os_map_goto_globe(self, unpin=True, skip_first_screenshot=True): """ Args: unpin (bool): skip_first_screenshot (bool): Pages: in: is_in_map out: is_in_globe """ click_count = 0 while 1: if skip_first_screenshot: skip_first_screenshot = False else: self.device.screenshot() if self.appear_then_click(MAP_GOTO_GLOBE, offset=(200, 5), interval=5): click_count += 1 if click_count >= 5: # When there's zone exploration reward, AL just don't let you go. logger.warning( 'Unable to goto globe, ' 'there might be uncollected zone exploration rewards preventing exit' ) raise GameTooManyClickError( f'Too many click for a button: {MAP_GOTO_GLOBE}') continue if self.handle_map_event(): continue # Popup: AUTO_SEARCH_REWARD appears slowly if self.appear_then_click(AUTO_SEARCH_REWARD, offset=(50, 50), interval=5): continue # Popup: Leaving current zone will terminate meowfficer searching. # Popup: Leaving current zone will retreat submarines # Searching reward will be shown after entering another zone. if self.handle_popup_confirm('GOTO_GLOBE'): continue # End if self.is_in_globe(): break skip_first_screenshot = True confirm_timer = Timer(1, count=2).start() unpinned = 0 while 1: if skip_first_screenshot: skip_first_screenshot = False else: self.device.screenshot() if unpin: if self.handle_zone_pinned(): unpinned += 1 confirm_timer.reset() else: if unpinned and confirm_timer.reached(): break else: if self.is_zone_pinned(): break
def meow_get(self, skip_first_screenshot=True): """ Transition through all the necessary screens to acquire each trained meowfficer Animation is waited for as the amount can vary Only gold variant meowfficer will prompt for confirmation Args: skip_first_screenshot (bool): Skip first screen shot or not Pages: in: MEOWFFICER_GET_CHECK out: MEOWFFICER_TRAIN """ # Loop through possible screen transitions confirm_timer = Timer(1.5, count=3).start() count = 0 while 1: if skip_first_screenshot: skip_first_screenshot = False else: self.device.screenshot() if self.handle_meow_popup_dismiss(): confirm_timer.reset() continue if self.appear(MEOWFFICER_GET_CHECK, offset=(40, 40), interval=3): count += 1 logger.attr('Meow_get', count) with self.stat.new(genre="meowfficer_talent", method=self.config. DropRecord_MeowfficerTalent) as drop: drop.add(self.device.image) list_talent_btn, special_talent = self._get_meow_talent_grid( ) if self.config.DropRecord_MeowfficerTalent != 'do_not': self._meow_talent_cap_handle(list_talent_btn, drop) if self.appear(MEOWFFICER_GOLD_CHECK, offset=(40, 40)): if not self.config.MeowfficerTrain_RetainTalentedGold or not special_talent: self._meow_skip_lock() skip_first_screenshot = True confirm_timer.reset() continue self._meow_apply_lock() if self.appear(MEOWFFICER_PURPLE_CHECK, offset=(40, 40)): if self.config.MeowfficerTrain_RetainTalentedPurple and special_talent: self._meow_apply_lock() # Susceptible to exception when collecting multiple # Mitigate by popping click_record self.device.click(MEOWFFICER_TRAIN_CLICK_SAFE_AREA) self.device.click_record.pop() confirm_timer.reset() self.interval_reset(MEOWFFICER_GET_CHECK) continue # End if self.appear(MEOWFFICER_TRAIN_START, offset=(20, 20)): if confirm_timer.reached(): break else: confirm_timer.reset()
def wait_until_walk_stable(self, confirm_timer=None, skip_first_screenshot=False, walk_out_of_step=True, drop=None): """ Wait until homo_loca stabled. DETECTION_BACKEND must be 'homography'. Args: confirm_timer (Timer): skip_first_screenshot (bool): walk_out_of_step (bool): If catch walk_out_of_step error. Default to True, use False in abyssal zones. drop (DropImage): Returns: str: Things that fleet met on its way, 'event', 'search', 'akashi', 'combat', or their combinations like 'event_akashi', 'event_combat', or an empty string '' if nothing met. Raises: MapWalkError: If unable to goto such grid. """ logger.hr('Wait until walk stable') record = None enemy_searching_appear = False self.device.screenshot_interval_set(0.35) if confirm_timer is None: confirm_timer = Timer(0.8, count=2) result = set() confirm_timer.reset() while 1: if skip_first_screenshot: skip_first_screenshot = False else: self.device.screenshot() # Map event if self.handle_map_event(drop=drop): confirm_timer.reset() result.add('event') continue if self.handle_retirement(): confirm_timer.reset() continue if self.handle_walk_out_of_step(): if walk_out_of_step: raise MapWalkError('walk_out_of_step') else: continue # Accident click if self.is_in_globe(): self.os_globe_goto_map() confirm_timer.reset() continue if self.is_in_storage(): self.storage_quit() confirm_timer.reset() continue if self.is_in_os_mission(): self.os_mission_quit() confirm_timer.reset() continue if self.handle_os_game_tips(): confirm_timer.reset() continue # Enemy searching if not enemy_searching_appear and self.enemy_searching_appear(): enemy_searching_appear = True confirm_timer.reset() continue else: if enemy_searching_appear: self.handle_enemy_flashing() self.device.sleep(0.3) logger.info('Enemy searching appeared.') enemy_searching_appear = False result.add('search') if self.is_in_map(): self.enemy_searching_color_initial() # Combat if self.combat_appear(): # Use ui_back() for testing, because there are too few abyssal loggers every month. # self.ui_back(check_button=self.is_in_map) self.combat(expected_end=self.is_in_map, fleet_index=self.fleet_show_index, save_get_items=drop) confirm_timer.reset() result.add('event') continue # Akashi shop if self.appear(PORT_SUPPLY_CHECK, offset=(20, 20)): self.interval_clear(PORT_SUPPLY_CHECK) self.handle_akashi_supply_buy(CLICK_SAFE_AREA) confirm_timer.reset() result.add('akashi') continue # Arrive # Check colors, because screen goes black when something is unlocking. if self.is_in_map() and IN_MAP.match_appear_on(self.device.image): self.update_os() current = self.view.backend.homo_loca logger.attr('homo_loca', current) if record is None or (current is not None and np.linalg.norm( np.subtract(current, record)) < 3): if confirm_timer.reached(): break else: confirm_timer.reset() record = current else: confirm_timer.reset() result = '_'.join(result) logger.info(f'Walk stabled, result: {result}') self.device.screenshot_interval_set() return result
def set(self, main, left=None, right=None, upper=None, bottom=None, skip_first_screenshot=True): """ Set nav bar from 1 direction. Args: main (ModuleBase): left (int): Index of nav item counted from left. Start from 1. right (int): Index of nav item counted from right. Start from 1. upper (int): Index of nav item counted from upper. Start from 1. bottom (int): Index of nav item counted from bottom. Start from 1. skip_first_screenshot (bool): Returns: bool: If success """ if left is None and right is None and upper is None and bottom is None: logger.warning( 'Invalid index to set, must set an index from 1 direction') return False text = '' if left is None and upper is not None: left = upper if right is None and bottom is not None: right = bottom for k in ['left', 'right', 'upper', 'bottom']: if locals().get(k, None) is not None: text += f'{k}={locals().get(k, None)} ' logger.info(f'{self.name} set to {text.strip()}') interval = Timer(2, count=4) timeout = Timer(10, count=20).start() while 1: if skip_first_screenshot: skip_first_screenshot = False else: main.device.screenshot() if timeout.reached(): logger.warning(f'{self.name} failed to set {text.strip()}') return False active, minimum, maximum = self.get_info(main=main) logger.info( f'Nav item active: {active} from range ({minimum}, {maximum})') # if active is None: # continue index = minimum + left - 1 if left is not None else maximum - right + 1 if not minimum <= index <= maximum: logger.warning( f'Index to set ({index}) is not within the nav items that appears ({minimum}, {maximum})' ) continue # End if active == index: return True if interval.reached(): main.device.click(self.grids.buttons[index]) main.device.sleep((0.1, 0.2)) interval.reset()
def wait_until_walk_stable(self, confirm_timer=None, skip_first_screenshot=False, walk_out_of_step=True, drop=None): """ Wait until homo_loca stabled. DETECTION_BACKEND must be 'homography'. Args: confirm_timer (Timer): skip_first_screenshot (bool): walk_out_of_step (bool): If catch walk_out_of_step error. Default to True, use False in abyssal zones. drop (DropImage): Raises: MapWalkError: If unable to goto such grid. """ logger.hr('Wait until walk stable') record = None enemy_searching_appear = False self.device.screenshot_interval_set(0.35) if confirm_timer is None: confirm_timer = Timer(0.8, count=2) confirm_timer.reset() while 1: if skip_first_screenshot: skip_first_screenshot = False else: self.device.screenshot() # Map event if self.handle_map_event(drop=drop): confirm_timer.reset() continue if self.handle_retirement(): confirm_timer.reset() continue if self.handle_walk_out_of_step(): if walk_out_of_step: raise MapWalkError('walk_out_of_step') else: continue # Enemy searching if not enemy_searching_appear and self.enemy_searching_appear(): enemy_searching_appear = True confirm_timer.reset() continue else: if enemy_searching_appear: self.handle_enemy_flashing() self.device.sleep(0.3) logger.info('Enemy searching appeared.') enemy_searching_appear = False if self.is_in_map(): self.enemy_searching_color_initial() # Combat if self.combat_appear(): # Use ui_back() for testing, because there are too few abyssal loggers every month. # self.ui_back(check_button=self.is_in_map) self.combat(expected_end=self.is_in_map, fleet_index=self.fleet_show_index, save_get_items=drop) confirm_timer.reset() continue # Arrive # Check colors, because screen goes black when something is unlocking. if self.is_in_map() and IN_MAP.match_appear_on(self.device.image): self.update_os() current = self.view.backend.homo_loca logger.attr('homo_loca', current) if record is None or (current is not None and np.linalg.norm( np.subtract(current, record)) < 3): if confirm_timer.reached(): break else: confirm_timer.reset() record = current else: confirm_timer.reset() logger.info('Walk stabled') self.device.screenshot_interval_set()
def _goto(self, location, expected=''): """Goto a grid directly and handle ambush, air raid, mystery picked up, combat. Args: location (tuple, str, GridInfo): Destination. expected (str): Expected result on destination grid, such as 'combat', 'combat_siren', 'mystery'. Will give a waring if arrive with unexpected result. """ location = location_ensure(location) result_mystery = '' self.movable_before = self.map.select(is_siren=True) self.movable_before_normal = self.map.select(is_enemy=True) if self.hp_retreat_triggered(): self.withdraw() is_portal = self.map[location].is_portal # The upper grid is submarine, may mess up predict_fleet() may_submarine_icon = self.map.grid_covered(self.map[location], location=[(0, -1)]) may_submarine_icon = may_submarine_icon and self.fleet_submarine_location == may_submarine_icon[ 0].location while 1: self.in_sight(location, sight=self._walk_sight) self.focus_to_grid_center() grid = self.convert_global_to_local(location) self.ambush_color_initial() self.enemy_searching_color_initial() grid.__str__ = location result = 'nothing' self.device.click(grid) arrived = False # Wait to confirm fleet arrived. It does't appear immediately if fleet in combat. extra = 0 if self.config.Submarine_Mode == 'hunt_only': extra += 4.5 if self.config.MAP_HAS_LAND_BASED and grid.is_mechanism_trigger: extra += grid.mechanism_wait arrive_timer = Timer(0.5 + self.round_wait + extra, count=2) arrive_unexpected_timer = Timer(1.5 + self.round_wait + extra, count=6) # Wait after ambushed. ambushed_retry = Timer(0.5) # If nothing happens, click again. walk_timeout = Timer(20) walk_timeout.start() while 1: self.device.screenshot() self.view.update(image=self.device.image) if is_portal: self.update() grid = self.view[self.view.center_loca] # Combat if self.config.Campaign_UseFleetLock and not self.is_in_map(): if self.handle_retirement(): self.map_offensive() walk_timeout.reset() if self.handle_combat_low_emotion(): walk_timeout.reset() if self.combat_appear(): self.combat(expected_end=self._expected_end(expected), fleet_index=self.fleet_show_index, submarine_mode=self._submarine_mode(expected)) self.hp_get() self.lv_get(after_battle=True) arrived = True if not self.config.MAP_HAS_MOVABLE_ENEMY else False result = 'combat' self.battle_count += 1 self.fleet_ammo -= 1 if 'siren' in expected or ( self.config.MAP_HAS_MOVABLE_ENEMY and not expected): self.siren_count += 1 elif self.map[location].may_enemy: self.map[location].is_cleared = True if self.catch_camera_repositioning(self.map[location]): self.handle_boss_appear_refocus() if self.config.MAP_FOCUS_ENEMY_AFTER_BATTLE: self.camera = location self.update() grid = self.convert_global_to_local(location) arrive_timer = Timer(0.5 + extra, count=2) arrive_unexpected_timer = Timer(1.5 + extra, count=6) walk_timeout.reset() if not (grid.predict_fleet() and grid.predict_current_fleet()): ambushed_retry.start() # Ambush if self.handle_ambush(): self.hp_get() self.lv_get(after_battle=True) walk_timeout.reset() self.view.update(image=self.device.image) if not (grid.predict_fleet() and grid.predict_current_fleet()): ambushed_retry.start() # Mystery mystery = self.handle_mystery(button=grid) if mystery: self.mystery_count += 1 result = 'mystery' result_mystery = mystery # Cat attack animation if self.handle_map_cat_attack(): walk_timeout.reset() continue # Guild popup # Usually handled in combat_status, but sometimes delayed until after battle on slow PCs. if self.handle_guild_popup_cancel(): walk_timeout.reset() continue if self.handle_walk_out_of_step(): raise MapWalkError('walk_out_of_step') # Arrive arrive_predict = '' arrive_checker = False if self.is_in_map(): if not may_submarine_icon and grid.predict_fleet(): arrive_predict = '(is_fleet)' arrive_checker = True elif may_submarine_icon and grid.predict_current_fleet(): arrive_predict = '(may_submarine_icon, is_current_fleet)' arrive_checker = True elif self.config.MAP_WALK_USE_CURRENT_FLEET and grid.predict_current_fleet( ): arrive_predict = '(MAP_WALK_USE_CURRENT_FLEET, is_current_fleet)' arrive_checker = True elif walk_timeout.reached() and grid.predict_current_fleet( ): arrive_predict = '(walk_timeout, is_current_fleet)' arrive_checker = True if arrive_checker: if not arrive_timer.started(): logger.info( f'Arrive {location2node(location)} {arrive_predict}' .strip()) arrive_timer.start() arrive_unexpected_timer.start() if result == 'nothing' and not arrive_timer.reached(): continue if expected and result not in expected: if arrive_unexpected_timer.reached(): logger.warning('Arrive with unexpected result') else: continue if is_portal: location = self.map[location].portal_link self.camera = location logger.info( f'Arrive {location2node(location)} confirm. Result: {result}. Expected: {expected}' ) arrived = True break else: if arrive_timer.started(): arrive_timer.reset() if arrive_unexpected_timer.started(): arrive_unexpected_timer.reset() # Story if expected == 'story': if self.handle_story_skip(): result = 'story' continue # End if ambushed_retry.started() and ambushed_retry.reached(): break if walk_timeout.reached(): logger.warning('Walk timeout. Retrying.') self.predict() self.ensure_edge_insight(skip_first_update=False) break # End if arrived: # Ammo grid needs to click again, otherwise the next click doesn't work. if self.map[location].may_ammo: self.device.click(grid) break self.map[self.fleet_current].is_fleet = False self.map[location].wipe_out() self.map[location].is_fleet = True self.__setattr__('fleet_%s_location' % self.fleet_current_index, location) if result_mystery == 'get_carrier': self.full_scan_carrier() if result == 'combat': self.round_battle(after_battle=True) self.predict() self.round_next() if self.round_is_new: if result != 'combat': self.predict() self.full_scan_movable(enemy_cleared=result == 'combat') self.find_path_initial() raise MapEnemyMoved if self.round_maze_changed: self.find_path_initial() raise MapEnemyMoved self.find_path_initial()
def os_mission_overview_accept(self): """ Accept all missions in mission overview. Returns: bool: True if all missions accepted or no mission found. False if unable to accept more missions. Pages: in: is_in_map out: is_in_map """ logger.hr('OS mission overview accept', level=1) # is_in_map self.os_map_goto_globe(unpin=False) # is_in_globe self.ui_click(MISSION_OVERVIEW_ENTER, check_button=MISSION_OVERVIEW_CHECK, offset=(200, 20), retry_wait=3, skip_first_screenshot=True) # MISSION_OVERVIEW_CHECK confirm_timer = Timer(1, count=3).start() skip_first_screenshot = True success = True while 1: if skip_first_screenshot: skip_first_screenshot = False else: self.device.screenshot() if self.info_bar_count(): logger.info( 'Unable to accept missions, because reached the maximum number of missions' ) success = False break if self.appear_then_click(MISSION_OVERVIEW_ACCEPT, offset=(20, 20), interval=0.2): confirm_timer.reset() continue else: # End if confirm_timer.reached(): success = True break if self.appear_then_click(MISSION_OVERVIEW_ACCEPT_SINGLE, offset=(20, 20), interval=0.2): confirm_timer.reset() continue # is_in_globe self.ui_back(appear_button=MISSION_OVERVIEW_CHECK, check_button=self.is_in_globe, skip_first_screenshot=True) # is_in_map self.os_globe_goto_map() return success
def shop_get_items(self, skip_first_screenshot=True): """ Args: skip_first_screenshot (bool): Returns: list[Item]: """ # Retrieve ShopItemGrid shop_items = self.shop_items() if shop_items is None: logger.warning('Expected type \'ShopItemGrid\' but was None') return [] # Loop on predict to ensure items # have loaded and can accurately # be read record = 0 timeout = Timer(3, count=9).start() while 1: if skip_first_screenshot: skip_first_screenshot = False else: self.device.screenshot() shop_items.predict( self.device.image, name=True, amount=False, cost=True, price=True, tag=False ) if timeout.reached(): logger.warning('Items loading timeout; continue and assumed has loaded') break # Check unloaded items, because AL loads items too slow. items = shop_items.items known = len([item for item in items if item.is_known_item]) logger.attr('Item detected', known) if known == 0 or known != record: record = known continue else: record = known # End if self.shop_has_loaded(items): break # Log final result on predicted items items = shop_items.items grids = shop_items.grids if len(items): min_row = grids[0, 0].area[1] row = [str(item) for item in items if item.button[1] == min_row] logger.info(f'Shop row 1: {row}') row = [str(item) for item in items if item.button[1] != min_row] logger.info(f'Shop row 2: {row}') return items else: logger.info('No shop items found') return []
def _reward_mission_collect(self, interval=1): """ Streamline handling of mission rewards for both 'all' and 'weekly' pages Args: interval (int): Configure the interval for assets involved Returns: bool, if encountered at least 1 GET_ITEMS_* """ # Reset any existing interval for the following assets [ self.interval_clear(asset) for asset in [ GET_ITEMS_1, GET_ITEMS_2, MISSION_MULTI, MISSION_SINGLE, GET_SHIP ] ] # Basic timers for certain scenarios exit_timer = Timer(2) click_timer = Timer(1) timeout = Timer(10) exit_timer.start() timeout.start() reward = False while 1: self.device.screenshot() for button in [GET_ITEMS_1, GET_ITEMS_2]: if self.appear_then_click(button, offset=(30, 30), interval=interval): exit_timer.reset() timeout.reset() reward = True continue for button in [MISSION_MULTI, MISSION_SINGLE]: if not click_timer.reached(): continue if self.appear(button, offset=(0, 200), interval=interval) \ and button.match_appear_on(self.device.image): self.device.click(button) exit_timer.reset() click_timer.reset() timeout.reset() continue if not self.appear(MISSION_CHECK): if self.appear_then_click(GET_SHIP, interval=interval): exit_timer.reset() click_timer.reset() timeout.reset() continue if self.handle_mission_popup_ack(): exit_timer.reset() click_timer.reset() timeout.reset() continue # Story if self.handle_vote_popup(): exit_timer.reset() click_timer.reset() timeout.reset() continue if self.story_skip(): exit_timer.reset() click_timer.reset() timeout.reset() continue if self.handle_popup_confirm('MISSION_REWARD'): exit_timer.reset() click_timer.reset() timeout.reset() continue # End if reward and exit_timer.reached(): break if timeout.reached(): logger.warning('Wait get items timeout.') break return reward
def _guild_operations_dispatch(self, skip_first_screenshot=True): """ Executes the dispatch sequence Pages: in: GUILD_OPERATIONS_DISPATCH out: GUILD_OPERATIONS_MAP """ confirm_timer = Timer(1.5, count=3).start() add_timer = Timer(1.5, count=3) close_timer = Timer(3, count=6).start() while 1: if skip_first_screenshot: skip_first_screenshot = False else: self.device.screenshot() if self.appear_then_click(GUILD_DISPATCH_QUICK, interval=5): confirm_timer.reset() close_timer.reset() continue if self.appear(GUILD_DISPATCH_EMPTY, interval=5): self.device.click(GUILD_DISPATCH_RECOMMEND) self.device.sleep((0.5, 0.8)) self.device.click(GUILD_DISPATCH_FLEET) confirm_timer.reset() close_timer.reset() continue # Pseudo interval timer for template match_result calls if not add_timer.started() or add_timer.reached(): sim, point = TEMPLATE_OPERATIONS_ADD.match_result( self.device.image) if sim > 0.85: # Use small area to reduce random click point button = area_offset(area=(-2, -2, 24, 12), offset=point) dispatch_add = Button(area=button, color=(), button=button, name='GUILD_DISPATCH_ADD') self.device.click(dispatch_add) confirm_timer.reset() add_timer.reset() close_timer.reset() continue add_timer.reset() if self.handle_popup_confirm('GUILD_DISPATCH'): # Explicit click since GUILD_DISPATCH_FLEET # does not automatically turn into # GUILD_DISPATCH_IN_PROGRESS after confirm self.device.sleep((0.5, 0.8)) self.device.click(GUILD_DISPATCH_CLOSE) confirm_timer.reset() close_timer.reset() continue if self.appear(GUILD_DISPATCH_IN_PROGRESS): # Independent timer used instead of interval # Since can appear if at least 1 fleet already # dispatched, don't want to exit prematurely if close_timer.reached_and_reset(): self.device.click(GUILD_DISPATCH_CLOSE) confirm_timer.reset() continue # End if self.appear(GUILD_OPERATIONS_ACTIVE_CHECK): if not self.info_bar_count() and confirm_timer.reached(): break else: confirm_timer.reset() close_timer.reset()
def _tactical_class_receive(self, skip_first_screenshot=True): """ Receive tactical rewards and fill books. Args: skip_first_screenshot (bool): Returns: bool: If rewarded. Pages: in: page_reward, TACTICAL_CLASS_START out: page_tactical """ logger.hr('Tactical class receive', level=1) tactical_class_timout = Timer(10, count=10).start() tactical_animation_timer = Timer(2, count=3).start() while 1: if skip_first_screenshot: skip_first_screenshot = False else: self.device.screenshot() if self.appear_then_click(REWARD_2, interval=1): tactical_class_timout.reset() tactical_animation_timer.reset() continue if self.appear_then_click(REWARD_GOTO_TACTICAL, offset=(20, 20), interval=1): tactical_class_timout.reset() tactical_animation_timer.reset() continue if self.handle_popup_confirm('TACTICAL'): tactical_class_timout.reset() tactical_animation_timer.reset() continue if self.handle_urgent_commission(): # Only one button in the middle, when skill reach max level. tactical_class_timout.reset() tactical_animation_timer.reset() continue if self.appear(TACTICAL_CLASS_CANCEL, offset=(30, 30), interval=2) \ and self.appear(TACTICAL_CLASS_START, offset=(30, 30)): self.device.sleep(0.3) self._tactical_books_choose() self.interval_reset(TACTICAL_CLASS_CANCEL) tactical_class_timout.reset() tactical_animation_timer.reset() continue # End if self.appear(TACTICAL_CHECK, offset=(20, 20)): self.ui_current = page_tactical if not self._tactical_animation_running(): if tactical_animation_timer.reached(): logger.info('Tactical reward end.') break else: tactical_animation_timer.reset() if tactical_class_timout.reached(): logger.info('Tactical reward timeout.') break return True
def _handle_app_login(self): """ Pages: in: Any page out: page_main """ logger.hr('App login') confirm_timer = Timer(1.5, count=4).start() orientation_timer = Timer(5) login_success = False while 1: self.device.screenshot() if not login_success and orientation_timer.reached(): # Screen may rotate after starting an app self.device.get_orientation() orientation_timer.reset() if self.handle_get_items(): 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(MAINTENANCE_ANNOUNCE, offset=(30, 30), interval=5): 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_RETURN_INFO, offset=(30, 30), interval=5): continue if server.server == 'cn' and not login_success: if self.handle_cn_user_agreement(): continue if self.handle_popup_confirm('LOGIN'): continue if self.handle_guild_popup_cancel(): continue if self.handle_urgent_commission(): continue if self.appear_then_click(GOTO_MAIN, offset=(30, 30), interval=5): continue if 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() self.config.start_time = datetime.now() return True
def enter_map(self, button, mode='normal'): """Enter a campaign. Args: button: Campaign to enter. mode (str): 'normal' or 'hard' or 'cd' """ logger.hr('Enter map') campaign_timer = Timer(2) map_timer = Timer(1) fleet_timer = Timer(1) checked_in_map = False while 1: self.device.screenshot() if not checked_in_map and self.is_in_map(): logger.info('Already in map, skip enter_map.') return False else: checked_in_map = True # Map preparation if map_timer.reached() and self.appear(MAP_PREPARATION): self.device.sleep(0.3) # Wait for map information. self.device.screenshot() if self.handle_map_clear_mode_stop(): self.enter_map_cancel() raise ScriptEnd( f'Reach condition: {self.config.CLEAR_MODE_STOP_CONDITION}' ) self.handle_fast_forward() self.device.click(MAP_PREPARATION) map_timer.reset() campaign_timer.reset() continue # Fleet preparation if fleet_timer.reached() and self.appear(FLEET_PREPARATION): if self.config.ENABLE_FLEET_CONTROL: if mode == 'normal' or mode == 'hard': self.fleet_preparation() self.device.click(FLEET_PREPARATION) fleet_timer.reset() campaign_timer.reset() continue # Retire if self.handle_retirement(): continue # Emotion if self.handle_combat_low_emotion(): continue # Urgent commission if self.handle_urgent_commission(): continue # Story skip if self.handle_story_skip(): campaign_timer.reset() continue # Enter campaign if campaign_timer.reached() and self.is_in_stage(): self.device.click(button) campaign_timer.reset() continue # End if self.handle_in_map_with_enemy_searching(): break return True
def _set_2x_book_status(self, status, check_button, box_button, skip_first_screenshot=True): """ Set appropriate 2x book setting with corresponding status and buttons Built with retry mechanism that limits to 3 attempts that span 3 second intervals each Args: status (string): on or off check_button (Button): button to check before attempting to click box_button (Button): button to click and image color count against skip_first_screenshot (bool): namesake Returns: bool: True if detected having set correctly False can occur for 2 reasons either assets insufficient to detect properly or 2x book setting is absent """ confirm_timer = Timer(1).start() clicked_threshold = 0 while 1: if skip_first_screenshot: skip_first_screenshot = False else: self.device.screenshot() if clicked_threshold > 3: break if self.appear(check_button, offset=self._auto_search_menu_offset, interval=3): box_button.load_offset(check_button) enabled = self.image_color_count(box_button.button, color=(156, 255, 82), threshold=221, count=20) if (status == 'on' and enabled) or (status == 'off' and not enabled): return True if (status == 'on' and not enabled) or (status == 'off' and enabled): self.device.click(box_button) clicked_threshold += 1 if not clicked_threshold and confirm_timer.reached(): logger.info('Map do not have 2x book setting') return False logger.warning(f'Wait time has expired; Cannot set 2x book setting') return False
class Scroll: color_threshold = 221 drag_threshold = 0.05 edge_add = (0.1, 0.2) def __init__(self, area, color, is_vertical=True, name='Scroll'): """ Args: area (Button, tuple): A button or area of the whole scroll. color (tuple): RGB of the scroll is_vertical (bool): True if vertical, false if horizontal. name (str): """ if isinstance(area, Button): name = area.name area = area.area self.area = area self.color = color self.is_vertical = is_vertical self.name = name if self.is_vertical: self.total = self.area[3] - self.area[1] else: self.total = self.area[2] - self.area[0] # Just default value, will change in match_color() self.length = self.total / 2 self.drag_interval = Timer(1) def match_color(self, main): """ Args: main (ModuleBase): Returns: np.ndarray: Shape (n,), dtype bool. """ image = main.image_crop(self.area) image = color_similarity_2d(image, color=self.color) mask = np.max(image, axis=1 if self.is_vertical else 0) > self.color_threshold self.length = np.sum(mask) return mask def cal_position(self, main): """ Args: main (ModuleBase): Returns: float: 0 to 1. """ mask = self.match_color(main) middle = np.mean(np.where(mask)[0]) position = (middle - self.length / 2) / (self.total - self.length) position = position if position > 0 else 0.0 position = position if position < 1 else 1.0 logger.attr( self.name, f'{position:.2f} ({middle}-{self.length / 2})/({self.total}-{self.length})' ) return position def position_to_screen(self, position, random_range=(-0.05, 0.05)): """ Convert scroll position to screen coordinates. Call cal_position() or match_color() to get length, before calling this. Args: position (int, float): random_range (tuple): Returns: tuple[int]: (upper_left_x, upper_left_y, bottom_right_x, bottom_right_y) """ position = np.add(position, random_range) middle = position * (self.total - self.length) + self.length / 2 middle = middle.astype(int) if self.is_vertical: middle += self.area[1] area = (self.area[0], middle[0], self.area[2], middle[1]) else: middle += self.area[0] area = (middle[0], self.area[1], middle[1], self.area[3]) return area def appear(self, main): """ Args: main (ModuleBase): Returns: bool """ return np.mean(self.match_color(main)) > 0.1 def at_top(self, main): return self.cal_position(main) < 0.05 def at_bottom(self, main): return self.cal_position(main) > 0.95 def set(self, position, main, random_range=(-0.05, 0.05), skip_first_screenshot=True): """ Set scroll to a specific position. Args: position (float, int): 0 to 1. main (ModuleBase): random_range (tuple(int, float)): skip_first_screenshot: """ logger.info(f'{self.name} set to {position}') self.drag_interval.clear() if position == 0: random_range = np.subtract(0, self.edge_add) if position == 1: random_range = self.edge_add while 1: if skip_first_screenshot: skip_first_screenshot = False else: main.device.screenshot() current = self.cal_position(main) if abs(position - current) < self.drag_threshold: break if not self.length: logger.warning('Scroll disappeared, assume scroll set') break if self.drag_interval.reached(): p1 = random_rectangle_point(self.position_to_screen(current), n=1) p2 = random_rectangle_point(self.position_to_screen( position, random_range=random_range), n=1) main.device.swipe(p1, p2, name=self.name) main.device.sleep(0.3) self.drag_interval.reset() def set_top(self, main, random_range=(-0.05, 0.05), skip_first_screenshot=True): return self.set(0.00, main=main, random_range=random_range, skip_first_screenshot=skip_first_screenshot) def set_bottom(self, main, random_range=(-0.05, 0.05), skip_first_screenshot=True): return self.set(1.00, main=main, random_range=random_range, skip_first_screenshot=skip_first_screenshot) def drag_page(self, page, main, random_range=(-0.05, 0.05), skip_first_screenshot=True): """ Drag scroll forward or backward. Args: page (int, float): Relative position to drag. 1.0 means next page, -1.0 means previous page. main (ModuleBase): random_range (tuple[int]): skip_first_screenshot: """ if not skip_first_screenshot: main.device.screenshot() current = self.cal_position(main) multiply = self.length / (self.total - self.length) target = current + page * multiply target = round(min(max(target, 0), 1), 3) self.set(target, main=main, random_range=random_range, skip_first_screenshot=True) def next_page(self, main, random_range=(-0.01, 0.01), skip_first_screenshot=True): self.drag_page(0.8, main=main, random_range=random_range, skip_first_screenshot=skip_first_screenshot) def prev_page(self, main, random_range=(-0.01, 0.01), skip_first_screenshot=True): self.drag_page(-0.8, main=main, random_range=random_range, skip_first_screenshot=skip_first_screenshot)
def _goto(self, location, expected=''): """Goto a grid directly and handle ambush, air raid, mystery picked up, combat. Args: location (tuple, str, GridInfo): Destination. """ location = location_ensure(location) result_mystery = '' self.movable_before = self.map.select(is_siren=True) if self.hp_withdraw_triggered(): self.withdraw() while 1: sight = self.map.camera_sight self.in_sight(location, sight=(sight[0], 0, sight[2], sight[3])) self.focus_to_grid_center() grid = self.convert_map_to_grid(location) self.ambush_color_initial() self.enemy_searching_color_initial() grid.__str__ = location result = 'nothing' self.device.click(grid) arrived = False # Wait to confirm fleet arrived. It does't appear immediately if fleet in combat . arrive_timer = Timer(0.5 + self.round_wait, count=2) arrive_unexpected_timer = Timer(1.5 + self.round_wait, count=6) # Wait after ambushed. ambushed_retry = Timer(0.5) # If nothing happens, click again. walk_timeout = Timer(20) walk_timeout.start() while 1: self.device.screenshot() grid.image = np.array(self.device.image) # Ambush if self.handle_ambush(): self.hp_get() ambushed_retry.start() walk_timeout.reset() # Mystery mystery = self.handle_mystery(button=grid) if mystery: self.mystery_count += 1 result = 'mystery' result_mystery = mystery # Combat if self.config.ENABLE_MAP_FLEET_LOCK and not self.is_in_map(): if self.handle_retirement(): self.map_offensive() walk_timeout.reset() if self.handle_combat_low_emotion(): walk_timeout.reset() if self.combat_appear(): self.combat( expected_end=self._expected_combat_end(expected), fleet_index=self.fleet_current_index) self.hp_get() arrived = True if not self.config.MAP_HAS_MOVABLE_ENEMY else False result = 'combat' self.battle_count += 1 self.fleet_ammo -= 1 if 'siren' in expected or ( self.config.MAP_HAS_MOVABLE_ENEMY and not expected): self.siren_count += 1 elif self.map[location].may_enemy: self.map[location].is_cleared = True self.handle_boss_appear_refocus() grid = self.convert_map_to_grid(location) walk_timeout.reset() # Cat attack animation if self.handle_map_cat_attack(): walk_timeout.reset() continue if self.handle_walk_out_of_step(): raise MapWalkError('walk_out_of_step') # Arrive if self.is_in_map() and \ (grid.predict_fleet() or (walk_timeout.reached() and grid.predict_current_fleet())): if not arrive_timer.started(): logger.info(f'Arrive {location2node(location)}') arrive_timer.start() arrive_unexpected_timer.start() if not arrive_timer.reached(): continue if expected and result not in expected: if arrive_unexpected_timer.reached(): logger.warning('Arrive with unexpected result') else: continue logger.info( f'Arrive {location2node(location)} confirm. Result: {result}. Expected: {expected}' ) arrived = True break # End if ambushed_retry.started() and ambushed_retry.reached(): break if walk_timeout.reached(): logger.warning('Walk timeout. Retrying.') self.ensure_edge_insight() break # End if arrived: # Ammo grid needs to click again, otherwise the next click doesn't work. if self.map[location].may_ammo: self.device.click(grid) break self.map[self.fleet_current].is_fleet = False self.map[location].wipe_out() self.map[location].is_fleet = True self.__setattr__('fleet_%s_location' % self.fleet_current_index, location) if result_mystery == 'get_carrier': self.full_scan_carrier() if result == 'combat': self.round_battle() self.round_next() if self.round_is_new: self.full_scan_movable(enemy_cleared=result == 'combat') self.find_path_initial() raise MapEnemyMoved self.find_path_initial()
def _guild_logistics_collect(self, skip_first_screenshot=True): """ Execute collect/accept screen transitions within logistics Args: skip_first_screenshot (bool): Returns: bool: If all guild logistics are check, no need to check them today. Pages: in: GUILD_LOGISTICS out: GUILD_LOGISTICS """ logger.hr('Guild logistics') logger.attr('Guild master/official', self.config.GuildOperation_SelectNewOperation) confirm_timer = Timer(1.5, count=3).start() exchange_interval = Timer(1.5, count=3) click_interval = Timer(0.5, count=1) supply_checked = False mission_checked = False exchange_checked = False exchange_count = 0 while 1: if skip_first_screenshot: skip_first_screenshot = False else: self.device.screenshot() # Handle all popups if self.handle_popup_confirm('GUILD_LOGISTICS'): confirm_timer.reset() exchange_interval.reset() continue if self.appear_then_click(GET_ITEMS_1, interval=2): confirm_timer.reset() exchange_interval.reset() continue if self._handle_guild_fleet_mission_start(): confirm_timer.reset() continue if self._is_in_guild_logistics(): # Supply if not supply_checked and self._guild_logistics_supply_available( ): if click_interval.reached(): self.device.click(GUILD_SUPPLY) click_interval.reset() confirm_timer.reset() continue else: supply_checked = True # Mission if not mission_checked and self._guild_logistics_mission_available( ): if click_interval.reached(): self.device.click(GUILD_MISSION) click_interval.reset() confirm_timer.reset() continue else: mission_checked = True # Exchange if not exchange_checked and exchange_interval.reached(): if self._guild_exchange(): confirm_timer.reset() exchange_interval.reset() exchange_count += 1 continue else: exchange_checked = True # End if not self.info_bar_count() and confirm_timer.reached(): break # if supply_checked and mission_checked and exchange_checked: # break if exchange_count >= 5: # If you run AL across days, then do guild exchange. # There will show an error, said time is not up. # Restart the game can't fix the problem. # To fix this, you have to enter guild logistics once, then restart. # If exchange for 5 times, this bug is considered to be triggered. logger.warning( 'Unable to do guild exchange, probably because the timer in game was bugged' ) raise GameBugError('Triggered guild logistics refresh bug') else: confirm_timer.reset() logger.info( f'supply_checked: {supply_checked}, mission_checked: {mission_checked}, ' f'exchange_checked: {exchange_checked}, mission_finished: {self._guild_logistics_mission_finished}' ) # Azur Lane receives new guild missions now # No longer consider `self._guild_logistics_mission_finished` as a check return all([supply_checked, mission_checked, exchange_checked])
def ui_get_current_page(self, skip_first_screenshot=True): """ Args: skip_first_screenshot: Returns: Page: """ logger.info('UI get current page') @run_once def app_check(): if not self.device.app_is_running(): raise GameNotRunningError('Game not running') @run_once def minicap_check(): if self.config.Emulator_ControlMethod == 'uiautomator2': self.device.uninstall_minicap() timeout = Timer(5, count=10).start() while 1: if skip_first_screenshot: skip_first_screenshot = False if not hasattr(self.device, 'image') or self.device.image is None: self.device.screenshot() else: self.device.screenshot() # End if timeout.reached(): break # 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), interval=2) or self.ui_additional(): timeout.reset() continue app_check() minicap_check() # Unknown page, need manual switching logger.warning('Unknown ui page') 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') logger.critical('Please switch to a supported page before starting Alas') raise GamePageUnknownError
def _submarine_goto(self, location): """ Move submarine to given location. Args: location (tuple, str, GridInfo): Destination. Returns: bool: If submarine moved. Pages: in: SUBMARINE_MOVE_CONFIRM out: SUBMARINE_MOVE_CONFIRM """ location = location_ensure(location) moved = True while 1: self.in_sight(location, sight=self._walk_sight) self.focus_to_grid_center() grid = self.convert_global_to_local(location) grid.__str__ = location self.device.click(grid) arrived = False # Usually no need to wait arrive_timer = Timer(0.1, count=0) # If nothing happens, click again. walk_timeout = Timer(2, count=6).start() while 1: self.device.screenshot() self.view.update(image=self.device.image) # Arrive arrive_checker = grid.predict_submarine_move() if grid.predict_submarine() or (walk_timeout.reached() and grid.predict_fleet()): arrive_checker = True moved = False if arrive_checker: if not arrive_timer.started(): logger.info(f'Arrive {location2node(location)}') arrive_timer.start() if not arrive_timer.reached(): continue logger.info( f'Submarine arrive {location2node(location)} confirm.') if not moved: logger.info( f'Submarine already at {location2node(location)}') arrived = True break # End if walk_timeout.reached(): logger.warning('Walk timeout. Retrying.') self.predict() self.ensure_edge_insight(skip_first_update=False) break # End if arrived: break return moved
def enter_map(self, button, mode='normal'): """Enter a campaign. Args: button: Campaign to enter. mode (str): 'normal' or 'hard' or 'cd' """ logger.hr('Enter map') campaign_timer = Timer(5) map_timer = Timer(5) fleet_timer = Timer(5) checked_in_map = False self.stage_entrance = button while 1: self.device.screenshot() if not checked_in_map and self.is_in_map(): logger.info('Already in map, skip enter_map.') return False else: checked_in_map = True # Map preparation if map_timer.reached() and self.handle_map_preparation(): self.map_get_info() self.handle_fast_forward() if self.handle_map_stop(): self.enter_map_cancel() raise ScriptEnd( f'Reach condition: {self.config.STOP_IF_MAP_REACH}') self.device.click(MAP_PREPARATION) map_timer.reset() campaign_timer.reset() continue # Fleet preparation if fleet_timer.reached() and self.appear(FLEET_PREPARATION): if self.config.ENABLE_FLEET_CONTROL: if mode == 'normal' or mode == 'hard': self.fleet_preparation() self.device.click(FLEET_PREPARATION) fleet_timer.reset() campaign_timer.reset() continue # Retire if self.handle_retirement(): continue # Use Data Key if self.handle_use_data_key(): continue # Emotion if self.handle_combat_low_emotion(): continue # Urgent commission if self.handle_urgent_commission(): continue # Story skip if self.handle_story_skip(): campaign_timer.reset() continue # Enter campaign if campaign_timer.reached() and self.appear_then_click(button): campaign_timer.reset() continue # End if self.handle_in_map_with_enemy_searching(): self.handle_map_after_combat_story() break return True
def tactical_class_receive(self, skip_first_screenshot=True): """ Receive tactical rewards and fill books. Args: skip_first_screenshot (bool): Returns: bool: If rewarded. Pages: in: page_reward, TACTICAL_CLASS_START out: page_reward """ logger.hr('Tactical class receive', level=1) received = False # tactical cards can't be loaded that fast, confirm if it's empty. empty_confirm = Timer(0.6, count=2).start() while 1: if skip_first_screenshot: skip_first_screenshot = False else: self.device.screenshot() # End if received and self.appear(REWARD_CHECK, offset=(20, 20)): break # Get finish time if self.appear(TACTICAL_CHECK, offset=(20, 20), interval=2): self.interval_clear([POPUP_CONFIRM, POPUP_CANCEL, GET_MISSION]) if self._tactical_get_finish(): self.device.click(BACK_ARROW) self.interval_reset(TACTICAL_CHECK) empty_confirm.reset() received = True continue else: self.interval_clear(TACTICAL_CHECK) if empty_confirm.reached(): self.device.click(BACK_ARROW) empty_confirm.reset() received = True continue else: empty_confirm.reset() # Popups if self.appear_then_click(REWARD_2, offset=(20, 20), interval=3): continue if self.appear_then_click(REWARD_GOTO_TACTICAL, offset=(20, 20), interval=3): continue if self.handle_popup_confirm('TACTICAL'): continue if self.handle_urgent_commission(): # Only one button in the middle, when skill reach max level. continue if self.ui_page_main_popups(): continue if self.appear(TACTICAL_CLASS_CANCEL, offset=(30, 30), interval=2) \ and self.appear(TACTICAL_CLASS_START, offset=(30, 30)): if self._tactical_books_choose(): self.interval_reset(TACTICAL_CLASS_CANCEL) self.interval_clear( [POPUP_CONFIRM, POPUP_CANCEL, GET_MISSION]) continue if self.appear(DOCK_CHECK, offset=(20, 20), interval=3): # Entered dock accidentally self.device.click(BACK_ARROW) continue if self.appear(SKILL_CONFIRM, offset=(20, 20), interval=3): # Game auto pops up the next skill to learn, close it self.device.click(BACK_ARROW) continue return True