205 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			205 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| 
								 | 
							
								from __future__ import annotations
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								from dataclasses import dataclass
							 | 
						||
| 
								 | 
							
								from functools import cached_property
							 | 
						||
| 
								 | 
							
								from itertools import chain
							 | 
						||
| 
								 | 
							
								from typing import List, Set
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								from .constants import IS_ENEMY, IS_MINE
							 | 
						||
| 
								 | 
							
								from .data import Alliance, DisplayType
							 | 
						||
| 
								 | 
							
								from .pixel_map import PixelMap
							 | 
						||
| 
								 | 
							
								from .position import Point2, Point3
							 | 
						||
| 
								 | 
							
								from .power_source import PsionicMatrix
							 | 
						||
| 
								 | 
							
								from .score import ScoreDetails
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class Blip:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __init__(self, proto):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        :param proto:
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        self._proto = proto
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def is_blip(self) -> bool:
							 | 
						||
| 
								 | 
							
								        """Detected by sensor tower."""
							 | 
						||
| 
								 | 
							
								        return self._proto.is_blip
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @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 position(self) -> Point2:
							 | 
						||
| 
								 | 
							
								        """2d position of the blip."""
							 | 
						||
| 
								 | 
							
								        return Point2.from_proto(self._proto.pos)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def position3d(self) -> Point3:
							 | 
						||
| 
								 | 
							
								        """3d position of the blip."""
							 | 
						||
| 
								 | 
							
								        return Point3.from_proto(self._proto.pos)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class Common:
							 | 
						||
| 
								 | 
							
								    ATTRIBUTES = [
							 | 
						||
| 
								 | 
							
								        "player_id",
							 | 
						||
| 
								 | 
							
								        "minerals",
							 | 
						||
| 
								 | 
							
								        "vespene",
							 | 
						||
| 
								 | 
							
								        "food_cap",
							 | 
						||
| 
								 | 
							
								        "food_used",
							 | 
						||
| 
								 | 
							
								        "food_army",
							 | 
						||
| 
								 | 
							
								        "food_workers",
							 | 
						||
| 
								 | 
							
								        "idle_worker_count",
							 | 
						||
| 
								 | 
							
								        "army_count",
							 | 
						||
| 
								 | 
							
								        "warp_gate_count",
							 | 
						||
| 
								 | 
							
								        "larva_count",
							 | 
						||
| 
								 | 
							
								    ]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __init__(self, proto):
							 | 
						||
| 
								 | 
							
								        self._proto = proto
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __getattr__(self, attr):
							 | 
						||
| 
								 | 
							
								        assert attr in self.ATTRIBUTES, f"'{attr}' is not a valid attribute"
							 | 
						||
| 
								 | 
							
								        return int(getattr(self._proto, attr))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class EffectData:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __init__(self, proto, fake=False):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        :param proto:
							 | 
						||
| 
								 | 
							
								        :param fake:
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        self._proto = proto
							 | 
						||
| 
								 | 
							
								        self.fake = fake
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def positions(self) -> Set[Point2]:
							 | 
						||
| 
								 | 
							
								        if self.fake:
							 | 
						||
| 
								 | 
							
								            return {Point2.from_proto(self._proto.pos)}
							 | 
						||
| 
								 | 
							
								        return {Point2.from_proto(p) for p in self._proto.pos}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def alliance(self) -> Alliance:
							 | 
						||
| 
								 | 
							
								        return self._proto.alliance
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def is_mine(self) -> bool:
							 | 
						||
| 
								 | 
							
								        """ Checks if the effect is caused by me. """
							 | 
						||
| 
								 | 
							
								        return self._proto.alliance == IS_MINE
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def is_enemy(self) -> bool:
							 | 
						||
| 
								 | 
							
								        """ Checks if the effect is hostile. """
							 | 
						||
| 
								 | 
							
								        return self._proto.alliance == IS_ENEMY
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def owner(self) -> int:
							 | 
						||
| 
								 | 
							
								        return self._proto.owner
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def radius(self) -> float:
							 | 
						||
| 
								 | 
							
								        return self._proto.radius
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __repr__(self) -> str:
							 | 
						||
| 
								 | 
							
								        return f"{self.id} with radius {self.radius} at {self.positions}"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								@dataclass
							 | 
						||
| 
								 | 
							
								class ChatMessage:
							 | 
						||
| 
								 | 
							
								    player_id: int
							 | 
						||
| 
								 | 
							
								    message: str
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								@dataclass
							 | 
						||
| 
								 | 
							
								class ActionRawCameraMove:
							 | 
						||
| 
								 | 
							
								    center_world_space: Point2
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class GameState:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __init__(self, response_observation, previous_observation=None):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        :param response_observation:
							 | 
						||
| 
								 | 
							
								        :param previous_observation:
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        # Only filled in realtime=True in case the bot skips frames
							 | 
						||
| 
								 | 
							
								        self.previous_observation = previous_observation
							 | 
						||
| 
								 | 
							
								        self.response_observation = response_observation
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # https://github.com/Blizzard/s2client-proto/blob/51662231c0965eba47d5183ed0a6336d5ae6b640/s2clientprotocol/sc2api.proto#L575
							 | 
						||
| 
								 | 
							
								        self.observation = response_observation.observation
							 | 
						||
| 
								 | 
							
								        self.observation_raw = self.observation.raw_data
							 | 
						||
| 
								 | 
							
								        self.player_result = response_observation.player_result
							 | 
						||
| 
								 | 
							
								        self.common: Common = Common(self.observation.player_common)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Area covered by Pylons and Warpprisms
							 | 
						||
| 
								 | 
							
								        self.psionic_matrix: PsionicMatrix = PsionicMatrix.from_proto(self.observation_raw.player.power_sources)
							 | 
						||
| 
								 | 
							
								        # 22.4 per second on faster game speed
							 | 
						||
| 
								 | 
							
								        self.game_loop: int = self.observation.game_loop
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # https://github.com/Blizzard/s2client-proto/blob/33f0ecf615aa06ca845ffe4739ef3133f37265a9/s2clientprotocol/score.proto#L31
							 | 
						||
| 
								 | 
							
								        self.score: ScoreDetails = ScoreDetails(self.observation.score)
							 | 
						||
| 
								 | 
							
								        self.abilities = self.observation.abilities  # abilities of selected units
							 | 
						||
| 
								 | 
							
								        self.upgrades = set()
							 | 
						||
| 
								 | 
							
								        # self.upgrades: Set[UpgradeId] = {UpgradeId(upgrade) for upgrade in self.observation_raw.player.upgrade_ids}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # self.visibility[point]: 0=Hidden, 1=Fogged, 2=Visible
							 | 
						||
| 
								 | 
							
								        self.visibility: PixelMap = PixelMap(self.observation_raw.map_state.visibility)
							 | 
						||
| 
								 | 
							
								        # self.creep[point]: 0=No creep, 1=creep
							 | 
						||
| 
								 | 
							
								        self.creep: PixelMap = PixelMap(self.observation_raw.map_state.creep, in_bits=True)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Effects like ravager bile shot, lurker attack, everything in effect_id.py
							 | 
						||
| 
								 | 
							
								        # self.effects: Set[EffectData] = {EffectData(effect) for effect in self.observation_raw.effects}
							 | 
						||
| 
								 | 
							
								        self.effects = set()
							 | 
						||
| 
								 | 
							
								        """ Usage:
							 | 
						||
| 
								 | 
							
								        for effect in self.state.effects:
							 | 
						||
| 
								 | 
							
								            if effect.id == EffectId.RAVAGERCORROSIVEBILECP:
							 | 
						||
| 
								 | 
							
								                positions = effect.positions
							 | 
						||
| 
								 | 
							
								                # dodge the ravager biles
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @cached_property
							 | 
						||
| 
								 | 
							
								    def dead_units(self) -> Set[int]:
							 | 
						||
| 
								 | 
							
								        """ A set of unit tags that died this frame """
							 | 
						||
| 
								 | 
							
								        _dead_units = set(self.observation_raw.event.dead_units)
							 | 
						||
| 
								 | 
							
								        if self.previous_observation:
							 | 
						||
| 
								 | 
							
								            return _dead_units | set(self.previous_observation.observation.raw_data.event.dead_units)
							 | 
						||
| 
								 | 
							
								        return _dead_units
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @cached_property
							 | 
						||
| 
								 | 
							
								    def chat(self) -> List[ChatMessage]:
							 | 
						||
| 
								 | 
							
								        """List of chat messages sent this frame (by either player)."""
							 | 
						||
| 
								 | 
							
								        previous_frame_chat = self.previous_observation.chat if self.previous_observation else []
							 | 
						||
| 
								 | 
							
								        return [
							 | 
						||
| 
								 | 
							
								            ChatMessage(message.player_id, message.message)
							 | 
						||
| 
								 | 
							
								            for message in chain(previous_frame_chat, self.response_observation.chat)
							 | 
						||
| 
								 | 
							
								        ]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @cached_property
							 | 
						||
| 
								 | 
							
								    def alerts(self) -> List[int]:
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        Game alerts, see https://github.com/Blizzard/s2client-proto/blob/01ab351e21c786648e4c6693d4aad023a176d45c/s2clientprotocol/sc2api.proto#L683-L706
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        if self.previous_observation:
							 | 
						||
| 
								 | 
							
								            return list(chain(self.previous_observation.observation.alerts, self.observation.alerts))
							 | 
						||
| 
								 | 
							
								        return self.observation.alerts
							 |