コード例 #1
0
    def unit_solve_combat(self, unit: Unit, current_command: Action) -> Action:
        if self.move_type == MoveType.DefensiveRetreat:
            return current_command

        closest = self.closest_units.get(unit.tag)
        if not closest or closest.distance_to(unit) > 14:
            # not in combat, follow the army
            return current_command

        time = self.knowledge.ai.time
        relevant_enemies = self.cache.enemy_in_range(unit.position, 14).not_structure.not_flying

        if time < self.last_used_any + INTERVAL or len(relevant_enemies) < 4:
            return self.stay_safe(unit, current_command)

        center = relevant_enemies.center

        if self.cd_manager.is_ready(unit.tag, AbilityId.EFFECT_PURIFICATIONNOVA):
            distance = self.pather.walk_distance(unit.position, center)
            if distance < 9:
                self.last_used_any = time
                return Action(center, False, AbilityId.EFFECT_PURIFICATIONNOVA)
            else:
                return Action(center, False)

        return self.stay_safe(unit, current_command)
コード例 #2
0
    def unit_solve_combat(self, unit: Unit, current_command: Action) -> Action:
        if self.engage_ratio < 0.25 and self.can_engage_ratio < 0.25:
            return current_command

        if self.cd_manager.is_ready(unit.tag, AbilityId.EFFECT_BLINK_STALKER):
            if self.is_locked_on(unit):
                cyclones = self.enemies_near_by(UnitTypeId.CYCLONE)
                if cyclones:
                    closest_cyclone = cyclones.closest_to(unit)
                    backstep: Point2 = closest_cyclone.position.towards(unit.position, 15)
                    backstep = self.pather.find_weak_influence_ground(backstep, 4)
                    return Action(backstep, False, AbilityId.EFFECT_BLINK_STALKER)

            if self.model == CombatModel.StalkerToSiege and \
                    (self.move_type == MoveType.Assault or self.move_type == MoveType.SearchAndDestroy):
                siege_units = self.enemies_near_by.of_type(siege)
                if siege_units:
                    target = siege_units.closest_to(unit)
                    if target.distance_to(unit) > 6:
                        return Action(target.position, False, AbilityId.EFFECT_BLINK_STALKER)

            if unit.shield_percentage < 0.05:
                # Blink to safety.

                target_pos = unit.position
                if self.closest_group:
                    target_pos = target_pos.towards(self.closest_group.center, -5)

                target = self.pather.find_weak_influence_ground_blink(target_pos, 6)
                if target.distance_to(unit) > 3:
                    return Action(target, False, AbilityId.EFFECT_BLINK_STALKER)

        return super().unit_solve_combat(unit, current_command)
コード例 #3
0
 def group_solve_combat(self, units: Units,
                        current_command: Action) -> Action:
     if self.engage_ratio > 0.5 and self.closest_group:
         if self.ready_to_attack_ratio > 0.8 or self.closest_group_distance < 2:
             return Action(self.closest_group.center, True)
         if self.ready_to_attack_ratio < 0.25:
             return Action(self.closest_group.center, True)
         return Action(self.closest_group.center.towards(self.center, -3),
                       False)
     #if self.engage_percentage == 0
     return current_command
コード例 #4
0
    def unit_solve_combat(self, unit: Unit, current_command: Action) -> Action:
        if self.closest_group and self.engaged_power.melee_percentage > 0.9:
            backstep: Point2 = unit.position.towards(self.closest_group.center,
                                                     -3)
            if ((unit.health + unit.shield <= 5
                 and not self.ready_to_shoot(unit))
                    or (unit.shield_health_percentage < 0.5
                        and unit.weapon_cooldown > 9)):
                backstep = self.pather.find_weak_influence_ground(backstep, 4)
                if self.cache.own_in_range(unit.position,
                                           1) or self.cache.own_in_range(
                                               unit.position, 1):
                    # Mineral walk
                    angle = sc2math.line_angle(unit.position, backstep)
                    best_angle = sc2math.pi / 6
                    best_mf = None

                    for mf in self.ai.mineral_field:  # type: Unit
                        new_angle = sc2math.line_angle(unit.position,
                                                       mf.position)
                        angle_distance = sc2math.angle_distance(
                            angle, new_angle)
                        if angle_distance < best_angle:
                            best_mf = mf
                            best_angle = angle_distance

                    if best_mf:
                        # Use backstep with gather command to pass through own units
                        return Action(best_mf,
                                      False,
                                      ability=AbilityId.HARVEST_GATHER)
                return Action(backstep, False)

        if self.ready_to_shoot(unit):
            if self.closest_group:
                current = Action(self.closest_group.center, True)
            else:
                current = Action(current_command.target, True)
            return self.melee_focus_fire(unit, current)

        # if unit.health + unit.shield <= 5:
        #     backstep = self.pather.find_weak_influence_ground(backstep, 4)
        #     return Action(backstep, False)

        # if self.knowledge.enemy_race == Race.Protoss:
        #     if self.engage_percentage < 0.25:
        #         buildings = self.enemies_near_by.sorted_by_distance_to(unit)
        #         if buildings:
        #             if buildings.first.health + buildings.first.shield < 200:
        #                 return Action(buildings.first, True)
        #             pylons = buildings(UnitTypeId.PYLON)
        #             if pylons:
        #                 return Action(buildings.first, True)
        return current_command
コード例 #5
0
    def group_solve_combat(self, units: Units,
                           current_command: Action) -> Action:
        if self.move_type == MoveType.DefensiveRetreat or self.move_type == MoveType.PanicRetreat:
            return current_command

        if self.engage_ratio > 0.25 and self.closest_group:
            if self.ready_to_attack_ratio > 0.25 or self.closest_group_distance < 2:
                return Action(self.closest_group.center, True)
            return Action(self.closest_group.center.towards(self.center, -3),
                          False)
        #if self.engage_percentage == 0
        return current_command
コード例 #6
0
    def unit_solve_combat(self, unit: Unit, current_command: Action) -> Action:
        if (self.stim_required > 0 and not self.has_stim(unit)
                and unit.shield_health_percentage > 0.5):
            if unit.type_id == UnitTypeId.MARAUDER and self.cd_manager.is_ready(
                    unit.tag, AbilityId.EFFECT_STIM_MARAUDER):
                self.stim_required -= 2
                return Action(None, False, AbilityId.EFFECT_STIM_MARAUDER)
            elif unit.type_id == UnitTypeId.MARINE and self.cd_manager.is_ready(
                    unit.tag, AbilityId.EFFECT_STIM_MARINE):
                self.stim_required -= 1
                return Action(None, False, AbilityId.EFFECT_STIM_MARINE)

        return super().unit_solve_combat(unit, current_command)
コード例 #7
0
    def unit_solve_combat(self, unit: Unit, current_command: Action) -> Action:
        if isinstance(current_command.target, Unit):
            target_pos = current_command.target.position
        else:
            target_pos = current_command.target

        if self.move_type == MoveType.PanicRetreat or self.move_type == MoveType.DefensiveRetreat:
            if unit.has_buff(BuffId.ORACLEWEAPON):
                return Action(None, False, AbilityId.BEHAVIOR_PULSARBEAMOFF)
            target = self.pather.find_influence_air_path(
                unit.position, target_pos)
            return Action(target, False)

        if self.move_type == MoveType.Harass:
            targets = self.cache.enemy(self.knowledge.enemy_worker_type)
            if targets:
                close_to_me = targets.closer_than(8, unit.position)
                close_to_target = targets.closer_than(10, target_pos)
                if close_to_me:
                    targets = close_to_me
                elif close_to_target:
                    targets = close_to_target
        else:
            targets = self.cache.enemy_in_range(
                unit.position,
                10).filter(lambda u: u.is_light and not u.is_flying)

        if targets:
            closest = targets.closest_to(unit)
            distance = closest.distance_to(unit)

            if distance > 40 and unit.has_buff(BuffId.ORACLEWEAPON):
                return Action(None, False, AbilityId.BEHAVIOR_PULSARBEAMOFF)
            if distance < 5:
                if not unit.has_buff(BuffId.ORACLEWEAPON):
                    if unit.energy > 40:
                        return Action(None, False,
                                      AbilityId.BEHAVIOR_PULSARBEAMON)
                    else:
                        target = self.pather.find_weak_influence_air(
                            unit.position, 10)
                        return Action(target, False)
                else:
                    return Action(closest, True)

            target = self.pather.find_weak_influence_air(closest.position, 10)
            target = self.pather.find_influence_air_path(unit.position, target)
            return Action(target, False)
        else:
            if unit.has_buff(BuffId.ORACLEWEAPON):
                return Action(None, False, AbilityId.BEHAVIOR_PULSARBEAMOFF)

        target = self.pather.find_influence_air_path(unit.position, target_pos)
        return Action(target, False)
コード例 #8
0
    def unit_solve_combat(self, unit: Unit, current_command: Action) -> Action:
        if isinstance(current_command.target, Unit):
            target_pos = current_command.target.position
        else:
            target_pos = current_command.target

        target = self.pather.find_path(self.group.center, target_pos,
                                       8)  # move ahead of group
        enemies = self.cache.enemy_in_range(target, 12, False)
        other_observers = self.cache.own(UnitTypeId.OBSERVER).tags_not_in(
            [unit.tag])
        if other_observers:
            # Try to keep observers separated from each other
            closest = other_observers.closest_to(unit)
            if closest.distance_to(unit) < 5:
                pos: Point2 = closest.position
                target = unit.position.towards(pos, -6)

        # for enemy in enemies:  # type: Unit
        #     if enemy.detect_range > 0 and enemy.detect_range > target.distance_to(enemy):
        #         break
        if enemies:
            target = self.pather.find_weak_influence_air(target, 10)

        return Action(target, False)
コード例 #9
0
    def unit_solve_combat(self, unit: Unit, current_command: Action) -> Action:
        if self.engage_ratio < 0.25 and self.can_engage_ratio < 0.25:
            return current_command

        if self.cd_manager.is_ready(unit.tag, AbilityId.PSISTORM_PSISTORM):
            stormable_enemies = self.cache.enemy_in_range(unit.position, 10).not_structure
            if len(stormable_enemies) > 6:
                center = stormable_enemies.center
                target = stormable_enemies.closest_to(center)
                return Action(target.position, False, AbilityId.PSISTORM_PSISTORM)

        if self.cd_manager.is_ready(unit.tag, AbilityId.FEEDBACK_FEEDBACK):
            feedback_enemies = self.cache.enemy_in_range(unit.position, 10) \
                .filter(lambda u: u.energy > 74 and not u.is_structure)
            if feedback_enemies:
                closest = feedback_enemies.closest_to(unit)
                return Action(closest, False, AbilityId.FEEDBACK_FEEDBACK)

        return super().unit_solve_combat(unit, current_command)
コード例 #10
0
    def final_solve(self, unit: Unit, command: Action) -> Action:
        is_fighter = unit.type_id == UnitTypeId.VIKINGFIGHTER
        if not self.enemies_near_by:
            if is_fighter:
                return command
            else:
                return Action(None, False, AbilityId.MORPH_VIKINGFIGHTERMODE)

        if is_fighter:
            if (not self.enemies_near_by(UnitTypeId.COLOSSUS).exists
                    and not self.enemies_near_by.flying.exists
                    and self.enemies_near_by.not_flying.exists):
                return Action(None, False, AbilityId.MORPH_VIKINGASSAULTMODE)
        else:
            if self.engaged_power.air_presence > 0 or self.enemies_near_by(
                    UnitTypeId.COLOSSUS).exists:
                return Action(None, False, AbilityId.MORPH_VIKINGFIGHTERMODE)

        return command
コード例 #11
0
    def unit_solve_combat(self, unit: Unit, current_command: Action) -> Action:
        if unit.tag not in self.spawned:
            self.spawned[unit.tag] = self.ai.time

        time_percentage = 1 - (self.ai.time - self.spawned.get(unit.tag, self.ai.time)) / NOVA_DURATION

        relevant_enemies = self.cache.enemy_in_range(unit.position, 9 * time_percentage).not_structure.not_flying

        if relevant_enemies.exists:
            target = relevant_enemies.closest_to(relevant_enemies.center)
            return Action(target, False)
        else:
            own_relevant = self.cache.own_in_range(unit.position, 4).not_structure.not_flying
            if own_relevant:
                closest_own = own_relevant.closest_to(unit.position)
                pos = unit.position.towards(closest_own.position, -3)
                return Action(pos, False)

        return current_command
コード例 #12
0
    def unit_solve_combat(self, unit: Unit, current_command: Action) -> Action:
        if self.engage_ratio < 0.25 and self.can_engage_ratio < 0.25:
            return current_command

        if self.move_type == MoveType.DefensiveRetreat:
            if self.ready_to_shoot(unit):
                closest = self.closest_units.get(unit.tag, None)
                if closest and closest.is_target:
                    unit_range = self.unit_values.real_range(
                        unit, closest, self.knowledge)
                    if unit_range > 0 and unit_range > unit.distance_to(
                            closest):
                        return Action(closest, True)
            return current_command

        elif self.move_type == MoveType.PanicRetreat:
            return current_command

        if self.ready_to_shoot(unit):
            if self.closest_group and self.closest_group.ground_units:
                current_command = Action(self.closest_group.center, True)
            else:
                current_command = Action(current_command.target, True)
        else:
            closest = self.closest_units[unit.tag]

            # d = unit.distance_to(closest)
            unit_range = self.unit_values.real_range(unit, closest,
                                                     self.knowledge) - 0.5

            if unit.is_flying:
                best_position = self.pather.find_low_inside_air(
                    unit.position, closest.position, unit_range)
            else:
                best_position = self.pather.find_low_inside_ground(
                    unit.position, closest.position, unit_range)

            return Action(best_position, False)

        if self.ready_to_shoot(unit) and current_command.is_attack:
            return self.focus_fire(unit, current_command, self.prio_dict)
        return current_command
コード例 #13
0
    def unit_solve_combat(self, unit: Unit, current_command: Action) -> Action:
        if self.cd_manager.is_ready(unit.tag,
                                    AbilityId.TRANSFUSION_TRANSFUSION):
            own_close = self.cache.own_in_range(unit.position, 7)
            for own_unit in own_close:  # type: Unit
                if own_unit.health_max - own_unit.health > 70 and not own_unit.has_buff(
                        BuffId.TRANSFUSION):
                    return Action(own_unit, False,
                                  AbilityId.TRANSFUSION_TRANSFUSION)

        return super().unit_solve_combat(unit, current_command)
コード例 #14
0
    def action_to(self, group: CombatUnits, target, move_type: MoveType,
                  is_attack: bool):
        if isinstance(target, Point2) and group.ground_units:
            if move_type in {MoveType.DefensiveRetreat, MoveType.PanicRetreat}:
                target = self.pather.find_influence_ground_path(
                    group.center, target, 14)
            else:
                target = self.pather.find_path(group.center, target, 14)

        own_unit_cache: Dict[UnitTypeId, Units] = {}

        for unit in group.units:
            units = own_unit_cache.get(unit.type_id, Units([], self.ai))
            if units.amount == 0:
                real_type = self.unit_values.real_type(unit.type_id)
                own_unit_cache[real_type] = units

            units.append(unit)

        for type_id, type_units in own_unit_cache.items():
            micro: MicroStep = self.unit_micros.get(type_id,
                                                    self.generic_micro)
            micro.init_group(group, type_units, self.enemy_groups, move_type)
            group_action = micro.group_solve_combat(type_units,
                                                    Action(target, is_attack))

            for unit in type_units:
                action = micro.unit_solve_combat(unit, group_action)
                order = action.to_commmand(unit)
                if order:
                    self.ai.do(order)

                if self.debug:
                    if action.debug_comment:
                        status = action.debug_comment
                    elif action.ability:
                        status = action.ability.name
                    elif action.is_attack:
                        status = "Attack"
                    else:
                        status = "Move"
                    if action.target is not None:
                        if isinstance(action.target, Unit):
                            status += f": {action.target.type_id.name}"
                        else:
                            status += f": {action.target}"

                    status += f" G: {group.debug_index}"
                    status += f"\n{move_type.name}"

                    pos3d: Point3 = unit.position3d
                    pos3d = Point3((pos3d.x, pos3d.y, pos3d.z + 2))
                    self.ai._client.debug_text_world(status, pos3d, size=10)
コード例 #15
0
    def should_force_field(self, position: Point2) -> Optional[Action]:
        for ff in self.cache.force_fields:  # type: EffectData
            for position in ff.positions:
                if position.distance_to(position) < 1.5:
                    return None

        for ff_pos in self.upcoming_fields:   # type: Point2
            if ff_pos.distance_to_point2(position) < 1.5:
                return None

        self.upcoming_fields.append(position)
        return Action(position, False, AbilityId.FORCEFIELD_FORCEFIELD)
コード例 #16
0
    def unit_solve_combat(self, unit: Unit, current_command: Action) -> Action:
        if isinstance(current_command.target, Unit):
            target_pos = current_command.target.position
        else:
            target_pos = current_command.target

        target = self.pather.find_path(self.group.center, target_pos, 8)
        enemies = self.cache.enemy_in_range(target, 12)

        if enemies:
            if self.cd_manager.is_ready(
                    unit.tag, AbilityId.SPAWNCHANGELING_SPAWNCHANGELING):
                return Action(None, False,
                              AbilityId.SPAWNCHANGELING_SPAWNCHANGELING)

        for enemy in enemies:  # type: Unit
            if enemy.detect_range > 0 and enemy.detect_range > target.distance_to(
                    enemy):
                target = self.pather.find_weak_influence_air(target, 10)
                break

        return Action(target, False)
コード例 #17
0
    def unit_solve_combat(self, unit: Unit, current_command: Action) -> Action:
        if self.engage_ratio < 0.25 and self.can_engage_ratio < 0.25:
            return current_command

        if self.move_type in {
                MoveType.PanicRetreat, MoveType.DefensiveRetreat
        }:
            return current_command

        if self.cd_manager.is_ready(
                unit.tag, AbilityId.EFFECT_VOIDRAYPRISMATICALIGNMENT):
            close_enemies = self.cache.enemy_in_range(
                unit.position, 7).filter(lambda u: u.is_armored)
            if close_enemies:
                return Action(None, False,
                              AbilityId.EFFECT_VOIDRAYPRISMATICALIGNMENT)

        if not self.should_shoot() and self.should_retreat(unit):
            pos = self.pather.find_weak_influence_air(unit.position, 4)
            return Action(pos, False)

        return self.focus_fire(unit, current_command, None)
コード例 #18
0
    def relay_order(self, tank: Unit, order: AbilityId,
                    time: float) -> Optional[Action]:
        if order is None:
            self.clear_order()
            return None

        if self.requested_status == order:
            # Do the change when delay matches request
            delay = self.delay
            if order == AbilityId.SIEGEMODE_SIEGEMODE:
                delay = delay * 0.25

            if time > self.requested_time + delay:
                return Action(None, False, self.requested_status)
        else:
            self.requested_status = order
            self.requested_time = time
        return None
コード例 #19
0
    def unit_solve_combat(self, unit: Unit, current_command: Action) -> Action:
        if unit.has_buff(BuffId.CHARGING):
            return NoAction()

        ground_units = self.enemies_near_by.not_flying
        if not ground_units:
            current_command.is_attack = False
            return current_command
        # if self.knowledge.enemy_race == Race.Protoss:
        #     if self.engage_percentage < 0.25:
        #         buildings = self.enemies_near_by.sorted_by_distance_to(unit)
        #         if buildings:
        #             if buildings.first.health + buildings.first.shield < 200:
        #                 return Action(buildings.first, True)
        #             pylons = buildings(UnitTypeId.PYLON)
        #             if pylons:
        #                 return Action(buildings.first, True)
        return current_command
コード例 #20
0
 def stay_safe(self, unit: Unit, current_command: Action):
     # TODO: Backstep
     pos = self.pather.find_weak_influence_ground(unit.position, 5)
     return Action(pos, False)
コード例 #21
0
    def unit_solve_combat(self, unit: Unit, current_command: Action) -> Action:

        if self.move_type == MoveType.DefensiveRetreat or self.move_type == MoveType.PanicRetreat:
            if self.ready_to_shoot(unit):
                closest = self.closest_units.get(unit.tag, None)
                if closest:
                    real_range = self.unit_values.real_range(unit, closest, self.knowledge)
                    if 0 < real_range < unit.distance_to(closest):
                        return Action(closest.position, True)

            return current_command

        # Phoenixes are generally faster than the rest of the army
        if self.engage_ratio < 0.25 and self.can_engage_ratio < 0.25:
            if self.group.ground_units:
                # Regroup with the ground army
                return Action(self.group.center, False)

        has_energy = unit.energy > GRAVITON_BEAM_ENERGY

        if has_energy and self.allow_lift:
            best_target: Optional[Unit] = None
            best_score: float = 0
            close_enemies = self.cache.enemy_in_range(unit.position, 14)

            for enemy in close_enemies:  # type: Unit
                if enemy.is_flying or enemy.is_structure or enemy.has_buff(BuffId.GRAVITONBEAM):
                    continue

                if self.move_type != MoveType.Harass and enemy.type_id in self.unit_values.worker_types:
                    # If we are not doing any harass, don't lift low priority workers up.
                    # We need to prioritize energy to actual combat units
                    continue

                pos: Point2 = enemy.position
                score = self.lift_priority.get(enemy.type_id, -1) + (1 - pos.distance_to(unit) / 10)
                if score > best_score:
                    best_target = enemy
                    best_score = score

            if best_target:
                if best_score > 5 or not close_enemies.flying.exists:
                    self.print(f"Phoenix at {unit.position} lifting {best_target.type_id} at {best_target.position}")

                    if unit.distance_to(best_target) > 8:
                        destination = self.knowledge.pathing_manager.find_influence_air_path(unit.position, best_target.position)
                        return Action(destination, False)
                    return Action(best_target, False, AbilityId.GRAVITONBEAM_GRAVITONBEAM)

        if self.engage_ratio < 0.25 and self.can_engage_ratio < 0.25:
            # Not in combat
            return current_command

        targets = self.enemies_near_by.flying

        if targets:
            closest = targets.closest_to(unit)
            # d = unit.distance_to(closest)
            real_range = self.unit_values.real_range(unit, closest, self.knowledge) - 0.5
            best_position = self.pather.find_low_inside_air(unit.position, closest.position, real_range)

            return Action(best_position, False)

        return current_command
コード例 #22
0
    def unit_solve_combat(self, unit: Unit, current_command: Action) -> Action:
        if self.force_fielding(unit):
            # Don't do anything if force field is ordered
            return NoAction()

        if not self.shield_up and self.should_shield_up and unit.energy >= SHIELD_ENERGY_COST \
                and self.last_shield_up + 0.5 < self.ai.time:
            self.shield_up = True
            self.last_shield_up = self.ai.time
            return Action(None, False, AbilityId.GUARDIANSHIELD_GUARDIANSHIELD)

        if unit.shield_percentage < 0.1:
            if self.range_power > 5 and unit.energy >= HALLUCINATION_ENERGY_COST:
                return Action(None, False, AbilityId.HALLUCINATION_ARCHON)
            if self.melee_power > 5 and unit.energy >= FORCE_FIELD_ENERGY_COST:
                melee = self.knowledge.unit_cache.enemy(UnitValue.melee)
                if melee:
                    closest = melee.closest_to(unit)
                    pos = unit.position.towards(closest, 0.6)
                    return Action(pos, False, AbilityId.FORCEFIELD_FORCEFIELD)

        if self.move_type == MoveType.SearchAndDestroy and unit.energy >= FORCE_FIELD_ENERGY_COST:
            # Look for defensive force field on ramp or other choke
            natural: Zone = self.knowledge.expansion_zones[1]
            main: Zone = self.knowledge.expansion_zones[0]
            d_natural = unit.distance_to(natural.center_location)
            d_main = unit.distance_to(main.center_location)

            if d_natural < 15 and d_natural < d_main and self.closest_group_distance < 10:
                # Sentry is at the natural
                zealot_pos: Point2 = self.knowledge.building_solver.zealot_position
                if self.knowledge.enemy_race == Race.Zerg and natural.our_wall() and zealot_pos:
                    # Protect gate keeper
                    our_keepers = self.cache.own_in_range(zealot_pos, 2).not_structure
                    combined_health = 0
                    for keeper in our_keepers:  # type: Unit
                        combined_health += keeper.health + keeper.shield

                    if combined_health < 70:
                        action = self.should_force_field(zealot_pos.towards(self.closest_group.center, 0.6))
                        if action:
                            return action

                if self.model == CombatModel.StalkerToSpeedlings:
                    # Protect buildings
                    buildings = self.cache.own_in_range(unit.position, 8).structure
                    for building in buildings:   # type: Unit
                        if building.health + building.shield < 300:
                            action = self.should_force_field(building.position.towards(self.closest_group.center, 1.2))
                            if action:
                                return action

            elif not natural.is_ours or natural.power_balance < 0 and d_main < main.radius:
                # Protect main base ramp
                not_flying = self.cache.enemy_in_range(self.main_ramp_position, 3).filter(lambda u: not u.is_flying and not u.is_structure)
                if not_flying:
                    action = self.should_force_field(self.main_ramp_position)
                    if action:
                        return action

            #  and self.model == CombatModel.StalkerToSpeedlings
        return super().unit_solve_combat(unit, current_command)