693 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			693 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| 
								 | 
							
								# pylint: disable=W0212
							 | 
						||
| 
								 | 
							
								from __future__ import annotations
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import math
							 | 
						||
| 
								 | 
							
								from dataclasses import dataclass
							 | 
						||
| 
								 | 
							
								from functools import cached_property
							 | 
						||
| 
								 | 
							
								from typing import TYPE_CHECKING, Any, List, Optional, Set, Tuple, Union
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								from .cache import CacheDict
							 | 
						||
| 
								 | 
							
								from .constants import (
							 | 
						||
| 
								 | 
							
								    CAN_BE_ATTACKED,
							 | 
						||
| 
								 | 
							
								    IS_ARMORED,
							 | 
						||
| 
								 | 
							
								    IS_BIOLOGICAL,
							 | 
						||
| 
								 | 
							
								    IS_CLOAKED,
							 | 
						||
| 
								 | 
							
								    IS_ENEMY,
							 | 
						||
| 
								 | 
							
								    IS_LIGHT,
							 | 
						||
| 
								 | 
							
								    IS_MASSIVE,
							 | 
						||
| 
								 | 
							
								    IS_MECHANICAL,
							 | 
						||
| 
								 | 
							
								    IS_MINE,
							 | 
						||
| 
								 | 
							
								    IS_PLACEHOLDER,
							 | 
						||
| 
								 | 
							
								    IS_PSIONIC,
							 | 
						||
| 
								 | 
							
								    IS_REVEALED,
							 | 
						||
| 
								 | 
							
								    IS_SNAPSHOT,
							 | 
						||
| 
								 | 
							
								    IS_STRUCTURE,
							 | 
						||
| 
								 | 
							
								    IS_VISIBLE,
							 | 
						||
| 
								 | 
							
								)
							 | 
						||
| 
								 | 
							
								from .data import Alliance, Attribute, CloakState, Race
							 | 
						||
| 
								 | 
							
								from .position import Point2, Point3
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								if TYPE_CHECKING:
							 | 
						||
| 
								 | 
							
								    from .bot_ai import BotAI
							 | 
						||
| 
								 | 
							
								    from .game_data import AbilityData, UnitTypeData
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								@dataclass
							 | 
						||
| 
								 | 
							
								class RallyTarget:
							 | 
						||
| 
								 | 
							
								    point: Point2
							 | 
						||
| 
								 | 
							
								    tag: Optional[int] = None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @classmethod
							 | 
						||
| 
								 | 
							
								    def from_proto(cls, proto: Any) -> RallyTarget:
							 | 
						||
| 
								 | 
							
								        return cls(
							 | 
						||
| 
								 | 
							
								            Point2.from_proto(proto.point),
							 | 
						||
| 
								 | 
							
								            proto.tag if proto.HasField("tag") else None,
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								@dataclass
							 | 
						||
| 
								 | 
							
								class UnitOrder:
							 | 
						||
| 
								 | 
							
								    ability: AbilityData  # TODO: Should this be AbilityId instead?
							 | 
						||
| 
								 | 
							
								    target: Optional[Union[int, Point2]] = None
							 | 
						||
| 
								 | 
							
								    progress: float = 0
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @classmethod
							 | 
						||
| 
								 | 
							
								    def from_proto(cls, proto: Any, bot_object: BotAI) -> UnitOrder:
							 | 
						||
| 
								 | 
							
								        target: Optional[Union[int, Point2]] = proto.target_unit_tag
							 | 
						||
| 
								 | 
							
								        if proto.HasField("target_world_space_pos"):
							 | 
						||
| 
								 | 
							
								            target = Point2.from_proto(proto.target_world_space_pos)
							 | 
						||
| 
								 | 
							
								        elif proto.HasField("target_unit_tag"):
							 | 
						||
| 
								 | 
							
								            target = proto.target_unit_tag
							 | 
						||
| 
								 | 
							
								        return cls(
							 | 
						||
| 
								 | 
							
								            ability=bot_object.game_data.abilities[proto.ability_id],
							 | 
						||
| 
								 | 
							
								            target=target,
							 | 
						||
| 
								 | 
							
								            progress=proto.progress,
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __repr__(self) -> str:
							 | 
						||
| 
								 | 
							
								        return f"UnitOrder({self.ability}, {self.target}, {self.progress})"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# pylint: disable=R0904
							 | 
						||
| 
								 | 
							
								class Unit:
							 | 
						||
| 
								 | 
							
								    class_cache = CacheDict()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __init__(
							 | 
						||
| 
								 | 
							
								        self,
							 | 
						||
| 
								 | 
							
								        proto_data,
							 | 
						||
| 
								 | 
							
								        bot_object: BotAI,
							 | 
						||
| 
								 | 
							
								        distance_calculation_index: int = -1,
							 | 
						||
| 
								 | 
							
								        base_build: int = -1,
							 | 
						||
| 
								 | 
							
								    ):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        :param proto_data:
							 | 
						||
| 
								 | 
							
								        :param bot_object:
							 | 
						||
| 
								 | 
							
								        :param distance_calculation_index:
							 | 
						||
| 
								 | 
							
								        :param base_build:
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        self._proto = proto_data
							 | 
						||
| 
								 | 
							
								        self._bot_object: BotAI = bot_object
							 | 
						||
| 
								 | 
							
								        self.game_loop: int = bot_object.state.game_loop
							 | 
						||
| 
								 | 
							
								        self.base_build = base_build
							 | 
						||
| 
								 | 
							
								        # Index used in the 2D numpy array to access the 2D distance between two units
							 | 
						||
| 
								 | 
							
								        self.distance_calculation_index: int = distance_calculation_index
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __repr__(self) -> str:
							 | 
						||
| 
								 | 
							
								        """ Returns string of this form: Unit(name='SCV', tag=4396941328). """
							 | 
						||
| 
								 | 
							
								        return f"Unit(name={self.name !r}, tag={self.tag})"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @cached_property
							 | 
						||
| 
								 | 
							
								    def _type_data(self) -> UnitTypeData:
							 | 
						||
| 
								 | 
							
								        """ Provides the unit type data. """
							 | 
						||
| 
								 | 
							
								        return self._bot_object.game_data.units[self._proto.unit_type]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @cached_property
							 | 
						||
| 
								 | 
							
								    def _creation_ability(self) -> AbilityData:
							 | 
						||
| 
								 | 
							
								        """ Provides the AbilityData of the creation ability of this unit. """
							 | 
						||
| 
								 | 
							
								        return self._type_data.creation_ability
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def name(self) -> str:
							 | 
						||
| 
								 | 
							
								        """ Returns the name of the unit. """
							 | 
						||
| 
								 | 
							
								        return self._type_data.name
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @cached_property
							 | 
						||
| 
								 | 
							
								    def race(self) -> Race:
							 | 
						||
| 
								 | 
							
								        """ Returns the race of the unit """
							 | 
						||
| 
								 | 
							
								        return Race(self._type_data._proto.race)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def tag(self) -> int:
							 | 
						||
| 
								 | 
							
								        """ Returns the unique tag of the unit. """
							 | 
						||
| 
								 | 
							
								        return self._proto.tag
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def is_structure(self) -> bool:
							 | 
						||
| 
								 | 
							
								        """ Checks if the unit is a structure. """
							 | 
						||
| 
								 | 
							
								        return IS_STRUCTURE in self._type_data.attributes
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def is_light(self) -> bool:
							 | 
						||
| 
								 | 
							
								        """ Checks if the unit has the 'light' attribute. """
							 | 
						||
| 
								 | 
							
								        return IS_LIGHT in self._type_data.attributes
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def is_armored(self) -> bool:
							 | 
						||
| 
								 | 
							
								        """ Checks if the unit has the 'armored' attribute. """
							 | 
						||
| 
								 | 
							
								        return IS_ARMORED in self._type_data.attributes
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def is_biological(self) -> bool:
							 | 
						||
| 
								 | 
							
								        """ Checks if the unit has the 'biological' attribute. """
							 | 
						||
| 
								 | 
							
								        return IS_BIOLOGICAL in self._type_data.attributes
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def is_mechanical(self) -> bool:
							 | 
						||
| 
								 | 
							
								        """ Checks if the unit has the 'mechanical' attribute. """
							 | 
						||
| 
								 | 
							
								        return IS_MECHANICAL in self._type_data.attributes
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def is_massive(self) -> bool:
							 | 
						||
| 
								 | 
							
								        """ Checks if the unit has the 'massive' attribute. """
							 | 
						||
| 
								 | 
							
								        return IS_MASSIVE in self._type_data.attributes
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def is_psionic(self) -> bool:
							 | 
						||
| 
								 | 
							
								        """ Checks if the unit has the 'psionic' attribute. """
							 | 
						||
| 
								 | 
							
								        return IS_PSIONIC in self._type_data.attributes
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @cached_property
							 | 
						||
| 
								 | 
							
								    def _weapons(self):
							 | 
						||
| 
								 | 
							
								        """ Returns the weapons of the unit. """
							 | 
						||
| 
								 | 
							
								        return self._type_data._proto.weapons
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @cached_property
							 | 
						||
| 
								 | 
							
								    def bonus_damage(self) -> Optional[Tuple[int, str]]:
							 | 
						||
| 
								 | 
							
								        """Returns a tuple of form '(bonus damage, armor type)' if unit does 'bonus damage' against 'armor type'.
							 | 
						||
| 
								 | 
							
								        Possible armor typs are: 'Light', 'Armored', 'Biological', 'Mechanical', 'Psionic', 'Massive', 'Structure'."""
							 | 
						||
| 
								 | 
							
								        # TODO: Consider units with ability attacks (Oracle, Baneling) or multiple attacks (Thor).
							 | 
						||
| 
								 | 
							
								        if self._weapons:
							 | 
						||
| 
								 | 
							
								            for weapon in self._weapons:
							 | 
						||
| 
								 | 
							
								                if weapon.damage_bonus:
							 | 
						||
| 
								 | 
							
								                    b = weapon.damage_bonus[0]
							 | 
						||
| 
								 | 
							
								                    return b.bonus, Attribute(b.attribute).name
							 | 
						||
| 
								 | 
							
								        return None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def armor(self) -> float:
							 | 
						||
| 
								 | 
							
								        """ Returns the armor of the unit. Does not include upgrades """
							 | 
						||
| 
								 | 
							
								        return self._type_data._proto.armor
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def sight_range(self) -> float:
							 | 
						||
| 
								 | 
							
								        """ Returns the sight range of the unit. """
							 | 
						||
| 
								 | 
							
								        return self._type_data._proto.sight_range
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def movement_speed(self) -> float:
							 | 
						||
| 
								 | 
							
								        """Returns the movement speed of the unit.
							 | 
						||
| 
								 | 
							
								        This is the unit movement speed on game speed 'normal'. To convert it to 'faster' movement speed, multiply it by a factor of '1.4'. E.g. reaper movement speed is listed here as 3.75, but should actually be 5.25.
							 | 
						||
| 
								 | 
							
								        Does not include upgrades or buffs."""
							 | 
						||
| 
								 | 
							
								        return self._type_data._proto.movement_speed
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def is_mineral_field(self) -> bool:
							 | 
						||
| 
								 | 
							
								        """ Checks if the unit is a mineral field. """
							 | 
						||
| 
								 | 
							
								        return self._type_data.has_minerals
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def is_vespene_geyser(self) -> bool:
							 | 
						||
| 
								 | 
							
								        """ Checks if the unit is a non-empty vespene geyser or gas extraction building. """
							 | 
						||
| 
								 | 
							
								        return self._type_data.has_vespene
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def health(self) -> float:
							 | 
						||
| 
								 | 
							
								        """ Returns the health of the unit. Does not include shields. """
							 | 
						||
| 
								 | 
							
								        return self._proto.health
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def health_max(self) -> float:
							 | 
						||
| 
								 | 
							
								        """ Returns the maximum health of the unit. Does not include shields. """
							 | 
						||
| 
								 | 
							
								        return self._proto.health_max
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @cached_property
							 | 
						||
| 
								 | 
							
								    def health_percentage(self) -> float:
							 | 
						||
| 
								 | 
							
								        """ Returns the percentage of health the unit has. Does not include shields. """
							 | 
						||
| 
								 | 
							
								        if not self._proto.health_max:
							 | 
						||
| 
								 | 
							
								            return 0
							 | 
						||
| 
								 | 
							
								        return self._proto.health / self._proto.health_max
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def shield(self) -> float:
							 | 
						||
| 
								 | 
							
								        """ Returns the shield points the unit has. Returns 0 for non-protoss units. """
							 | 
						||
| 
								 | 
							
								        return self._proto.shield
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def shield_max(self) -> float:
							 | 
						||
| 
								 | 
							
								        """ Returns the maximum shield points the unit can have. Returns 0 for non-protoss units. """
							 | 
						||
| 
								 | 
							
								        return self._proto.shield_max
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @cached_property
							 | 
						||
| 
								 | 
							
								    def shield_percentage(self) -> float:
							 | 
						||
| 
								 | 
							
								        """ Returns the percentage of shield points the unit has. Returns 0 for non-protoss units. """
							 | 
						||
| 
								 | 
							
								        if not self._proto.shield_max:
							 | 
						||
| 
								 | 
							
								            return 0
							 | 
						||
| 
								 | 
							
								        return self._proto.shield / self._proto.shield_max
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @cached_property
							 | 
						||
| 
								 | 
							
								    def shield_health_percentage(self) -> float:
							 | 
						||
| 
								 | 
							
								        """Returns the percentage of combined shield + hp points the unit has.
							 | 
						||
| 
								 | 
							
								        Also takes build progress into account."""
							 | 
						||
| 
								 | 
							
								        max_ = (self._proto.shield_max + self._proto.health_max) * self.build_progress
							 | 
						||
| 
								 | 
							
								        if max_ == 0:
							 | 
						||
| 
								 | 
							
								            return 0
							 | 
						||
| 
								 | 
							
								        return (self._proto.shield + self._proto.health) / max_
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def energy(self) -> float:
							 | 
						||
| 
								 | 
							
								        """ Returns the amount of energy the unit has. Returns 0 for units without energy. """
							 | 
						||
| 
								 | 
							
								        return self._proto.energy
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def energy_max(self) -> float:
							 | 
						||
| 
								 | 
							
								        """ Returns the maximum amount of energy the unit can have. Returns 0 for units without energy. """
							 | 
						||
| 
								 | 
							
								        return self._proto.energy_max
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @cached_property
							 | 
						||
| 
								 | 
							
								    def energy_percentage(self) -> float:
							 | 
						||
| 
								 | 
							
								        """ Returns the percentage of amount of energy the unit has. Returns 0 for units without energy. """
							 | 
						||
| 
								 | 
							
								        if not self._proto.energy_max:
							 | 
						||
| 
								 | 
							
								            return 0
							 | 
						||
| 
								 | 
							
								        return self._proto.energy / self._proto.energy_max
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def age_in_frames(self) -> int:
							 | 
						||
| 
								 | 
							
								        """ Returns how old the unit object data is (in game frames). This age does not reflect the unit was created / trained / morphed! """
							 | 
						||
| 
								 | 
							
								        return self._bot_object.state.game_loop - self.game_loop
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def age(self) -> float:
							 | 
						||
| 
								 | 
							
								        """ Returns how old the unit object data is (in game seconds). This age does not reflect when the unit was created / trained / morphed! """
							 | 
						||
| 
								 | 
							
								        return (self._bot_object.state.game_loop - self.game_loop) / 22.4
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def is_memory(self) -> bool:
							 | 
						||
| 
								 | 
							
								        """ Returns True if this Unit object is referenced from the future and is outdated. """
							 | 
						||
| 
								 | 
							
								        return self.game_loop != self._bot_object.state.game_loop
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @cached_property
							 | 
						||
| 
								 | 
							
								    def is_snapshot(self) -> bool:
							 | 
						||
| 
								 | 
							
								        """Checks if the unit is only available as a snapshot for the bot.
							 | 
						||
| 
								 | 
							
								        Enemy buildings that have been scouted and are in the fog of war or
							 | 
						||
| 
								 | 
							
								        attacking enemy units on higher, not visible ground appear this way."""
							 | 
						||
| 
								 | 
							
								        if self.base_build >= 82457:
							 | 
						||
| 
								 | 
							
								            return self._proto.display_type == IS_SNAPSHOT
							 | 
						||
| 
								 | 
							
								        # TODO: Fixed in version 5.0.4, remove if a new linux binary is released: https://github.com/Blizzard/s2client-proto/issues/167
							 | 
						||
| 
								 | 
							
								        position = self.position.rounded
							 | 
						||
| 
								 | 
							
								        return self._bot_object.state.visibility.data_numpy[position[1], position[0]] != 2
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @cached_property
							 | 
						||
| 
								 | 
							
								    def is_visible(self) -> bool:
							 | 
						||
| 
								 | 
							
								        """Checks if the unit is visible for the bot.
							 | 
						||
| 
								 | 
							
								        NOTE: This means the bot has vision of the position of the unit!
							 | 
						||
| 
								 | 
							
								        It does not give any information about the cloak status of the unit."""
							 | 
						||
| 
								 | 
							
								        if self.base_build >= 82457:
							 | 
						||
| 
								 | 
							
								            return self._proto.display_type == IS_VISIBLE
							 | 
						||
| 
								 | 
							
								        # TODO: Remove when a new linux binary (5.0.4 or newer) is released
							 | 
						||
| 
								 | 
							
								        return self._proto.display_type == IS_VISIBLE and not self.is_snapshot
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def is_placeholder(self) -> bool:
							 | 
						||
| 
								 | 
							
								        """Checks if the unit is a placerholder for the bot.
							 | 
						||
| 
								 | 
							
								        Raw information about placeholders:
							 | 
						||
| 
								 | 
							
								            display_type: Placeholder
							 | 
						||
| 
								 | 
							
								            alliance: Self
							 | 
						||
| 
								 | 
							
								            unit_type: 86
							 | 
						||
| 
								 | 
							
								            owner: 1
							 | 
						||
| 
								 | 
							
								            pos {
							 | 
						||
| 
								 | 
							
								              x: 29.5
							 | 
						||
| 
								 | 
							
								              y: 53.5
							 | 
						||
| 
								 | 
							
								              z: 7.98828125
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								            radius: 2.75
							 | 
						||
| 
								 | 
							
								            is_on_screen: false
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        return self._proto.display_type == IS_PLACEHOLDER
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def alliance(self) -> Alliance:
							 | 
						||
| 
								 | 
							
								        """ Returns the team the unit belongs to. """
							 | 
						||
| 
								 | 
							
								        return self._proto.alliance
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def is_mine(self) -> bool:
							 | 
						||
| 
								 | 
							
								        """ Checks if the unit is controlled by the bot. """
							 | 
						||
| 
								 | 
							
								        return self._proto.alliance == IS_MINE
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def is_enemy(self) -> bool:
							 | 
						||
| 
								 | 
							
								        """ Checks if the unit is hostile. """
							 | 
						||
| 
								 | 
							
								        return self._proto.alliance == IS_ENEMY
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def owner_id(self) -> int:
							 | 
						||
| 
								 | 
							
								        """ Returns the owner of the unit. This is a value of 1 or 2 in a two player game. """
							 | 
						||
| 
								 | 
							
								        return self._proto.owner
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def position_tuple(self) -> Tuple[float, float]:
							 | 
						||
| 
								 | 
							
								        """ Returns the 2d position of the unit as tuple without conversion to Point2. """
							 | 
						||
| 
								 | 
							
								        return self._proto.pos.x, self._proto.pos.y
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @cached_property
							 | 
						||
| 
								 | 
							
								    def position(self) -> Point2:
							 | 
						||
| 
								 | 
							
								        """ Returns the 2d position of the unit. """
							 | 
						||
| 
								 | 
							
								        return Point2.from_proto(self._proto.pos)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @cached_property
							 | 
						||
| 
								 | 
							
								    def position3d(self) -> Point3:
							 | 
						||
| 
								 | 
							
								        """ Returns the 3d position of the unit. """
							 | 
						||
| 
								 | 
							
								        return Point3.from_proto(self._proto.pos)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def distance_to(self, p: Union[Unit, Point2]) -> float:
							 | 
						||
| 
								 | 
							
								        """Using the 2d distance between self and p.
							 | 
						||
| 
								 | 
							
								        To calculate the 3d distance, use unit.position3d.distance_to(p)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        :param p:
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        if isinstance(p, Unit):
							 | 
						||
| 
								 | 
							
								            return self._bot_object._distance_squared_unit_to_unit(self, p)**0.5
							 | 
						||
| 
								 | 
							
								        return self._bot_object.distance_math_hypot(self.position_tuple, p)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def distance_to_squared(self, p: Union[Unit, Point2]) -> float:
							 | 
						||
| 
								 | 
							
								        """Using the 2d distance squared between self and p. Slightly faster than distance_to, so when filtering a lot of units, this function is recommended to be used.
							 | 
						||
| 
								 | 
							
								        To calculate the 3d distance, use unit.position3d.distance_to(p)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        :param p:
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        if isinstance(p, Unit):
							 | 
						||
| 
								 | 
							
								            return self._bot_object._distance_squared_unit_to_unit(self, p)
							 | 
						||
| 
								 | 
							
								        return self._bot_object.distance_math_hypot_squared(self.position_tuple, p)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def facing(self) -> float:
							 | 
						||
| 
								 | 
							
								        """Returns direction the unit is facing as a float in range [0,2π). 0 is in direction of x axis."""
							 | 
						||
| 
								 | 
							
								        return self._proto.facing
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def is_facing(self, other_unit: Unit, angle_error: float = 0.05) -> bool:
							 | 
						||
| 
								 | 
							
								        """Check if this unit is facing the target unit. If you make angle_error too small, there might be rounding errors. If you make angle_error too big, this function might return false positives.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        :param other_unit:
							 | 
						||
| 
								 | 
							
								        :param angle_error:
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        # TODO perhaps return default True for units that cannot 'face' another unit? e.g. structures (planetary fortress, bunker, missile turret, photon cannon, spine, spore) or sieged tanks
							 | 
						||
| 
								 | 
							
								        angle = math.atan2(
							 | 
						||
| 
								 | 
							
								            other_unit.position_tuple[1] - self.position_tuple[1], other_unit.position_tuple[0] - self.position_tuple[0]
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								        if angle < 0:
							 | 
						||
| 
								 | 
							
								            angle += math.pi * 2
							 | 
						||
| 
								 | 
							
								        angle_difference = math.fabs(angle - self.facing)
							 | 
						||
| 
								 | 
							
								        return angle_difference < angle_error
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def footprint_radius(self) -> Optional[float]:
							 | 
						||
| 
								 | 
							
								        """For structures only.
							 | 
						||
| 
								 | 
							
								        For townhalls this returns 2.5
							 | 
						||
| 
								 | 
							
								        For barracks, spawning pool, gateway, this returns 1.5
							 | 
						||
| 
								 | 
							
								        For supply depot, this returns 1
							 | 
						||
| 
								 | 
							
								        For sensor tower, creep tumor, this return 0.5
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        NOTE: This can be None if a building doesn't have a creation ability.
							 | 
						||
| 
								 | 
							
								        For rich vespene buildings, flying terran buildings, this returns None"""
							 | 
						||
| 
								 | 
							
								        return self._type_data.footprint_radius
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def radius(self) -> float:
							 | 
						||
| 
								 | 
							
								        """ Half of unit size. See https://liquipedia.net/starcraft2/Unit_Statistics_(Legacy_of_the_Void) """
							 | 
						||
| 
								 | 
							
								        return self._proto.radius
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def build_progress(self) -> float:
							 | 
						||
| 
								 | 
							
								        """ Returns completion in range [0,1]."""
							 | 
						||
| 
								 | 
							
								        return self._proto.build_progress
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def is_ready(self) -> bool:
							 | 
						||
| 
								 | 
							
								        """ Checks if the unit is completed. """
							 | 
						||
| 
								 | 
							
								        return self.build_progress == 1
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def cloak(self) -> CloakState:
							 | 
						||
| 
								 | 
							
								        """Returns cloak state.
							 | 
						||
| 
								 | 
							
								        See https://github.com/Blizzard/s2client-api/blob/d9ba0a33d6ce9d233c2a4ee988360c188fbe9dbf/include/sc2api/sc2_unit.h#L95
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        return CloakState(self._proto.cloak)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def is_cloaked(self) -> bool:
							 | 
						||
| 
								 | 
							
								        """ Checks if the unit is cloaked. """
							 | 
						||
| 
								 | 
							
								        return self._proto.cloak in IS_CLOAKED
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def is_revealed(self) -> bool:
							 | 
						||
| 
								 | 
							
								        """ Checks if the unit is revealed. """
							 | 
						||
| 
								 | 
							
								        return self._proto.cloak == IS_REVEALED
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def can_be_attacked(self) -> bool:
							 | 
						||
| 
								 | 
							
								        """ Checks if the unit is revealed or not cloaked and therefore can be attacked. """
							 | 
						||
| 
								 | 
							
								        return self._proto.cloak in CAN_BE_ATTACKED
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def detect_range(self) -> float:
							 | 
						||
| 
								 | 
							
								        """ Returns the detection distance of the unit. """
							 | 
						||
| 
								 | 
							
								        return self._proto.detect_range
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def radar_range(self) -> float:
							 | 
						||
| 
								 | 
							
								        return self._proto.radar_range
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def is_selected(self) -> bool:
							 | 
						||
| 
								 | 
							
								        """ Checks if the unit is currently selected. """
							 | 
						||
| 
								 | 
							
								        return self._proto.is_selected
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def is_on_screen(self) -> bool:
							 | 
						||
| 
								 | 
							
								        """ Checks if the unit is on the screen. """
							 | 
						||
| 
								 | 
							
								        return self._proto.is_on_screen
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def is_blip(self) -> bool:
							 | 
						||
| 
								 | 
							
								        """ Checks if the unit is detected by a sensor tower. """
							 | 
						||
| 
								 | 
							
								        return self._proto.is_blip
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def is_powered(self) -> bool:
							 | 
						||
| 
								 | 
							
								        """ Checks if the unit is powered by a pylon or warppism. """
							 | 
						||
| 
								 | 
							
								        return self._proto.is_powered
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def is_active(self) -> bool:
							 | 
						||
| 
								 | 
							
								        """ Checks if the unit has an order (e.g. unit is currently moving or attacking, structure is currently training or researching). """
							 | 
						||
| 
								 | 
							
								        return self._proto.is_active
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # PROPERTIES BELOW THIS COMMENT ARE NOT POPULATED FOR SNAPSHOTS
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def mineral_contents(self) -> int:
							 | 
						||
| 
								 | 
							
								        """ Returns the amount of minerals remaining in a mineral field. """
							 | 
						||
| 
								 | 
							
								        return self._proto.mineral_contents
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def vespene_contents(self) -> int:
							 | 
						||
| 
								 | 
							
								        """ Returns the amount of gas remaining in a geyser. """
							 | 
						||
| 
								 | 
							
								        return self._proto.vespene_contents
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def has_vespene(self) -> bool:
							 | 
						||
| 
								 | 
							
								        """Checks if a geyser has any gas remaining.
							 | 
						||
| 
								 | 
							
								        You can't build extractors on empty geysers."""
							 | 
						||
| 
								 | 
							
								        return bool(self._proto.vespene_contents)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def is_burrowed(self) -> bool:
							 | 
						||
| 
								 | 
							
								        """ Checks if the unit is burrowed. """
							 | 
						||
| 
								 | 
							
								        return self._proto.is_burrowed
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def is_hallucination(self) -> bool:
							 | 
						||
| 
								 | 
							
								        """ Returns True if the unit is your own hallucination or detected. """
							 | 
						||
| 
								 | 
							
								        return self._proto.is_hallucination
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def attack_upgrade_level(self) -> int:
							 | 
						||
| 
								 | 
							
								        """Returns the upgrade level of the units attack.
							 | 
						||
| 
								 | 
							
								        # NOTE: Returns 0 for units without a weapon."""
							 | 
						||
| 
								 | 
							
								        return self._proto.attack_upgrade_level
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def armor_upgrade_level(self) -> int:
							 | 
						||
| 
								 | 
							
								        """ Returns the upgrade level of the units armor. """
							 | 
						||
| 
								 | 
							
								        return self._proto.armor_upgrade_level
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def shield_upgrade_level(self) -> int:
							 | 
						||
| 
								 | 
							
								        """Returns the upgrade level of the units shield.
							 | 
						||
| 
								 | 
							
								        # NOTE: Returns 0 for units without a shield."""
							 | 
						||
| 
								 | 
							
								        return self._proto.shield_upgrade_level
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def buff_duration_remain(self) -> int:
							 | 
						||
| 
								 | 
							
								        """Returns the amount of remaining frames of the visible timer bar.
							 | 
						||
| 
								 | 
							
								        # NOTE: Returns 0 for units without a timer bar."""
							 | 
						||
| 
								 | 
							
								        return self._proto.buff_duration_remain
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def buff_duration_max(self) -> int:
							 | 
						||
| 
								 | 
							
								        """Returns the maximum amount of frames of the visible timer bar.
							 | 
						||
| 
								 | 
							
								        # NOTE: Returns 0 for units without a timer bar."""
							 | 
						||
| 
								 | 
							
								        return self._proto.buff_duration_max
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # PROPERTIES BELOW THIS COMMENT ARE NOT POPULATED FOR ENEMIES
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @cached_property
							 | 
						||
| 
								 | 
							
								    def orders(self) -> List[UnitOrder]:
							 | 
						||
| 
								 | 
							
								        """ Returns the a list of the current orders. """
							 | 
						||
| 
								 | 
							
								        # TODO: add examples on how to use unit orders
							 | 
						||
| 
								 | 
							
								        return [UnitOrder.from_proto(order, self._bot_object) for order in self._proto.orders]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @cached_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, returns None if the unit is idle"""
							 | 
						||
| 
								 | 
							
								        if self.orders:
							 | 
						||
| 
								 | 
							
								            target = self.orders[0].target
							 | 
						||
| 
								 | 
							
								            if isinstance(target, int):
							 | 
						||
| 
								 | 
							
								                return target
							 | 
						||
| 
								 | 
							
								            return Point2.from_proto(target)
							 | 
						||
| 
								 | 
							
								        return None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def is_idle(self) -> bool:
							 | 
						||
| 
								 | 
							
								        """ Checks if unit is idle. """
							 | 
						||
| 
								 | 
							
								        return not self._proto.orders
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def add_on_tag(self) -> int:
							 | 
						||
| 
								 | 
							
								        """Returns the tag of the addon of unit. If the unit has no addon, returns 0."""
							 | 
						||
| 
								 | 
							
								        return self._proto.add_on_tag
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def has_add_on(self) -> bool:
							 | 
						||
| 
								 | 
							
								        """ Checks if unit has an addon attached. """
							 | 
						||
| 
								 | 
							
								        return bool(self._proto.add_on_tag)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @cached_property
							 | 
						||
| 
								 | 
							
								    def has_techlab(self) -> bool:
							 | 
						||
| 
								 | 
							
								        """Check if a structure is connected to a techlab addon. This should only ever return True for BARRACKS, FACTORY, STARPORT. """
							 | 
						||
| 
								 | 
							
								        return self.add_on_tag in self._bot_object.techlab_tags
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @cached_property
							 | 
						||
| 
								 | 
							
								    def has_reactor(self) -> bool:
							 | 
						||
| 
								 | 
							
								        """Check if a structure is connected to a reactor addon. This should only ever return True for BARRACKS, FACTORY, STARPORT. """
							 | 
						||
| 
								 | 
							
								        return self.add_on_tag in self._bot_object.reactor_tags
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @cached_property
							 | 
						||
| 
								 | 
							
								    def add_on_land_position(self) -> Point2:
							 | 
						||
| 
								 | 
							
								        """If this unit is an addon (techlab, reactor), returns the position
							 | 
						||
| 
								 | 
							
								        where a terran building (BARRACKS, FACTORY, STARPORT) has to land to connect to this addon.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        Why offset (-2.5, 0.5)? See description in 'add_on_position'
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        return self.position.offset(Point2((-2.5, 0.5)))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @cached_property
							 | 
						||
| 
								 | 
							
								    def add_on_position(self) -> Point2:
							 | 
						||
| 
								 | 
							
								        """If this unit is a terran production building (BARRACKS, FACTORY, STARPORT),
							 | 
						||
| 
								 | 
							
								        this property returns the position of where the addon should be, if it should build one or has one attached.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        Why offset (2.5, -0.5)?
							 | 
						||
| 
								 | 
							
								        A barracks is of size 3x3. The distance from the center to the edge is 1.5.
							 | 
						||
| 
								 | 
							
								        An addon is 2x2 and the distance from the edge to center is 1.
							 | 
						||
| 
								 | 
							
								        The total distance from center to center on the x-axis is 2.5.
							 | 
						||
| 
								 | 
							
								        The distance from center to center on the y-axis is -0.5.
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        return self.position.offset(Point2((2.5, -0.5)))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @cached_property
							 | 
						||
| 
								 | 
							
								    def passengers(self) -> Set[Unit]:
							 | 
						||
| 
								 | 
							
								        """ Returns the units inside a Bunker, CommandCenter, PlanetaryFortress, Medivac, Nydus, Overlord or WarpPrism. """
							 | 
						||
| 
								 | 
							
								        return {Unit(unit, self._bot_object) for unit in self._proto.passengers}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @cached_property
							 | 
						||
| 
								 | 
							
								    def passengers_tags(self) -> Set[int]:
							 | 
						||
| 
								 | 
							
								        """ Returns the tags of the units inside a Bunker, CommandCenter, PlanetaryFortress, Medivac, Nydus, Overlord or WarpPrism. """
							 | 
						||
| 
								 | 
							
								        return {unit.tag for unit in self._proto.passengers}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def cargo_used(self) -> int:
							 | 
						||
| 
								 | 
							
								        """Returns how much cargo space is currently used in the unit.
							 | 
						||
| 
								 | 
							
								        Note that some units take up more than one space."""
							 | 
						||
| 
								 | 
							
								        return self._proto.cargo_space_taken
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def has_cargo(self) -> bool:
							 | 
						||
| 
								 | 
							
								        """ Checks if this unit has any units loaded. """
							 | 
						||
| 
								 | 
							
								        return bool(self._proto.cargo_space_taken)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def cargo_size(self) -> int:
							 | 
						||
| 
								 | 
							
								        """ Returns the amount of cargo space the unit needs. """
							 | 
						||
| 
								 | 
							
								        return self._type_data.cargo_size
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def cargo_max(self) -> int:
							 | 
						||
| 
								 | 
							
								        """ How much cargo space is available at maximum. """
							 | 
						||
| 
								 | 
							
								        return self._proto.cargo_space_max
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def cargo_left(self) -> int:
							 | 
						||
| 
								 | 
							
								        """ Returns how much cargo space is currently left in the unit. """
							 | 
						||
| 
								 | 
							
								        return self._proto.cargo_space_max - self._proto.cargo_space_taken
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def assigned_harvesters(self) -> int:
							 | 
						||
| 
								 | 
							
								        """ Returns the number of workers currently gathering resources at a geyser or mining base."""
							 | 
						||
| 
								 | 
							
								        return self._proto.assigned_harvesters
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def ideal_harvesters(self) -> int:
							 | 
						||
| 
								 | 
							
								        """Returns the ideal harverster count for unit.
							 | 
						||
| 
								 | 
							
								        3 for gas buildings, 2*n for n mineral patches on that base."""
							 | 
						||
| 
								 | 
							
								        return self._proto.ideal_harvesters
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def surplus_harvesters(self) -> int:
							 | 
						||
| 
								 | 
							
								        """Returns a positive int if unit has too many harvesters mining,
							 | 
						||
| 
								 | 
							
								        a negative int if it has too few mining.
							 | 
						||
| 
								 | 
							
								        Will only works on townhalls, and gas buildings.
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        return self._proto.assigned_harvesters - self._proto.ideal_harvesters
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def weapon_cooldown(self) -> float:
							 | 
						||
| 
								 | 
							
								        """Returns the time until the unit can fire again,
							 | 
						||
| 
								 | 
							
								        returns -1 for units that can't attack.
							 | 
						||
| 
								 | 
							
								        Usage:
							 | 
						||
| 
								 | 
							
								        if unit.weapon_cooldown == 0:
							 | 
						||
| 
								 | 
							
								            unit.attack(target)
							 | 
						||
| 
								 | 
							
								        elif unit.weapon_cooldown < 0:
							 | 
						||
| 
								 | 
							
								            unit.move(closest_allied_unit_because_cant_attack)
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            unit.move(retreatPosition)"""
							 | 
						||
| 
								 | 
							
								        if self.can_attack:
							 | 
						||
| 
								 | 
							
								            return self._proto.weapon_cooldown
							 | 
						||
| 
								 | 
							
								        return -1
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def weapon_ready(self) -> bool:
							 | 
						||
| 
								 | 
							
								        """Checks if the weapon is ready to be fired."""
							 | 
						||
| 
								 | 
							
								        return self.weapon_cooldown == 0
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def engaged_target_tag(self) -> int:
							 | 
						||
| 
								 | 
							
								        # TODO What does this do?
							 | 
						||
| 
								 | 
							
								        return self._proto.engaged_target_tag
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @cached_property
							 | 
						||
| 
								 | 
							
								    def rally_targets(self) -> List[RallyTarget]:
							 | 
						||
| 
								 | 
							
								        """ Returns the queue of rallytargets of the structure. """
							 | 
						||
| 
								 | 
							
								        return [RallyTarget.from_proto(rally_target) for rally_target in self._proto.rally_targets]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Unit functions
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __hash__(self) -> int:
							 | 
						||
| 
								 | 
							
								        return self.tag
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __eq__(self, other: Union[Unit, Any]) -> bool:
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        :param other:
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        return self.tag == getattr(other, "tag", -1)
							 |