From d2450fecbad24b67a7c93455709607787b2c2523 Mon Sep 17 00:00:00 2001 From: an-swe Date: Tue, 4 Nov 2025 20:08:34 -0800 Subject: [PATCH 1/2] add type stubs for static type checking support Add PEP 561 compliant type stub files to enable static type checkers (Pylance, Pyright, mypy) to resolve dynamically created enums. The sc2.data module creates enums at runtime using enum.Enum() with protobuf descriptors, which are invisible to static analysis tools. --- pyproject.toml | 3 + sc2/data.pyi | 216 +++++++++++++++++++++++++++++++++++++++++++++++++ sc2/py.typed | 0 3 files changed, 219 insertions(+) create mode 100644 sc2/data.pyi create mode 100644 sc2/py.typed diff --git a/pyproject.toml b/pyproject.toml index 22aac81d..71b23f93 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,6 +69,9 @@ dev = [ license-files = [] package-dir = { sc2 = "sc2" } +[tool.setuptools.package-data] +sc2 = ["py.typed", "*.pyi"] + [build-system] # https://packaging.python.org/en/latest/tutorials/packaging-projects/#choosing-a-build-backend # https://setuptools.pypa.io/en/latest/userguide/package_discovery.html#custom-discovery diff --git a/sc2/data.pyi b/sc2/data.pyi new file mode 100644 index 00000000..488ccc6b --- /dev/null +++ b/sc2/data.pyi @@ -0,0 +1,216 @@ +"""Type stubs for sc2.data module + +This stub provides static type information for dynamically generated enums. +The enums in sc2.data are created at runtime using enum.Enum() with protobuf +enum descriptors, which makes them invisible to static type checkers. + +This stub file (PEP 561 compliant) allows type checkers like Pylance, Pyright, +and mypy to understand the structure and members of these enums. +""" + +from enum import Enum +from typing import Dict, Set + +from sc2.ids.ability_id import AbilityId +from sc2.ids.unit_typeid import UnitTypeId + +# Enums created from sc2api_pb2 +class CreateGameError(Enum): + MissingMap: int + InvalidMapPath: int + InvalidMapData: int + InvalidMapName: int + InvalidMapHandle: int + MissingPlayerSetup: int + InvalidPlayerSetup: int + MultiplayerUnsupported: int + +class PlayerType(Enum): + Participant: int + Computer: int + Observer: int + +class Difficulty(Enum): + VeryEasy: int + Easy: int + Medium: int + MediumHard: int + Hard: int + Harder: int + VeryHard: int + CheatVision: int + CheatMoney: int + CheatInsane: int + +class AIBuild(Enum): + RandomBuild: int + Rush: int + Timing: int + Power: int + Macro: int + Air: int + +class Status(Enum): + launched: int + init_game: int + in_game: int + in_replay: int + ended: int + quit: int + unknown: int + +class Result(Enum): + Victory: int + Defeat: int + Tie: int + Undecided: int + +class Alert(Enum): + AlertError: int + AddOnComplete: int + BuildingComplete: int + BuildingUnderAttack: int + LarvaHatched: int + MergeComplete: int + MineralsExhausted: int + MorphComplete: int + MothershipComplete: int + MULEExpired: int + NuclearLaunchDetected: int + NukeComplete: int + NydusWormDetected: int + ResearchComplete: int + TrainError: int + TrainUnitComplete: int + TrainWorkerComplete: int + TransformationComplete: int + UnitUnderAttack: int + UpgradeComplete: int + VespeneExhausted: int + WarpInComplete: int + +class ChatChannel(Enum): + Broadcast: int + Team: int + +# Enums created from common_pb2 +class Race(Enum): + """StarCraft II race enum. + + Members: + NoRace: No race specified + Terran: Terran race + Zerg: Zerg race + Protoss: Protoss race + Random: Random race selection + """ + NoRace: int + Terran: int + Zerg: int + Protoss: int + Random: int + +# Enums created from raw_pb2 +class DisplayType(Enum): + Visible: int + Snapshot: int + Hidden: int + Placeholder: int + +class Alliance(Enum): + Self: int + Ally: int + Neutral: int + Enemy: int + +class CloakState(Enum): + CloakedUnknown: int + Cloaked: int + CloakedDetected: int + NotCloaked: int + CloakedAllied: int + +# Enums created from data_pb2 +class Attribute(Enum): + Light: int + Armored: int + Biological: int + Mechanical: int + Robotic: int + Psionic: int + Massive: int + Structure: int + Hover: int + Heroic: int + Summoned: int + +class TargetType(Enum): + Ground: int + Air: int + Any: int + +class Target(Enum): + # Note: The protobuf enum member 'None' is a Python keyword, + # so at runtime it may need special handling + Point: int + Unit: int + PointOrUnit: int + PointOrNone: int + +# Enums created from error_pb2 +class ActionResult(Enum): + """Action result codes from game engine. + + This enum contains a large number of members (~200+) representing + various action results and error conditions. Only the most commonly + used members are listed here. All members are available at runtime. + """ + Success: int + NotSupported: int + Error: int + CantQueueThatOrder: int + Retry: int + Cooldown: int + QueueIsFull: int + RallyQueueIsFull: int + NotEnoughMinerals: int + NotEnoughVespene: int + NotEnoughTerrazine: int + NotEnoughCustom: int + NotEnoughFood: int + FoodUsageImpossible: int + NotEnoughLife: int + NotEnoughShields: int + NotEnoughEnergy: int + LifeSuppressed: int + ShieldsSuppressed: int + EnergySuppressed: int + NotEnoughCharges: int + CantAddMoreCharges: int + TooMuchMinerals: int + TooMuchVespene: int + TooMuchTerrazine: int + TooMuchCustom: int + TooMuchFood: int + TooMuchLife: int + TooMuchShields: int + TooMuchEnergy: int + MustTargetUnitWithLife: int + MustTargetUnitWithShields: int + MustTargetUnitWithEnergy: int + CantTrade: int + CantSpend: int + CantTargetThatUnit: int + CouldntAllocateUnit: int + UnitCantMove: int + TransportIsHoldingPosition: int + BuildTechRequirementsNotMet: int + CantFindPlacementLocation: int + CantBuildOnThat: int + # ... approximately 150+ more members exist at runtime + +# Module-level dictionaries +race_worker: Dict[Race, UnitTypeId] +race_townhalls: Dict[Race, Set[UnitTypeId]] +warpgate_abilities: Dict[AbilityId, AbilityId] +race_gas: Dict[Race, UnitTypeId] diff --git a/sc2/py.typed b/sc2/py.typed new file mode 100644 index 00000000..e69de29b From 46a43830d77b615a798422f05d1454a4c466adfa Mon Sep 17 00:00:00 2001 From: burnysc2 Date: Mon, 10 Nov 2025 20:39:46 +0100 Subject: [PATCH 2/2] Start adding stubs for s2clientprotocol --- pyproject.toml | 2 +- s2clientprotocol/__init__.pyi | 0 s2clientprotocol/common_pb2.pyi | 26 +++++ sc2/constants.py | 2 +- sc2/data.py | 5 +- sc2/data.pyi | 195 ++++++++++++++++++++++++++++++-- sc2/position.py | 4 +- 7 files changed, 216 insertions(+), 18 deletions(-) create mode 100644 s2clientprotocol/__init__.pyi create mode 100644 s2clientprotocol/common_pb2.pyi diff --git a/pyproject.toml b/pyproject.toml index 71b23f93..f634fb32 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,7 +67,7 @@ dev = [ [tool.setuptools] license-files = [] -package-dir = { sc2 = "sc2" } +package-dir = { sc2 = "sc2", s2clientprotocol = "s2clientprotocol" } [tool.setuptools.package-data] sc2 = ["py.typed", "*.pyi"] diff --git a/s2clientprotocol/__init__.pyi b/s2clientprotocol/__init__.pyi new file mode 100644 index 00000000..e69de29b diff --git a/s2clientprotocol/common_pb2.pyi b/s2clientprotocol/common_pb2.pyi new file mode 100644 index 00000000..af539a99 --- /dev/null +++ b/s2clientprotocol/common_pb2.pyi @@ -0,0 +1,26 @@ +# https://github.com/Blizzard/s2client-proto/blob/bff45dae1fc685e6acbaae084670afb7d1c0832c/s2clientprotocol/common.proto +from enum import Enum +from google.protobuf.message import Message + +class PointI(Message): + x: int + y: int + def __init__(self, x: int = ..., y: int = ...) -> None: ... + +class Point2D(Message): + x: float + y: float + def __init__(self, x: float = ..., y: float = ...) -> None: ... + +class Point(Message): + x: float + y: float + z: float + def __init__(self, x: float = ..., y: float = ..., z: float = ...) -> None: ... + +class Race(Enum): + NoRace: int + Terran: int + Zerg: int + Protoss: int + Random: int diff --git a/sc2/constants.py b/sc2/constants.py index e23d4c13..6478ff01 100644 --- a/sc2/constants.py +++ b/sc2/constants.py @@ -495,7 +495,7 @@ def return_NOTAUNIT() -> UnitTypeId: UnitTypeId.EXTRACTORRICH, } # pyre-ignore[11] -DAMAGE_BONUS_PER_UPGRADE: dict[UnitTypeId, dict[TargetType, Any]] = { +DAMAGE_BONUS_PER_UPGRADE: dict[UnitTypeId, dict[int, Any]] = { # # Protoss # diff --git a/sc2/data.py b/sc2/data.py index b0c9425f..a4377b8a 100644 --- a/sc2/data.py +++ b/sc2/data.py @@ -1,10 +1,7 @@ # pyre-ignore-all-errors[16, 19] """For the list of enums, see here -https://github.com/Blizzard/s2client-api/blob/d9ba0a33d6ce9d233c2a4ee988360c188fbe9dbf/include/sc2api/sc2_gametypes.h -https://github.com/Blizzard/s2client-api/blob/d9ba0a33d6ce9d233c2a4ee988360c188fbe9dbf/include/sc2api/sc2_action.h -https://github.com/Blizzard/s2client-api/blob/d9ba0a33d6ce9d233c2a4ee988360c188fbe9dbf/include/sc2api/sc2_unit.h -https://github.com/Blizzard/s2client-api/blob/d9ba0a33d6ce9d233c2a4ee988360c188fbe9dbf/include/sc2api/sc2_data.h +https://github.com/Blizzard/s2client-proto/tree/bff45dae1fc685e6acbaae084670afb7d1c0832c/s2clientprotocol """ from __future__ import annotations diff --git a/sc2/data.pyi b/sc2/data.pyi index 488ccc6b..b625d769 100644 --- a/sc2/data.pyi +++ b/sc2/data.pyi @@ -8,8 +8,9 @@ This stub file (PEP 561 compliant) allows type checkers like Pylance, Pyright, and mypy to understand the structure and members of these enums. """ +from __future__ import annotations + from enum import Enum -from typing import Dict, Set from sc2.ids.ability_id import AbilityId from sc2.ids.unit_typeid import UnitTypeId @@ -96,7 +97,7 @@ class ChatChannel(Enum): # Enums created from common_pb2 class Race(Enum): """StarCraft II race enum. - + Members: NoRace: No race specified Terran: Terran race @@ -104,6 +105,7 @@ class Race(Enum): Protoss: Protoss race Random: Random race selection """ + NoRace: int Terran: int Zerg: int @@ -143,11 +145,13 @@ class Attribute(Enum): Hover: int Heroic: int Summoned: int + Invalid: int class TargetType(Enum): Ground: int Air: int Any: int + Invalid: int class Target(Enum): # Note: The protobuf enum member 'None' is a Python keyword, @@ -160,11 +164,11 @@ class Target(Enum): # Enums created from error_pb2 class ActionResult(Enum): """Action result codes from game engine. - + This enum contains a large number of members (~200+) representing - various action results and error conditions. Only the most commonly - used members are listed here. All members are available at runtime. + various action results and error conditions. """ + Success: int NotSupported: int Error: int @@ -207,10 +211,181 @@ class ActionResult(Enum): BuildTechRequirementsNotMet: int CantFindPlacementLocation: int CantBuildOnThat: int - # ... approximately 150+ more members exist at runtime + CantBuildTooCloseToDropOff: int + CantBuildLocationInvalid: int + CantSeeBuildLocation: int + CantBuildTooCloseToCreepSource: int + CantBuildTooCloseToResources: int + CantBuildTooFarFromWater: int + CantBuildTooFarFromCreepSource: int + CantBuildTooFarFromBuildPowerSource: int + CantBuildOnDenseTerrain: int + CantTrainTooFarFromTrainPowerSource: int + CantLandLocationInvalid: int + CantSeeLandLocation: int + CantLandTooCloseToCreepSource: int + CantLandTooCloseToResources: int + CantLandTooFarFromWater: int + CantLandTooFarFromCreepSource: int + CantLandTooFarFromBuildPowerSource: int + CantLandTooFarFromTrainPowerSource: int + CantLandOnDenseTerrain: int + AddOnTooFarFromBuilding: int + MustBuildRefineryFirst: int + BuildingIsUnderConstruction: int + CantFindDropOff: int + CantLoadOtherPlayersUnits: int + NotEnoughRoomToLoadUnit: int + CantUnloadUnitsThere: int + CantWarpInUnitsThere: int + CantLoadImmobileUnits: int + CantRechargeImmobileUnits: int + CantRechargeUnderConstructionUnits: int + CantLoadThatUnit: int + NoCargoToUnload: int + LoadAllNoTargetsFound: int + NotWhileOccupied: int + CantAttackWithoutAmmo: int + CantHoldAnyMoreAmmo: int + TechRequirementsNotMet: int + MustLockdownUnitFirst: int + MustTargetUnit: int + MustTargetInventory: int + MustTargetVisibleUnit: int + MustTargetVisibleLocation: int + MustTargetWalkableLocation: int + MustTargetPawnableUnit: int + YouCantControlThatUnit: int + YouCantIssueCommandsToThatUnit: int + MustTargetResources: int + RequiresHealTarget: int + RequiresRepairTarget: int + NoItemsToDrop: int + CantHoldAnyMoreItems: int + CantHoldThat: int + TargetHasNoInventory: int + CantDropThisItem: int + CantMoveThisItem: int + CantPawnThisUnit: int + MustTargetCaster: int + CantTargetCaster: int + MustTargetOuter: int + CantTargetOuter: int + MustTargetYourOwnUnits: int + CantTargetYourOwnUnits: int + MustTargetFriendlyUnits: int + CantTargetFriendlyUnits: int + MustTargetNeutralUnits: int + CantTargetNeutralUnits: int + MustTargetEnemyUnits: int + CantTargetEnemyUnits: int + MustTargetAirUnits: int + CantTargetAirUnits: int + MustTargetGroundUnits: int + CantTargetGroundUnits: int + MustTargetStructures: int + CantTargetStructures: int + MustTargetLightUnits: int + CantTargetLightUnits: int + MustTargetArmoredUnits: int + CantTargetArmoredUnits: int + MustTargetBiologicalUnits: int + CantTargetBiologicalUnits: int + MustTargetHeroicUnits: int + CantTargetHeroicUnits: int + MustTargetRoboticUnits: int + CantTargetRoboticUnits: int + MustTargetMechanicalUnits: int + CantTargetMechanicalUnits: int + MustTargetPsionicUnits: int + CantTargetPsionicUnits: int + MustTargetMassiveUnits: int + CantTargetMassiveUnits: int + MustTargetMissile: int + CantTargetMissile: int + MustTargetWorkerUnits: int + CantTargetWorkerUnits: int + MustTargetEnergyCapableUnits: int + CantTargetEnergyCapableUnits: int + MustTargetShieldCapableUnits: int + CantTargetShieldCapableUnits: int + MustTargetFlyers: int + CantTargetFlyers: int + MustTargetBuriedUnits: int + CantTargetBuriedUnits: int + MustTargetCloakedUnits: int + CantTargetCloakedUnits: int + MustTargetUnitsInAStasisField: int + CantTargetUnitsInAStasisField: int + MustTargetUnderConstructionUnits: int + CantTargetUnderConstructionUnits: int + MustTargetDeadUnits: int + CantTargetDeadUnits: int + MustTargetRevivableUnits: int + CantTargetRevivableUnits: int + MustTargetHiddenUnits: int + CantTargetHiddenUnits: int + CantRechargeOtherPlayersUnits: int + MustTargetHallucinations: int + CantTargetHallucinations: int + MustTargetInvulnerableUnits: int + CantTargetInvulnerableUnits: int + MustTargetDetectedUnits: int + CantTargetDetectedUnits: int + CantTargetUnitWithEnergy: int + CantTargetUnitWithShields: int + MustTargetUncommandableUnits: int + CantTargetUncommandableUnits: int + MustTargetPreventDefeatUnits: int + CantTargetPreventDefeatUnits: int + MustTargetPreventRevealUnits: int + CantTargetPreventRevealUnits: int + MustTargetPassiveUnits: int + CantTargetPassiveUnits: int + MustTargetStunnedUnits: int + CantTargetStunnedUnits: int + MustTargetSummonedUnits: int + CantTargetSummonedUnits: int + MustTargetUser1: int + CantTargetUser1: int + MustTargetUnstoppableUnits: int + CantTargetUnstoppableUnits: int + MustTargetResistantUnits: int + CantTargetResistantUnits: int + MustTargetDazedUnits: int + CantTargetDazedUnits: int + CantLockdown: int + CantMindControl: int + MustTargetDestructibles: int + CantTargetDestructibles: int + MustTargetItems: int + CantTargetItems: int + NoCalldownAvailable: int + WaypointListFull: int + MustTargetRace: int + CantTargetRace: int + MustTargetSimilarUnits: int + CantTargetSimilarUnits: int + CantFindEnoughTargets: int + AlreadySpawningLarva: int + CantTargetExhaustedResources: int + CantUseMinimap: int + CantUseInfoPanel: int + OrderQueueIsFull: int + CantHarvestThatResource: int + HarvestersNotRequired: int + AlreadyTargeted: int + CantAttackWeaponsDisabled: int + CouldntReachTarget: int + TargetIsOutOfRange: int + TargetIsTooClose: int + TargetIsOutOfArc: int + CantFindTeleportLocation: int + InvalidItemClass: int + CantFindCancelOrder: int # Module-level dictionaries -race_worker: Dict[Race, UnitTypeId] -race_townhalls: Dict[Race, Set[UnitTypeId]] -warpgate_abilities: Dict[AbilityId, AbilityId] -race_gas: Dict[Race, UnitTypeId] +race_worker: dict[Race, UnitTypeId] +race_townhalls: dict[Race, set[UnitTypeId]] +warpgate_abilities: dict[AbilityId, AbilityId] +race_gas: dict[Race, UnitTypeId] diff --git a/sc2/position.py b/sc2/position.py index 36a0922f..3e37ad2f 100644 --- a/sc2/position.py +++ b/sc2/position.py @@ -142,7 +142,7 @@ def __hash__(self) -> int: class Point2(Pointlike): @classmethod - def from_proto(cls, data) -> Point2: + def from_proto(cls, data: common_pb.Point2D) -> Point2: """ :param data: """ @@ -324,7 +324,7 @@ def center(points: list[Point2]) -> Point2: class Point3(Point2): @classmethod - def from_proto(cls, data) -> Point3: + def from_proto(cls, data: common_pb.Point) -> Point3: """ :param data: """