class InfoHandler(ModuleBase): """ Class to handle all kinds of message. """ """ Info bar """ def info_bar_count(self): if self.appear(INFO_BAR_3): return 3 elif self.appear(INFO_BAR_2): return 2 elif self.appear(INFO_BAR_1): return 1 else: return 0 def handle_info_bar(self): if self.info_bar_count(): self.wait_until_disappear(INFO_BAR_1) return True else: return False def ensure_no_info_bar(self, timeout=0.6): timeout = Timer(timeout) timeout.start() while 1: self.device.screenshot() self.handle_info_bar() if timeout.reached(): break """ Popup info """ _popup_offset = (3, 30) def handle_popup_confirm(self, name=''): if self.appear(POPUP_CANCEL, offset=self._popup_offset) \ and self.appear(POPUP_CONFIRM, offset=self._popup_offset, interval=2): POPUP_CONFIRM.name = POPUP_CONFIRM.name + '_' + name self.device.click(POPUP_CONFIRM) POPUP_CONFIRM.name = POPUP_CONFIRM.name[:-len(name) - 1] return True else: return False def handle_popup_cancel(self, name=''): if self.appear(POPUP_CONFIRM, offset=self._popup_offset) \ and self.appear(POPUP_CANCEL, offset=self._popup_offset, interval=2): POPUP_CANCEL.name = POPUP_CANCEL.name + '_' + name self.device.click(POPUP_CANCEL) POPUP_CANCEL.name = POPUP_CANCEL.name[:-len(name) - 1] return True else: return False def handle_popup_single(self, name=''): if self.appear(GET_MISSION, offset=self._popup_offset, interval=2): prev_name = GET_MISSION.name GET_MISSION.name = POPUP_CONFIRM.name + '_' + name self.device.click(GET_MISSION) GET_MISSION.name = prev_name return True return False def handle_urgent_commission(self, save_get_items=None): """ Args: save_get_items (bool): Returns: bool: """ if save_get_items is None: save_get_items = self.config.ENABLE_SAVE_GET_ITEMS appear = self.appear(GET_MISSION, offset=True, interval=2) if appear: logger.info('Get urgent commission') if save_get_items: self.handle_info_bar() self.device.save_screenshot('get_mission') self.device.click(GET_MISSION) return appear def handle_combat_low_emotion(self): if not self.config.IGNORE_LOW_EMOTION_WARN: return False return self.handle_popup_confirm('IGNORE_LOW_EMOTION') def handle_use_data_key(self): if not self.config.USE_DATA_KEY: return False if not self.appear(POPUP_CONFIRM, offset=self._popup_offset) \ and not self.appear(POPUP_CANCEL, offset=self._popup_offset, interval=2): return False if self.appear(USE_DATA_KEY, offset=(20, 20)): self.device.click(USE_DATA_KEY_NOTIFIED) self.device.sleep((0.5, 0.8)) return self.handle_popup_confirm('USE_DATA_KEY') return False """ Guild popup info """ def handle_guild_popup_confirm(self): if self.appear(GUILD_POPUP_CANCEL, offset=self._popup_offset) \ and self.appear(GUILD_POPUP_CONFIRM, offset=self._popup_offset, interval=2): self.device.click(GUILD_POPUP_CONFIRM) return True return False def handle_guild_popup_cancel(self): if self.appear(GUILD_POPUP_CONFIRM, offset=self._popup_offset) \ and self.appear(GUILD_POPUP_CANCEL, offset=self._popup_offset, interval=2): self.device.click(GUILD_POPUP_CANCEL) return True return False """ Mission popup info """ def handle_mission_popup_go(self): if self.appear(MISSION_POPUP_ACK, offset=self._popup_offset) \ and self.appear(MISSION_POPUP_GO, offset=self._popup_offset, interval=2): self.device.click(MISSION_POPUP_GO) return True return False def handle_mission_popup_ack(self): if self.appear(MISSION_POPUP_GO, offset=self._popup_offset) \ and self.appear(MISSION_POPUP_ACK, offset=self._popup_offset, interval=2): self.device.click(MISSION_POPUP_ACK) return True return False """ Story """ story_popup_timout = Timer(10, count=20) map_has_fast_forward = False # Will be override in fast_forward.py # Area to detect the options, should include at least 3 options. _story_option_area = (730, 188, 1140, 480) # Background color of the left part of the option. _story_option_color = (99, 121, 156) _story_option_timer = Timer(2) def _story_option_buttons(self): """ Returns: list[Button]: List of story options, from upper to bottom. If no option found, return an empty list. """ image = color_similarity_2d(self.image_area(self._story_option_area), color=self._story_option_color) > 225 x_count = np.where(np.sum(image, axis=0) > 40)[0] if not len(x_count): return [] x_min, x_max = np.min(x_count), np.max(x_count) parameters = { # Option is 300`320px x 50~52px. 'height': 280, 'width': 45, 'distance': 50, # Chooses the relative height at which the peak width is measured as a percentage of its prominence. # 1.0 calculates the width of the peak at its lowest contour line, # while 0.5 evaluates at half the prominence height. # Must be at least 0. 'rel_height': 5, } y_count = np.sum(image, axis=1) peaks, properties = signal.find_peaks(y_count, **parameters) buttons = [] total = len(peaks) if not total: return [] for n, bases in enumerate(zip(properties['left_bases'], properties['right_bases'])): area = (x_min, bases[0], x_max, bases[1]) area = area_pad(area_offset(area, offset=self._story_option_area[:2]), pad=5) buttons.append( Button(area=area, color=self._story_option_color, button=area, name=f'STORY_OPTION_{n + 1}_OF_{total}')) return buttons def story_skip(self): if self.story_popup_timout.started() and not self.story_popup_timout.reached(): if self.handle_popup_confirm('STORY_SKIP'): self.story_popup_timout = Timer(10) self.interval_reset(STORY_SKIP) self.interval_reset(STORY_LETTERS_ONLY) return True if self.appear(STORY_LETTER_BLACK) and self.appear_then_click(STORY_LETTERS_ONLY, offset=True, interval=2): self.story_popup_timout.reset() return True if self._story_option_timer.reached() and self.appear(STORY_SKIP, offset=True, interval=0): options = self._story_option_buttons() if len(options): self.device.click(options[0]) self._story_option_timer.reset() self.story_popup_timout.reset() self.interval_reset(STORY_SKIP) self.interval_reset(STORY_LETTERS_ONLY) return True if self.appear_then_click(STORY_SKIP, offset=True, interval=2): self.story_popup_timout.reset() return True if self.appear_then_click(GAME_TIPS, offset=(20, 20), interval=2): self.story_popup_timout.reset() return True return False def handle_story_skip(self): if self.map_has_fast_forward: return False return self.story_skip() def ensure_no_story(self, skip_first_screenshot=True): logger.info('Ensure no story') story_timer = Timer(3, count=6).start() while 1: if skip_first_screenshot: skip_first_screenshot = False else: self.device.screenshot() if self.story_skip(): story_timer.reset() if story_timer.reached(): break def handle_map_after_combat_story(self): if not self.config.MAP_HAS_MAP_STORY: return False self.ensure_no_story()
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 # 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 os_mission_enter(self, skip_first_screenshot=True): """ Enter mission list and claim mission reward. Pages: in: MISSION_ENTER out: MISSION_CHECK """ logger.info('OS mission enter') confirm_timer = Timer(2, count=6).start() while 1: if skip_first_screenshot: skip_first_screenshot = False else: self.device.screenshot() if self.appear_then_click(MISSION_ENTER, offset=(200, 5), interval=5): confirm_timer.reset() continue if self.appear_then_click(MISSION_FINISH, offset=(20, 20), interval=2): confirm_timer.reset() continue if self.handle_popup_confirm('MISSION_FINISH'): confirm_timer.reset() continue if self.handle_map_get_items(): confirm_timer.reset() continue if self.handle_info_bar(): confirm_timer.reset() continue # End if self.appear(MISSION_CHECK, offset=(20, 20)) \ and not self.appear(MISSION_FINISH, offset=(20, 20)) \ and not self.appear(MISSION_CHECKOUT, offset=(20, 20)): # No mission found, wait to confirm. Missions might not be loaded so fast. if confirm_timer.reached(): break elif self.appear(MISSION_CHECK, offset=(20, 20)) \ and self.appear(MISSION_CHECKOUT, offset=(20, 20)): # Found one mission. break else: confirm_timer.reset()
def _enhance_choose(self, skip_first_screenshot=True): """ Pages: in: page_ship_enhance, without info_bar out: EQUIP_CONFIRM """ end_activate_timer = Timer(2, count=2) if self.config.DEVICE_CONTROL_METHOD == 'minitouch' else Timer(2, count=1) trapped_timer = Timer(15, count=3).start() trapped = False while 1: if skip_first_screenshot: skip_first_screenshot = False else: self.device.screenshot() if self.appear(EQUIP_CONFIRM, offset=(30, 30)): return True if not end_activate_timer.reached_and_reset(): continue ensured = self.equip_sidebar_ensure(index=4) if ensured: self.wait_until_appear(ENHANCE_RECOMMEND, offset=(5, 5), skip_first_screenshot=True) else: continue status = color_bar_percentage(self.device.image, area=ENHANCE_RELOAD.area, prev_color=(231, 178, 74)) logger.attr('Reload_enhanced', f'{int(status * 100)}%') choose = np.sum(np.array(self.device.image.crop(ENHANCE_FILLED.area)) > 200) > 100 if trapped or self.info_bar_count(): if status > 0.98: logger.info('Fully enhanced for this ship') swiped = self.equip_view_next(check_button=ENHANCE_RECOMMEND) self.ensure_no_info_bar() if not swiped: return False trapped_timer.reset() trapped = False continue else: if choose: logger.info('Unable to enhance this ship') swiped = self.equip_view_next(check_button=ENHANCE_RECOMMEND) self.ensure_no_info_bar() if not swiped: return False trapped_timer.reset() trapped = False continue else: logger.info('Enhancement material exhausted') return False if not trapped_timer.reached_and_reset() and self.appear_then_click(ENHANCE_RECOMMEND, offset=(5, 5), interval=2): self.device.sleep(0.3) self.device.click(ENHANCE_CONFIRM) else: logger.warning('Current status appears trapped, will force stat gauge check') trapped = True
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 meow_chores(self, skip_first_screenshot=True): """ Loop through all chore mechanics to get fort xp points Args: skip_first_screenshot (bool): Skip first screen shot or not Pages: in: MEOWFFICER_FORT out: MEOWFFICER_FORT """ self.interval_clear(GET_ITEMS_1) check_timer = Timer(1, count=2) confirm_timer = Timer(1.5, count=4).start() while 1: if skip_first_screenshot: skip_first_screenshot = False else: self.device.screenshot() if self.appear(MEOWFFICER_FORT_GET_XP_1) or \ self.appear(MEOWFFICER_FORT_GET_XP_2): check_timer.reset() confirm_timer.reset() continue if self.appear(GET_ITEMS_1, offset=5, interval=3): self.device.click(MEOWFFICER_FORT_CHECK) check_timer.reset() confirm_timer.reset() continue if check_timer.reached(): is_chore = self.image_color_count(MEOWFFICER_FORT_CHORE, color=(247, 186, 90), threshold=235, count=50) check_timer.reset() if is_chore: self.device.click(MEOWFFICER_FORT_CHORE) confirm_timer.reset() continue # End if self.appear(MEOWFFICER_FORT_CHECK, offset=(20, 20)): if confirm_timer.reached(): break else: confirm_timer.reset()
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() is_portal = self.map[location].is_portal 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 . extra = 4.5 if self.config.SUBMARINE_MODE == 'hunt_only' else 0 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() grid.image = np.array(self.device.image) if is_portal: self.update() grid = self.view[self.view.center_loca] # 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() # 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 # 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 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 # 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.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 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 battle_pass_receive(self, skip_first_screenshot=True): """ Returns: bool: If received. Pages: in: page_battle_pass out: page_battle_pass """ logger.hr('Battle pass receive', level=1) self.battle_status_click_interval = 2 confirm_timer = Timer(1, count=3).start() received = False while 1: if skip_first_screenshot: skip_first_screenshot = False else: self.device.screenshot() if self.appear_then_click(REWARD_RECEIVE, offset=(20, 20), interval=2): confirm_timer.reset() continue if self.appear_then_click(REWARD_RECEIVE_SP, offset=(20, 20), interval=2): confirm_timer.reset() continue if self.handle_battle_pass_popup(): confirm_timer.reset() continue if self.handle_popup_confirm('BATTLE_PASS'): # Lock new META ships confirm_timer.reset() continue if self.handle_get_items(): received = True confirm_timer.reset() continue if self.handle_get_ship(): received = True confirm_timer.reset() continue # End if self.appear(BATTLE_PASS_CHECK, offset=(20, 20)) and not self.appear( REWARD_RECEIVE, offset=(20, 20)): if confirm_timer.reached(): break else: confirm_timer.reset() return received
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 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 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 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 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() break # Additional if self.ui_additional(): continue # Reset connection for page in self.ui_pages: page.parent = None
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 _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 _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 _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 _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 _ash_assist_enter_from_map(self, offset=(200, 5), skip_first_screenshot=True): """ Args: offset: skip_first_screenshot: Pages: in: IN_MAP out: is_in_ash """ confirm_timer = Timer(1, count=2).start() while 1: if skip_first_screenshot: skip_first_screenshot = False else: self.device.screenshot() if self.appear_then_click(MAP_GOTO_GLOBE, offset=offset, interval=5): confirm_timer.reset() continue if self.appear_then_click(ASH_SHOWDOWN, offset=(30, 30), interval=5): confirm_timer.reset() continue if self.appear_then_click(ASH_ENTRANCE, offset=offset, interval=5): confirm_timer.reset() continue if self._handle_ash_beacon_reward(): confirm_timer.reset() continue if self.handle_popup_confirm('GOTO_GLOBE'): # Popup: Leaving current zone will terminate meowfficer searching confirm_timer.reset() continue if self.handle_map_event(): confirm_timer.reset() continue # End if self.is_in_ash(): if confirm_timer.reached(): break else: confirm_timer.reset()
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 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 reward_receive(self, oil, coin, exp, skip_first_screenshot=True): """ Args: oil (bool): coin (bool): exp (bool): skip_first_screenshot (bool): Returns: bool: If rewarded. Pages: in: page_reward out: page_reward, with info_bar if received """ if not oil and not coin and not exp: return False logger.hr('Reward receive') logger.info(f'oil={oil}, coin={coin}, exp={exp}') confirm_timer = Timer(1, count=3).start() # Set click interval to 0.3, because game can't respond that fast. click_timer = Timer(0.3) while 1: if skip_first_screenshot: skip_first_screenshot = False else: self.device.screenshot() if oil and click_timer.reached() and self.appear_then_click( OIL, interval=60): confirm_timer.reset() click_timer.reset() continue if coin and click_timer.reached() and self.appear_then_click( COIN, interval=60): confirm_timer.reset() click_timer.reset() continue if exp and click_timer.reached() and self.appear_then_click( EXP, interval=60): confirm_timer.reset() click_timer.reset() continue # End if confirm_timer.reached(): break logger.info('Reward receive end') return True
class InfoHandler(ModuleBase): """ Class to handle all kinds of message. """ """ Info bar """ def info_bar_count(self): if self.appear(INFO_BAR_3): return 3 elif self.appear(INFO_BAR_2): return 2 elif self.appear(INFO_BAR_1): return 1 else: return 0 def handle_info_bar(self): if self.info_bar_count(): self.wait_until_disappear(INFO_BAR_1) return True else: return False def ensure_no_info_bar(self, timeout=0.6): timeout = Timer(timeout) timeout.start() while 1: self.device.screenshot() self.handle_info_bar() if timeout.reached(): break """ Popup info """ _popup_offset = (3, 30) def handle_popup_confirm(self, name=''): if self.appear(POPUP_CANCEL, offset=self._popup_offset) \ and self.appear(POPUP_CONFIRM, offset=self._popup_offset, interval=2): POPUP_CONFIRM.name = POPUP_CONFIRM.name + '_' + name self.device.click(POPUP_CONFIRM) POPUP_CONFIRM.name = POPUP_CONFIRM.name[:-len(name) - 1] return True else: return False def handle_popup_cancel(self, name=''): if self.appear(POPUP_CONFIRM, offset=self._popup_offset) \ and self.appear(POPUP_CANCEL, offset=self._popup_offset, interval=2): POPUP_CANCEL.name = POPUP_CANCEL.name + '_' + name self.device.click(POPUP_CANCEL) POPUP_CANCEL.name = POPUP_CANCEL.name[:-len(name) - 1] return True else: return False def handle_urgent_commission(self, save_get_items=None): """ Args: save_get_items (bool): Returns: bool: """ if save_get_items is None: save_get_items = self.config.ENABLE_SAVE_GET_ITEMS appear = self.appear(GET_MISSION, offset=True, interval=2) if appear: logger.info('Get urgent commission') if save_get_items: self.handle_info_bar() self.device.save_screenshot('get_mission') self.device.click(GET_MISSION) return appear def handle_combat_low_emotion(self): if not self.config.IGNORE_LOW_EMOTION_WARN: return False return self.handle_popup_confirm('IGNORE_LOW_EMOTION') """ Story """ story_popup_timout = Timer(10, count=20) def story_skip(self): if self.story_popup_timout.started( ) and not self.story_popup_timout.reached(): if self.handle_popup_confirm('STORY_SKIP'): self.story_popup_timout = Timer(10) return True if self.appear_then_click(STORY_SKIP, offset=True, interval=2): self.story_popup_timout.reset() return True if self.appear(STORY_LETTER_BLACK) and self.appear_then_click( STORY_LETTERS_ONLY, offset=True, interval=2): self.story_popup_timout.reset() return True if self.appear_then_click(STORY_CHOOSE, offset=True, interval=2): self.story_popup_timout.reset() return True if self.appear_then_click(STORY_CHOOSE_2, offset=True, interval=2): self.story_popup_timout.reset() return True return False def handle_story_skip(self): if not self.config.ENABLE_MAP_CLEAR_MODE: return False if self.config.ENABLE_FAST_FORWARD: return False return self.story_skip() def ensure_no_story(self, skip_first_screenshot=True): logger.info('Ensure no story') story_timer = Timer(5, count=10).start() while 1: if skip_first_screenshot: skip_first_screenshot = False else: self.device.screenshot() if self.story_skip(): story_timer.reset() if story_timer.reached(): break def handle_map_after_combat_story(self): if not self.config.MAP_HAS_MAP_STORY: return False self.ensure_no_story()
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_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 _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 _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) siren_count = self.map.select(is_siren=True).count result_mystery = '' 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 . add = self.config.MAP_SIREN_MOVE_WAIT * min(self.config.MAP_SIREN_COUNT, siren_count) \ if self.config.MAP_HAS_MOVABLE_ENEMY and not self.config.ENABLE_FAST_FORWARD else 0 arrive_timer = Timer(0.3 + add) arrive_unexpected_timer = Timer(1.5 + add) # 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 = self.device.image # Ambush if self.handle_ambush(): ambushed_retry.start() # 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() if self.hp_withdraw_triggered(): self.withdraw() 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: 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': prev_enemy = self.map.select(is_enemy=True) self.full_scan(is_carrier_scan=True) diff = self.map.select(is_enemy=True).delete(prev_enemy) logger.info(f'Carrier spawn: {diff}') self.find_path_initial()
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 _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.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 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