def os_meowfficer_farming(self, hazard_level=5, daily=False): """ Args: hazard_level (int): 1 to 6. Recommend 3 or 5 for higher meowfficer searching point per action points ratio. daily (bool): If false, loop until AP lower than OS_ACTION_POINT_PRESERVE. If True, loop until run out of AP (not including boxes). If True and ENABLE_OS_ASH_ATTACK, loop until ash beacon fully collected today, then loop until run out of AP (not including boxes). """ logger.hr(f'OS meowfficer farming, hazard_level={hazard_level}', level=1) while 1: if daily: if self.config.ENABLE_OS_ASH_ATTACK: if self._ash_fully_collected: self.config.OS_ACTION_POINT_BOX_USE = False else: self.config.OS_ACTION_POINT_BOX_USE = False # (1252, 1012) is the coordinate of zone 134 (the center zone) in os_globe_map.png zones = self.zone_select(hazard_level=hazard_level) \ .delete(SelectedGrids([self.zone])) \ .delete(SelectedGrids(self.zones.select(is_port=True))) \ .sort_by_clock_degree(center=(1252, 1012), start=self.zone.location) self.globe_goto(zones[0]) self.run_auto_search() self.handle_fleet_repair(revert=False)
def battle_0(self): if self.config.C72_BOSS_FLEET_STEP_ON_A3: if self.fleet_2_step_on(FLEET_2_STEP_ON, roadblocks=[ROAD_MAIN]): return True ignore = None if self.fleet_at(A3, fleet=2) and A1.enemy_scale != 3 and not self.fleet_at(A1, fleet=1): ignore = SelectedGrids([A2]) if self.fleet_at(G3, fleet=2): ignore = SelectedGrids([H3]) self.clear_all_mystery(nearby=False, ignore=ignore) else: self.clear_all_mystery(nearby=False) if self.clear_roadblocks([ROAD_MAIN], strongest=True): return True if self.clear_potential_roadblocks([ROAD_MAIN], strongest=True): return True if self.clear_enemy(scale=(3,)): return True if self.clear_grids_for_faster(GRIDS_FOR_FASTER, scale=(2,)): return True if self.clear_enemy(scale=(2,)): return True if self.clear_grids_for_faster(GRIDS_FOR_FASTER): return True return self.battle_default()
def clear_potential_roadblocks(self, roads, **kwargs): """Avoid roadblock that only has one grid empty. Args: roads(list[RoadGrids]): Returns: bool: True if clear an enemy. """ grids = SelectedGrids([]) for road in roads: grids = grids.add(road.potential_roadblocks()) target = self.config.EnemyPriority_EnemyScaleBalanceWeight if target == 'S3_enemy_first': grids = self.select_grids(grids, strongest=True, **kwargs) elif target == 'S1_enemy_first': grids = self.select_grids(grids, weakest=True, **kwargs) else: grids = self.select_grids(grids, **kwargs) if grids: logger.hr('Avoid potential roadblock') self.show_select_grids(grids, **kwargs) self.clear_chosen_enemy(grids[0]) return True return False
def clear_all_objects(self, grid=None): """Method to clear all objects around specific grid. Args: grid (GridInfo): Returns: int: Cleared count """ if grid is not None: logger.hr(f'clear_all_objects: {grid}', level=2) for count in range(0, 10): grids = self.map.select(is_resource=True) \ .add(self.map.select(is_enemy=True)) \ .add(self.map.select(is_meowfficer=True)) \ .add(self.map.select(is_exclamation=True)).delete(self.map.select(is_interactive_only=True)) if grid is not None: grids = SelectedGrids([ g for g in grids if self.grid_is_in_sight(g, camera=grid) ]) if not grids: logger.info(f'OS object cleared: {count}') return count grids = grids.sort_by_camera_distance(self.fleet_current) logger.hr('Clear all resource') logger.info(f'Grids: {grids}') logger.info(f'Clear object {grids[0]}') self.goto(grids[0], expected=self._get_goto_expected(grids[0])) self.handle_meowfficer_searching() logger.warning('Too many objects to clear, stopped') return 10
def os_meowfficer_farming(self): """ Recommend 3 or 5 for higher meowfficer searching point per action points ratio. """ logger.hr( f'OS meowfficer farming, hazard_level={self.config.OpsiMeowfficerFarming_HazardLevel}', level=1) self.action_point_limit_override() while 1: self.config.OS_ACTION_POINT_PRESERVE = self.config.OpsiMeowfficerFarming_ActionPointPreserve if self.config.OpsiAshBeacon_AshAttack \ and not self._ash_fully_collected \ and self.config.OpsiAshBeacon_EnsureFullyCollected: logger.info( 'Ash beacon not fully collected, ignore action point limit temporarily' ) self.config.OS_ACTION_POINT_PRESERVE = 0 logger.attr('OS_ACTION_POINT_PRESERVE', self.config.OS_ACTION_POINT_PRESERVE) # (1252, 1012) is the coordinate of zone 134 (the center zone) in os_globe_map.png if self.config.OpsiMeowfficerFarming_TargetZone != 0: try: zone = self.name_to_zone( self.config.OpsiMeowfficerFarming_TargetZone) except ScriptError: logger.warning( f'wrong zone_id input:{self.config.OpsiMeowfficerFarming_TargetZone}' ) self.config.task_stop(message=f'wrong input, task stopped') else: logger.hr(f'OS meowfficer farming, zone_id={zone.zone_id}', level=1) self.globe_goto(zone) self.fleet_set(self.config.OpsiFleet_Fleet) self.os_order_execute( recon_scan=False, submarine_call=self.config.OpsiFleet_Submarine) self.run_auto_search() if not self.handle_after_auto_search(): self.globe_goto(self.zone_nearest_azur_port(zone=zone)) self.config.check_task_switch() else: zones = self.zone_select(hazard_level=self.config.OpsiMeowfficerFarming_HazardLevel) \ .delete(SelectedGrids([self.zone])) \ .delete(SelectedGrids(self.zones.select(is_port=True))) \ .sort_by_clock_degree(center=(1252, 1012), start=self.zone.location) logger.hr(f'OS meowfficer farming, zone_id={zones[0].zone_id}', level=1) self.globe_goto(zones[0]) self.fleet_set(self.config.OpsiFleet_Fleet) self.os_order_execute( recon_scan=False, submarine_call=self.config.OpsiFleet_Submarine) self.run_auto_search() self.handle_after_auto_search() self.config.check_task_switch()
def _commission_choose(self, daily, urgent): """ Args: daily (SelectedGrids): urgent (SelectedGrids): Returns: SelectedGrids, SelectedGrids: Chosen daily commission, Chosen urgent commission """ # Count Commission total = daily.add_by_eq(urgent) self.max_commission = 4 for comm in total: if comm.genre == 'event_daily': self.max_commission = 5 running_count = int(np.sum([1 for c in total if c.status == 'running'])) logger.attr('Running', f'{running_count}/{self.max_commission}') if running_count >= self.max_commission: return SelectedGrids([]), SelectedGrids([]) # Filter COMMISSION_FILTER.load(self.config.Commission_CommissionFilter) run = COMMISSION_FILTER.apply(total.grids, func=self._commission_check) logger.attr('Filter_sort', ' > '.join([str(c) for c in run])) run = SelectedGrids(run) # Add shortest no_shortest = run.delete(SelectedGrids(['shortest'])) if no_shortest.count + running_count < self.max_commission: if no_shortest.count < run.count: logger.info( 'Not enough commissions to run, add shortest daily commissions' ) COMMISSION_FILTER.load(SHORTEST_FILTER) shortest = COMMISSION_FILTER.apply(daily, func=self._commission_check) run = no_shortest.add_by_eq(SelectedGrids(shortest)) logger.attr('Filter_sort', ' > '.join([str(c) for c in run])) else: logger.info('Not enough commissions to run') # Separate daily and urgent run = run[:self.max_commission - running_count] daily_choose = run.intersect_by_eq(daily) urgent_choose = run.intersect_by_eq(urgent) if daily_choose: logger.info('Choose daily commission') for comm in daily_choose: logger.info(comm) if urgent_choose: logger.info('Choose urgent commission') for comm in urgent_choose: logger.info(comm) return daily_choose, urgent_choose
def _commission_scan_list(self): """ Returns: SelectedGrids: SelectedGrids containing Commission objects """ commission = SelectedGrids([]) for _ in range(15): new = self._commission_detect(self.device.image) commission = commission.add_by_eq(new) # End if not self._commission_swipe(): break return commission
def list_device(self): """ Returns: SelectedGrids[AdbDeviceWithStatus]: """ class AdbDeviceWithStatus(AdbDevice): def __init__(self, client: AdbClient, serial: str, status: str): self.status = status super().__init__(client, serial) def __str__(self): return f'AdbDevice({self.serial}, {self.status})' __repr__ = __str__ def __bool__(self): return True devices = [] with self.adb_client._connect() as c: c.send_command("host:devices") c.check_okay() output = c.read_string_block() for line in output.splitlines(): parts = line.strip().split("\t") if len(parts) != 2: continue device = AdbDeviceWithStatus(self.adb_client, parts[0], parts[1]) devices.append(device) return SelectedGrids(devices)
def nearest_object(self, camera_sight=(-4, -3, 3, 3)): """ Args: camera_sight: Returns: RadarGrid: Or None if no objects """ objects = [] for grid in self: if grid.is_port: continue if grid.is_enemy or grid.is_resource or grid.is_meowfficer \ or grid.is_exclamation or grid.is_question or grid.is_archive: objects.append(grid) objects = SelectedGrids(objects).sort_by_camera_distance((0, 0)) if not objects: return None nearest = objects[0] limited = point_limit(nearest.location, area=camera_sight) if nearest.location == limited: return nearest else: return self[limited]
def _tactical_books_filter_exp(self): """ Complex filter to remove specific grade books from self.books based on current progress of the tactical skill. """ # Shorthand referencing first, last = self.books[0], self.books[-1] # Read 'current' and 'remain' will be inaccurate # since first exp_value is factored into it current, remain, total = SKILL_EXP.ocr(self.device.image) # Max level in progress; so selective books # should be removed to prevent waste if total == 5800: if current == 0: # Lvl 9+1, using first will reach max level # Swap to last and re-OCR self._tactical_book_select(last) current, remain, total = SKILL_EXP.ocr(self.device.image) if current == 0: # Still Lvl 9+1 even with last # Must re-calculate to accurately gauge current = total - last.exp_value remain = last.exp_value else: # Lvl 9, so can calculate normally # but use last current -= last.exp_value remain += last.exp_value else: # Lvl 9, so can calculate normally current -= first.exp_value remain += first.exp_value logger.info('About to reach level 10; will remove ' 'detected books based on actual ' f'progress: {current}/{total}; {remain}') def filter_exp_func(book): # Retain at least non-T1 bonus books if nothing else if book.exp_value == 100: return True # Acquire 'overflow' for respective tier book if enabled overflow = 0 if self.config.ControlExpOverflow_Enable: overflow = getattr( self.config, f'ControlExpOverflow_T{book.tier}Allow') # Remove book if sum to be gained exceeds total (+ overflow) if (current + book.exp_value) > (total + overflow): return False return True before = self.books.count self.books = SelectedGrids( [book for book in self.books if filter_exp_func(book)]) logger.attr('Filtered', before - self.books.count) logger.attr('Books', str(self.books))
def map_covered(self, nodes): """ Args: nodes (list): Contains str. """ self._map_covered = SelectedGrids( [self[node2location(node)] for node in nodes])
def camera_data_spawn_point(self, nodes): """ Args: nodes (list): Contains str. """ self._camera_data_spawn_point = SelectedGrids( [self[node2location(node)] for node in nodes])
def fleet_2_push_forward(self): """Move fleet 2 to the grid with lower grid.weight This will reduce the possibility of Boss fleet get stuck by enemies, especially for those one-way-road map from chapter 7 to chapter 9. Know more (in Chinese simplified): 9章道中战最小化路线规划 (Route Planning for battle minimization in chapter 9) https://wiki.biligame.com/blhx/9%E7%AB%A0%E9%81%93%E4%B8%AD%E6%88%98%E6%9C%80%E5%B0%8F%E5%8C%96%E8%B7%AF%E7%BA%BF%E8%A7%84%E5%88%92 Returns: bool: If pushed forward. """ if not self.config.FLEET_2: return False logger.info('Fleet_2 push forward') grids = self.map.select(is_land=False).sort('weight', 'cost') if self.map[self.fleet_2_location].weight <= grids[0].weight: logger.info('Fleet_2 pushed to destination') return False fleets = SelectedGrids([self.map[self.fleet_1_location], self.map[self.fleet_2_location]]) grids = grids.select(is_accessible_2=True, is_sea=True).delete(fleets) if not grids: logger.info('Fleet_2 has no where to push') return False if self.map[self.fleet_2_location].weight <= grids[0].weight: logger.info('Fleet_2 pushed to closest grid') return False logger.info(f'Grids: {grids}') logger.info(f'Push forward: {grids[0]}') self.fleet_2.goto(grids[0]) self.fleet_1.switch_to() return True
def _commission_detect(self, image): """ Get all commissions from an image. Args: image: Pillow image Returns: SelectedGrids: """ commission = [] # Find white lines under each commission to locate them. # (597, 0, 619, 720) is somewhere with white lines only. color_height = np.mean(image.crop((597, 0, 619, 720)).convert('L'), axis=1) parameters = {'height': 200, 'distance': 100} peaks, _ = signal.find_peaks(color_height, **parameters) # 67 is the height of commission list header # 117 is the height of one commission card. peaks = [y for y in peaks if y > 67 + 117] # Add commission to list for y in peaks: comm = Commission(image, y=y, config=self.config) logger.attr('Commission', comm) repeat = len([c for c in commission if c == comm]) comm.repeat_count += repeat commission.append(comm) return SelectedGrids(commission)
def zones(self): """ Returns: SelectedGrids: """ return SelectedGrids( [Zone(zone_id, info) for zone_id, info in DIC_OS_MAP.items()])
def __init__(self, name=None): self.name = name self.grids = {} self._shape = (0, 0) self._map_data = '' self._map_data_loop = '' self._weight_data = '' self._wall_data = '' self._portal_data = [] self._land_based_data = [] self._maze_data = [] self.maze_round = 9 self._fortress_data = [(), ()] self._bouncing_enemy_data = [] self._spawn_data = [] self._spawn_data_stack = [] self._spawn_data_loop = [] self._spawn_data_use_loop = False self._camera_data = [] self._camera_data_spawn_point = [] self._map_covered = SelectedGrids([]) self._ignore_prediction = [] self.in_map_swipe_preset_data = None self.poor_map_data = False self.camera_sight = (-3, -1, 3, 2) self.grid_connection = {}
def _commission_scan_list(self): """ Returns: SelectedGrids: SelectedGrids containing Commission objects """ self.device.click_record_clear() commission = SelectedGrids([]) for _ in range(15): new = self.commission_detect(trial=2) commission = commission.add_by_eq(new) # End if not self._commission_swipe(): break self.device.click_record_clear() return commission
def battle_5(self): ignore = None if self.fleet_at(A3, fleet=2): ignore = SelectedGrids([A2]) if self.fleet_at(G3, fleet=2): ignore = SelectedGrids([H3]) self.clear_all_mystery(nearby=False, ignore=ignore) if self.clear_roadblocks([ROAD_MAIN]): return True if self.fleet_at(A3, fleet=2) and A2.is_mystery: self.fleet_2.clear_chosen_mystery(A2) if self.fleet_at(G3, fleet=2) and H3.is_mystery: self.fleet_2.clear_chosen_mystery(H3) return self.fleet_2.clear_boss()
def _tactical_books_get(self, skip_first_screenshot=True): """ Get books. Handle loadings, wait 10 times at max. When TACTICAL_CLASS_START appears, game may stuck in loading, wait and retry detection. If loading still exists, raise ScriptError. Returns: BookGroup: Pages: in: TACTICAL_CLASS_START out: TACTICAL_CLASS_START """ prev = SelectedGrids([]) for n in range(1, 16): if skip_first_screenshot: skip_first_screenshot = False else: self.device.screenshot() self.handle_info_bar( ) # info_bar appears when get ship in Launch Ceremony commissions if not self.appear(TACTICAL_CLASS_START, offset=(30, 30)): logger.info('Not in TACTICAL_CLASS_START anymore, exit') return False books = SelectedGrids([ Book(self.device.image, button) for button in BOOKS_GRID.buttons ]).select(valid=True) self.books = books logger.attr('Book_count', books.count) logger.attr('Books', str(books)) # End if books and books.count == prev.count: return books else: prev = books if n % 3 == 0: self.device.sleep(3) continue logger.warning('No book found.') raise ScriptError('No book found, after 15 attempts.')
def to_selected(self, grids): """ Args: grids (list): Returns: SelectedGrids: """ return SelectedGrids([self[location_ensure(loca)] for loca in grids])
def map_covered(self): """ Returns: SelectedGrids: """ covered = [] for grid in self: covered += self.grid_covered(grid).grids return SelectedGrids(covered).add(self._map_covered)
def _clear_os_world(self): for hazard_level in range(self.config.OS_WORLD_MIN_LEVEL, (self.config.OS_WORLD_MAX_LEVEL + 1)): zones = self.zone_select(hazard_level=hazard_level) \ .delete(SelectedGrids(self.zones.select(is_port=True))) \ .sort_by_clock_degree(center=(1252, 1012), start=self.zone.location) for zone in zones: if not self.globe_goto(zone, stop_if_safe=True): continue self.run_auto_search() self.handle_fleet_repair(revert=False)
def clear_first_roadblocks(self, roads, **kwargs): """Ensure every roadblocks have one grid with is_cleared=True. Args: roads(list[RoadGrids]): Returns: bool: True if clear an enemy. """ grids = SelectedGrids([]) for road in roads: grids = grids.add(road.first_roadblocks()) grids = self.select_grids(grids, **kwargs) if grids: logger.hr('Clear first roadblock') self.show_select_grids(grids, **kwargs) self.clear_chosen_enemy(grids[0]) return True return False
def clear_potential_roadblocks(self, roads, **kwargs): """Avoid roadblock that only has one grid empty. Args: roads(list[RoadGrids]): Returns: bool: True if clear an enemy. """ grids = SelectedGrids([]) for road in roads: grids = grids.add(road.potential_roadblocks()) grids = self.select_grids(grids, **kwargs) if grids: logger.hr('Avoid potential roadblock') self.show_select_grids(grids, **kwargs) self.clear_chosen_enemy(grids[0]) return True return False
def clear_roadblocks(self, roads, **kwargs): """Clear roadblocks. Args: roads(list[RoadGrids]): Returns: bool: True if clear an enemy. """ grids = SelectedGrids([]) for road in roads: grids = grids.add(road.roadblocks()) grids = self.select_grids(grids, **kwargs) if grids: logger.hr('Clear roadblock') self.show_select_grids(grids, **kwargs) self.clear_chosen_enemy(grids[0]) return True return False
def get_boss_leave_button(self): for grid in self.view: if grid.predict_current_fleet(): return None grids = [grid for grid in self.view if grid.predict_caught_by_siren()] if len(grids) == 1: center = grids[0] elif len(grids) > 1: logger.warning( f'Found multiple fleets in boss ({grids}), use the center one') center = SelectedGrids(grids).sort_by_camera_distance( self.view.center_loca)[0] else: logger.warning('No fleet in boss, use camera center instead') center = self.view[self.view.center_loca] logger.info(f'Fleet in boss: {center}') # The left half grid next to the center grid. area = corner2inner( center.grid2screen(area2corner((1, 0.25, 1.5, 0.75)))) button = Button(area=area, color=(), button=area, name='BOSS_LEAVE') return button
def battle_3(self): if self.config.C72MysteryFarming_StepOnA3: ignore = None if self.fleet_at(A3, fleet=2): ignore = SelectedGrids([A2]) if self.fleet_at(G3, fleet=2): ignore = SelectedGrids([H3]) self.clear_all_mystery(nearby=False, ignore=ignore) if self.fleet_at(A3, fleet=2) and A2.is_mystery: self.fleet_2.clear_chosen_mystery(A2) if self.fleet_at(G3, fleet=2) and H3.is_mystery: self.fleet_2.clear_chosen_mystery(H3) else: self.clear_all_mystery(nearby=False) if self.map.select(is_mystery=True, is_accessible=False): logger.info('Roadblock blocks mystery.') if self.fleet_1.clear_roadblocks([ROAD_MAIN]): return True if not self.map.select(is_mystery=True): self.withdraw()
def battle_0(self): if self.fleet_2_step_on(FLEET_2_STEP_ON, roadblocks=[ROAD_MAIN]): return True ignore = None if self.fleet_at(A3, fleet=2): ignore = SelectedGrids([A2]) if self.fleet_at(G3, fleet=2): ignore = SelectedGrids([H3]) self.clear_all_mystery(nearby=False, ignore=ignore) if self.clear_roadblocks([ROAD_MAIN], strongest=True): return True if self.clear_enemy(scale=(3, )): return True if self.clear_potential_roadblocks([ROAD_MAIN], strongest=True): return True if self.clear_enemy(strongest=True, weight=True): return True return self.battle_default()
def commission_detect(self, trial=1, area=None, skip_first_screenshot=True): """ Args: trial (int): Retry if has one invalid commission, usually because info_bar didn't disappear completely. area (tuple): skip_first_screenshot (bool): Returns: SelectedGrids: """ commissions = SelectedGrids([]) for _ in range(trial): if skip_first_screenshot: skip_first_screenshot = False else: self.device.screenshot() image = self.device.image if area is not None: image = crop(image, area) commissions = self._commission_detect(image) if commissions.count >= 2 and commissions.select( valid=False).count == 1: logger.info( 'Found 1 invalid commission, retry commission detect') continue else: return commissions logger.info('trials of commission detect exhausted, stop') return commissions
def grid_covered(self, grid, location=None): """ Args: grid (GridInfo) location (list[tuple[int]]): Relative coordinate of the covered grid. Returns: SelectedGrids: """ if location is None: covered = [tuple(np.array(grid.location) + upper) for upper in grid.covered_grid()] else: covered = [tuple(np.array(grid.location) + upper) for upper in location] covered = [self[upper] for upper in covered if upper in self] return SelectedGrids(covered)