示例#1
0
    def in_sight(self, location, sight=None):
        """Make sure location in camera sight

        Args:
            location:
            sight (tuple): Such as (-3, -1, 3, 2).
        """
        location = location_ensure(location)
        logger.info('In sight: %s' % location2node(location))
        if sight is None:
            sight = self.map.camera_sight

        diff = np.array(location) - self.camera
        if diff[1] > sight[3]:
            y = diff[1] - sight[3]
        elif diff[1] < sight[1]:
            y = diff[1] - sight[1]
        else:
            y = 0
        if diff[0] > sight[2]:
            x = diff[0] - sight[2]
        elif diff[0] < sight[0]:
            x = diff[0] - sight[0]
        else:
            x = 0
        self.focus_to((self.camera[0] + x, self.camera[1] + y))
示例#2
0
    def convert_map_to_grid(self, location):
        """If self.grids doesn't contain this location, focus camera on the location and re-convert it.

        Args:
            location: Grid instance in self.map.

        Returns:
            Grid: Grid instance in self.grids.
        """
        location = location_ensure(location)

        local = np.array(location) - self.camera + self.view.center_loca
        logger.info('Global %s (camera=%s) -> Local %s (center=%s)' % (
            location2node(location),
            location2node(self.camera),
            location2node(local),
            location2node(self.view.center_loca)
        ))
        if local in self.view:
            return self.view[local]
        else:
            logger.warning('Convert global to local Failed.')
            self.focus_to(location)
            local = np.array(location) - self.camera + self.view.center_loca
            return self.view[local]
示例#3
0
    def convert_local_to_global(self, location):
        """
        If self.map doesn't contain this location, camera might be wrong, correct camera and re-convert it.

        Args:
            location: Grid instance in self.view

        Returns:
            Grid: Grid instance in self.map
        """
        location = location_ensure(location)

        global_ = np.array(location) + self.camera - self.view.center_loca
        logger.info(
            'Global %s (camera=%s) <- Local %s (center=%s)' %
            (location2node(global_), location2node(self.camera),
             location2node(location), location2node(self.view.center_loca)))

        if global_ in self.map:
            return self.map[global_]
        else:
            logger.warning('Convert local to global Failed.')
            self.ensure_edge_insight(reverse=True)
            global_ = np.array(location) + self.camera - self.view.center_loca
            return self.map[global_]
示例#4
0
 def goto(self, location, optimize=True, expected=''):
     # self.device.sleep(1000)
     location = location_ensure(location)
     if optimize and (self.config.MAP_HAS_AMBUSH
                      or self.config.MAP_HAS_FLEET_STEP
                      or self.config.MAP_HAS_PORTAL
                      or self.config.MAP_HAS_MAZE):
         nodes = self.map.find_path(location, step=self.fleet_step)
         for node in nodes:
             if self.maze_active_on(node):
                 logger.info(
                     f'Maze is active on {location2node(node)}, bouncing to wait'
                 )
                 for _ in range(10):
                     grids = self.map[node].maze_nearby.delete(
                         self.map.select(is_fleet=True))
                     if grids.select(is_enemy=False):
                         grids = grids.select(is_enemy=False)
                     grids = grids.sort('cost')
                     self._goto(grids[0], expected='')
             try:
                 self._goto(node,
                            expected=expected if node == nodes[-1] else '')
             except MapWalkError:
                 logger.warning('Map walk error.')
                 self.ensure_edge_insight()
                 nodes_ = self.map.find_path(node, step=1)
                 for node_ in nodes_:
                     self._goto(
                         node_,
                         expected=expected if node == nodes[-1] else '')
     else:
         self._goto(location, expected=expected)
示例#5
0
    def convert_map_to_grid(self, location):
        """If self.grids doesn't contain this location, focus camera on the location and re-convert it.

        Args:
            location: Grid instance in self.map.

        Returns:
            Grid: Grid instance in self.grids.
        """
        location = location_ensure(location)

        grid = np.array(location) - self.camera + self.grids.center_grid
        logger.info('Convert_map_to_grid')
        logger.info(f'Map: {location2node(location)}, '
                    f'Camera: {location2node(self.camera)}, '
                    f'Center: {location2node(self.grids.center_grid)}, '
                    f'grid: {location2node(grid)}')
        if grid in self.grids:
            return self.grids[grid]
        else:
            logger.warning('Convert_map_to_grid Failed.')
            self.grids.save_error_image()
            self.focus_to(location)
            grid = np.array(location) - self.camera + self.grids.center_grid
            return self.grids[grid]
示例#6
0
    def submarine_move_near_boss(self, boss):
        """
        Args:
            boss (tuple, str, GridInfo): Destination.

        Returns:
            bool: If submarine moved
        """
        if not (self.is_call_submarine_at_boss
                and self.map.select(is_submarine_spawn_point=True)):
            return False
        if self.config.Submarine_DistanceToBoss == 'use_U522_skill':
            logger.info('Going to use U522 skill, skip moving submarines')
            return False

        boss = location_ensure(boss)
        logger.info(f'Move submarine near {location2node(boss)}')

        self.map.find_path_initial(self.fleet_submarine_location,
                                   has_ambush=False,
                                   has_enemy=False)
        self.map.show_cost()

        def get_location(distance=2):
            grids = self.map.select(is_land=False).filter(lambda grid: np.sum(
                np.abs(np.subtract(grid.location, boss))) <= distance)
            if grids:
                return grids.sort('cost')[0].location
            elif distance > 0:
                logger.info(
                    f'Unable to find a grid near boss in distance {distance}, fallback to {distance - 1}'
                )
                return get_location(distance - 1)
            else:
                logger.warning(
                    f'Unable to find a grid near boss in distance {distance}, return boss position'
                )
                return boss

        distance_dict = {
            'to_boss_position': 0,
            '1_grid_to_boss': 1,
            '2_grid_to_boss': 2
        }
        distance_to_boss = distance_dict.get(
            self.config.Submarine_DistanceToBoss, 0)
        logger.attr('Distance to boss', distance_to_boss)

        if np.sum(np.abs(np.subtract(self.fleet_submarine_location,
                                     boss))) <= distance_to_boss:
            logger.info('Boss is already in hunting zone')
            self.find_path_initial()
            return False
        else:
            near = get_location(distance_to_boss)
            self.find_path_initial()
            logger.info(f'Move submarine to {location2node(near)}')
            return self.submarine_goto(near)
示例#7
0
    def grid_is_in_sight(self, grid, camera=None, sight=None):
        location = location_ensure(grid)
        camera = location_ensure(camera) if camera is not None else self.camera
        if sight is None:
            sight = self.map.camera_sight

        diff = np.array(location) - camera
        if diff[1] > sight[3]:
            y = diff[1] - sight[3]
        elif diff[1] < sight[1]:
            y = diff[1] - sight[1]
        else:
            y = 0
        if diff[0] > sight[2]:
            x = diff[0] - sight[2]
        elif diff[0] < sight[0]:
            x = diff[0] - sight[0]
        else:
            x = 0
        return x == 0 and y == 0
示例#8
0
    def convert_radar_to_local(self, location):
        """
        Converts the coordinate on radar to the coordinate of local map view,
        also handles a rare game bug.

        Usually, OPSI camera focus on current fleet, which is (5, 4) in local view.
        The convert should be `local = view[np.add(radar, view.center_loca)]`
        However, Azur Lane may bugged, not focusing current.
        In this case, the convert should base on fleet position.

        Args:
            location: (x, y), Position on radar.

        Returns:
            OSGrid: Grid instance in self.view
        """
        location = location_ensure(location)

        fleets = self.view.select(is_current_fleet=True)
        if fleets.count == 1:
            center = fleets[0].location
        elif fleets.count > 1:
            logger.warning(
                f'Convert radar to local, but found multiple current fleets: {fleets}'
            )
            distance = np.linalg.norm(
                np.subtract(fleets.location, self.view.center_loca))
            center = fleets.grids[np.argmin(distance)].location
            logger.warning(
                f'Assuming the nearest fleet to camera canter is current fleet: {location2node(center)}'
            )
        else:
            logger.warning(
                f'Convert radar to local, but current fleet not found. '
                f'Assuming camera center is current fleet: {location2node(self.view.center_loca)}'
            )
            center = self.view.center_loca

        try:
            local = self.view[np.add(location, center)]
        except KeyError:
            logger.warning(
                f'Convert radar to local, but target grid not in local view. '
                f'Assuming camera center is current fleet: {location2node(self.view.center_loca)}'
            )
            center = self.view.center_loca
            local = self.view[np.add(location, center)]

        logger.info('Radar %s -> Local %s (fleet=%s)' %
                    (str(location), location2node(
                        local.location), location2node(center)))
        return local
示例#9
0
 def find_path_initial(self, grid=None):
     """
     Args:
         grid (tuple, GridInfo): Current fleet grid
     """
     if grid is None:
         grid = self.fleet_current
     else:
         grid = location_ensure(grid)
     if self.fleet_1_location:
         self.map[self.fleet_1_location].is_fleet = True
     if self.fleet_2_location:
         self.map[self.fleet_2_location].is_fleet = True
     self.map.find_path_initial(grid, has_ambush=self.config.MAP_HAS_AMBUSH)
示例#10
0
    def maze_active_on(self, grid):
        """
        Args:
            grid:

        Returns:
            bool: If maze wall is on a the specific grid.
        """
        if not self.config.MAP_HAS_MAZE:
            return False

        grid = self.map[location_ensure(grid)]
        if not grid.is_maze:
            return False
        return self.round % self.map.maze_round in grid.maze_round
示例#11
0
 def goto(self, location, optimize=True, expected=''):
     # self.device.sleep(1000)
     location = location_ensure(location)
     if (self.config.MAP_HAS_AMBUSH or self.config.MAP_HAS_FLEET_STEP or self.config.MAP_HAS_PORTAL) and optimize:
         nodes = self.map.find_path(location, step=self.fleet_step)
         for node in nodes:
             try:
                 self._goto(node, expected=expected if node == nodes[-1] else '')
             except MapWalkError:
                 logger.warning('Map walk error.')
                 self.ensure_edge_insight()
                 nodes_ = self.map.find_path(node, step=1)
                 for node_ in nodes_:
                     self._goto(node_, expected=expected if node == nodes[-1] else '')
     else:
         self._goto(location, expected=expected)
示例#12
0
    def goto(self, location, optimize=None, expected=''):
        """
        Args:
            location (tuple, str, GridInfo): Destination.
            optimize (bool): Optimize walk path, reducing ambushes.
                If None, loads MAP_WALK_OPTIMIZE
            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)
        if optimize is None:
            optimize = self.config.MAP_WALK_OPTIMIZE

        # self.device.sleep(1000)
        if optimize and (self.config.MAP_HAS_AMBUSH
                         or self.config.MAP_HAS_FLEET_STEP
                         or self.config.MAP_HAS_PORTAL
                         or self.config.MAP_HAS_MAZE):
            nodes = self.map.find_path(location, step=self.fleet_step)
            for node in nodes:
                if self.maze_active_on(node):
                    logger.info(
                        f'Maze is active on {location2node(node)}, bouncing to wait'
                    )
                    for _ in range(10):
                        grids = self.map[node].maze_nearby.delete(
                            self.map.select(is_fleet=True))
                        if grids.select(is_enemy=False):
                            grids = grids.select(is_enemy=False)
                        grids = grids.sort('cost')
                        self._goto(grids[0], expected='')
                try:
                    self._goto(node,
                               expected=expected if node == nodes[-1] else '')
                except MapWalkError:
                    logger.warning('Map walk error.')
                    self.predict()
                    self.ensure_edge_insight()
                    nodes_ = self.map.find_path(node, step=1)
                    for node_ in nodes_:
                        self._goto(
                            node_,
                            expected=expected if node == nodes[-1] else '')
        else:
            self._goto(location, expected=expected)
示例#13
0
    def focus_to(self, location, swipe_limit=(3, 2)):
        """Focus camera on a grid

        Args:
            location: grid
            swipe_limit(tuple): (x, y). Limit swipe in (-x, -y, x, y).
        """
        location = location_ensure(location)
        logger.info('Focus to: %s' % location2node(location))

        while 1:
            vector = np.array(location) - self.camera
            swipe = tuple(
                np.min([np.abs(vector), swipe_limit], axis=0) *
                np.sign(vector))
            self.map_swipe(swipe)

            if np.all(np.abs(vector) <= 0):
                break
示例#14
0
    def focus_to(self, location, swipe_limit=(3, 2)):
        """Focus camera on a grid

        Args:
            location: grid
            swipe_limit(tuple): (x, y). Limit swipe in (-x, -y, x, y).
        """
        location = location_ensure(location)
        logger.info('Focus to: %s' % location2node(location))

        vector = np.array(location) - self.camera
        vector, sign = np.abs(vector), np.sign(vector)
        while 1:

            swipe = (
                vector[0] if vector[0] < swipe_limit[0] else swipe_limit[0],
                vector[1] if vector[1] < swipe_limit[1] else swipe_limit[1])
            self.map_swipe(tuple(sign * swipe))

            vector -= swipe
            if np.all(np.abs(vector) <= 0):
                break
示例#15
0
    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
示例#16
0
    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()
示例#17
0
    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()
示例#18
0
    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.fleet_ensure(self.fleet_current_index)
            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(allow_error=True)
                    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 expected != 'combat_boss' \
                            and not ('combat' in expected and grid.may_boss) \
                            and (grid.predict_fleet() or 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()
        if self.config.MAP_HAS_DECOY_ENEMY:
            if result == 'nothing' and expected == 'combat':
                raise MapEnemyMoved