Top

sc2.unit module

from typing import Any, Dict, List, Optional, Set, Tuple, Union  # mypy type checking

from s2clientprotocol import raw_pb2 as raw_pb
from s2clientprotocol import sc2api_pb2 as sc_pb
from sc2.ids.buff_id import BuffId

from . import unit_command
from .data import Alliance, Attribute, CloakState, DisplayType, Race, TargetType, warpgate_abilities
from .game_data import GameData
from .ids.ability_id import AbilityId
from .ids.unit_typeid import UnitTypeId
from .position import Point2, Point3


class Unit:
    def __init__(self, proto_data, game_data):
        assert isinstance(proto_data, raw_pb.Unit)
        assert isinstance(game_data, GameData)
        self._proto = proto_data
        self._game_data = game_data

    @property
    def type_id(self) -> UnitTypeId:
        return UnitTypeId(self._proto.unit_type)

    @property
    def _type_data(self) -> "UnitTypeData":
        return self._game_data.units[self._proto.unit_type]

    @property
    def is_snapshot(self) -> bool:
        return self._proto.display_type == DisplayType.Snapshot.value

    @property
    def is_visible(self) -> bool:
        return self._proto.display_type == DisplayType.Visible.value

    @property
    def alliance(self) -> Alliance:
        return self._proto.alliance

    @property
    def is_mine(self) -> bool:
        return self._proto.alliance == Alliance.Self.value

    @property
    def is_enemy(self) -> bool:
        return self._proto.alliance == Alliance.Enemy.value

    @property
    def tag(self) -> int:
        return self._proto.tag

    @property
    def owner_id(self) -> int:
        return self._proto.owner

    @property
    def position(self) -> Point2:
        """2d position of the unit."""
        return self.position3d.to2

    @property
    def position3d(self) -> Point3:
        """3d position of the unit."""
        return Point3.from_proto(self._proto.pos)

    def distance_to(self, p: Union["Unit", Point2, Point3]) -> Union[int, float]:
        """ Using the 2d distance between self and p. To calculate the 3d distance, use unit.position3d.distance_to(p) """
        return self.position.distance_to_point2(p.position)

    @property
    def facing(self) -> Union[int, float]:
        return self._proto.facing

    @property
    def radius(self) -> Union[int, float]:
        return self._proto.radius

    @property
    def detect_range(self) -> Union[int, float]:
        return self._proto.detect_range

    @property
    def radar_range(self) -> Union[int, float]:
        return self._proto.radar_range

    @property
    def build_progress(self) -> Union[int, float]:
        return self._proto.build_progress

    @property
    def is_ready(self) -> bool:
        return self.build_progress == 1.0

    @property
    def cloak(self) -> CloakState:
        return self._proto.cloak

    @property
    def is_blip(self) -> bool:
        """ Detected by sensor tower. """
        return self._proto.is_blip

    @property
    def is_powered(self) -> bool:
        """ Is powered by a pylon nearby. """
        return self._proto.is_powered

    @property
    def is_burrowed(self) -> bool:
        return self._proto.is_burrowed

    @property
    def is_flying(self) -> bool:
        return self._proto.is_flying

    @property
    def is_structure(self) -> bool:
        return Attribute.Structure.value in self._type_data.attributes

    @property
    def is_light(self) -> bool:
        return Attribute.Light.value in self._type_data.attributes

    @property
    def is_armored(self) -> bool:
        return Attribute.Armored.value in self._type_data.attributes

    @property
    def is_biological(self) -> bool:
        return Attribute.Biological.value in self._type_data.attributes

    @property
    def is_mechanical(self) -> bool:
        return Attribute.Mechanical.value in self._type_data.attributes

    @property
    def is_robotic(self) -> bool:
        return Attribute.Robotic.value in self._type_data.attributes

    @property
    def is_massive(self) -> bool:
        return Attribute.Massive.value in self._type_data.attributes

    @property
    def is_psionic(self) -> bool:
        return Attribute.Psionic.value in self._type_data.attributes

    @property
    def is_mineral_field(self) -> bool:
        return self._type_data.has_minerals

    @property
    def is_vespene_geyser(self) -> bool:
        return self._type_data.has_vespene

    @property
    def tech_alias(self) -> Optional[List[UnitTypeId]]:
        """ Building tech equality, e.g. OrbitalCommand is the same as CommandCenter """
        """ For Hive, this returns [UnitTypeId.Hatchery, UnitTypeId.Lair] """
        """ For SCV, this returns None """
        return self._type_data.tech_alias

    @property
    def unit_alias(self) -> Optional[UnitTypeId]:
        """ Building type equality, e.g. FlyingOrbitalCommand is the same as OrbitalCommand """
        """ For flying OrbitalCommand, this returns UnitTypeId.OrbitalCommand """
        """ For SCV, this returns None """
        return self._type_data.unit_alias

    @property
    def race(self) -> Race:
        return Race(self._type_data._proto.race)

    @property
    def health(self) -> Union[int, float]:
        return self._proto.health

    @property
    def health_max(self) -> Union[int, float]:
        return self._proto.health_max

    @property
    def health_percentage(self) -> Union[int, float]:
        if self._proto.health_max == 0:
            return 0
        return self._proto.health / self._proto.health_max

    @property
    def shield(self) -> Union[int, float]:
        return self._proto.shield

    @property
    def shield_max(self) -> Union[int, float]:
        return self._proto.shield_max

    @property
    def shield_percentage(self) -> Union[int, float]:
        if self._proto.shield_max == 0:
            return 0
        return self._proto.shield / self._proto.shield_max

    @property
    def energy(self) -> Union[int, float]:
        return self._proto.energy

    @property
    def energy_max(self) -> Union[int, float]:
        return self._proto.energy_max

    @property
    def energy_percentage(self) -> Union[int, float]:
        if self._proto.energy_max == 0:
            return 0
        return self._proto.energy / self._proto.energy_max

    @property
    def mineral_contents(self) -> int:
        """ How many minerals a mineral field has left to mine from """
        return self._proto.mineral_contents

    @property
    def vespene_contents(self) -> int:
        """ How much gas is remaining in a geyser """
        return self._proto.vespene_contents

    @property
    def has_vespene(self) -> bool:
        """ Checks if a geyser has any gas remaining (can't build extractors on empty geysers), useful for lategame """
        return bool(self._proto.vespene_contents)

    @property
    def weapon_cooldown(self) -> Union[int, float]:
        """ Returns some time (more than game loops) until the unit can fire again, returns -1 for units that can't attack
        Usage:
        if not unit.weapon_cooldown:
            await self.do(unit.attack(target))
        elif unit.weapon_cooldown < 0:
            await self.do(unit.move(closest_allied_unit_because_cant_attack))
        else:
            await self.do(unit.move(retreatPosition))
        """
        if self.can_attack:
            return self._proto.weapon_cooldown
        return -1

    @property
    def cargo_size(self) -> Union[float, int]:
        """ How much cargo this unit uses up in cargo_space """
        return self._type_data.cargo_size

    @property
    def has_cargo(self) -> bool:
        """ If this unit has units loaded """
        return bool(self._proto.cargo_space_taken)

    @property
    def cargo_used(self) -> Union[float, int]:
        """ How much cargo space is used (some units take up more than 1 space) """
        return self._proto.cargo_space_taken

    @property
    def cargo_max(self) -> Union[float, int]:
        """ How much cargo space is totally available - CC: 5, Bunker: 4, Medivac: 8 and Bunker can only load infantry, CC only SCVs """
        return self._proto.cargo_space_max

    @property
    def passengers(self) -> Set["PassengerUnit"]:
        """ Units inside a Bunker, CommandCenter, Nydus, Medivac, WarpPrism, Overlord """
        return {PassengerUnit(unit, self._game_data) for unit in self._proto.passengers}

    @property
    def passengers_tags(self) -> Set[int]:
        return {unit.tag for unit in self._proto.passengers}
    
    @property
    def can_attack(self) -> bool:
        if hasattr(self._type_data._proto, "weapons"):
            weapons = self._type_data._proto.weapons
            return bool(weapons)
        return False
    
    @property
    def can_attack_ground(self) -> bool:
        if hasattr(self._type_data._proto, "weapons"):
            weapons = self._type_data._proto.weapons
            weapon = next((weapon for weapon in weapons if weapon.type in {TargetType.Ground.value, TargetType.Any.value}), None)
            return weapon is not None
        return False
    
    @property
    def ground_dps(self) -> Union[int, float]:
        """ Does not include upgrades """
        if hasattr(self._type_data._proto, "weapons"):
            weapons = self._type_data._proto.weapons
            weapon = next((weapon for weapon in weapons if weapon.type in {TargetType.Ground.value, TargetType.Any.value}), None)            
            if weapon:
                return (weapon.damage * weapon.attacks) / weapon.speed
        return 0

    @property
    def ground_range(self) -> Union[int, float]:
        """ Does not include upgrades """
        if hasattr(self._type_data._proto, "weapons"):
            weapons = self._type_data._proto.weapons
            weapon = next((weapon for weapon in weapons if weapon.type in {TargetType.Ground.value, TargetType.Any.value}), None)
            if weapon:
                return weapon.range
        return 0

    @property
    def can_attack_air(self) -> bool:
        """ Does not include upgrades """
        if hasattr(self._type_data._proto, "weapons"):
            weapons = self._type_data._proto.weapons
            weapon = next((weapon for weapon in weapons if weapon.type in {TargetType.Air.value, TargetType.Any.value}), None)
            return weapon is not None
        return False

    @property
    def air_dps(self) -> Union[int, float]:
        """ Does not include upgrades """
        if hasattr(self._type_data._proto, "weapons"):
            weapons = self._type_data._proto.weapons
            weapon = next((weapon for weapon in weapons if weapon.type in {TargetType.Air.value, TargetType.Any.value}), None)
            if weapon:
                return (weapon.damage * weapon.attacks) / weapon.speed
        return 0

    @property
    def air_range(self) -> Union[int, float]:
        """ Does not include upgrades """
        if hasattr(self._type_data._proto, "weapons"):
            weapons = self._type_data._proto.weapons
            weapon = next((weapon for weapon in weapons if weapon.type in {TargetType.Air.value, TargetType.Any.value}), None)
            if weapon:
                return weapon.range
        return 0

    def target_in_range(self, target: "Unit", bonus_distance: Union[int, float] = 0) -> bool:
        """ Includes the target's radius when calculating distance to target """
        if self.can_attack_ground and not target.is_flying:
            unit_attack_range = self.ground_range
        elif self.can_attack_air and (target.is_flying or target.type_id == UnitTypeId.COLOSSUS):
            unit_attack_range = self.air_range
        else:
            unit_attack_range = -1
        return self.position._distance_squared(target.position) <= (self.radius + target.radius + unit_attack_range - bonus_distance) ** 2


    @property
    def armor(self) -> Union[int, float]:
        """ Does not include upgrades """
        return self._type_data._proto.armor

    @property
    def sight_range(self) -> Union[int, float]:
        return self._type_data._proto.sight_range

    @property
    def movement_speed(self) -> Union[int, float]:
        return self._type_data._proto.movement_speed

    @property
    def is_carrying_minerals(self) -> bool:
        """ Checks if a worker (or MULE) is carrying (gold-)minerals. """
        return any(
            buff.value in self._proto.buff_ids
            for buff in {BuffId.CARRYMINERALFIELDMINERALS, BuffId.CARRYHIGHYIELDMINERALFIELDMINERALS}
        )
    @property
    def is_carrying_vespene(self) -> bool:
        """ Checks if a worker is carrying vespene. """
        return any(
            buff.value in self._proto.buff_ids
            for buff in {
                BuffId.CARRYHARVESTABLEVESPENEGEYSERGAS,
                BuffId.CARRYHARVESTABLEVESPENEGEYSERGASPROTOSS,
                BuffId.CARRYHARVESTABLEVESPENEGEYSERGASZERG,
            }
        )

    @property
    def is_selected(self) -> bool:
        return self._proto.is_selected

    @property
    def orders(self) -> List["UnitOrder"]:
        return [UnitOrder.from_proto(o, self._game_data) for o in self._proto.orders]

    @property
    def noqueue(self) -> bool:
        return not self.orders

    @property
    def is_moving(self) -> bool:
        return self.orders and self.orders[0].ability.id is AbilityId.MOVE

    @property
    def is_attacking(self) -> bool:
        return self.orders and self.orders[0].ability.id in {
            AbilityId.ATTACK,
            AbilityId.ATTACK_ATTACK,
            AbilityId.ATTACK_ATTACKTOWARDS,
            AbilityId.ATTACK_ATTACKBARRAGE,
            AbilityId.SCAN_MOVE,
        }

    @property
    def is_gathering(self) -> bool:
        """ Checks if a unit is on its way to a mineral field / vespene geyser to mine. """
        return self.orders and self.orders[0].ability.id is AbilityId.HARVEST_GATHER

    @property
    def is_returning(self) -> bool:
        """ Checks if a unit is returning from mineral field / vespene geyser to deliver resources to townhall. """
        return self.orders and self.orders[0].ability.id is AbilityId.HARVEST_RETURN

    @property
    def is_collecting(self) -> bool:
        """ Combines the two properties above. """
        return self.orders and self.orders[0].ability.id in {AbilityId.HARVEST_GATHER, AbilityId.HARVEST_RETURN}

    @property
    def is_constructing_scv(self) -> bool:
        """ Checks if the unit is an SCV that is currently building. """
        return self.orders and self.orders[0].ability.id in {
            AbilityId.TERRANBUILD_ARMORY,
            AbilityId.TERRANBUILD_BARRACKS,
            AbilityId.TERRANBUILD_BUNKER,
            AbilityId.TERRANBUILD_COMMANDCENTER,
            AbilityId.TERRANBUILD_ENGINEERINGBAY,
            AbilityId.TERRANBUILD_FACTORY,
            AbilityId.TERRANBUILD_FUSIONCORE,
            AbilityId.TERRANBUILD_GHOSTACADEMY,
            AbilityId.TERRANBUILD_MISSILETURRET,
            AbilityId.TERRANBUILD_REFINERY,
            AbilityId.TERRANBUILD_SENSORTOWER,
            AbilityId.TERRANBUILD_STARPORT,
            AbilityId.TERRANBUILD_SUPPLYDEPOT,
        }

    @property
    def is_repairing(self) -> bool:
        return self.orders and self.orders[0].ability.id in {
            AbilityId.EFFECT_REPAIR,
            AbilityId.EFFECT_REPAIR_MULE,
            AbilityId.EFFECT_REPAIR_SCV,
        }
    
    @property
    def order_target(self) -> Optional[Union[int, Point2]]:
        """ Returns the target tag (if it is a Unit) or Point2 (if it is a Position) from the first order, reutrn None if the unit is idle """
        if self.orders:
            if isinstance(self.orders[0].target, int):
                return self.orders[0].target
            else:
                return Point2.from_proto(self.orders[0].target)
        return None

    @property
    def is_idle(self) -> bool:
        return not self.orders

    @property
    def add_on_tag(self) -> int:
        return self._proto.add_on_tag

    @property
    def add_on_land_position(self) -> Point2:
        """ If unit is addon (techlab or reactor), returns the position where a terran building has to land to connect to addon """
        return self.position.offset(Point2((-2.5, 0.5)))

    @property
    def has_add_on(self) -> bool:
        return self.add_on_tag != 0

    @property
    def assigned_harvesters(self) -> int:
        return self._proto.assigned_harvesters

    @property
    def ideal_harvesters(self) -> int:
        return self._proto.ideal_harvesters

    @property
    def surplus_harvesters(self) -> int:
        """ Returns a positive number if it has too many harvesters mining, a negative number if it has too few mining """
        return -(self._proto.ideal_harvesters - self._proto.assigned_harvesters)

    @property
    def name(self) -> str:
        return self._type_data.name

    def train(self, unit, *args, **kwargs):
        return self(self._game_data.units[unit.value].creation_ability.id, *args, **kwargs)

    def build(self, unit, *args, **kwargs):
        return self(self._game_data.units[unit.value].creation_ability.id, *args, **kwargs)

    def research(self, upgrade, *args, **kwargs):
        """ Requires UpgradeId to be passed instead of AbilityId """
        return self(self._game_data.upgrades[upgrade.value].research_ability.id, *args, **kwargs)

    def has_buff(self, buff):
        assert isinstance(buff, BuffId)
        return buff.value in self._proto.buff_ids

    def warp_in(self, unit, placement, *args, **kwargs):
        normal_creation_ability = self._game_data.units[unit.value].creation_ability.id
        return self(warpgate_abilities[normal_creation_ability], placement, *args, **kwargs)

    def attack(self, *args, **kwargs):
        return self(AbilityId.ATTACK, *args, **kwargs)

    def gather(self, *args, **kwargs):
        return self(AbilityId.HARVEST_GATHER, *args, **kwargs)

    def return_resource(self, *args, **kwargs):
        return self(AbilityId.HARVEST_RETURN, *args, **kwargs)

    def move(self, *args, **kwargs):
        return self(AbilityId.MOVE, *args, **kwargs)
    
    def scan_move(self, *args, **kwargs):
        return self(AbilityId.SCAN_MOVE, *args, **kwargs)

    def scan_move(self, *args, **kwargs):
        return self(AbilityId.SCAN_MOVE, *args, **kwargs)

    def hold_position(self, *args, **kwargs):
        return self(AbilityId.HOLDPOSITION, *args, **kwargs)

    def stop(self, *args, **kwargs):
        return self(AbilityId.STOP, *args, **kwargs)

    def repair(self, *args, **kwargs):
        return self(AbilityId.EFFECT_REPAIR, *args, **kwargs)

    def __hash__(self):
        return hash(self.tag)

    def __call__(self, ability, *args, **kwargs):
        return unit_command.UnitCommand(ability, self, *args, **kwargs)

    def __repr__(self):
        return f"Unit(name={self.name !r}, tag={self.tag})"


class UnitOrder:
    @classmethod
    def from_proto(cls, proto, game_data):
        return cls(
            game_data.abilities[proto.ability_id],
            (proto.target_world_space_pos
                if proto.HasField("target_world_space_pos") else
                proto.target_unit_tag),
            proto.progress
        )

    def __init__(self, ability, target, progress=None):
        self.ability = ability
        self.target = target
        self.progress = progress

    def __repr__(self):
        return f"UnitOrder({self.ability}, {self.target}, {self.progress})"


class PassengerUnit:
    def __init__(self, proto_data, game_data):
        assert isinstance(game_data, GameData)
        self._proto = proto_data
        self._game_data = game_data

    def __repr__(self):
        return f"PassengerUnit(name={self.name !r}, tag={self.tag})"

    @property
    def type_id(self) -> UnitTypeId:
        return UnitTypeId(self._proto.unit_type)

    @property
    def _type_data(self) -> "UnitTypeData":
        return self._game_data.units[self._proto.unit_type]

    @property
    def name(self) -> str:
        return self._type_data.name

    @property
    def race(self) -> Race:
        return Race(self._type_data._proto.race)

    @property
    def tag(self) -> int:
        return self._proto.tag

    @property
    def is_structure(self) -> bool:
        return Attribute.Structure.value in self._type_data.attributes

    @property
    def is_light(self) -> bool:
        return Attribute.Light.value in self._type_data.attributes

    @property
    def is_armored(self) -> bool:
        return Attribute.Armored.value in self._type_data.attributes

    @property
    def is_biological(self) -> bool:
        return Attribute.Biological.value in self._type_data.attributes

    @property
    def is_mechanical(self) -> bool:
        return Attribute.Mechanical.value in self._type_data.attributes

    @property
    def is_robotic(self) -> bool:
        return Attribute.Robotic.value in self._type_data.attributes

    @property
    def is_massive(self) -> bool:
        return Attribute.Massive.value in self._type_data.attributes

    @property
    def cargo_size(self) -> Union[float, int]:
        """ How much cargo this unit uses up in cargo_space """
        return self._type_data.cargo_size

    @property
    def can_attack(self) -> bool:
        if hasattr(self._type_data._proto, "weapons"):
            weapons = self._type_data._proto.weapons
            return bool(weapons)
        return False

    @property
    def can_attack_ground(self) -> bool:
        if hasattr(self._type_data._proto, "weapons"):
            weapons = self._type_data._proto.weapons
            weapon = next((weapon for weapon in weapons if weapon.type in [TargetType.Ground.value, TargetType.Any.value]), None)
            return weapon is not None
        return False

    @property
    def ground_dps(self) -> Union[int, float]:
        """ Does not include upgrades """
        if hasattr(self._type_data._proto, "weapons"):
            weapons = self._type_data._proto.weapons
            weapon = next((weapon for weapon in weapons if weapon.type in [TargetType.Ground.value, TargetType.Any.value]), None)
            if weapon:
                return (weapon.damage * weapon.attacks) / weapon.speed
        return 0

    @property
    def ground_range(self) -> Union[int, float]:
        """ Does not include upgrades """
        if hasattr(self._type_data._proto, "weapons"):
            weapons = self._type_data._proto.weapons
            weapon = next((weapon for weapon in weapons if weapon.type in [TargetType.Ground.value, TargetType.Any.value]), None)
            if weapon:
                return weapon.range
        return 0

    @property
    def can_attack_air(self) -> bool:
        """ Does not include upgrades """
        if hasattr(self._type_data._proto, "weapons"):
            weapons = self._type_data._proto.weapons
            weapon = next((weapon for weapon in weapons if weapon.type in [TargetType.Air.value, TargetType.Any.value]), None)
            return weapon is not None
        return False

    @property
    def air_dps(self) -> Union[int, float]:
        """ Does not include upgrades """
        if hasattr(self._type_data._proto, "weapons"):
            weapons = self._type_data._proto.weapons
            weapon = next((weapon for weapon in weapons if weapon.type in [TargetType.Air.value, TargetType.Any.value]), None)
            if weapon:
                return (weapon.damage * weapon.attacks) / weapon.speed
        return 0

    @property
    def air_range(self) -> Union[int, float]:
        """ Does not include upgrades """
        if hasattr(self._type_data._proto, "weapons"):
            weapons = self._type_data._proto.weapons
            weapon = next((weapon for weapon in weapons if weapon.type in [TargetType.Air.value, TargetType.Any.value]), None)
            if weapon:
                return weapon.range
        return 0

    @property
    def armor(self) -> Union[int, float]:
        """ Does not include upgrades """
        return self._type_data._proto.armor

    @property
    def sight_range(self) -> Union[int, float]:
        return self._type_data._proto.sight_range

    @property
    def movement_speed(self) -> Union[int, float]:
        return self._type_data._proto.movement_speed

    @property
    def health(self) -> Union[int, float]:
        return self._proto.health

    @property
    def health_max(self) -> Union[int, float]:
        return self._proto.health_max

    @property
    def health_percentage(self) -> Union[int, float]:
        if self._proto.health_max == 0:
            return 0
        return self._proto.health / self._proto.health_max

    @property
    def shield(self) -> Union[int, float]:
        return self._proto.shield

    @property
    def shield_max(self) -> Union[int, float]:
        return self._proto.shield_max

    @property
    def shield_percentage(self) -> Union[int, float]:
        if self._proto.shield_max == 0:
            return 0
        return self._proto.shield / self._proto.shield_max

    @property
    def energy(self) -> Union[int, float]:
        return self._proto.energy

    @property
    def energy_max(self) -> Union[int, float]:
        return self._proto.energy_max

    @property
    def energy_percentage(self) -> Union[int, float]:
        if self._proto.energy_max == 0:
            return 0
        return self._proto.energy / self._proto.energy_max

Module variables

var warpgate_abilities

Classes

class PassengerUnit

class PassengerUnit:
    def __init__(self, proto_data, game_data):
        assert isinstance(game_data, GameData)
        self._proto = proto_data
        self._game_data = game_data

    def __repr__(self):
        return f"PassengerUnit(name={self.name !r}, tag={self.tag})"

    @property
    def type_id(self) -> UnitTypeId:
        return UnitTypeId(self._proto.unit_type)

    @property
    def _type_data(self) -> "UnitTypeData":
        return self._game_data.units[self._proto.unit_type]

    @property
    def name(self) -> str:
        return self._type_data.name

    @property
    def race(self) -> Race:
        return Race(self._type_data._proto.race)

    @property
    def tag(self) -> int:
        return self._proto.tag

    @property
    def is_structure(self) -> bool:
        return Attribute.Structure.value in self._type_data.attributes

    @property
    def is_light(self) -> bool:
        return Attribute.Light.value in self._type_data.attributes

    @property
    def is_armored(self) -> bool:
        return Attribute.Armored.value in self._type_data.attributes

    @property
    def is_biological(self) -> bool:
        return Attribute.Biological.value in self._type_data.attributes

    @property
    def is_mechanical(self) -> bool:
        return Attribute.Mechanical.value in self._type_data.attributes

    @property
    def is_robotic(self) -> bool:
        return Attribute.Robotic.value in self._type_data.attributes

    @property
    def is_massive(self) -> bool:
        return Attribute.Massive.value in self._type_data.attributes

    @property
    def cargo_size(self) -> Union[float, int]:
        """ How much cargo this unit uses up in cargo_space """
        return self._type_data.cargo_size

    @property
    def can_attack(self) -> bool:
        if hasattr(self._type_data._proto, "weapons"):
            weapons = self._type_data._proto.weapons
            return bool(weapons)
        return False

    @property
    def can_attack_ground(self) -> bool:
        if hasattr(self._type_data._proto, "weapons"):
            weapons = self._type_data._proto.weapons
            weapon = next((weapon for weapon in weapons if weapon.type in [TargetType.Ground.value, TargetType.Any.value]), None)
            return weapon is not None
        return False

    @property
    def ground_dps(self) -> Union[int, float]:
        """ Does not include upgrades """
        if hasattr(self._type_data._proto, "weapons"):
            weapons = self._type_data._proto.weapons
            weapon = next((weapon for weapon in weapons if weapon.type in [TargetType.Ground.value, TargetType.Any.value]), None)
            if weapon:
                return (weapon.damage * weapon.attacks) / weapon.speed
        return 0

    @property
    def ground_range(self) -> Union[int, float]:
        """ Does not include upgrades """
        if hasattr(self._type_data._proto, "weapons"):
            weapons = self._type_data._proto.weapons
            weapon = next((weapon for weapon in weapons if weapon.type in [TargetType.Ground.value, TargetType.Any.value]), None)
            if weapon:
                return weapon.range
        return 0

    @property
    def can_attack_air(self) -> bool:
        """ Does not include upgrades """
        if hasattr(self._type_data._proto, "weapons"):
            weapons = self._type_data._proto.weapons
            weapon = next((weapon for weapon in weapons if weapon.type in [TargetType.Air.value, TargetType.Any.value]), None)
            return weapon is not None
        return False

    @property
    def air_dps(self) -> Union[int, float]:
        """ Does not include upgrades """
        if hasattr(self._type_data._proto, "weapons"):
            weapons = self._type_data._proto.weapons
            weapon = next((weapon for weapon in weapons if weapon.type in [TargetType.Air.value, TargetType.Any.value]), None)
            if weapon:
                return (weapon.damage * weapon.attacks) / weapon.speed
        return 0

    @property
    def air_range(self) -> Union[int, float]:
        """ Does not include upgrades """
        if hasattr(self._type_data._proto, "weapons"):
            weapons = self._type_data._proto.weapons
            weapon = next((weapon for weapon in weapons if weapon.type in [TargetType.Air.value, TargetType.Any.value]), None)
            if weapon:
                return weapon.range
        return 0

    @property
    def armor(self) -> Union[int, float]:
        """ Does not include upgrades """
        return self._type_data._proto.armor

    @property
    def sight_range(self) -> Union[int, float]:
        return self._type_data._proto.sight_range

    @property
    def movement_speed(self) -> Union[int, float]:
        return self._type_data._proto.movement_speed

    @property
    def health(self) -> Union[int, float]:
        return self._proto.health

    @property
    def health_max(self) -> Union[int, float]:
        return self._proto.health_max

    @property
    def health_percentage(self) -> Union[int, float]:
        if self._proto.health_max == 0:
            return 0
        return self._proto.health / self._proto.health_max

    @property
    def shield(self) -> Union[int, float]:
        return self._proto.shield

    @property
    def shield_max(self) -> Union[int, float]:
        return self._proto.shield_max

    @property
    def shield_percentage(self) -> Union[int, float]:
        if self._proto.shield_max == 0:
            return 0
        return self._proto.shield / self._proto.shield_max

    @property
    def energy(self) -> Union[int, float]:
        return self._proto.energy

    @property
    def energy_max(self) -> Union[int, float]:
        return self._proto.energy_max

    @property
    def energy_percentage(self) -> Union[int, float]:
        if self._proto.energy_max == 0:
            return 0
        return self._proto.energy / self._proto.energy_max

Ancestors (in MRO)

Static methods

def __init__(

self, proto_data, game_data)

Initialize self. See help(type(self)) for accurate signature.

def __init__(self, proto_data, game_data):
    assert isinstance(game_data, GameData)
    self._proto = proto_data
    self._game_data = game_data

Instance variables

var air_dps

Does not include upgrades

var air_range

Does not include upgrades

var armor

Does not include upgrades

var can_attack

var can_attack_air

Does not include upgrades

var can_attack_ground

var cargo_size

How much cargo this unit uses up in cargo_space

var energy

var energy_max

var energy_percentage

var ground_dps

Does not include upgrades

var ground_range

Does not include upgrades

var health

var health_max

var health_percentage

var is_armored

var is_biological

var is_light

var is_massive

var is_mechanical

var is_robotic

var is_structure

var movement_speed

var name

var race

var shield

var shield_max

var shield_percentage

var sight_range

var tag

var type_id

class Unit

class Unit:
    def __init__(self, proto_data, game_data):
        assert isinstance(proto_data, raw_pb.Unit)
        assert isinstance(game_data, GameData)
        self._proto = proto_data
        self._game_data = game_data

    @property
    def type_id(self) -> UnitTypeId:
        return UnitTypeId(self._proto.unit_type)

    @property
    def _type_data(self) -> "UnitTypeData":
        return self._game_data.units[self._proto.unit_type]

    @property
    def is_snapshot(self) -> bool:
        return self._proto.display_type == DisplayType.Snapshot.value

    @property
    def is_visible(self) -> bool:
        return self._proto.display_type == DisplayType.Visible.value

    @property
    def alliance(self) -> Alliance:
        return self._proto.alliance

    @property
    def is_mine(self) -> bool:
        return self._proto.alliance == Alliance.Self.value

    @property
    def is_enemy(self) -> bool:
        return self._proto.alliance == Alliance.Enemy.value

    @property
    def tag(self) -> int:
        return self._proto.tag

    @property
    def owner_id(self) -> int:
        return self._proto.owner

    @property
    def position(self) -> Point2:
        """2d position of the unit."""
        return self.position3d.to2

    @property
    def position3d(self) -> Point3:
        """3d position of the unit."""
        return Point3.from_proto(self._proto.pos)

    def distance_to(self, p: Union["Unit", Point2, Point3]) -> Union[int, float]:
        """ Using the 2d distance between self and p. To calculate the 3d distance, use unit.position3d.distance_to(p) """
        return self.position.distance_to_point2(p.position)

    @property
    def facing(self) -> Union[int, float]:
        return self._proto.facing

    @property
    def radius(self) -> Union[int, float]:
        return self._proto.radius

    @property
    def detect_range(self) -> Union[int, float]:
        return self._proto.detect_range

    @property
    def radar_range(self) -> Union[int, float]:
        return self._proto.radar_range

    @property
    def build_progress(self) -> Union[int, float]:
        return self._proto.build_progress

    @property
    def is_ready(self) -> bool:
        return self.build_progress == 1.0

    @property
    def cloak(self) -> CloakState:
        return self._proto.cloak

    @property
    def is_blip(self) -> bool:
        """ Detected by sensor tower. """
        return self._proto.is_blip

    @property
    def is_powered(self) -> bool:
        """ Is powered by a pylon nearby. """
        return self._proto.is_powered

    @property
    def is_burrowed(self) -> bool:
        return self._proto.is_burrowed

    @property
    def is_flying(self) -> bool:
        return self._proto.is_flying

    @property
    def is_structure(self) -> bool:
        return Attribute.Structure.value in self._type_data.attributes

    @property
    def is_light(self) -> bool:
        return Attribute.Light.value in self._type_data.attributes

    @property
    def is_armored(self) -> bool:
        return Attribute.Armored.value in self._type_data.attributes

    @property
    def is_biological(self) -> bool:
        return Attribute.Biological.value in self._type_data.attributes

    @property
    def is_mechanical(self) -> bool:
        return Attribute.Mechanical.value in self._type_data.attributes

    @property
    def is_robotic(self) -> bool:
        return Attribute.Robotic.value in self._type_data.attributes

    @property
    def is_massive(self) -> bool:
        return Attribute.Massive.value in self._type_data.attributes

    @property
    def is_psionic(self) -> bool:
        return Attribute.Psionic.value in self._type_data.attributes

    @property
    def is_mineral_field(self) -> bool:
        return self._type_data.has_minerals

    @property
    def is_vespene_geyser(self) -> bool:
        return self._type_data.has_vespene

    @property
    def tech_alias(self) -> Optional[List[UnitTypeId]]:
        """ Building tech equality, e.g. OrbitalCommand is the same as CommandCenter """
        """ For Hive, this returns [UnitTypeId.Hatchery, UnitTypeId.Lair] """
        """ For SCV, this returns None """
        return self._type_data.tech_alias

    @property
    def unit_alias(self) -> Optional[UnitTypeId]:
        """ Building type equality, e.g. FlyingOrbitalCommand is the same as OrbitalCommand """
        """ For flying OrbitalCommand, this returns UnitTypeId.OrbitalCommand """
        """ For SCV, this returns None """
        return self._type_data.unit_alias

    @property
    def race(self) -> Race:
        return Race(self._type_data._proto.race)

    @property
    def health(self) -> Union[int, float]:
        return self._proto.health

    @property
    def health_max(self) -> Union[int, float]:
        return self._proto.health_max

    @property
    def health_percentage(self) -> Union[int, float]:
        if self._proto.health_max == 0:
            return 0
        return self._proto.health / self._proto.health_max

    @property
    def shield(self) -> Union[int, float]:
        return self._proto.shield

    @property
    def shield_max(self) -> Union[int, float]:
        return self._proto.shield_max

    @property
    def shield_percentage(self) -> Union[int, float]:
        if self._proto.shield_max == 0:
            return 0
        return self._proto.shield / self._proto.shield_max

    @property
    def energy(self) -> Union[int, float]:
        return self._proto.energy

    @property
    def energy_max(self) -> Union[int, float]:
        return self._proto.energy_max

    @property
    def energy_percentage(self) -> Union[int, float]:
        if self._proto.energy_max == 0:
            return 0
        return self._proto.energy / self._proto.energy_max

    @property
    def mineral_contents(self) -> int:
        """ How many minerals a mineral field has left to mine from """
        return self._proto.mineral_contents

    @property
    def vespene_contents(self) -> int:
        """ How much gas is remaining in a geyser """
        return self._proto.vespene_contents

    @property
    def has_vespene(self) -> bool:
        """ Checks if a geyser has any gas remaining (can't build extractors on empty geysers), useful for lategame """
        return bool(self._proto.vespene_contents)

    @property
    def weapon_cooldown(self) -> Union[int, float]:
        """ Returns some time (more than game loops) until the unit can fire again, returns -1 for units that can't attack
        Usage:
        if not unit.weapon_cooldown:
            await self.do(unit.attack(target))
        elif unit.weapon_cooldown < 0:
            await self.do(unit.move(closest_allied_unit_because_cant_attack))
        else:
            await self.do(unit.move(retreatPosition))
        """
        if self.can_attack:
            return self._proto.weapon_cooldown
        return -1

    @property
    def cargo_size(self) -> Union[float, int]:
        """ How much cargo this unit uses up in cargo_space """
        return self._type_data.cargo_size

    @property
    def has_cargo(self) -> bool:
        """ If this unit has units loaded """
        return bool(self._proto.cargo_space_taken)

    @property
    def cargo_used(self) -> Union[float, int]:
        """ How much cargo space is used (some units take up more than 1 space) """
        return self._proto.cargo_space_taken

    @property
    def cargo_max(self) -> Union[float, int]:
        """ How much cargo space is totally available - CC: 5, Bunker: 4, Medivac: 8 and Bunker can only load infantry, CC only SCVs """
        return self._proto.cargo_space_max

    @property
    def passengers(self) -> Set["PassengerUnit"]:
        """ Units inside a Bunker, CommandCenter, Nydus, Medivac, WarpPrism, Overlord """
        return {PassengerUnit(unit, self._game_data) for unit in self._proto.passengers}

    @property
    def passengers_tags(self) -> Set[int]:
        return {unit.tag for unit in self._proto.passengers}
    
    @property
    def can_attack(self) -> bool:
        if hasattr(self._type_data._proto, "weapons"):
            weapons = self._type_data._proto.weapons
            return bool(weapons)
        return False
    
    @property
    def can_attack_ground(self) -> bool:
        if hasattr(self._type_data._proto, "weapons"):
            weapons = self._type_data._proto.weapons
            weapon = next((weapon for weapon in weapons if weapon.type in {TargetType.Ground.value, TargetType.Any.value}), None)
            return weapon is not None
        return False
    
    @property
    def ground_dps(self) -> Union[int, float]:
        """ Does not include upgrades """
        if hasattr(self._type_data._proto, "weapons"):
            weapons = self._type_data._proto.weapons
            weapon = next((weapon for weapon in weapons if weapon.type in {TargetType.Ground.value, TargetType.Any.value}), None)            
            if weapon:
                return (weapon.damage * weapon.attacks) / weapon.speed
        return 0

    @property
    def ground_range(self) -> Union[int, float]:
        """ Does not include upgrades """
        if hasattr(self._type_data._proto, "weapons"):
            weapons = self._type_data._proto.weapons
            weapon = next((weapon for weapon in weapons if weapon.type in {TargetType.Ground.value, TargetType.Any.value}), None)
            if weapon:
                return weapon.range
        return 0

    @property
    def can_attack_air(self) -> bool:
        """ Does not include upgrades """
        if hasattr(self._type_data._proto, "weapons"):
            weapons = self._type_data._proto.weapons
            weapon = next((weapon for weapon in weapons if weapon.type in {TargetType.Air.value, TargetType.Any.value}), None)
            return weapon is not None
        return False

    @property
    def air_dps(self) -> Union[int, float]:
        """ Does not include upgrades """
        if hasattr(self._type_data._proto, "weapons"):
            weapons = self._type_data._proto.weapons
            weapon = next((weapon for weapon in weapons if weapon.type in {TargetType.Air.value, TargetType.Any.value}), None)
            if weapon:
                return (weapon.damage * weapon.attacks) / weapon.speed
        return 0

    @property
    def air_range(self) -> Union[int, float]:
        """ Does not include upgrades """
        if hasattr(self._type_data._proto, "weapons"):
            weapons = self._type_data._proto.weapons
            weapon = next((weapon for weapon in weapons if weapon.type in {TargetType.Air.value, TargetType.Any.value}), None)
            if weapon:
                return weapon.range
        return 0

    def target_in_range(self, target: "Unit", bonus_distance: Union[int, float] = 0) -> bool:
        """ Includes the target's radius when calculating distance to target """
        if self.can_attack_ground and not target.is_flying:
            unit_attack_range = self.ground_range
        elif self.can_attack_air and (target.is_flying or target.type_id == UnitTypeId.COLOSSUS):
            unit_attack_range = self.air_range
        else:
            unit_attack_range = -1
        return self.position._distance_squared(target.position) <= (self.radius + target.radius + unit_attack_range - bonus_distance) ** 2


    @property
    def armor(self) -> Union[int, float]:
        """ Does not include upgrades """
        return self._type_data._proto.armor

    @property
    def sight_range(self) -> Union[int, float]:
        return self._type_data._proto.sight_range

    @property
    def movement_speed(self) -> Union[int, float]:
        return self._type_data._proto.movement_speed

    @property
    def is_carrying_minerals(self) -> bool:
        """ Checks if a worker (or MULE) is carrying (gold-)minerals. """
        return any(
            buff.value in self._proto.buff_ids
            for buff in {BuffId.CARRYMINERALFIELDMINERALS, BuffId.CARRYHIGHYIELDMINERALFIELDMINERALS}
        )
    @property
    def is_carrying_vespene(self) -> bool:
        """ Checks if a worker is carrying vespene. """
        return any(
            buff.value in self._proto.buff_ids
            for buff in {
                BuffId.CARRYHARVESTABLEVESPENEGEYSERGAS,
                BuffId.CARRYHARVESTABLEVESPENEGEYSERGASPROTOSS,
                BuffId.CARRYHARVESTABLEVESPENEGEYSERGASZERG,
            }
        )

    @property
    def is_selected(self) -> bool:
        return self._proto.is_selected

    @property
    def orders(self) -> List["UnitOrder"]:
        return [UnitOrder.from_proto(o, self._game_data) for o in self._proto.orders]

    @property
    def noqueue(self) -> bool:
        return not self.orders

    @property
    def is_moving(self) -> bool:
        return self.orders and self.orders[0].ability.id is AbilityId.MOVE

    @property
    def is_attacking(self) -> bool:
        return self.orders and self.orders[0].ability.id in {
            AbilityId.ATTACK,
            AbilityId.ATTACK_ATTACK,
            AbilityId.ATTACK_ATTACKTOWARDS,
            AbilityId.ATTACK_ATTACKBARRAGE,
            AbilityId.SCAN_MOVE,
        }

    @property
    def is_gathering(self) -> bool:
        """ Checks if a unit is on its way to a mineral field / vespene geyser to mine. """
        return self.orders and self.orders[0].ability.id is AbilityId.HARVEST_GATHER

    @property
    def is_returning(self) -> bool:
        """ Checks if a unit is returning from mineral field / vespene geyser to deliver resources to townhall. """
        return self.orders and self.orders[0].ability.id is AbilityId.HARVEST_RETURN

    @property
    def is_collecting(self) -> bool:
        """ Combines the two properties above. """
        return self.orders and self.orders[0].ability.id in {AbilityId.HARVEST_GATHER, AbilityId.HARVEST_RETURN}

    @property
    def is_constructing_scv(self) -> bool:
        """ Checks if the unit is an SCV that is currently building. """
        return self.orders and self.orders[0].ability.id in {
            AbilityId.TERRANBUILD_ARMORY,
            AbilityId.TERRANBUILD_BARRACKS,
            AbilityId.TERRANBUILD_BUNKER,
            AbilityId.TERRANBUILD_COMMANDCENTER,
            AbilityId.TERRANBUILD_ENGINEERINGBAY,
            AbilityId.TERRANBUILD_FACTORY,
            AbilityId.TERRANBUILD_FUSIONCORE,
            AbilityId.TERRANBUILD_GHOSTACADEMY,
            AbilityId.TERRANBUILD_MISSILETURRET,
            AbilityId.TERRANBUILD_REFINERY,
            AbilityId.TERRANBUILD_SENSORTOWER,
            AbilityId.TERRANBUILD_STARPORT,
            AbilityId.TERRANBUILD_SUPPLYDEPOT,
        }

    @property
    def is_repairing(self) -> bool:
        return self.orders and self.orders[0].ability.id in {
            AbilityId.EFFECT_REPAIR,
            AbilityId.EFFECT_REPAIR_MULE,
            AbilityId.EFFECT_REPAIR_SCV,
        }
    
    @property
    def order_target(self) -> Optional[Union[int, Point2]]:
        """ Returns the target tag (if it is a Unit) or Point2 (if it is a Position) from the first order, reutrn None if the unit is idle """
        if self.orders:
            if isinstance(self.orders[0].target, int):
                return self.orders[0].target
            else:
                return Point2.from_proto(self.orders[0].target)
        return None

    @property
    def is_idle(self) -> bool:
        return not self.orders

    @property
    def add_on_tag(self) -> int:
        return self._proto.add_on_tag

    @property
    def add_on_land_position(self) -> Point2:
        """ If unit is addon (techlab or reactor), returns the position where a terran building has to land to connect to addon """
        return self.position.offset(Point2((-2.5, 0.5)))

    @property
    def has_add_on(self) -> bool:
        return self.add_on_tag != 0

    @property
    def assigned_harvesters(self) -> int:
        return self._proto.assigned_harvesters

    @property
    def ideal_harvesters(self) -> int:
        return self._proto.ideal_harvesters

    @property
    def surplus_harvesters(self) -> int:
        """ Returns a positive number if it has too many harvesters mining, a negative number if it has too few mining """
        return -(self._proto.ideal_harvesters - self._proto.assigned_harvesters)

    @property
    def name(self) -> str:
        return self._type_data.name

    def train(self, unit, *args, **kwargs):
        return self(self._game_data.units[unit.value].creation_ability.id, *args, **kwargs)

    def build(self, unit, *args, **kwargs):
        return self(self._game_data.units[unit.value].creation_ability.id, *args, **kwargs)

    def research(self, upgrade, *args, **kwargs):
        """ Requires UpgradeId to be passed instead of AbilityId """
        return self(self._game_data.upgrades[upgrade.value].research_ability.id, *args, **kwargs)

    def has_buff(self, buff):
        assert isinstance(buff, BuffId)
        return buff.value in self._proto.buff_ids

    def warp_in(self, unit, placement, *args, **kwargs):
        normal_creation_ability = self._game_data.units[unit.value].creation_ability.id
        return self(warpgate_abilities[normal_creation_ability], placement, *args, **kwargs)

    def attack(self, *args, **kwargs):
        return self(AbilityId.ATTACK, *args, **kwargs)

    def gather(self, *args, **kwargs):
        return self(AbilityId.HARVEST_GATHER, *args, **kwargs)

    def return_resource(self, *args, **kwargs):
        return self(AbilityId.HARVEST_RETURN, *args, **kwargs)

    def move(self, *args, **kwargs):
        return self(AbilityId.MOVE, *args, **kwargs)
    
    def scan_move(self, *args, **kwargs):
        return self(AbilityId.SCAN_MOVE, *args, **kwargs)

    def scan_move(self, *args, **kwargs):
        return self(AbilityId.SCAN_MOVE, *args, **kwargs)

    def hold_position(self, *args, **kwargs):
        return self(AbilityId.HOLDPOSITION, *args, **kwargs)

    def stop(self, *args, **kwargs):
        return self(AbilityId.STOP, *args, **kwargs)

    def repair(self, *args, **kwargs):
        return self(AbilityId.EFFECT_REPAIR, *args, **kwargs)

    def __hash__(self):
        return hash(self.tag)

    def __call__(self, ability, *args, **kwargs):
        return unit_command.UnitCommand(ability, self, *args, **kwargs)

    def __repr__(self):
        return f"Unit(name={self.name !r}, tag={self.tag})"

Ancestors (in MRO)

  • Unit
  • builtins.object

Static methods

def __init__(

self, proto_data, game_data)

Initialize self. See help(type(self)) for accurate signature.

def __init__(self, proto_data, game_data):
    assert isinstance(proto_data, raw_pb.Unit)
    assert isinstance(game_data, GameData)
    self._proto = proto_data
    self._game_data = game_data

def attack(

self, *args, **kwargs)

def attack(self, *args, **kwargs):
    return self(AbilityId.ATTACK, *args, **kwargs)

def build(

self, unit, *args, **kwargs)

def build(self, unit, *args, **kwargs):
    return self(self._game_data.units[unit.value].creation_ability.id, *args, **kwargs)

def distance_to(

self, p)

Using the 2d distance between self and p. To calculate the 3d distance, use unit.position3d.distance_to(p)

def distance_to(self, p: Union["Unit", Point2, Point3]) -> Union[int, float]:
    """ Using the 2d distance between self and p. To calculate the 3d distance, use unit.position3d.distance_to(p) """
    return self.position.distance_to_point2(p.position)

def gather(

self, *args, **kwargs)

def gather(self, *args, **kwargs):
    return self(AbilityId.HARVEST_GATHER, *args, **kwargs)

def has_buff(

self, buff)

def has_buff(self, buff):
    assert isinstance(buff, BuffId)
    return buff.value in self._proto.buff_ids

def hold_position(

self, *args, **kwargs)

def hold_position(self, *args, **kwargs):
    return self(AbilityId.HOLDPOSITION, *args, **kwargs)

def move(

self, *args, **kwargs)

def move(self, *args, **kwargs):
    return self(AbilityId.MOVE, *args, **kwargs)

def repair(

self, *args, **kwargs)

def repair(self, *args, **kwargs):
    return self(AbilityId.EFFECT_REPAIR, *args, **kwargs)

def research(

self, upgrade, *args, **kwargs)

Requires UpgradeId to be passed instead of AbilityId

def research(self, upgrade, *args, **kwargs):
    """ Requires UpgradeId to be passed instead of AbilityId """
    return self(self._game_data.upgrades[upgrade.value].research_ability.id, *args, **kwargs)

def return_resource(

self, *args, **kwargs)

def return_resource(self, *args, **kwargs):
    return self(AbilityId.HARVEST_RETURN, *args, **kwargs)

def scan_move(

self, *args, **kwargs)

def scan_move(self, *args, **kwargs):
    return self(AbilityId.SCAN_MOVE, *args, **kwargs)

def stop(

self, *args, **kwargs)

def stop(self, *args, **kwargs):
    return self(AbilityId.STOP, *args, **kwargs)

def target_in_range(

self, target, bonus_distance=0)

Includes the target's radius when calculating distance to target

def target_in_range(self, target: "Unit", bonus_distance: Union[int, float] = 0) -> bool:
    """ Includes the target's radius when calculating distance to target """
    if self.can_attack_ground and not target.is_flying:
        unit_attack_range = self.ground_range
    elif self.can_attack_air and (target.is_flying or target.type_id == UnitTypeId.COLOSSUS):
        unit_attack_range = self.air_range
    else:
        unit_attack_range = -1
    return self.position._distance_squared(target.position) <= (self.radius + target.radius + unit_attack_range - bonus_distance) ** 2

def train(

self, unit, *args, **kwargs)

def train(self, unit, *args, **kwargs):
    return self(self._game_data.units[unit.value].creation_ability.id, *args, **kwargs)

def warp_in(

self, unit, placement, *args, **kwargs)

def warp_in(self, unit, placement, *args, **kwargs):
    normal_creation_ability = self._game_data.units[unit.value].creation_ability.id
    return self(warpgate_abilities[normal_creation_ability], placement, *args, **kwargs)

Instance variables

var add_on_land_position

If unit is addon (techlab or reactor), returns the position where a terran building has to land to connect to addon

var add_on_tag

var air_dps

Does not include upgrades

var air_range

Does not include upgrades

var alliance

var armor

Does not include upgrades

var assigned_harvesters

var build_progress

var can_attack

var can_attack_air

Does not include upgrades

var can_attack_ground

var cargo_max

How much cargo space is totally available - CC: 5, Bunker: 4, Medivac: 8 and Bunker can only load infantry, CC only SCVs

var cargo_size

How much cargo this unit uses up in cargo_space

var cargo_used

How much cargo space is used (some units take up more than 1 space)

var cloak

var detect_range

var energy

var energy_max

var energy_percentage

var facing

var ground_dps

Does not include upgrades

var ground_range

Does not include upgrades

var has_add_on

var has_cargo

If this unit has units loaded

var has_vespene

Checks if a geyser has any gas remaining (can't build extractors on empty geysers), useful for lategame

var health

var health_max

var health_percentage

var ideal_harvesters

var is_armored

var is_attacking

var is_biological

var is_blip

Detected by sensor tower.

var is_burrowed

var is_carrying_minerals

Checks if a worker (or MULE) is carrying (gold-)minerals.

var is_carrying_vespene

Checks if a worker is carrying vespene.

var is_collecting

Combines the two properties above.

var is_constructing_scv

Checks if the unit is an SCV that is currently building.

var is_enemy

var is_flying

var is_gathering

Checks if a unit is on its way to a mineral field / vespene geyser to mine.

var is_idle

var is_light

var is_massive

var is_mechanical

var is_mine

var is_mineral_field

var is_moving

var is_powered

Is powered by a pylon nearby.

var is_psionic

var is_ready

var is_repairing

var is_returning

Checks if a unit is returning from mineral field / vespene geyser to deliver resources to townhall.

var is_robotic

var is_selected

var is_snapshot

var is_structure

var is_vespene_geyser

var is_visible

var mineral_contents

How many minerals a mineral field has left to mine from

var movement_speed

var name

var noqueue

var order_target

Returns the target tag (if it is a Unit) or Point2 (if it is a Position) from the first order, reutrn None if the unit is idle

var orders

var owner_id

var passengers

Units inside a Bunker, CommandCenter, Nydus, Medivac, WarpPrism, Overlord

var passengers_tags

var position

2d position of the unit.

var position3d

3d position of the unit.

var race

var radar_range

var radius

var shield

var shield_max

var shield_percentage

var sight_range

var surplus_harvesters

Returns a positive number if it has too many harvesters mining, a negative number if it has too few mining

var tag

var tech_alias

Building tech equality, e.g. OrbitalCommand is the same as CommandCenter

var type_id

var unit_alias

Building type equality, e.g. FlyingOrbitalCommand is the same as OrbitalCommand

var vespene_contents

How much gas is remaining in a geyser

var weapon_cooldown

Returns some time (more than game loops) until the unit can fire again, returns -1 for units that can't attack Usage: if not unit.weapon_cooldown: await self.do(unit.attack(target)) elif unit.weapon_cooldown < 0: await self.do(unit.move(closest_allied_unit_because_cant_attack)) else: await self.do(unit.move(retreatPosition))

class UnitOrder

class UnitOrder:
    @classmethod
    def from_proto(cls, proto, game_data):
        return cls(
            game_data.abilities[proto.ability_id],
            (proto.target_world_space_pos
                if proto.HasField("target_world_space_pos") else
                proto.target_unit_tag),
            proto.progress
        )

    def __init__(self, ability, target, progress=None):
        self.ability = ability
        self.target = target
        self.progress = progress

    def __repr__(self):
        return f"UnitOrder({self.ability}, {self.target}, {self.progress})"

Ancestors (in MRO)

Static methods

def __init__(

self, ability, target, progress=None)

Initialize self. See help(type(self)) for accurate signature.

def __init__(self, ability, target, progress=None):
    self.ability = ability
    self.target = target
    self.progress = progress

Instance variables

var ability

var progress

var target

Methods

def from_proto(

cls, proto, game_data)

@classmethod
def from_proto(cls, proto, game_data):
    return cls(
        game_data.abilities[proto.ability_id],
        (proto.target_world_space_pos
            if proto.HasField("target_world_space_pos") else
            proto.target_unit_tag),
        proto.progress
    )