mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 20:21:32 -06:00
204
worlds/_sc2common/bot/game_state.py
Normal file
204
worlds/_sc2common/bot/game_state.py
Normal file
@@ -0,0 +1,204 @@
|
||||
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
|
||||
Reference in New Issue
Block a user