Skip to content

API Reference

Utilize the menu to delve into the available functionalities, currently organized into three primary sections:

  • Behaviors - Discover the art of crafting personalized combat maneuvers or macro plans. Each Behavior can be executed individually, tailored to your specific requirements.
  • Manager mediator - Seamlessly orchestrating numerous managers in the background, the mediator serves as the recommended approach to access data and functions within these managers.

Convenient methods globally available:

AresBot

AresBot(game_step_override=None)

Bases: CustomBotAI

Final setup of CustomBotAI for usage.

Most bot logic should go in Hub.

Load config and set up necessary attributes.

Parameters:

Name Type Description Default
game_step_override Optional[int]

If provided, set the game_step to this value regardless of how it was specified elsewhere

None
Source code in src/ares/main.py
def __init__(self, game_step_override: Optional[int] = None):
    """Load config and set up necessary attributes.

    Parameters
    ----------
    game_step_override :
        If provided, set the game_step to this value regardless of how it was
        specified elsewhere
    """
    super().__init__()
    # use this Dict when compiling
    # self.config: Dict = CONFIG
    # otherwise we use the config.yml file
    __ares_config_location__: str = path.realpath(
        path.join(getcwd(), path.dirname(__file__))
    )
    self.__user_config_location__: str = path.abspath(".")
    config_parser: ConfigParser = ConfigParser(
        __ares_config_location__, self.__user_config_location__
    )

    self.config = config_parser.parse()

    self.game_step_override: Optional[int] = game_step_override
    self.unit_tag_dict: Dict[int, Unit] = {}
    self.chat_debug = None
    self.forcefield_to_bile_dict: Dict[Point2, int] = {}
    self.last_game_loop: int = -1

    # track adept shades as we only add them towards shade completion (160 frames)
    # Key: tag of shade Value: frame shade commenced (+32 if no adept owner found)
    self.adept_shades: DefaultDict[int, Dict] = defaultdict(dict)
    self.adept_tags_with_shades_assigned: Set[int] = set()
    # we skip python-sc2 iterations in realtime, so we keep track of our own one
    self.actual_iteration: int = 0
    self.WORKER_TYPES = WORKER_TYPES | {UnitID.DRONEBURROWED}
    self.supply_type: UnitID = UnitID.OVERLORD
    self.num_larva_left: int = 0

    self._same_order_actions: list[
        tuple[AbilityId, set[int], Optional[Union[Unit, Point2]]]
    ] = []
    self._drop_unload_actions: list[tuple[int, int]] = []

    self.arcade_mode: bool = False

mediator property

mediator

Register behavior.

Shortcut to self.manager_hub.manager_mediator

Returns:

Type Description
ManagerMediator

get_build_structures

get_build_structures(
    structure_unit_types,
    unit_type,
    build_dict=None,
    ignored_build_from_tags=None,
)

Get all structures (or units) where we can spawn unit_type. Takes into account techlabs and reactors. And Gateway / warp gate

Parameters:

Name Type Description Default
structure_unit_types set[UnitTypeId]

The valid build structures we can spawn this unit_type from.

required
unit_type UnitTypeId

The target unit we are trying to spawn.

required
build_dict dict[Unit, UnitTypeId](optional)

Use to prevent selecting idle build structures that have already got a pending order this frame. Key: Unit that should get order, value: what UnitID to build

None
ignored_build_from_tags Set[int]

Pass in if you don't want certain build structures selected.

None

Returns:

Type Description
list[Unit] :

List of structures / units where this unit could possibly be spawned from.

Source code in src/ares/main.py
def get_build_structures(
    self,
    structure_unit_types: set[UnitID],
    unit_type: UnitID,
    build_dict=None,
    ignored_build_from_tags=None,
) -> list[Unit]:
    """Get all structures (or units) where we can spawn unit_type.
    Takes into account techlabs and reactors. And Gateway / warp gate


    Parameters
    ----------
    structure_unit_types :
        The valid build structures we can spawn this unit_type from.
    unit_type :
        The target unit we are trying to spawn.
    build_dict : dict[Unit, UnitID] (optional)
        Use to prevent selecting idle build structures that
        have already got a pending order this frame.
        Key: Unit that should get order, value: what UnitID to build
    ignored_build_from_tags : Set[int]
        Pass in if you don't want certain build structures selected.

    Returns
    -------
    list[Unit] :
        List of structures / units where this unit could possibly be spawned from.
    """
    if ignored_build_from_tags is None:
        ignored_build_from_tags = {}
    if build_dict is None:
        build_dict = {}

    structures_dict: dict[UnitID:Units] = self.mediator.get_own_structures_dict
    own_army_dict: dict[UnitID:Units] = self.mediator.get_own_army_dict
    build_from_dict: dict[UnitID:Units] = structures_dict
    if self.race == Race.Zerg:
        build_from_dict: dict[UnitID:Units] = {
            **structures_dict,
            **own_army_dict,
        }
    build_from_tags: list[int] = []
    using_larva: bool = False
    for structure_type in structure_unit_types:
        if structure_type not in build_from_dict:
            continue

        if structure_type == UnitID.LARVA:
            using_larva = True

        build_from: Units = build_from_dict[structure_type]
        requires_techlab: bool = TRAIN_INFO[structure_type][unit_type].get(
            "requires_techlab", False
        )
        if not requires_techlab:
            build_from_tags.extend(
                [
                    u.tag
                    for u in build_from
                    if u.is_ready and u.is_idle and u not in build_dict
                ]
            )
            if self.race == Race.Terran:
                build_from_tags.extend(
                    u.tag
                    for u in build_from
                    if u.is_ready
                    and u.has_reactor
                    and len(u.orders) < 2
                    and u not in build_dict
                )
        else:
            build_from_tags.extend(
                [
                    u.tag
                    for u in build_from
                    if u.is_ready
                    and u.is_idle
                    and u.has_add_on
                    and self.unit_tag_dict[u.add_on_tag].is_ready
                    and u.add_on_tag in self.techlab_tags
                    and u not in build_dict
                ]
            )

    build_structures: list[Unit] = [
        self.unit_tag_dict[u]
        for u in build_from_tags
        if u not in ignored_build_from_tags
    ]
    # sort build structures with reactors first
    if self.race == Race.Terran:
        build_structures = sorted(
            build_structures,
            key=lambda structure: -1 * (structure.add_on_tag in self.reactor_tags)
            + 1 * (structure.add_on_tag in self.techlab_tags),
        )
    # limit build structures to number of larva left
    if self.race == Race.Zerg and using_larva:
        build_structures = build_structures[: self.num_larva_left]

    return build_structures

on_before_start async

on_before_start()

Train a drone and split workers before managers are set up

Called before bot properly initializes

Returns:

Type Description
None
Source code in src/ares/main.py
async def on_before_start(self) -> None:
    """Train a drone and split workers before managers are set up

    Called before bot properly initializes

    Returns
    -------
    None
    """
    # optional build order config from a user, add to the existing config dictionary
    __user_build_orders_location__: str = path.join(
        self.__user_config_location__, f"{self.race.name.lower()}_builds.yml"
    )
    if path.isfile(__user_build_orders_location__):
        with open(__user_build_orders_location__, "r") as config_file:
            build_order_config: dict = yaml.safe_load(config_file)
            self.config.update(build_order_config)

    self.gas_type = race_gas[self.race]
    self.worker_type = race_worker[self.race]
    self.supply_type = RACE_SUPPLY[self.race]
    if self.race != Race.Zerg:
        self.base_townhall_type = (
            UnitID.COMMANDCENTER if self.race == Race.Terran else UnitID.NEXUS
        )
    else:
        self.base_townhall_type = UnitID.HATCHERY

on_building_construction_complete async

on_building_construction_complete(unit)

On structure completion event (own units)

Parameters:

Name Type Description Default
unit Unit

The Unit that just finished building

required

Returns:

Type Description
None
Source code in src/ares/main.py
async def on_building_construction_complete(self, unit: Unit) -> None:
    """On structure completion event (own units)

    Parameters
    ----------
    unit :
        The Unit that just finished building

    Returns
    -------
    None
    """
    await self.manager_hub.on_structure_complete(unit)

on_building_construction_started async

on_building_construction_started(unit)

On structure starting

Parameters:

Name Type Description Default
unit Unit
required

Returns:

Type Description
None
Source code in src/ares/main.py
async def on_building_construction_started(self, unit: Unit) -> None:
    """On structure starting

    Parameters
    ----------
    unit :

    Returns
    -------
    None
    """
    self.manager_hub.on_building_started(unit)

on_end async

on_end(game_result)

Output game info to the log and save data (if enabled)

Called on game end

Parameters:

Name Type Description Default
game_result Result

The game result

required

Returns:

Type Description
None
Source code in src/ares/main.py
async def on_end(self, game_result: Result) -> None:
    """Output game info to the log and save data (if enabled)

    Called on game end


    Parameters
    ----------
    game_result : Result
        The game result

    Returns
    -------
    None
    """
    # TODO: Put this in a method somewhere
    logger.info("END GAME REPORT")
    logger.info(f"Idle worker time: {self.state.score.idle_worker_time}")
    logger.info(f"Killed value units: {self.state.score.killed_value_units}")
    logger.info(
        f"Killed value structures: {self.state.score.killed_value_structures}"
    )
    logger.info(f"Collected minerals: {self.state.score.collected_minerals}")
    logger.info(f"Collected vespene: {self.state.score.collected_vespene}")
    if self.config[USE_DATA]:
        self.manager_hub.on_game_end(game_result)

on_start async

on_start()

Set up game step, managers, and information that requires game data

Called just before the first step, all game info is available

Returns:

Type Description
None
Source code in src/ares/main.py
async def on_start(self) -> None:
    """Set up game step, managers, and information that requires game data

    Called just before the first step, all game info is available

    Returns
    -------
    None
    """
    # manually skip the frames in realtime
    if self.realtime:
        self.client.game_step = 1
    elif self.game_step_override:
        self.client.game_step = self.game_step_override
    else:
        # set the game step from config
        self.client.game_step = (
            self.config[GAME_STEP]
            if not self.config[DEBUG]
            else self.config[DEBUG_GAME_STEP]
        )

    if not self.enemy_start_locations or not self.townhalls:
        self.arcade_mode = True

    self.register_managers()

    self.build_order_runner: BuildOrderRunner = BuildOrderRunner(
        self,
        self.manager_hub.data_manager.chosen_opening,
        self.config,
        self.manager_hub.manager_mediator,
    )
    self.behavior_executioner: BehaviorExecutioner = BehaviorExecutioner(
        self, self.config, self.manager_hub.manager_mediator
    )

    if self.config[DEBUG] and self.config[DEBUG_OPTIONS][CHAT_DEBUG]:
        from ares.chat_debug import ChatDebug

        self.chat_debug = ChatDebug(self)

    self.cost_dict: Dict[UnitID, Cost] = COST_DICT

on_step async

on_step(iteration)

Play the game

Called on every game step

Parameters:

Name Type Description Default
iteration int

The current game iteration

required

Returns:

Type Description
None
Source code in src/ares/main.py
async def on_step(self, iteration: int) -> None:
    """Play the game

    Called on every game step

    Parameters
    ----------
    iteration : int
        The current game iteration

    Returns
    -------
    None

    """

    """
    If playing in realtime, we set the game step to 1 (in on_start) and then
    manually skip frames. This gives Ares a time limit of 4 frames (45ms per frame)
    to finish an iteration. Playing every 4th frame seems to be the generally
    accepted solution to prevent weird things going on. And from Ares' point of
    view, they have a better chance of running smoothly on older PC's.
    """
    if self.realtime and self.last_game_loop + 4 > self.state.game_loop:
        return

    self.last_game_loop = self.state.game_loop

    await self.manager_hub.update_managers(self.actual_iteration)
    if not self.build_order_runner.build_completed:
        await self.build_order_runner.run_build()

    self.actual_iteration += 1
    if self.chat_debug:
        await self.chat_debug.parse_commands()

on_unit_created async

on_unit_created(unit)

On unit created event (own units)

Parameters:

Name Type Description Default
unit Unit

The Unit that was just created

required

Returns:

Type Description
None
Source code in src/ares/main.py
async def on_unit_created(self, unit: Unit) -> None:
    """On unit created event (own units)

    Parameters
    ----------
    unit :
        The Unit that was just created

    Returns
    -------
    None
    """
    await self.manager_hub.on_unit_created(unit)

on_unit_destroyed async

on_unit_destroyed(unit_tag)

On unit or structure destroyed event

Parameters:

Name Type Description Default
unit_tag int

The tag of the unit that was just destroyed

required

Returns:

Type Description
None
Source code in src/ares/main.py
async def on_unit_destroyed(self, unit_tag: int) -> None:
    """On unit or structure destroyed event

    Parameters
    ----------
    unit_tag :
        The tag of the unit that was just destroyed

    Returns
    -------
    None
    """
    await self.manager_hub.on_unit_destroyed(unit_tag)

on_unit_took_damage async

on_unit_took_damage(unit, amount_damage_taken)

On unit or structure taking damage

Parameters:

Name Type Description Default
unit Unit

The Unit that took damage

required
amount_damage_taken float

The amount of damage the Unit took

required

Returns:

Type Description
None
Source code in src/ares/main.py
async def on_unit_took_damage(self, unit: Unit, amount_damage_taken: float) -> None:
    """On unit or structure taking damage

    Parameters
    ----------
    unit :
        The Unit that took damage
    amount_damage_taken :
        The amount of damage the Unit took

    Returns
    -------
    None
    """
    await self.manager_hub.on_unit_took_damage(unit)

register_behavior

register_behavior(behavior)

Register behavior.

Shortcut to self.behavior_executioner.register_behavior

Parameters:

Name Type Description Default
behavior Behavior

Class that follows the Behavior interface.

required

Returns:

Type Description
None
Source code in src/ares/main.py
def register_behavior(self, behavior: Behavior) -> None:
    """Register behavior.

    Shortcut to `self.behavior_executioner.register_behavior`

    Parameters
    ----------
    behavior : Behavior
        Class that follows the Behavior interface.

    Returns
    -------
    None
    """
    self.behavior_executioner.register_behavior(behavior)

register_managers

register_managers()

Register standard and custom managers.

Override in your bot class if you wish to use custom managers.

Examples:

custom_production_manager = CustomProductionManager( self, self.config, manager_mediator ) new_manager = NewManager(self, self.config, manager_mediator)

self.manager_hub = Hub( self, self.config, manager_mediator, production_manager=custom_production_manager, additional_managers=[new_manager], )

Returns:

Type Description
None
Source code in src/ares/main.py
def register_managers(self) -> None:
    """Register standard and custom managers.

    Override in your bot class if you wish to use custom managers.

    Examples
    --------
    custom_production_manager = CustomProductionManager(
        self, self.config, manager_mediator
    )
    new_manager = NewManager(self, self.config, manager_mediator)

    self.manager_hub = Hub(
        self,
        self.config,
        manager_mediator,
        production_manager=custom_production_manager,
        additional_managers=[new_manager],
    )

    Returns
    -------
    None
    """
    manager_mediator: ManagerMediator = ManagerMediator()
    self.manager_hub = Hub(self, self.config, manager_mediator)
    self.manager_hub.init_managers()

structure_pending

structure_pending(structure_type)

Checks pending structures, includes workers on route. Alternative and faster version of self.already_pending

Parameters:

Name Type Description Default
structure_type UnitTypeId
required

Returns:

Type Description
int
Source code in src/ares/main.py
def structure_pending(self, structure_type: UnitID) -> int:
    """
    Checks pending structures, includes workers on route.
    Alternative and faster version of `self.already_pending`

    Parameters
    ----------
    structure_type

    Returns
    -------
    int

    """
    num_pending: int = 0
    building_tracker: dict = self.mediator.get_building_tracker_dict
    for tag, info in building_tracker.items():
        structure_id: UnitID = building_tracker[tag][ID]
        if structure_id != structure_type:
            continue

        num_pending += 1

    if self.race != Race.Terran or structure_type in ADD_ONS:
        num_pending += len(
            [
                s
                for s in self.mediator.get_own_structures_dict[structure_type]
                if s.build_progress < 1.0
            ]
        )

    return num_pending

structure_present_or_pending

structure_present_or_pending(structure_type)

Checks presence of a structure, or if worker is on route to build structure.

Parameters:

Name Type Description Default
structure_type UnitTypeId
required

Returns:

Type Description
bool
Source code in src/ares/main.py
def structure_present_or_pending(self, structure_type: UnitID) -> bool:
    """
    Checks presence of a structure, or if worker is on route to
    build structure.

    Parameters
    ----------
    structure_type

    Returns
    -------
    bool

    """
    return (
        len(self.mediator.get_own_structures_dict[structure_type]) > 0
        or self.mediator.get_building_counter[structure_type] > 0
    )

unit_pending

unit_pending(unit_type)

Checks pending units. Alternative and faster version of self.already_pending

Parameters:

Name Type Description Default
unit_type UnitTypeId
required

Returns:

Type Description
int
Source code in src/ares/main.py
def unit_pending(self, unit_type: UnitID) -> int:
    """
    Checks pending units.
    Alternative and faster version of `self.already_pending`

    Parameters
    ----------
    unit_type

    Returns
    -------
    int

    """
    return cy_unit_pending(self, unit_type)