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 withself.do()
(e.g.,await self.do(unit(...))
).
This unified syntax handles all types of abilities.
Target Type | Example | Use Case |
---|---|---|
No Target | marine(AbilityId.EFFECT_STIMPACK) | Self-cast abilities like Stimpack or a Medivac's Heal. |
Unit Target | ghost(AbilityId.SNIPEDOT_SNIPE, target_ultralisk) | Abilities that must be cast on another unit. |
Point Target | sentry(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
orPoint2
. - 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,
)