Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: combat behaviors #218

Merged
merged 9 commits into from
Mar 2, 2025
707 changes: 321 additions & 386 deletions poetry.lock

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions src/ares/behaviors/combat/individual/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,15 @@
from ares.behaviors.combat.individual.ghost_snipe import GhostSnipe
from ares.behaviors.combat.individual.keep_unit_safe import KeepUnitSafe
from ares.behaviors.combat.individual.medivac_heal import MedivacHeal
from ares.behaviors.combat.individual.move_to_safe_target import MoveToSafeTarget
from ares.behaviors.combat.individual.path_unit_to_target import PathUnitToTarget
from ares.behaviors.combat.individual.pick_up_and_drop_cargo import PickUpAndDropCargo
from ares.behaviors.combat.individual.pick_up_cargo import PickUpCargo
from ares.behaviors.combat.individual.raven_auto_turret import RavenAutoTurret
from ares.behaviors.combat.individual.reaper_grenade import ReaperGrenade
from ares.behaviors.combat.individual.shoot_and_move_to_target import (
ShootAndMoveToTarget,
)
from ares.behaviors.combat.individual.shoot_target_in_range import ShootTargetInRange
from ares.behaviors.combat.individual.siege_tank_decision import SiegeTankDecision
from ares.behaviors.combat.individual.stutter_unit_back import StutterUnitBack
Expand Down
2 changes: 1 addition & 1 deletion src/ares/behaviors/combat/individual/drop_cargo.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class DropCargo(CombatIndividualBehavior):

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

unit: Unit
target: Unit
Expand Down
2 changes: 1 addition & 1 deletion src/ares/behaviors/combat/individual/keep_unit_safe.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class KeepUnitSafe(CombatIndividualBehavior):

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

unit: Unit
grid: np.ndarray = self.mediator.get_ground_grid
Expand Down
89 changes: 89 additions & 0 deletions src/ares/behaviors/combat/individual/move_to_safe_target.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
from dataclasses import dataclass
from typing import TYPE_CHECKING

import numpy as np
from cython_extensions import cy_distance_to
from sc2.ids.ability_id import AbilityId
from sc2.position import Point2
from sc2.unit import Unit

from ares.behaviors.combat.individual.combat_individual_behavior import (
CombatIndividualBehavior,
)
from ares.behaviors.combat.individual.path_unit_to_target import PathUnitToTarget
from ares.behaviors.combat.individual.use_ability import UseAbility
from ares.managers.manager_mediator import ManagerMediator

if TYPE_CHECKING:
from ares import AresBot


@dataclass
class MoveToSafeTarget(CombatIndividualBehavior):
"""Given a destination `target`, find the nearest safe position and move
unit there.

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

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

Attributes:
unit: The unit to path.
grid: 2D grid to path on.
target: Target destination.
use_pathing: Whether to use pathing or just move directly towards the target.
Defaults to True.
success_at_distance: If the unit has gotten this close, consider path
behavior complete. Defaults to 0.0.
sensitivity: Path precision. Defaults to 5.
smoothing: Whether to smooth out the path. Defaults to False.
sense_danger: Whether to check for dangers. If none are present,
the pathing query is skipped. Defaults to True.
danger_distance: If `sense_danger` is True, how far to check for dangers.
Defaults to 20.0.
danger_threshold: Influence at which a danger is respected.
Defaults to 5.0.
radius: How far to look for a safe spot around the target.
Defaults to 12.0.

"""

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

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

safe_spot: Point2 = mediator.find_closest_safe_spot(
from_pos=self.target, grid=self.grid, radius=self.radius
)
if self.use_pathing:
return PathUnitToTarget(
unit=self.unit,
grid=self.grid,
target=safe_spot,
success_at_distance=self.success_at_distance,
sensitivity=self.sensitivity,
sense_danger=self.sense_danger,
smoothing=self.smoothing,
).execute(ai, config, mediator)
else:
return UseAbility(AbilityId.MOVE_MOVE, self.unit, safe_spot).execute(
ai, config, mediator
)
5 changes: 1 addition & 4 deletions src/ares/behaviors/combat/individual/path_unit_to_target.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,9 @@
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
from ares.behaviors.combat.individual import PathUnitToTarget

unit: Unit
grid: np.ndarray = self.mediator.get_ground_grid
Expand Down
94 changes: 94 additions & 0 deletions src/ares/behaviors/combat/individual/pick_up_and_drop_cargo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
from dataclasses import dataclass
from typing import TYPE_CHECKING, Optional, Union

import numpy as np
from sc2.position import Point2
from sc2.unit import Unit
from sc2.units import Units

from ares.behaviors.combat.individual import DropCargo, KeepUnitSafe, PathUnitToTarget
from ares.behaviors.combat.individual.combat_individual_behavior import (
CombatIndividualBehavior,
)
from ares.behaviors.combat.individual.pick_up_cargo import PickUpCargo
from ares.consts import UnitRole
from ares.managers.manager_mediator import ManagerMediator

if TYPE_CHECKING:
from ares import AresBot


@dataclass
class PickUpAndDropCargo(CombatIndividualBehavior):
"""Combines `PickUpCargo` and `DropCargo` behaviors.

Medivacs, WarpPrism, Overlords.

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

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

Attributes:
unit: The container unit.
grid: Pathing grid for the container unit.
pickup_targets: Units we want to load into the container.
target: Drop at this target
cargo_switch_to_role: Sometimes useful to switch cargo to
a new role immediately after loading. Defaults to None.
should_drop_units: Whether to drop units once they are loaded.
Defaults to True.
keep_dropship_safe: If true, will keep dropship safe if nothing else to do.
Defaults to True.
success_at_distance: Distance at which pathing to target stops being checked.
Defaults to 2.0.

"""

unit: Unit
grid: np.ndarray
pickup_targets: Union[Units, list[Unit]]
target: Point2
cargo_switch_to_role: Optional[UnitRole] = None
should_drop_units: bool = True
keep_dropship_safe: bool = True
success_at_distance: float = 2.0

def execute(self, ai: "AresBot", config: dict, mediator: ManagerMediator) -> bool:
# check for units to pick up.
if PickUpCargo(
unit=self.unit,
grid=self.grid,
pickup_targets=self.pickup_targets,
cargo_switch_to_role=self.cargo_switch_to_role,
).execute(ai, config, mediator):
return True

if self.unit.has_cargo:
# path to target
if PathUnitToTarget(
unit=self.unit,
grid=self.grid,
target=self.target,
success_at_distance=self.success_at_distance,
).execute(ai, config, mediator):
return True
if self.should_drop_units:
if DropCargo(unit=self.unit, target=self.unit.position).execute(
ai, config, mediator
):
return True

if self.keep_dropship_safe and KeepUnitSafe(
unit=self.unit, grid=self.grid
).execute(ai, config, mediator):
return True

return False
2 changes: 1 addition & 1 deletion src/ares/behaviors/combat/individual/pick_up_cargo.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class PickUpCargo(CombatIndividualBehavior):

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

unit: Unit # medivac for example
grid: np.ndarray = self.mediator.get_ground_grid
Expand Down
116 changes: 116 additions & 0 deletions src/ares/behaviors/combat/individual/reaper_grenade.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
from dataclasses import dataclass
from typing import TYPE_CHECKING

import numpy as np
from cython_extensions import (
cy_closest_to,
cy_distance_to,
cy_distance_to_squared,
cy_is_facing,
)
from sc2.ids.ability_id import AbilityId
from sc2.ids.unit_typeid import UnitTypeId as UnitID
from sc2.position import Point2
from sc2.unit import Unit
from sc2.units import Units

from ares.behaviors.combat.individual.combat_individual_behavior import (
CombatIndividualBehavior,
)
from ares.behaviors.combat.individual.place_predictive_aoe import PlacePredictiveAoE
from ares.behaviors.combat.individual.use_aoe_ability import UseAOEAbility
from ares.consts import ALL_WORKER_TYPES
from ares.dicts.unit_data import UNIT_DATA
from ares.managers.manager_mediator import ManagerMediator

if TYPE_CHECKING:
from ares import AresBot


@dataclass
class ReaperGrenade(CombatIndividualBehavior):
"""Do reaper grenade.

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

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

Attributes:
unit: The container unit.
enemy_units: The enemy units.
retreat_target: The target position where reaper would retreat.
grid: The grid used to predicatively place the grenade.
place_predictive: Whether to predicatively place the grenade.
reaper_grenade_range: The range at which to use the grenade.

"""

unit: Unit
enemy_units: Units | list[Unit]
retreat_target: Point2
grid: np.ndarray
place_predictive: bool = True
reaper_grenade_range: float = 5.0

def execute(self, ai: "AresBot", config: dict, mediator: ManagerMediator) -> bool:
if (
not self.enemy_units
or AbilityId.KD8CHARGE_KD8CHARGE not in self.unit.abilities
):
return False

unit_pos: Point2 = self.unit.position
targets: list[Unit] = [
t
for t in self.enemy_units
if t.is_visible and not UNIT_DATA[UnitID.REAPER]["flying"]
]

close_unit: Unit = cy_closest_to(unit_pos, targets)

# close unit is not chasing reaper, throw aggressive grenade
if (
self.place_predictive
and close_unit.type_id not in ALL_WORKER_TYPES
and cy_is_facing(self.unit, close_unit, 0.1)
):
if path_to_target := mediator.find_raw_path(
start=unit_pos,
target=close_unit.position,
grid=self.grid,
sensitivity=1,
):

if PlacePredictiveAoE(
unit=self.unit,
path=path_to_target[:30],
enemy_center_unit=close_unit,
aoe_ability=AbilityId.KD8CHARGE_KD8CHARGE,
ability_delay=34,
).execute(ai, config=config, mediator=mediator):
return True

close_targets: list[Unit] = [
t
for t in self.enemy_units
if cy_distance_to_squared(t.position, close_unit.position) < 20
]
if (
cy_distance_to(close_unit.position, self.unit.position)
< self.reaper_grenade_range + close_unit.radius
and len(close_targets) >= 2
):
if UseAOEAbility(
unit=self.unit,
ability_id=AbilityId.KD8CHARGE_KD8CHARGE,
targets=close_targets,
min_targets=2,
).execute(ai, config, mediator):
return True

return False
Loading
Loading