Skip to content

Individual Combat Behaviors

CombatManeuver dataclass

Bases: Behavior

Execute behaviors sequentially.

Add behaviors

Example:

from ares import AresBot
from ares.behaviors.combat import CombatManeuver
from ares.behaviors.combat.individual import (
    DropCargo,
    KeepUnitSafe,
    PathUnitToTarget,
    PickUpCargo,
)

class MyBot(AresBot):
    mine_drop_medivac_tag: int

    async def on_step(self, iteration):
        # Left out here, but `self.mine_drop_medivac_tag`
        # bookkeeping is up to the user
        medivac: Optional[Unit] = self.unit_tag_dict.get(
            self.mine_drop_medivac_tag, None
        )
        if not medivac:
            return

        air_grid: np.ndarray = self.mediator.get_air_grid

        # initiate a new CombatManeuver
        mine_drop: CombatManeuver = CombatManeuver()

        # then add behaviors in the order they should be executed
        # first priority is picking up units
        # (will return False if no cargo and move to next behavior)
        mine_drop.add(
            PickUpCargo(
                unit=medivac,
                grid=air_grid,
                pickup_targets=mines_to_pickup
            )
        )

        # if there is cargo, path to target and drop them off
        if medivac.has_cargo:
            # path
            mine_drop.add(
                PathUnitToTarget(
                    unit=medivac,
                    grid=air_grid,
                    target=self.enemy_start_locations[0],
                )
            )
            # drop off the mines
            mine_drop.add(DropCargo(unit=medivac, target=medivac.position))

        # no cargo and no units to pick up, stay safe
        else:
            mine_drop.add(KeepUnitSafe(unit=medivac, grid=air_grid))

        # register the mine_drop behavior
        self.register_behavior(mine_drop)

Attributes

micros : list[Behavior] (optional, default: []) A list of behaviors that should be executed. (Optional)

Source code in src/ares/behaviors/combat/combat_maneuver.py
@dataclass
class CombatManeuver(Behavior):
    """Execute behaviors sequentially.

    Add behaviors

    Example:
    ```py
    from ares import AresBot
    from ares.behaviors.combat import CombatManeuver
    from ares.behaviors.combat.individual import (
        DropCargo,
        KeepUnitSafe,
        PathUnitToTarget,
        PickUpCargo,
    )

    class MyBot(AresBot):
        mine_drop_medivac_tag: int

        async def on_step(self, iteration):
            # Left out here, but `self.mine_drop_medivac_tag`
            # bookkeeping is up to the user
            medivac: Optional[Unit] = self.unit_tag_dict.get(
                self.mine_drop_medivac_tag, None
            )
            if not medivac:
                return

            air_grid: np.ndarray = self.mediator.get_air_grid

            # initiate a new CombatManeuver
            mine_drop: CombatManeuver = CombatManeuver()

            # then add behaviors in the order they should be executed
            # first priority is picking up units
            # (will return False if no cargo and move to next behavior)
            mine_drop.add(
                PickUpCargo(
                    unit=medivac,
                    grid=air_grid,
                    pickup_targets=mines_to_pickup
                )
            )

            # if there is cargo, path to target and drop them off
            if medivac.has_cargo:
                # path
                mine_drop.add(
                    PathUnitToTarget(
                        unit=medivac,
                        grid=air_grid,
                        target=self.enemy_start_locations[0],
                    )
                )
                # drop off the mines
                mine_drop.add(DropCargo(unit=medivac, target=medivac.position))

            # no cargo and no units to pick up, stay safe
            else:
                mine_drop.add(KeepUnitSafe(unit=medivac, grid=air_grid))

            # register the mine_drop behavior
            self.register_behavior(mine_drop)
    ```

    Attributes
    ----------
    micros : list[Behavior] (optional, default: [])
        A list of behaviors that should be executed. (Optional)
    """

    micros: list[Behavior] = field(default_factory=list)

    def add(
        self,
        behavior: Union[
            "CombatIndividualBehavior", "CombatGroupBehavior", "CombatManeuver"
        ],
    ) -> None:
        """
        Parameters
        ----------
        behavior : CombatBehavior
            Add a new combat behavior to the current maneuver object.

        Returns
        -------
            None
        """
        self.micros.append(behavior)

    def execute(self, ai: "AresBot", config: dict, mediator: ManagerMediator) -> bool:
        for order in self.micros:
            if order.execute(ai, config, mediator):
                # executed an action
                return True
        # none of the combat micros completed, no actions executed
        return False

add(behavior)

Parameters

behavior : CombatBehavior Add a new combat behavior to the current maneuver object.

Returns
None
Source code in src/ares/behaviors/combat/combat_maneuver.py
def add(
    self,
    behavior: Union[
        "CombatIndividualBehavior", "CombatGroupBehavior", "CombatManeuver"
    ],
) -> None:
    """
    Parameters
    ----------
    behavior : CombatBehavior
        Add a new combat behavior to the current maneuver object.

    Returns
    -------
        None
    """
    self.micros.append(behavior)

AMove dataclass

Bases: CombatIndividualBehavior

A-Move a unit to a target.

Example:

from ares.behaviors.combat.individual import AMove

self.register_behavior(AMove(unit, self.game_info.map_center))

Attributes

unit : Unit The unit to stay safe. target: Point2 Where the unit is going.

Source code in src/ares/behaviors/combat/individual/a_move.py
@dataclass
class AMove(CombatIndividualBehavior):
    """A-Move a unit to a target.

    Example:
    ```py
    from ares.behaviors.combat.individual import AMove

    self.register_behavior(AMove(unit, self.game_info.map_center))
    ```

    Attributes
    ----------
    unit : Unit
        The unit to stay safe.
    target: Point2
        Where the unit is going.
    """

    unit: Unit
    target: Union[Point2, Unit]

    def execute(self, ai: "AresBot", config: dict, mediator: ManagerMediator) -> bool:
        self.unit.attack(self.target)
        return True

AttackTarget dataclass

Bases: CombatIndividualBehavior

Shoot a target.

Example:

from ares.behaviors.combat.individual import AttackTarget

unit: Unit
target: Unit
self.register_behavior(AttackTarget(unit, target))

Attributes

unit: Unit The unit to shoot. target : Unit The unit we want to shoot at.

Source code in src/ares/behaviors/combat/individual/attack_target.py
@dataclass
class AttackTarget(CombatIndividualBehavior):
    """Shoot a target.

    Example:
    ```py
    from ares.behaviors.combat.individual import AttackTarget

    unit: Unit
    target: Unit
    self.register_behavior(AttackTarget(unit, target))
    ```

    Attributes
    ----------
    unit: Unit
        The unit to shoot.
    target : Unit
        The unit we want to shoot at.
    """

    unit: Unit
    target: Unit
    extra_range: float = 0.0

    def execute(
        self, ai: "AresBot", config: dict, mediator: ManagerMediator, **kwargs
    ) -> bool:
        self.unit.attack(self.target)
        return True

DropCargo dataclass

Bases: CombatIndividualBehavior

Handle releasing cargo from a container.

Medivacs, WarpPrism, Overlords, Nydus.

Example:

from ares.behaviors.combat import DropCargo

unit: Unit
target: Unit
self.register_behavior(DropCargo(unit, target))

Attributes

unit : Unit The container unit. target : Point2 The target position where to drop the cargo.

Source code in src/ares/behaviors/combat/individual/drop_cargo.py
@dataclass
class DropCargo(CombatIndividualBehavior):
    """Handle releasing cargo from a container.

    Medivacs, WarpPrism, Overlords, Nydus.

    Example:
    ```py
    from ares.behaviors.combat import DropCargo

    unit: Unit
    target: Unit
    self.register_behavior(DropCargo(unit, target))
    ```

    Attributes
    ----------
    unit : Unit
        The container unit.
    target : Point2
        The target position where to drop the cargo.
    """

    unit: Unit
    target: Point2

    def execute(self, ai: "AresBot", config: dict, mediator: ManagerMediator) -> bool:
        # TODO: Expand logic as needed, initial working version.
        # no action executed
        if self.unit.cargo_used == 0 or not ai.in_pathing_grid(self.unit.position):
            return False

        ai.do_unload_container(self.unit.tag)
        return True

KeepUnitSafe dataclass

Bases: CombatIndividualBehavior

Get a unit to safety based on the influence grid passed in.

Example:

from ares.behaviors.combat import KeepUnitSafe

unit: Unit
grid: np.ndarray = self.mediator.get_ground_grid
self.register_behavior(KeepUnitSafe(unit, grid))

Attributes

unit : Unit The unit to stay safe. grid : np.ndarray 2D Grid which usually contains enemy influence.

Source code in src/ares/behaviors/combat/individual/keep_unit_safe.py
@dataclass
class KeepUnitSafe(CombatIndividualBehavior):
    """Get a unit to safety based on the influence grid passed in.

    Example:
    ```py
    from ares.behaviors.combat import KeepUnitSafe

    unit: Unit
    grid: np.ndarray = self.mediator.get_ground_grid
    self.register_behavior(KeepUnitSafe(unit, grid))
    ```

    Attributes
    ----------
    unit : Unit
        The unit to stay safe.
    grid : np.ndarray
        2D Grid which usually contains enemy influence.
    """

    unit: Unit
    grid: np.ndarray

    def execute(self, ai: "AresBot", config: dict, mediator: ManagerMediator) -> bool:
        # no action executed
        if mediator.is_position_safe(grid=self.grid, position=self.unit.position):
            return False
        else:
            safe_spot: Point2 = mediator.find_closest_safe_spot(
                from_pos=self.unit.position, grid=self.grid
            )
            path: Behavior = PathUnitToTarget(
                unit=self.unit,
                grid=self.grid,
                target=safe_spot,
                success_at_distance=0.0,
            )
            return path.execute(ai, config, mediator)

PathUnitToTarget dataclass

Bases: CombatIndividualBehavior

Path a unit to its target destination.

Add attack enemy in range logic / parameter

Not added yet since that may be it's own Behavior

Example:

from ares.behaviors.combat import PathUnitToTarget

unit: Unit
grid: np.ndarray = self.mediator.get_ground_grid
target: Point2 = self.game_info.map_center
self.register_behavior(PathUnitToTarget(unit, grid, target))

Attributes

unit : Unit The unit to path. grid : np.ndarray 2D Grid to path on. target : Point2 Target destination. success_at_distance : float (default: 0.0) If unit has got this close, consider path behavior complete. sensitivity : int (default: 5) Path precision. smoothing : bool (default: False) Smooth out the path. sense_danger : bool (default: True) Check for dangers, if none are present pathing query is skipped. danger_distance : float (default: 20.0) If sense_danger=True, how far to check for dangers? danger_threshold : float (default: 5.0) Influence at which a danger is respected.

Source code in src/ares/behaviors/combat/individual/path_unit_to_target.py
@dataclass
class PathUnitToTarget(CombatIndividualBehavior):
    """Path a unit to its target destination.

    TODO: Add attack enemy in range logic / parameter
        Not added yet since that may be it's own Behavior

    Example:
    ```py
    from ares.behaviors.combat import PathUnitToTarget

    unit: Unit
    grid: np.ndarray = self.mediator.get_ground_grid
    target: Point2 = self.game_info.map_center
    self.register_behavior(PathUnitToTarget(unit, grid, target))
    ```

    Attributes
    ----------
    unit : Unit
        The unit to path.
    grid : np.ndarray
        2D Grid to path on.
    target : Point2
        Target destination.
    success_at_distance : float (default: 0.0)
        If unit has got this close, consider path behavior complete.
    sensitivity : int (default: 5)
        Path precision.
    smoothing : bool (default: False)
        Smooth out the path.
    sense_danger : bool (default: True)
        Check for dangers, if none are present pathing query is skipped.
    danger_distance : float (default: 20.0)
        If sense_danger=True, how far to check for dangers?
    danger_threshold : float (default: 5.0)
        Influence at which a danger is respected.
    """

    unit: Unit
    grid: np.ndarray
    target: Point2
    success_at_distance: float = 0.0
    sensitivity: int = 5
    smoothing: bool = False
    sense_danger: bool = True
    danger_distance: float = 20.0
    danger_threshold: float = 5.0

    def execute(self, ai: "AresBot", config: dict, mediator: ManagerMediator) -> bool:
        distance_to_target: float = cy_distance_to(self.unit.position, self.target)
        # no action executed
        if distance_to_target < self.success_at_distance:
            return False

        move_to: Point2 = mediator.find_path_next_point(
            start=self.unit.position,
            target=self.target,
            grid=self.grid,
            sensitivity=self.sensitivity,
            smoothing=self.smoothing,
            sense_danger=self.sense_danger,
            danger_distance=self.danger_distance,
            danger_threshold=self.danger_threshold,
        )
        self.unit.move(move_to)
        return True

PickUpCargo dataclass

Bases: CombatIndividualBehavior

Handle loading cargo into a container.

Medivacs, WarpPrism, Overlords, Nydus.

Example:

from ares.behaviors.combat import PickUpCargo

unit: Unit # medivac for example
grid: np.ndarray = self.mediator.get_ground_grid
pickup_targets: Union[Units, list[Unit]] = self.workers
self.register_behavior(PickUpCargo(unit, grid, pickup_targets))

Attributes

unit : Unit The container unit. grid : np.ndarray Pathing grid for container unit. pickup_targets : Union[Units, list[Unit]] Units we want to load into the container. cargo_switch_to_role : UnitRole (default: None) Sometimes useful to switch cargo tp new role immediately after loading.

Source code in src/ares/behaviors/combat/individual/pick_up_cargo.py
@dataclass
class PickUpCargo(CombatIndividualBehavior):
    """Handle loading cargo into a container.

    Medivacs, WarpPrism, Overlords, Nydus.

    Example:
    ```py
    from ares.behaviors.combat import PickUpCargo

    unit: Unit # medivac for example
    grid: np.ndarray = self.mediator.get_ground_grid
    pickup_targets: Union[Units, list[Unit]] = self.workers
    self.register_behavior(PickUpCargo(unit, grid, pickup_targets))
    ```

    Attributes
    ----------
    unit : Unit
        The container unit.
    grid : np.ndarray
        Pathing grid for container unit.
    pickup_targets : Union[Units, list[Unit]]
        Units we want to load into the container.
    cargo_switch_to_role : UnitRole (default: None)
        Sometimes useful to switch cargo tp new role
        immediately after loading.
    """

    unit: Unit
    grid: np.ndarray
    pickup_targets: Union[Units, list[Unit]]
    cargo_switch_to_role: Optional[UnitRole] = None

    def execute(self, ai: "AresBot", config: dict, mediator: ManagerMediator) -> bool:
        # no action executed
        if not self.pickup_targets or self.unit.type_id not in PICKUP_RANGE:
            # just ensure tags inside are assigned correctly
            if len(self.unit.passengers_tags) > 0 and self.cargo_switch_to_role:
                for tag in self.unit.passengers_tags:
                    mediator.assign_role(tag=tag, role=self.cargo_switch_to_role)
            return False

        unit_pos: Point2 = self.unit.position
        target: Unit = cy_closest_to(unit_pos, self.pickup_targets)
        distance: float = cy_distance_to(self.unit.position, target.position)

        if distance <= PICKUP_RANGE[self.unit.type_id]:
            self.unit(AbilityId.SMART, target)
        else:
            move_to: Point2 = mediator.find_path_next_point(
                start=unit_pos, target=target.position, grid=self.grid
            )
            self.unit.move(move_to)

        return True

PlacePredictiveAoE dataclass

Bases: CombatIndividualBehavior

Predict an enemy position and fire AoE accordingly.

Warning: Use this at your own risk. Work in progress.

Guess where the enemy is going based on how it's been moving.

Cythonize this.

Attributes

unit: Unit The unit to fire the AoE. path : List[Point2] How we're getting to the target position (the last point in the list) enemy_center_unit: Unit Enemy unit to calculate positions based on. aoe_ability: AbilityId AoE ability to use. ability_delay: int Amount of frames between using the ability and the ability occurring.

Source code in src/ares/behaviors/combat/individual/place_predictive_aoe.py
@dataclass
class PlacePredictiveAoE(CombatIndividualBehavior):
    """Predict an enemy position and fire AoE accordingly.

    Warning: Use this at your own risk. Work in progress.

    TODO: Guess where the enemy is going based on how it's been moving.
        Cythonize this.

    Attributes
    ----------
    unit: Unit
        The unit to fire the AoE.
    path : List[Point2]
        How we're getting to the target position (the last point in the list)
    enemy_center_unit: Unit
        Enemy unit to calculate positions based on.
    aoe_ability: AbilityId
        AoE ability to use.
    ability_delay: int
        Amount of frames between using the ability and the ability occurring.
    """

    unit: Unit
    path: List[Point2]
    enemy_center_unit: Unit
    aoe_ability: AbilityId
    ability_delay: int

    def execute(
        self, ai: "AresBot", config: dict, mediator: ManagerMediator, **kwargs
    ) -> bool:
        if self.aoe_ability in self.unit.abilities:
            # try to fire the ability if we find a position
            if pos := self._calculate_target_position(ai):
                if ai.is_visible(pos):
                    return self.unit(self.aoe_ability, pos)
        # no position found or the ability isn't ready
        return False

    def _calculate_target_position(self, ai: "AresBot") -> Point2:
        """Calculate where we want to put the AoE.

        Returns
        -------
        Point2 :
            Where we want to place the AoE.

        """
        # figure out where our unit is going to be during the chase
        own_unit_path = self._get_unit_real_path(self.path, self.unit.distance_per_step)

        # enemy path, assuming it moves directly towards our unit at all times
        chasing_path, _reached_target = self._get_chasing_unit_path(
            own_unit_path,
            self.enemy_center_unit.position,
            self.enemy_center_unit.distance_per_step,
        )

        # pick the spot along the predicted path that the enemy will have reached when
        # the ability goes off
        delayed_idx = math.ceil(self.ability_delay / ai.client.game_step)

        return chasing_path[min(delayed_idx, len(chasing_path) - 1)]

    @staticmethod
    def _get_unit_next_position(
        current_position: Point2,
        current_target: Point2,
        distance_per_step: float,
        next_target: Optional[Point2] = None,
    ) -> Tuple[Point2, bool]:
        """Calculate where a unit will be on the next step.

        Assumes knowledge of the unit's current position, target position, and that the
        unit will not change direction.

        TODO: handle the case where the unit is fast enough to travel multiple path
            points per game step

        Parameters
        ----------
        current_position: Point2
            Where the unit currently is.
        current_target: Point2
            Where the unit is going.
        distance_per_step: float
            How far the unit moves per game step.
        next_target: Optional[Point2]
            Where the unit should head if it reaches the target point between steps.

        Returns
        -------
        Tuple[Point2, bool] :
            Point2 is where the unit will be
            bool is whether the unit reached `current_target` in this step

        """
        reached_target: bool = False

        # make sure we won't run past the target point
        distance_to_target: float = current_position.distance_to(current_target)
        if distance_to_target < distance_per_step:
            if next_target:
                """
                Overwrite initial values to reflect moving from the current target
                to the next position.
                """
                distance_per_step = (
                    1 - distance_to_target / distance_per_step
                ) * distance_per_step
                current_position = current_target
                current_target = next_target
                reached_target = True
            else:
                # we don't have a next point to go to, so stop at the current target
                return current_target, True

        # offset the current position towards the target position by the amount of
        # distance covered per game step
        return (
            current_position.towards(current_target, distance_per_step),
            reached_target,
        )

    def _get_unit_real_path(
        self, unit_path: List[Point2], unit_speed: float
    ) -> List[Point2]:
        """Find the location of the unit at each game step given its path.

        Parameters
        ----------
        unit_path: List[Point2]
            Where the unit is being told to move.
        unit_speed: float
            How far the unit moves each game step.

        Returns
        -------
        List[Point2] :
            Where the unit will be at each game iteration.

        """
        real_path: List[Point2] = [unit_path[0]]
        curr_target_idx: int = 1
        # 100 should be overkill, but I'm really just trying to avoid a `while` loop
        for step in range(100):
            if curr_target_idx >= len(unit_path):
                # we've made it to the end of the path
                break
            # travel directly towards the next point on the path, updating the target
            # point when the one before it is reached
            next_position, increase_target_idx = self._get_unit_next_position(
                current_position=real_path[-1],
                current_target=unit_path[curr_target_idx],
                distance_per_step=unit_speed,
                next_target=unit_path[curr_target_idx + 1]
                if curr_target_idx != len(unit_path) - 1
                else None,
            )
            real_path.append(next_position)

            if increase_target_idx:
                # we made it to the current target point, get the next one
                curr_target_idx += 1

        return real_path

    def _get_chasing_unit_path(
        self, target_unit_path: List[Point2], start_position: Point2, unit_speed: float
    ) -> Tuple[List[Point2], bool]:
        """Calculate the path the chasing unit will take to catch the target unit.

        Arguments
        ---------
        target_unit_path: List[Point2]
            Where the target unit is going to be at each game iteration.
        start_position: Point2
            Where the chasing unit is starting from.
        unit_speed: float
            How far the chasing unit moves per game step.

        Returns
        -------
        Tuple[List[Point2], bool] :
            List[Point2] is the chasing unit's path
            bool is whether the chasing unit caught the target unit

        """
        reached_target: bool = False

        unit_path: List[Point2] = [start_position]
        target_idx = 0

        for i in range(100):
            next_position, reached_target = self._get_unit_next_position(
                current_position=unit_path[-1],
                current_target=target_unit_path[target_idx],
                distance_per_step=unit_speed,
            )
            unit_path.append(next_position)
            # keep updating the target index because we're chasing a moving target, but
            # stop if the unit we're chasing isn't moving any further
            target_idx = min(len(target_unit_path) - 1, target_idx + 1)

            if reached_target:
                # we caught the unit
                break

        return unit_path, reached_target

ShootTargetInRange dataclass

Bases: CombatIndividualBehavior

Find something to shoot at.

Currently only picks lowest health.

Might want to pick best one shot KO for example

Example:

from ares.behaviors.combat import ShootTargetInRange

unit: Unit
target: Unit
self.register_behavior(ShootTargetInRange(unit, target))

Attributes

unit: Unit The unit to shoot. targets : Union[list[Unit], Units] Units we want to check. extra_range: float (optional) Look outside unit weapon range. This might be useful for hunting down low hp units.

Source code in src/ares/behaviors/combat/individual/shoot_target_in_range.py
@dataclass
class ShootTargetInRange(CombatIndividualBehavior):
    """Find something to shoot at.

    TODO: Currently only picks lowest health.
        Might want to pick best one shot KO for example

    Example:
    ```py
    from ares.behaviors.combat import ShootTargetInRange

    unit: Unit
    target: Unit
    self.register_behavior(ShootTargetInRange(unit, target))
    ```

    Attributes
    ----------
    unit: Unit
        The unit to shoot.
    targets : Union[list[Unit], Units]
        Units we want to check.
    extra_range: float (optional)
        Look outside unit weapon range.
        This might be useful for hunting down low hp units.
    """

    unit: Unit
    targets: Union[list[Unit], Units]
    extra_range: float = 0.0

    def execute(
        self, ai: "AresBot", config: dict, mediator: ManagerMediator, **kwargs
    ) -> bool:
        if not self.targets:
            return False

        targets = [
            t
            for t in self.targets
            if not t.is_cloaked or t.is_cloaked and t.is_revealed
        ]
        in_attack_range: list[Unit] = cy_in_attack_range(
            self.unit, targets, self.extra_range
        )

        if len(in_attack_range) == 0:
            return False

        # idea here is if our unit already has an order to shoot one of these
        # in attack range enemies then we return True but don't issue a
        # new action
        if (
            self.unit.orders
            and len([u for u in in_attack_range if u.tag == self.unit.order_target])
            and self.unit.weapon_cooldown == 0.0
        ):
            return True

        enemy_target: Unit = cy_pick_enemy_target(in_attack_range)

        if cy_attack_ready(ai, self.unit, enemy_target):
            self.unit.attack(enemy_target)
            return True

        return False

StutterUnitBack dataclass

Bases: CombatIndividualBehavior

Shoot at the target if possible, else move back.

Example:

from ares.behaviors.combat import StutterUnitBack

unit: Unit
target: Unit
self.register_behavior(StutterUnitBack(unit, target))

Attributes

unit: Unit The unit to shoot. target : Unit The unit we want to shoot at. kite_via_pathing : bool Kite back using pathing? Value for grid must be present. grid : Optional[np.ndarray] Pass in if using kite_via_pathing.

Source code in src/ares/behaviors/combat/individual/stutter_unit_back.py
@dataclass
class StutterUnitBack(CombatIndividualBehavior):
    """Shoot at the target if possible, else move back.

    Example:
    ```py
    from ares.behaviors.combat import StutterUnitBack

    unit: Unit
    target: Unit
    self.register_behavior(StutterUnitBack(unit, target))
    ```

    Attributes
    ----------
    unit: Unit
        The unit to shoot.
    target : Unit
        The unit we want to shoot at.
    kite_via_pathing : bool
        Kite back using pathing? Value for `grid` must be present.
    grid : Optional[np.ndarray]
        Pass in if using kite_via_pathing.
    """

    unit: Unit
    target: Unit
    kite_via_pathing: bool = True
    grid: Optional[np.ndarray] = None

    def execute(
        self, ai: "AresBot", config: dict, mediator: ManagerMediator, **kwargs
    ) -> bool:
        unit = self.unit
        target = self.target
        if self.kite_via_pathing and self.grid is None:
            self.grid = mediator.get_ground_grid

        if cy_attack_ready(ai, unit, target):
            return AttackTarget(unit=unit, target=target).execute(ai, config, mediator)
        elif self.kite_via_pathing and self.grid is not None:
            return KeepUnitSafe(unit=unit, grid=self.grid).execute(ai, config, mediator)
        # TODO: Implement non pathing kite back
        else:
            logger.warning("Stutter back doesn't work for kite_via_pathing=False yet")
            return False

StutterUnitForward dataclass

Bases: CombatIndividualBehavior

Shoot at the target if possible, else move back.

Example:

from ares.behaviors.combat import StutterUnitForward

unit: Unit
target: Unit
self.register_behavior(StutterUnitForward(unit, target))

Attributes

unit: Unit The unit to shoot. target : Unit The unit we want to shoot at.

Source code in src/ares/behaviors/combat/individual/stutter_unit_forward.py
@dataclass
class StutterUnitForward(CombatIndividualBehavior):
    """Shoot at the target if possible, else move back.

    Example:
    ```py
    from ares.behaviors.combat import StutterUnitForward

    unit: Unit
    target: Unit
    self.register_behavior(StutterUnitForward(unit, target))
    ```

    Attributes
    ----------
    unit: Unit
        The unit to shoot.
    target : Unit
        The unit we want to shoot at.
    """

    unit: Unit
    target: Unit

    def execute(
        self, ai: "AresBot", config: dict, mediator: ManagerMediator, **kwargs
    ) -> bool:
        unit = self.unit
        target = self.target
        if cy_attack_ready(ai, unit, target):
            return AttackTarget(unit=unit, target=target).execute(ai, config, mediator)
        else:
            unit.move(target.position)
            return True

UseAbility dataclass

Bases: CombatIndividualBehavior

A-Move a unit to a target.

Example:

from ares.behaviors.combat import UseAbility
from sc2.ids.ability_id import AbilityId

unit: Unit
target: Union[Unit, Point2]
self.register_behavior(
    UseAbility(
        AbilityId.FUNGALGROWTH_FUNGALGROWTH, unit, target
    )
)

Attributes

ability : AbilityId The ability we want to use. unit : Unit The unit to use the ability. target: Union[Point2, Unit, None] Target for this ability.

Source code in src/ares/behaviors/combat/individual/use_ability.py
@dataclass
class UseAbility(CombatIndividualBehavior):
    """A-Move a unit to a target.

    Example:
    ```py
    from ares.behaviors.combat import UseAbility
    from sc2.ids.ability_id import AbilityId

    unit: Unit
    target: Union[Unit, Point2]
    self.register_behavior(
        UseAbility(
            AbilityId.FUNGALGROWTH_FUNGALGROWTH, unit, target
        )
    )
    ```

    Attributes
    ----------
    ability : AbilityId
        The ability we want to use.
    unit : Unit
        The unit to use the ability.
    target: Union[Point2, Unit, None]
        Target for this ability.
    """

    ability: AbilityId
    unit: Unit
    target: Union[Point2, Unit, None]

    def execute(
        self, ai: "AresBot", config: dict, mediator: ManagerMediator, **kwargs
    ) -> bool:
        if self.ability not in self.unit.abilities:
            return False

        if self.target:
            self.unit(self.ability, self.target)
        else:
            self.unit(self.ability)

        return True

WorkerKiteBack dataclass

Bases: CombatIndividualBehavior

Shoot at the target if possible, else move back.

This is similar to stutter unit back, but takes advantage of mineral walking.

Example:

from ares.behaviors.combat import WorkerKiteBack

unit: Unit
target: Unit
self.register_behavior(
    WorkerKiteBack(
        unit, target
    )
)

Attributes

unit: Unit The unit to shoot. target : Unit The unit we want to shoot at.

Source code in src/ares/behaviors/combat/individual/worker_kite_back.py
@dataclass
class WorkerKiteBack(CombatIndividualBehavior):
    """Shoot at the target if possible, else move back.

    This is similar to stutter unit back, but takes advantage of
    mineral walking.

    Example:
    ```py
    from ares.behaviors.combat import WorkerKiteBack

    unit: Unit
    target: Unit
    self.register_behavior(
        WorkerKiteBack(
            unit, target
        )
    )
    ```

    Attributes
    ----------
    unit: Unit
        The unit to shoot.
    target : Unit
        The unit we want to shoot at.
    """

    unit: Unit
    target: Unit

    def execute(
        self, ai: "AresBot", config: dict, mediator: ManagerMediator, **kwargs
    ) -> bool:
        unit = self.unit
        target = self.target
        if not target.is_memory and cy_attack_ready(ai, unit, target):
            return AttackTarget(unit=unit, target=target).execute(ai, config, mediator)
        elif mfs := ai.mineral_field:
            unit.gather(cy_closest_to(position=ai.start_location, units=mfs))