Skip to main content

5.5 - Using Special Abilities

Beyond basic commands, many units have powerful abilities that define high-level strategy—a Sentry's Force Field, a Ghost's Snipe, or a Queen's Inject Larva. The library provides a direct and intuitive syntax for using these abilities by making the Unit object itself "callable."

The Callable Unit: A Unified Syntax

The core method for using any ability is to treat the Unit object as a function.

  • Syntax: unit(ability_id, target, queue)
  • What it returns: A UnitCommand object. This means it must be executed with self.do() (e.g., await self.do(unit(...))).

This unified syntax handles all types of abilities.

Target TypeExampleUse Case
No Targetmarine(AbilityId.EFFECT_STIMPACK)Self-cast abilities like Stimpack or a Medivac's Heal.
Unit Targetghost(AbilityId.SNIPEDOT_SNIPE, target_ultralisk)Abilities that must be cast on another unit.
Point Targetsentry(AbilityId.FORCEFIELD_FORCEFIELD, location)Abilities that are cast on a specific point on the ground.

The Professional Ability Workflow

Issuing an ability command blindly will lead to errors. A professional bot follows a strict, four-step workflow to ensure the command is valid and will succeed.

(You want to use an ability...)
|
v
.--- 1. SELECT CASTER ---------.
| `queens = self.units(QUEEN)` |
`------------------------------`
|
v
.--- 2. CHECK PREREQUISITES --------------------.
| - `queen.energy >= 25` |
| - `await self.get_available_abilities(queen)` |
`-----------------------------------------------`
| (Checks pass)
v
.--- 3. FIND VALID TARGET --------------------.
| `target = self.townhalls.closest_to(queen)` |
`---------------------------------------------`
| (Target found)
v
.---- 4. ISSUE COMMAND ----------------------------------------.
| `await self.do(queen(AbilityId.EFFECT_INJECTLARVA, target))` |
`--------------------------------------------------------------`

Step 2 Deep Dive: self.get_available_abilities()

Just checking a unit's energy (unit.energy) is a common beginner mistake. An ability might have enough energy but still be on cooldown.

await self.get_available_abilities(unit) is the only reliable way to check. This async function queries the game and returns a list of every AbilityId that a specific unit can use on the current game tick.

Rule of Thumb: Before casting any ability with a cooldown, you must check if its AbilityId is present in the list returned by this function.


Developer's Checklist for Using an Ability

Before writing your code, follow this plan:

  • 1. Identify the Caster: Get a Units collection of the units that can cast the spell.
  • 2. Check Resources: Filter the collection for units with enough energy.
  • 3. Check Cooldown: In a loop, use await self.get_available_abilities() for each potential caster to see if the ability is ready.
  • 4. Find a Target: If the ability is targeted, find a suitable Unit or Point2.
  • 5. Issue the Command: Use await self.do(unit(ABILITY_ID, target)) to execute the action.

Pro Tip: To find the AbilityId for a specific action, you can explore the burnysc2.ids.ability_id file in the library's source code, or use a tool like the StarCraft II Data Dumper.


Code Example: The Zerg Matriarch Bot

This bot demonstrates the complete, professional workflow for using a Queen's "Inject Larva" ability. Each step in the code directly corresponds to a step in the workflow diagram.

# matriarch_bot.py

from sc2 import maps
from sc2.bot_ai import BotAI
from sc2.data import Difficulty, Race
from sc2.main import run_game
from sc2.player import Bot, Computer
from sc2.ids.unit_typeid import UnitTypeId
from sc2.ids.ability_id import AbilityId
from sc2.ids.buff_id import BuffId
from sc2.units import Units

class MatriarchBot(BotAI):
"""Demonstrates the robust workflow for using a Queen's inject ability."""

async def on_step(self, iteration: int):
await self.manage_injects()
# Helper to build a basic Zerg setup for the demo.
await self.build_infrastructure()

async def manage_injects(self):
# WORKFLOW STEP 1: SELECT CASTERS
# Find all Queens that have enough energy for an inject.
queens_with_energy: Units = self.units(UnitTypeId.QUEEN).filter(lambda q: q.energy >= 25)
if not queens_with_energy:
return

for queen in queens_with_energy:
# WORKFLOW STEP 2: CHECK PREREQUISITES (COOLDOWN)
# Query the game to see which abilities this specific queen can use right now.
available_abilities = await self.get_available_abilities(queen)
if AbilityId.EFFECT_INJECTLARVA not in available_abilities:
continue # This queen can't inject yet, move to the next one.

# WORKFLOW STEP 3: FIND VALID TARGET
# Find a ready townhall that is not already buffed with "Queen's Spawn Larva".
unbuffed_townhalls = self.townhalls.ready.filter(lambda th: not th.has_buff(BuffId.QUEENSPAWNLARVATIMER))
if not unbuffed_townhalls:
# No valid targets, maybe creep spread instead?
# For now, we'll just skip.
continue

# Find the closest unbuffed townhall to this queen.
closest_target = unbuffed_townhalls.closest_to(queen)

# WORKFLOW STEP 4: ISSUE COMMAND
# The queen(...) call creates a UnitCommand object. self.do() executes it.
await self.do(queen(AbilityId.EFFECT_INJECTLARVA, closest_target))
print(f"ACTION: Queen {queen.tag} is injecting Hatchery {closest_target.tag}")


async def build_infrastructure(self):
"""A simple helper to build a basic Zerg setup."""
if self.townhalls and self.townhalls.amount < 2 and self.can_afford(UnitTypeId.HATCHERY):
await self.expand_now()
if self.structures(UnitTypeId.SPAWNINGPOOL).amount < 1 and self.can_afford(UnitTypeId.SPAWNINGPOOL):
await self.build(UnitTypeId.SPAWNINGPOOL, near=self.start_location.position)
if self.townhalls and self.units(UnitTypeId.QUEEN).amount < self.townhalls.amount and self.can_afford(UnitTypeId.QUEEN):
self.train(UnitTypeId.QUEEN)


if __name__ == "__main__":
run_game(
maps.get("BlackburnAIE"),
[
Bot(Race.Zerg, MatriarchBot()),
Computer(Race.Terran, Difficulty.VeryEasy)
],
realtime=True,
)