diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 645001d4..64a37745 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,7 +51,7 @@ jobs: uv run pre-commit install - name: Run pre-commit hooks - run: uv run pre-commit run --all-files --hook-stage push + run: uv run pre-commit run --all-files --hook-stage pre-push generate_dicts_from_data_json: name: Generate dicts from data.json @@ -80,7 +80,7 @@ jobs: run: | mv sc2/dicts sc2/dicts_old uv run python generate_dicts_from_data_json.py - uv run pre-commit run --all-files --hook-stage push || true + uv run pre-commit run --all-files --hook-stage pre-push || true rm -rf sc2/dicts/__pycache__ sc2/dicts_old/__pycache__ - name: Upload generated dicts folder as artifact diff --git a/.github/workflows/docker-ci.yml b/.github/workflows/docker-ci.yml index fb096edd..e3f04b23 100644 --- a/.github/workflows/docker-ci.yml +++ b/.github/workflows/docker-ci.yml @@ -163,7 +163,6 @@ jobs: docker cp pyproject.toml test_container:/root/python-sc2/ docker cp uv.lock test_container:/root/python-sc2/ docker cp sc2 test_container:/root/python-sc2/sc2 - docker cp s2clientprotocol test_container:/root/python-sc2/s2clientprotocol docker cp test test_container:/root/python-sc2/test docker cp examples test_container:/root/python-sc2/examples docker exec -i test_container bash -c "pip install uv \ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0c784213..03917649 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -38,22 +38,25 @@ repos: # Autoformat code - id: ruff-format-check name: Check if files are formatted - stages: [push] + stages: [pre-push] language: system + # Run the following command to fix: + # uv run ruff format . entry: uv run ruff format . --check --diff pass_filenames: false - id: ruff-lint name: Lint files - stages: [push] + stages: [pre-push] language: system + # Run the following command to fix: + # uv run ruff check . --fix entry: uv run ruff check . pass_filenames: false - # TODO Fix issues - # - id: pyrefly - # name: Static types checking with pyrefly - # stages: [push] - # language: system - # entry: uv run pyrefly check - # pass_filenames: false + - id: pyrefly + name: Static types checking with pyrefly + stages: [pre-push] + language: system + entry: uv run pyrefly check + pass_filenames: false diff --git a/.pyre_configuration b/.pyre_configuration deleted file mode 100644 index 6c670b47..00000000 --- a/.pyre_configuration +++ /dev/null @@ -1,17 +0,0 @@ -{ - "site_package_search_strategy": "pep561", - "source_directories": [ - { - "import_root": ".", - "source": "sc2" - }, - { - "import_root": ".", - "source": "examples" - }, - { - "import_root": ".", - "source": "test" - } - ] -} diff --git a/README.md b/README.md index 0ae3d0b8..835e80a0 100644 --- a/README.md +++ b/README.md @@ -186,5 +186,5 @@ Git commit messages use [imperative-style messages](https://stackoverflow.com/a/ To run pre-commit hooks (which run autoformatting and autosort imports) you can run ```sh uv run pre-commit install -uv run pre-commit run --all-files --hook-stage push +uv run pre-commit run --all-files --hook-stage pre-push ``` diff --git a/dockerfiles/test_docker_image.sh b/dockerfiles/test_docker_image.sh index 6b1e17e1..fcbd17de 100644 --- a/dockerfiles/test_docker_image.sh +++ b/dockerfiles/test_docker_image.sh @@ -35,13 +35,12 @@ docker cp uv.lock test_container:/root/python-sc2/ docker exec -i test_container bash -c "pip install uv && cd python-sc2 && uv sync --no-cache --no-install-project" docker cp sc2 test_container:/root/python-sc2/sc2 -docker cp s2clientprotocol test_container:/root/python-sc2/s2clientprotocol docker cp test test_container:/root/python-sc2/test # Run various test bots docker exec -i test_container bash -c "cd python-sc2 && uv run python test/travis_test_script.py test/autotest_bot.py" docker exec -i test_container bash -c "cd python-sc2 && uv run python test/travis_test_script.py test/queries_test_bot.py" -#docker exec -i test_container bash -c "cd python-sc2 && uv run python test/travis_test_script.py test/damagetest_bot.py" +docker exec -i test_container bash -c "cd python-sc2 && uv run python test/travis_test_script.py test/damagetest_bot.py" docker cp examples test_container:/root/python-sc2/examples docker exec -i test_container bash -c "cd python-sc2 && uv run python test/run_example_bots_vs_computer.py" diff --git a/dockerfiles/test_new_python_candidate.sh b/dockerfiles/test_new_python_candidate.sh index 80f45b58..987c2976 100644 --- a/dockerfiles/test_new_python_candidate.sh +++ b/dockerfiles/test_new_python_candidate.sh @@ -36,7 +36,6 @@ docker cp uv.lock test_container:/root/python-sc2/ docker exec -i test_container bash -c "pip install uv && cd python-sc2 && uv sync --no-cache --no-install-project" docker cp sc2 test_container:/root/python-sc2/sc2 -docker cp s2clientprotocol test_container:/root/python-sc2/s2clientprotocol docker cp test test_container:/root/python-sc2/test # Run various test bots diff --git a/examples/arcade_bot.py b/examples/arcade_bot.py index 811ee944..0c85286d 100644 --- a/examples/arcade_bot.py +++ b/examples/arcade_bot.py @@ -101,7 +101,6 @@ def position_around_unit( step_size: int = 1, exclude_out_of_bounds: bool = True, ): - # pyre-ignore[16] pos = pos.position.rounded positions = { pos.offset(Point2((x, y))) @@ -114,7 +113,6 @@ def position_around_unit( positions = { p for p in positions - # pyre-ignore[16] if 0 <= p[0] < self.game_info.pathing_grid.width and 0 <= p[1] < self.game_info.pathing_grid.height } return positions diff --git a/examples/bot_vs_bot.py b/examples/bot_vs_bot.py index 73d69fa7..5de26601 100644 --- a/examples/bot_vs_bot.py +++ b/examples/bot_vs_bot.py @@ -15,7 +15,7 @@ def main_old(): - result: list[Result] = run_game( + result: Result | list[Result | None] = run_game( maps.get("AcropolisLE"), [ Bot(Race.Protoss, WarpGateBot()), diff --git a/examples/competitive/__init__.py b/examples/competitive/__init__.py index 28a10595..2b3e2872 100644 --- a/examples/competitive/__init__.py +++ b/examples/competitive/__init__.py @@ -4,8 +4,9 @@ import aiohttp from loguru import logger -import sc2 from sc2.client import Client +from sc2.main import _play_game +from sc2.portconfig import Portconfig from sc2.protocol import ConnectionAlreadyClosedError @@ -41,7 +42,7 @@ def run_ladder_game(bot): else: ports = [lan_port + p for p in range(1, 6)] - portconfig = sc2.portconfig.Portconfig() + portconfig = Portconfig() portconfig.server = [ports[1], ports[2]] portconfig.players = [[ports[3], ports[4]]] @@ -56,10 +57,11 @@ def run_ladder_game(bot): # Modified version of sc2.main._join_game to allow custom host and port, and to not spawn an additional sc2process (thanks to alkurbatov for fix) async def join_ladder_game(host, port, players, realtime, portconfig, save_replay_as=None, game_time_limit=None): ws_url = f"ws://{host}:{port}/sc2api" + # pyrefly: ignore ws_connection = await aiohttp.ClientSession().ws_connect(ws_url, timeout=120) client = Client(ws_connection) try: - result = await sc2.main._play_game(players[0], client, realtime, portconfig, game_time_limit) + result = await _play_game(players[0], client, realtime, portconfig, game_time_limit) if save_replay_as is not None: await client.save_replay(save_replay_as) # await client.leave() @@ -68,6 +70,6 @@ async def join_ladder_game(host, port, players, realtime, portconfig, save_repla logger.error("Connection was closed before the game ended") return None finally: - ws_connection.close() + await ws_connection.close() return result diff --git a/examples/competitive/bot.py b/examples/competitive/bot.py index 253337b6..f575ae79 100644 --- a/examples/competitive/bot.py +++ b/examples/competitive/bot.py @@ -11,7 +11,6 @@ async def on_step(self, iteration: int): # Populate this function with whatever your bot should do! pass - # pyre-ignore[11] async def on_end(self, game_result: Result): print("Game ended.") # Do things here after the game ends diff --git a/examples/competitive/run.py b/examples/competitive/run.py index 10984103..eb911442 100644 --- a/examples/competitive/run.py +++ b/examples/competitive/run.py @@ -1,10 +1,9 @@ -# pyre-ignore-all-errors[16, 21] import sys -from __init__ import run_ladder_game +from examples.competitive.__init__ import run_ladder_game # Load bot -from bot import CompetitiveBot +from examples.competitive.bot import CompetitiveBot from sc2 import maps from sc2.data import Difficulty, Race diff --git a/examples/distributed_workers.py b/examples/distributed_workers.py index 9e7940e5..fa54588c 100644 --- a/examples/distributed_workers.py +++ b/examples/distributed_workers.py @@ -1,4 +1,3 @@ -# pyre-ignore-all-errors[16] from sc2 import maps from sc2.bot_ai import BotAI from sc2.data import Difficulty, Race diff --git a/examples/fastreload.py b/examples/fastreload.py index 4fc5439a..0837e8f3 100644 --- a/examples/fastreload.py +++ b/examples/fastreload.py @@ -20,7 +20,8 @@ def main(): input("Press enter to reload ") reload(zerg_rush) - player_config[0].ai = zerg_rush.ZergRushBot() + if isinstance(player_config[0], Bot): + player_config[0].ai = zerg_rush.ZergRushBot() gen.send(player_config) diff --git a/examples/host_external_norestart.py b/examples/host_external_norestart.py index c5626ac0..c3e33255 100644 --- a/examples/host_external_norestart.py +++ b/examples/host_external_norestart.py @@ -10,6 +10,7 @@ def main(): portconfig: Portconfig = Portconfig() print(portconfig.as_json) + # pyrefly: ignore player_config = [Bot(Race.Zerg, ZergRushBot()), Bot(Race.Zerg, None)] for g in _host_game_iter(maps.get("Abyssal Reef LE"), player_config, realtime=False, portconfig=portconfig): diff --git a/examples/protoss/find_adept_shades.py b/examples/protoss/find_adept_shades.py index d10896d3..2b287f55 100644 --- a/examples/protoss/find_adept_shades.py +++ b/examples/protoss/find_adept_shades.py @@ -18,7 +18,7 @@ def __init__(self): async def on_start(self): self.client.game_step = 2 await self.client.debug_create_unit( - [[UnitTypeId.ADEPT, 10, self.townhalls[0].position.towards(self.game_info.map_center, 5), 1]] + [(UnitTypeId.ADEPT, 10, self.townhalls[0].position.towards(self.game_info.map_center, 5), 1)] ) async def on_step(self, iteration: int): @@ -26,7 +26,6 @@ async def on_step(self, iteration: int): if adepts and not self.shaded: # Wait for adepts to spawn and then cast ability for adept in adepts: - # pyre-ignore[16] adept(AbilityId.ADEPTPHASESHIFT_ADEPTPHASESHIFT, self.game_info.map_center) self.shaded = True elif self.shades_mapping: @@ -38,7 +37,6 @@ async def on_step(self, iteration: int): # logger.info(f"Remaining shade time: {shade.buff_duration_remain} / {shade.buff_duration_max}") pass if adept and shade: - # pyre-ignore[16] self.client.debug_line_out(adept, shade, (0, 255, 0)) # logger.info(self.shades_mapping) elif self.shaded: @@ -53,7 +51,7 @@ async def on_step(self, iteration: int): previous_shade_location = shade.position.towards( forward_position, -(self.client.game_step / 16) * shade.movement_speed ) # See docstring of movement_speed attribute - # pyre-ignore[6] + closest_adept = remaining_adepts.closest_to(previous_shade_location) self.shades_mapping[closest_adept.tag] = shade.tag diff --git a/examples/protoss/threebase_voidray.py b/examples/protoss/threebase_voidray.py index 314f6696..be6429b7 100644 --- a/examples/protoss/threebase_voidray.py +++ b/examples/protoss/threebase_voidray.py @@ -94,16 +94,12 @@ async def on_step(self, iteration: int): for nexus in self.townhalls.ready: vgs = self.vespene_geyser.closer_than(15, nexus) for vg in vgs: - if not self.can_afford(UnitTypeId.ASSIMILATOR): - break - - worker = self.select_build_worker(vg.position) - if worker is None: - break - - if not self.gas_buildings or not self.gas_buildings.closer_than(1, vg): - worker.build_gas(vg) - worker.stop(queue=True) + if self.can_afford(UnitTypeId.ASSIMILATOR): + worker = self.select_build_worker(vg.position) + if worker is not None: + if not self.gas_buildings or not self.gas_buildings.closer_than(1, vg): + worker.build_gas(vg) + worker.stop(queue=True) # If we have less than 3 but at least 3 nexuses, build stargate if self.structures(UnitTypeId.PYLON).ready and self.structures(UnitTypeId.CYBERNETICSCORE).ready: diff --git a/examples/protoss/warpgate_push.py b/examples/protoss/warpgate_push.py index f1b36079..70404f89 100644 --- a/examples/protoss/warpgate_push.py +++ b/examples/protoss/warpgate_push.py @@ -79,14 +79,12 @@ async def on_step(self, iteration: int): for nexus in self.townhalls.ready: vgs = self.vespene_geyser.closer_than(15, nexus) for vg in vgs: - if not self.can_afford(UnitTypeId.ASSIMILATOR): - break - worker = self.select_build_worker(vg.position) - if worker is None: - break - if not self.gas_buildings or not self.gas_buildings.closer_than(1, vg): - worker.build_gas(vg) - worker.stop(queue=True) + if self.can_afford(UnitTypeId.ASSIMILATOR): + worker = self.select_build_worker(vg.position) + if worker is not None: + if not self.gas_buildings or not self.gas_buildings.closer_than(1, vg): + worker.build_gas(vg) + worker.stop(queue=True) # Research warp gate if cybercore is completed if ( diff --git a/examples/simulate_fight_scenario.py b/examples/simulate_fight_scenario.py index 2bee4390..00c95278 100644 --- a/examples/simulate_fight_scenario.py +++ b/examples/simulate_fight_scenario.py @@ -42,19 +42,21 @@ async def on_step(self, iteration: int): return async def reset_arena(self): + if self.enemy_location is None: + return await self.client.debug_kill_unit(self.all_units) await self.client.debug_create_unit( [ - [UnitTypeId.SUPPLYDEPOT, 1, self.enemy_location, OPPONENT_PLAYER_ID], - [UnitTypeId.MARINE, 4, self.enemy_location.towards(self.start_location, 8), OPPONENT_PLAYER_ID], + (UnitTypeId.SUPPLYDEPOT, 1, self.enemy_location, OPPONENT_PLAYER_ID), + (UnitTypeId.MARINE, 4, self.enemy_location.towards(self.start_location, 8), OPPONENT_PLAYER_ID), ] ) await self.client.debug_create_unit( [ - [UnitTypeId.SUPPLYDEPOT, 1, self.start_location, MY_PLAYER_ID], - [UnitTypeId.MARINE, 4, self.start_location.towards(self.enemy_location, 8), MY_PLAYER_ID], + (UnitTypeId.SUPPLYDEPOT, 1, self.start_location, MY_PLAYER_ID), + (UnitTypeId.MARINE, 4, self.start_location.towards(self.enemy_location, 8), MY_PLAYER_ID), ] ) @@ -63,6 +65,8 @@ async def manage_enemy_units(self): unit.attack(self.start_location) async def manage_own_units(self): + if self.enemy_location is None: + return for unit in self.units(UnitTypeId.MARINE): unit.attack(self.enemy_location) # TODO: implement your fight logic here diff --git a/examples/terran/cyclone_push.py b/examples/terran/cyclone_push.py index f66a18bb..e0aeb6ae 100644 --- a/examples/terran/cyclone_push.py +++ b/examples/terran/cyclone_push.py @@ -78,31 +78,29 @@ async def on_step(self, iteration: int): # Near same command as above with the depot await self.build(UnitTypeId.BARRACKS, near=cc.position.towards(self.game_info.map_center, 8)) - # If we have a barracks (complete or under construction) and less than 2 gas structures (here: refineries) - elif self.structures(UnitTypeId.BARRACKS) and self.gas_buildings.amount < 2: - if self.can_afford(UnitTypeId.REFINERY): - # All the vespene geysirs nearby, including ones with a refinery on top of it - vgs = self.vespene_geyser.closer_than(10, cc) - for vg in vgs: - if self.gas_buildings.filter(lambda unit: unit.distance_to(vg) < 1): - continue - # Select a worker closest to the vespene geysir - worker: Unit | None = self.select_build_worker(vg) - # Worker can be none in cases where all workers are dead - # or 'select_build_worker' function only selects from workers which carry no minerals - if worker is None: - continue + # If we have a barracks (complete or under construction) and less than 2 gas structures (here: refineries) + if self.structures(UnitTypeId.BARRACKS) and self.gas_buildings.amount < 2: + if self.can_afford(UnitTypeId.REFINERY): + # All the vespene geysirs nearby, including ones with a refinery on top of it + vgs = self.vespene_geyser.closer_than(10, cc) + for vg in vgs: + has_refinery = self.gas_buildings.filter(lambda unit: unit.distance_to(vg) < 1) + if has_refinery: + continue + # Select a worker closest to the vespene geysir + worker: Unit | None = self.select_build_worker(vg) + # Worker can be none in cases where all workers are dead + # or 'select_build_worker' function only selects from workers which carry no minerals + if worker is not None: # Issue the build command to the worker, important: vg has to be a Unit, not a position worker.build_gas(vg) - # Only issue one build geysir command per frame - break - - # If we have at least one barracks that is compelted, build factory - if self.structures(UnitTypeId.BARRACKS).ready: - if self.structures(UnitTypeId.FACTORY).amount < 3 and not self.already_pending(UnitTypeId.FACTORY): - if self.can_afford(UnitTypeId.FACTORY): - position: Point2 = cc.position.towards_with_random_angle(self.game_info.map_center, 16) - await self.build(UnitTypeId.FACTORY, near=position) + + # If we have at least one barracks that is completed, build factory + if self.structures(UnitTypeId.BARRACKS).ready: + if self.structures(UnitTypeId.FACTORY).amount < 3 and not self.already_pending(UnitTypeId.FACTORY): + if self.can_afford(UnitTypeId.FACTORY): + position: Point2 = cc.position.towards_with_random_angle(self.game_info.map_center, 16) + await self.build(UnitTypeId.FACTORY, near=position) for factory in self.structures(UnitTypeId.FACTORY).ready.idle: # Reactor allows us to build two at a time diff --git a/examples/terran/mass_reaper.py b/examples/terran/mass_reaper.py index 4ca3f1aa..6e7f598c 100644 --- a/examples/terran/mass_reaper.py +++ b/examples/terran/mass_reaper.py @@ -6,7 +6,10 @@ Bot made by Burny """ +from __future__ import annotations + import random +from typing import Literal from sc2 import maps from sc2.bot_ai import BotAI @@ -124,9 +127,6 @@ async def on_step(self, iteration: int): # Caution: the target for the refinery has to be the vespene geyser, not its position! worker.build_gas(vg) - # Dont build more than one each frame - break - # Make scvs until 22, usually you only need 1:1 mineral:gas ratio for reapers, but if you don't lose any then you will need additional depots (mule income should take care of that) # Stop scv production when barracks is complete but we still have a command center (priotize morphing to orbital command) if ( @@ -156,32 +156,32 @@ async def on_step(self, iteration: int): # Reaper micro enemies: Units = self.enemy_units | self.enemy_structures enemies_can_attack: Units = enemies.filter(lambda unit: unit.can_attack_ground) - for r in self.units(UnitTypeId.REAPER): + for reaper_unit in self.units(UnitTypeId.REAPER): # Move to range 15 of closest unit if reaper is below 20 hp and not regenerating enemy_threats_close: Units = enemies_can_attack.filter( - lambda unit: unit.distance_to(r) < 15 + lambda unit: unit.distance_to(reaper_unit) < 15 ) # Threats that can attack the reaper - if r.health_percentage < 2 / 5 and enemy_threats_close: - retreat_points: set[Point2] = self.neighbors8(r.position, distance=2) | self.neighbors8( - r.position, distance=4 + if reaper_unit.health_percentage < 2 / 5 and enemy_threats_close: + retreat_points: set[Point2] = self.neighbors8(reaper_unit.position, distance=2) | self.neighbors8( + reaper_unit.position, distance=4 ) # Filter points that are pathable retreat_points: set[Point2] = {x for x in retreat_points if self.in_pathing_grid(x)} if retreat_points: - closest_enemy: Unit = enemy_threats_close.closest_to(r) + closest_enemy: Unit = enemy_threats_close.closest_to(reaper_unit) retreat_point: Point2 = closest_enemy.position.furthest(retreat_points) - r.move(retreat_point) + reaper_unit.move(retreat_point) continue # Continue for loop, dont execute any of the following # Reaper is ready to attack, shoot nearest ground unit enemy_ground_units: Units = enemies.filter( - lambda unit: unit.distance_to(r) < 5 and not unit.is_flying + lambda unit: unit.distance_to(reaper_unit) < 5 and not unit.is_flying ) # Hardcoded attackrange of 5 - if r.weapon_cooldown == 0 and enemy_ground_units: - enemy_ground_units: Units = enemy_ground_units.sorted(lambda x: x.distance_to(r)) + if reaper_unit.weapon_cooldown == 0 and enemy_ground_units: + enemy_ground_units: Units = enemy_ground_units.sorted(lambda x: x.distance_to(reaper_unit)) closest_enemy: Unit = enemy_ground_units[0] - r.attack(closest_enemy) + reaper_unit.attack(closest_enemy) continue # Continue for loop, dont execute any of the following # Attack is on cooldown, check if grenade is on cooldown, if not then throw it to furthest enemy in range 5 @@ -192,51 +192,53 @@ async def on_step(self, iteration: int): lambda unit: not unit.is_structure and not unit.is_flying and unit.type_id not in {UnitTypeId.LARVA, UnitTypeId.EGG} - and unit.distance_to(r) < reaper_grenade_range + and unit.distance_to(reaper_unit) < reaper_grenade_range ) - if enemy_ground_units_in_grenade_range and (r.is_attacking or r.is_moving): + if enemy_ground_units_in_grenade_range and (reaper_unit.is_attacking or reaper_unit.is_moving): # If AbilityId.KD8CHARGE_KD8CHARGE in abilities, we check that to see if the reaper grenade is off cooldown - abilities = await self.get_available_abilities(r) + abilities: list[AbilityId] = await self.get_available_abilities(reaper_unit) # pyrefly: ignore enemy_ground_units_in_grenade_range = enemy_ground_units_in_grenade_range.sorted( - lambda x: x.distance_to(r), reverse=True + lambda x: x.distance_to(reaper_unit), reverse=True ) - furthest_enemy: Unit = None + furthest_enemy: Unit | None = None for enemy in enemy_ground_units_in_grenade_range: - if await self.can_cast(r, AbilityId.KD8CHARGE_KD8CHARGE, enemy, cached_abilities_of_unit=abilities): - furthest_enemy: Unit = enemy + if await self.can_cast( + reaper_unit, AbilityId.KD8CHARGE_KD8CHARGE, enemy, cached_abilities_of_unit=abilities + ): + furthest_enemy = enemy break - if furthest_enemy: - r(AbilityId.KD8CHARGE_KD8CHARGE, furthest_enemy) + if furthest_enemy is not None: + reaper_unit(AbilityId.KD8CHARGE_KD8CHARGE, furthest_enemy) continue # Continue for loop, don't execute any of the following # Move to max unit range if enemy is closer than 4 enemy_threats_very_close: Units = enemies.filter( - lambda unit: unit.can_attack_ground and unit.distance_to(r) < 4.5 + lambda unit: unit.can_attack_ground and unit.distance_to(reaper_unit) < 4.5 ) # Hardcoded attackrange minus 0.5 # Threats that can attack the reaper - if r.weapon_cooldown != 0 and enemy_threats_very_close: - retreat_points: set[Point2] = self.neighbors8(r.position, distance=2) | self.neighbors8( - r.position, distance=4 + if reaper_unit.weapon_cooldown != 0 and enemy_threats_very_close: + retreat_points: set[Point2] = self.neighbors8(reaper_unit.position, distance=2) | self.neighbors8( + reaper_unit.position, distance=4 ) # Filter points that are pathable by a reaper retreat_points: set[Point2] = {x for x in retreat_points if self.in_pathing_grid(x)} if retreat_points: - closest_enemy: Unit = enemy_threats_very_close.closest_to(r) + closest_enemy: Unit = enemy_threats_very_close.closest_to(reaper_unit) retreat_point: Point2 = max( - retreat_points, key=lambda x: x.distance_to(closest_enemy) - x.distance_to(r) + retreat_points, key=lambda x: x.distance_to(closest_enemy) - x.distance_to(reaper_unit) ) - r.move(retreat_point) + reaper_unit.move(retreat_point) continue # Continue for loop, don't execute any of the following # Move to nearest enemy ground unit/building because no enemy unit is closer than 5 all_enemy_ground_units: Units = self.enemy_units.not_flying if all_enemy_ground_units: - closest_enemy: Unit = all_enemy_ground_units.closest_to(r) - r.move(closest_enemy) + closest_enemy: Unit = all_enemy_ground_units.closest_to(reaper_unit) + reaper_unit.move(closest_enemy) continue # Continue for loop, don't execute any of the following # Move to random enemy start location if no enemy buildings have been seen - r.move(random.choice(self.enemy_start_locations)) + reaper_unit.move(random.choice(self.enemy_start_locations)) # Manage idle scvs, would be taken care by distribute workers aswell if self.townhalls: @@ -287,7 +289,7 @@ async def my_distribute_workers(self, performance_heavy=True, only_saturate_gas= # Find all gas_buildings that have surplus or deficit deficit_gas_buildings = {} - surplusgas_buildings = {} + surplus_gas_buildings = {} for g in self.gas_buildings.filter(lambda x: x.vespene_contents > 0): # Only loop over gas_buildings that have still gas in them deficit = g.ideal_harvesters - g.assigned_harvesters @@ -305,11 +307,11 @@ async def my_distribute_workers(self, performance_heavy=True, only_saturate_gas= w = surplus_workers.pop() worker_pool.append(w) worker_pool_tags.add(w.tag) - surplusgas_buildings[g.tag] = {"unit": g, "deficit": deficit} + surplus_gas_buildings[g.tag] = {"unit": g, "deficit": deficit} # Find all townhalls that have surplus or deficit - deficit_townhalls = {} - surplus_townhalls = {} + deficit_townhalls: dict[int, dict[Literal["unit", "deficit"], Unit | int]] = {} + surplus_townhalls: dict[int, dict[Literal["unit", "deficit"], Unit | int]] = {} if not only_saturate_gas: for th in self.townhalls: deficit = th.ideal_harvesters - th.assigned_harvesters @@ -333,7 +335,7 @@ async def my_distribute_workers(self, performance_heavy=True, only_saturate_gas= if all( [ len(deficit_gas_buildings) == 0, - len(surplusgas_buildings) == 0, + len(surplus_gas_buildings) == 0, len(surplus_townhalls) == 0 or deficit_townhalls == 0, ] ): @@ -341,15 +343,26 @@ async def my_distribute_workers(self, performance_heavy=True, only_saturate_gas= return # Check if deficit in gas less or equal than what we have in surplus, else grab some more workers from surplus bases + # pyrefly: ignore deficit_gas_count = sum( - gasInfo["deficit"] for gasTag, gasInfo in deficit_gas_buildings.items() if gasInfo["deficit"] > 0 + # pyrefly: ignore + gas_info["deficit"] + for _gas_tag, gas_info in deficit_gas_buildings.items() + # pyrefly: ignore + if gas_info["deficit"] > 0 ) surplus_count = sum( - -gasInfo["deficit"] for gasTag, gasInfo in surplusgas_buildings.items() if gasInfo["deficit"] < 0 + # pyrefly: ignore + -gas_info["deficit"] + for _gas_tag, gas_info in surplus_gas_buildings.items() + # pyrefly: ignore + if gas_info["deficit"] < 0 ) surplus_count += sum( + # pyrefly: ignore -townhall_info["deficit"] - for townhall_tag, townhall_info in surplus_townhalls.items() + for _townhall_tag, townhall_info in surplus_townhalls.items() + # pyrefly: ignore if townhall_info["deficit"] < 0 ) @@ -358,6 +371,7 @@ async def my_distribute_workers(self, performance_heavy=True, only_saturate_gas= for _gas_tag, gas_info in deficit_gas_buildings.items(): if worker_pool.amount >= deficit_gas_count: break + # pyrefly: ignore workers_near_gas = self.workers.closer_than(10, gas_info["unit"]).filter( lambda w: w.tag not in worker_pool_tags and len(w.orders) == 1 @@ -374,12 +388,15 @@ async def my_distribute_workers(self, performance_heavy=True, only_saturate_gas= if performance_heavy: # Sort furthest away to closest (as the pop() function will take the last element) worker_pool.sort(key=lambda x: x.distance_to(gas_info["unit"]), reverse=True) + # pyrefly: ignore for _ in range(gas_info["deficit"]): if worker_pool.amount > 0: w = worker_pool.pop() if len(w.orders) == 1 and w.orders[0].ability.id in [AbilityId.HARVEST_RETURN]: + # pyrefly: ignore w.gather(gas_info["unit"], queue=True) else: + # pyrefly: ignore w.gather(gas_info["unit"]) if not only_saturate_gas: @@ -388,10 +405,15 @@ async def my_distribute_workers(self, performance_heavy=True, only_saturate_gas= if performance_heavy: # Sort furthest away to closest (as the pop() function will take the last element) worker_pool.sort(key=lambda x: x.distance_to(townhall_info["unit"]), reverse=True) + # pyrefly: ignore for _ in range(townhall_info["deficit"]): if worker_pool.amount > 0: w = worker_pool.pop() - mf = self.mineral_field.closer_than(10, townhall_info["unit"]).closest_to(w) + mf = self.mineral_field.closer_than( + 10, + # pyrefly: ignore + townhall_info["unit"], + ).closest_to(w) if len(w.orders) == 1 and w.orders[0].ability.id in [AbilityId.HARVEST_RETURN]: w.gather(mf, queue=True) else: diff --git a/examples/terran/onebase_battlecruiser.py b/examples/terran/onebase_battlecruiser.py index 47cd5f62..1c0b1f58 100644 --- a/examples/terran/onebase_battlecruiser.py +++ b/examples/terran/onebase_battlecruiser.py @@ -24,7 +24,6 @@ def select_target(self) -> tuple[Point2, bool]: return targets.random.position, True if self.units and min(u.position.distance_to(self.enemy_start_locations[0]) for u in self.units) < 5: - # pyre-ignore[7] return self.enemy_start_locations[0].position, False return self.mineral_field.random.position, False @@ -60,11 +59,10 @@ async def on_step(self, iteration: int): # Build more BCs if self.structures(UnitTypeId.FUSIONCORE) and self.can_afford(UnitTypeId.BATTLECRUISER): - for sp in self.structures(UnitTypeId.STARPORT).idle: - if sp.has_add_on: - if not self.can_afford(UnitTypeId.BATTLECRUISER): - break - sp.train(UnitTypeId.BATTLECRUISER) + for starport in self.structures(UnitTypeId.STARPORT).idle: + if starport.has_add_on: + if self.can_afford(UnitTypeId.BATTLECRUISER): + starport.train(UnitTypeId.BATTLECRUISER) # Build more supply depots if self.supply_left < 6 and self.supply_used >= 14 and not self.already_pending(UnitTypeId.SUPPLYDEPOT): @@ -82,15 +80,11 @@ async def on_step(self, iteration: int): if self.can_afford(UnitTypeId.REFINERY): vgs: Units = self.vespene_geyser.closer_than(20, cc) for vg in vgs: - if self.gas_buildings.filter(lambda unit: unit.distance_to(vg) < 1): - break - - worker: Unit | None = self.select_build_worker(vg.position) - if worker is None: - break - - worker.build_gas(vg) - break + has_gas_building = self.gas_buildings.filter(lambda unit: unit.distance_to(vg) < 1) + if not has_gas_building: + worker: Unit | None = self.select_build_worker(vg.position) + if worker is not None: + worker.build_gas(vg) # Build factory if we dont have one if self.tech_requirement_progress(UnitTypeId.FACTORY) == 1: @@ -121,47 +115,53 @@ def starport_points_to_build_addon(sp_position: Point2) -> list[Point2]: return addon_points # Build starport techlab or lift if no room to build techlab - sp: Unit - for sp in self.structures(UnitTypeId.STARPORT).ready.idle: - if not sp.has_add_on and self.can_afford(UnitTypeId.STARPORTTECHLAB): - addon_points = starport_points_to_build_addon(sp.position) + starport: Unit + for starport in self.structures(UnitTypeId.STARPORT).ready.idle: + if not starport.has_add_on and self.can_afford(UnitTypeId.STARPORTTECHLAB): + addon_points = starport_points_to_build_addon(starport.position) if all( self.in_map_bounds(addon_point) and self.in_placement_grid(addon_point) and self.in_pathing_grid(addon_point) for addon_point in addon_points ): - sp.build(UnitTypeId.STARPORTTECHLAB) + starport.build(UnitTypeId.STARPORTTECHLAB) else: - sp(AbilityId.LIFT) + starport(AbilityId.LIFT) def starport_land_positions(sp_position: Point2) -> list[Point2]: """Return all points that need to be checked when trying to land at a location where there is enough space to build an addon. Returns 13 points.""" land_positions = [(sp_position + Point2((x, y))).rounded for x in range(-1, 2) for y in range(-1, 2)] return land_positions + starport_points_to_build_addon(sp_position) - # Find a position to land for a flying starport so that it can build an addon - for sp in self.structures(UnitTypeId.STARPORTFLYING).idle: - possible_land_positions_offset = sorted( - (Point2((x, y)) for x in range(-10, 10) for y in range(-10, 10)), - key=lambda point: point.x**2 + point.y**2, - ) - offset_point: Point2 = Point2((-0.5, -0.5)) - possible_land_positions = (sp.position.rounded + offset_point + p for p in possible_land_positions_offset) - for target_land_position in possible_land_positions: - land_and_addon_points: list[Point2] = starport_land_positions(target_land_position) - if all( - self.in_map_bounds(land_pos) and self.in_placement_grid(land_pos) and self.in_pathing_grid(land_pos) - for land_pos in land_and_addon_points - ): - sp(AbilityId.LAND, target_land_position) - break + def try_land_starports(): + # Find a position to land for a flying starport so that it can build an addon + for starport in self.structures(UnitTypeId.STARPORTFLYING).idle: + possible_land_positions_offset = sorted( + (Point2((x, y)) for x in range(-10, 10) for y in range(-10, 10)), + key=lambda point: point.x**2 + point.y**2, + ) + offset_point: Point2 = Point2((-0.5, -0.5)) + possible_land_positions = ( + starport.position.rounded + offset_point + p for p in possible_land_positions_offset + ) + for target_land_position in possible_land_positions: + land_and_addon_points: list[Point2] = starport_land_positions(target_land_position) + if all( + self.in_map_bounds(land_pos) + and self.in_placement_grid(land_pos) + and self.in_pathing_grid(land_pos) + for land_pos in land_and_addon_points + ): + starport(AbilityId.LAND, target_land_position) + return + + try_land_starports() # Show where it is flying to and show grid - unit: Unit - for sp in self.structures(UnitTypeId.STARPORTFLYING).filter(lambda unit: not unit.is_idle): - if isinstance(sp.order_target, Point2): - p: Point3 = Point3((*sp.order_target, self.get_terrain_z_height(sp.order_target))) + for starport in self.structures(UnitTypeId.STARPORTFLYING).filter(lambda unit: not unit.is_idle): + if isinstance(starport.order_target, Point2): + p: Point3 = Point3((*starport.order_target, self.get_terrain_z_height(starport.order_target))) self.client.debug_box2_out(p, color=Point3((255, 0, 0))) # Build fusion core diff --git a/examples/terran/ramp_wall.py b/examples/terran/ramp_wall.py index 0aa12fea..3711c1b1 100644 --- a/examples/terran/ramp_wall.py +++ b/examples/terran/ramp_wall.py @@ -31,20 +31,22 @@ async def on_step(self, iteration: int): if self.can_afford(UnitTypeId.SCV) and self.workers.amount < 16 and cc.is_idle: cc.train(UnitTypeId.SCV) - # Raise depos when enemies are nearby - for depo in self.structures(UnitTypeId.SUPPLYDEPOT).ready: - for unit in self.enemy_units: - if unit.distance_to(depo) < 15: - break - else: - depo(AbilityId.MORPH_SUPPLYDEPOT_LOWER) - - # Lower depos when no enemies are nearby - for depo in self.structures(UnitTypeId.SUPPLYDEPOTLOWERED).ready: - for unit in self.enemy_units: - if unit.distance_to(depo) < 10: - depo(AbilityId.MORPH_SUPPLYDEPOT_RAISE) - break + def raise_and_lower_depots(): + # Raise depos when enemies are nearby + for depo in self.structures(UnitTypeId.SUPPLYDEPOT).ready: + for unit in self.enemy_units: + if unit.distance_to(depo) < 15: + return + else: + depo(AbilityId.MORPH_SUPPLYDEPOT_LOWER) + # Lower depos when no enemies are nearby + for depo in self.structures(UnitTypeId.SUPPLYDEPOTLOWERED).ready: + for unit in self.enemy_units: + if unit.distance_to(depo) < 10: + depo(AbilityId.MORPH_SUPPLYDEPOT_RAISE) + return + + raise_and_lower_depots() # Draw ramp points self.draw_ramp_points() @@ -252,7 +254,7 @@ def draw_facing_units(self): for selected_unit2 in self.units.selected: if selected_unit1 == selected_unit2: continue - if selected_unit2.is_facing_unit(selected_unit1): + if selected_unit2.is_facing(selected_unit1): self.client.debug_box2_out(selected_unit2, half_vertex_length=0.25, color=green) else: self.client.debug_box2_out(selected_unit2, half_vertex_length=0.25, color=red) diff --git a/examples/watch_replay.py b/examples/watch_replay.py index 251eb5ad..5d9793c3 100644 --- a/examples/watch_replay.py +++ b/examples/watch_replay.py @@ -21,7 +21,7 @@ async def on_step(self, iteration: int): if __name__ == "__main__": - my_observer_ai = ObserverBot() + my_observer_ai = ObserverBot() # pyrefly: ignore # Enter replay name here # The replay should be either in this folder and you can give it a relative path, or change it to the absolute path replay_name = "WorkerRush.SC2Replay" diff --git a/examples/worker_stack_bot.py b/examples/worker_stack_bot.py index 0a0cbbb2..805b24d6 100644 --- a/examples/worker_stack_bot.py +++ b/examples/worker_stack_bot.py @@ -89,7 +89,7 @@ async def on_step(self, iteration: int): # Move worker in front of the nexus to avoid deceleration until the last moment if worker.distance_to(th) > th.radius + worker.radius + self.townhall_distance_threshold: pos: Point2 = th.position - # pyre-ignore[6] + worker.move(pos.towards(worker, th.radius * self.townhall_distance_factor)) worker.return_resource(queue=True) else: @@ -97,7 +97,7 @@ async def on_step(self, iteration: int): worker.gather(mineral, queue=True) # Print info every 30 game-seconds - # pyre-ignore[16] + if self.state.game_loop % (22.4 * 30) == 0: logger.info(f"{self.time_formatted} Mined a total of {int(self.state.score.collected_minerals)} minerals") diff --git a/examples/zerg/banes_banes_banes.py b/examples/zerg/banes_banes_banes.py index c5216977..8d105423 100644 --- a/examples/zerg/banes_banes_banes.py +++ b/examples/zerg/banes_banes_banes.py @@ -99,7 +99,7 @@ async def on_step(self, iteration: int): for vg in self.vespene_geyser.closer_than(10, hq): drone: Unit = self.workers.random drone.build_gas(vg) - break + return # If we have less than 22 drones, build drones if self.supply_workers + self.already_pending(UnitTypeId.DRONE) < 22: diff --git a/examples/zerg/hydralisk_push.py b/examples/zerg/hydralisk_push.py index 34f80003..56d7a939 100644 --- a/examples/zerg/hydralisk_push.py +++ b/examples/zerg/hydralisk_push.py @@ -86,6 +86,13 @@ async def on_step(self, iteration: int): if self.can_afford(UnitTypeId.HYDRALISKDEN): await self.build(UnitTypeId.HYDRALISKDEN, near=hq.position.towards(self.game_info.map_center, 5)) + # If we have less than 22 drones, build drones + if self.supply_workers + self.already_pending(UnitTypeId.DRONE) < 22: + if larvae and self.can_afford(UnitTypeId.DRONE): + larva: Unit = larvae.random + larva.train(UnitTypeId.DRONE) + return + # If we dont have both extractors: build them if ( self.structures(UnitTypeId.SPAWNINGPOOL) @@ -93,17 +100,10 @@ async def on_step(self, iteration: int): ): if self.can_afford(UnitTypeId.EXTRACTOR): # May crash if we dont have any drones - for vg in self.vespene_geyser.closer_than(10, hq): + for vespene_geyser in self.vespene_geyser.closer_than(10, hq): drone: Unit = self.workers.random - drone.build_gas(vg) - break - - # If we have less than 22 drones, build drones - if self.supply_workers + self.already_pending(UnitTypeId.DRONE) < 22: - if larvae and self.can_afford(UnitTypeId.DRONE): - larva: Unit = larvae.random - larva.train(UnitTypeId.DRONE) - return + drone.build_gas(vespene_geyser) + return # Saturate gas for a in self.gas_buildings: diff --git a/examples/zerg/zerg_rush.py b/examples/zerg/zerg_rush.py index 15d0df50..e8a6fedb 100644 --- a/examples/zerg/zerg_rush.py +++ b/examples/zerg/zerg_rush.py @@ -136,7 +136,6 @@ def draw_creep_pixelmap(self): color = Point3((0, 255, 0)) self.client.debug_box2_out(pos, half_vertex_length=0.25, color=color) - # pyre-ignore[11] async def on_end(self, game_result: Result): self.on_end_called = True logger.info(f"{self.time_formatted} On end was called") diff --git a/pyproject.toml b/pyproject.toml index 6d0a140b..09207a8a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,7 +54,7 @@ dev = [ "pyglet>=2.0.20", "pylint>=3.3.2", # Type checker - "pyrefly>=0.21.0", + "pyrefly>=0.58.0", "pytest>=8.3.4", "pytest-asyncio>=0.25.0", "pytest-benchmark>=5.1.0", @@ -70,7 +70,7 @@ dev = [ ] [tool.setuptools] -package-dir = { sc2 = "sc2", s2clientprotocol = "s2clientprotocol" } +package-dir = { sc2 = "sc2" } [tool.setuptools.package-data] sc2 = ["py.typed", "*.pyi"] @@ -93,7 +93,19 @@ dedent_closing_brackets = true allow_split_before_dict_value = false [tool.pyrefly] -project_includes = ["sc2", "examples", "test"] +project_includes = [ + "sc2", + "examples", + "test" +] +project-excludes = [ + # Disable for those files and folders + "sc2/data.py", +] + +[tool.pyrefly.errors] +bad-override = false +inconsistent-overload = false [tool.ruff] target-version = 'py310' @@ -132,11 +144,11 @@ ignore = [ "UP038", # Use `X | Y` in `isinstance` call instead of `(X, Y)` ] -[tool.ruff.pyupgrade] +[tool.pyupgrade] # Preserve types, even if a file imports `from __future__ import annotations`. # Remove once support for py3.8 and 3.9 is dropped keep-runtime-typing = true -[tool.ruff.pep8-naming] +[tool.pep8-naming] # Allow Pydantic's `@validator` decorator to trigger class method treatment. classmethod-decorators = ["pydantic.validator", "classmethod"] diff --git a/s2clientprotocol/__init__.pyi b/s2clientprotocol/__init__.pyi deleted file mode 100644 index e69de29b..00000000 diff --git a/s2clientprotocol/common_pb2.pyi b/s2clientprotocol/common_pb2.pyi deleted file mode 100644 index e586cfef..00000000 --- a/s2clientprotocol/common_pb2.pyi +++ /dev/null @@ -1,48 +0,0 @@ -# https://github.com/Blizzard/s2client-proto/blob/bff45dae1fc685e6acbaae084670afb7d1c0832c/s2clientprotocol/common.proto -from enum import Enum - -from google.protobuf.message import Message - -class AvailableAbility(Message): - ability_id: int - requires_point: bool - def __init__(self, ability_id: int = ..., requires_point: bool = ...) -> None: ... - -class ImageData(Message): - bits_per_pixel: int - size: Size2DI - data: bytes - def __init__(self, bits_per_pixel: int = ..., size: Size2DI = ..., data: bytes = ...) -> None: ... - -class PointI(Message): - x: int - y: int - def __init__(self, x: int = ..., y: int = ...) -> None: ... - -class RectangleI(Message): - p0: PointI - p1: PointI - def __init__(self, p0: PointI = ..., p1: PointI = ...) -> 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 Size2DI(Message): - x: int - y: int - def __init__(self, x: int = ..., y: int = ...) -> None: ... - -class Race(Enum): - NoRace: int - Terran: int - Zerg: int - Protoss: int - Random: int diff --git a/s2clientprotocol/data_pb2.pyi b/s2clientprotocol/data_pb2.pyi deleted file mode 100644 index 8b839cf5..00000000 --- a/s2clientprotocol/data_pb2.pyi +++ /dev/null @@ -1,170 +0,0 @@ -from collections.abc import Iterable -from enum import Enum - -from google.protobuf.message import Message - -class Target(Enum): - # NONE: int - Point: int - Unit: int - PointOrUnit: int - PointOrNone: int - -class AbilityData(Message): - ability_id: int - link_name: str - link_index: int - button_name: str - friendly_name: str - hotkey: str - remaps_to_ability_id: int - available: bool - target: int - allow_minimap: bool - allow_autocast: bool - is_building: bool - footprint_radius: float - is_instant_placement: bool - cast_range: float - def __init__( - self, - ability_id: int = ..., - link_name: str = ..., - link_index: int = ..., - button_name: str = ..., - friendly_name: str = ..., - hotkey: str = ..., - remaps_to_ability_id: int = ..., - available: bool = ..., - target: int = ..., - allow_minimap: bool = ..., - allow_autocast: bool = ..., - is_building: bool = ..., - footprint_radius: float = ..., - is_instant_placement: bool = ..., - cast_range: float = ..., - ) -> None: ... - -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 DamageBonus(Message): - attribute: int - bonus: float - def __init__(self, attribute: int = ..., bonus: float = ...) -> None: ... - -class TargetType(Enum): - Ground: int - Air: int - Any: int - -class Weapon(Message): - type: int - damage: float - damage_bonus: Iterable[DamageBonus] - attacks: int - range: float - speed: float - def __init__( - self, - type: int = ..., - damage: float = ..., - damage_bonus: Iterable[DamageBonus] = ..., - attacks: int = ..., - range: float = ..., - speed: float = ..., - ) -> None: ... - -class UnitTypeData(Message): - unit_id: int - name: str - available: bool - cargo_size: int - mineral_cost: int - vespene_cost: int - food_required: float - food_provided: float - ability_id: int - race: int - build_time: float - has_vespene: bool - has_minerals: bool - sight_range: float - tech_alias: Iterable[int] - unit_alias: int - tech_requirement: int - require_attached: bool - attributes: Iterable[int] - movement_speed: float - armor: float - weapons: Iterable[Weapon] - def __init__( - self, - unit_id: int = ..., - name: str = ..., - available: bool = ..., - cargo_size: int = ..., - mineral_cost: int = ..., - vespene_cost: int = ..., - food_required: float = ..., - food_provided: float = ..., - ability_id: int = ..., - race: int = ..., - build_time: float = ..., - has_vespene: bool = ..., - has_minerals: bool = ..., - sight_range: float = ..., - tech_alias: Iterable[int] = ..., - unit_alias: int = ..., - tech_requirement: int = ..., - require_attached: bool = ..., - attributes: Iterable[int] = ..., - movement_speed: float = ..., - armor: float = ..., - weapons: Iterable[Weapon] = ..., - ) -> None: ... - -class UpgradeData(Message): - upgrade_id: int - name: str - mineral_cost: int - vespene_cost: int - research_time: float - ability_id: int - def __init__( - self, - upgrade_id: int = ..., - name: str = ..., - mineral_cost: int = ..., - vespene_cost: int = ..., - research_time: float = ..., - ability_id: int = ..., - ) -> None: ... - -class BuffData(Message): - buff_id: int - name: str - def __init__(self, buff_id: int = ..., name: str = ...) -> None: ... - -class EffectData(Message): - effect_id: int - name: str - friendly_name: str - radius: float - def __init__( - self, - effect_id: int = ..., - name: str = ..., - friendly_name: str = ..., - radius: float = ..., - ) -> None: ... diff --git a/s2clientprotocol/debug_pb2.pyi b/s2clientprotocol/debug_pb2.pyi deleted file mode 100644 index edc956ee..00000000 --- a/s2clientprotocol/debug_pb2.pyi +++ /dev/null @@ -1,152 +0,0 @@ -from collections.abc import Iterable -from enum import Enum - -from google.protobuf.message import Message - -from .common_pb2 import Point, Point2D - -class DebugCommand(Message): - draw: DebugDraw - game_state: int - create_unit: DebugCreateUnit - kill_unit: DebugKillUnit - test_process: DebugTestProcess - score: DebugSetScore - end_game: DebugEndGame - unit_value: DebugSetUnitValue - def __init__( - self, - draw: DebugDraw = ..., - game_state: int = ..., - create_unit: DebugCreateUnit = ..., - kill_unit: DebugKillUnit = ..., - test_process: DebugTestProcess = ..., - score: DebugSetScore = ..., - end_game: DebugEndGame = ..., - unit_value: DebugSetUnitValue = ..., - ) -> None: ... - -class DebugDraw(Message): - text: Iterable[DebugText] - lines: Iterable[DebugLine] - boxes: Iterable[DebugBox] - spheres: Iterable[DebugSphere] - def __init__( - self, - text: Iterable[DebugText] = ..., - lines: Iterable[DebugLine] = ..., - boxes: Iterable[DebugBox] = ..., - spheres: Iterable[DebugSphere] = ..., - ) -> None: ... - -class Line(Message): - p0: Point - p1: Point - def __init__(self, p0: Point = ..., p1: Point = ...) -> None: ... - -class Color(Message): - r: int - g: int - b: int - def __init__(self, r: int = ..., g: int = ..., b: int = ...) -> None: ... - -class DebugText(Message): - color: Color - text: str - virtual_pos: Point - world_pos: Point - size: int - def __init__( - self, - color: Color = ..., - text: str = ..., - virtual_pos: Point = ..., - world_pos: Point = ..., - size: int = ..., - ) -> None: ... - -class DebugLine(Message): - color: Color - line: Line - def __init__(self, color: Color = ..., line: Line = ...) -> None: ... - -class DebugBox(Message): - color: Color - min: Point - max: Point - def __init__(self, color: Color = ..., min: Point = ..., max: Point = ...) -> None: ... - -class DebugSphere(Message): - color: Color - p: Point - r: float - def __init__(self, color: Color = ..., p: Point = ..., r: float = ...) -> None: ... - -class DebugGameState(Enum): - show_map: int - control_enemy: int - food: int - free: int - all_resources: int - god: int - minerals: int - gas: int - cooldown: int - tech_tree: int - upgrade: int - fast_build: int - -class DebugCreateUnit(Message): - unit_type: int - owner: int - pos: Point2D - quantity: int - def __init__( - self, - unit_type: int = ..., - owner: int = ..., - pos: Point2D = ..., - quantity: int = ..., - ) -> None: ... - -class DebugKillUnit(Message): - tag: Iterable[int] - def __init__(self, tag: Iterable[int] = ...) -> None: ... - -class Test(Enum): - hang: int - crash: int - exit: int - -class DebugTestProcess(Message): - test: int - delay_ms: int - def __init__(self, test: int = ..., delay_ms: int = ...) -> None: ... - -class DebugSetScore(Message): - score: float - def __init__(self, score: float = ...) -> None: ... - -class EndResult(Enum): - Surrender: int - DeclareVictory: int - -class DebugEndGame(Message): - end_result: int - def __init__(self, end_result: int = ...) -> None: ... - -class UnitValue(Enum): - Energy: int - Life: int - Shields: int - -class DebugSetUnitValue(Message): - unit_value: int - value: float - unit_tag: int - def __init__( - self, - unit_value: int = ..., - value: float = ..., - unit_tag: int = ..., - ) -> None: ... diff --git a/s2clientprotocol/error_pb2.pyi b/s2clientprotocol/error_pb2.pyi deleted file mode 100644 index 017262d9..00000000 --- a/s2clientprotocol/error_pb2.pyi +++ /dev/null @@ -1,217 +0,0 @@ -from enum import Enum - -class ActionResult(Enum): - 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 - 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 diff --git a/s2clientprotocol/query_pb2.pyi b/s2clientprotocol/query_pb2.pyi deleted file mode 100644 index 746d86d9..00000000 --- a/s2clientprotocol/query_pb2.pyi +++ /dev/null @@ -1,74 +0,0 @@ -from collections.abc import Iterable - -from google.protobuf.message import Message - -from .common_pb2 import AvailableAbility, Point2D - -class RequestQuery(Message): - pathing: Iterable[RequestQueryPathing] - abilities: Iterable[RequestQueryAvailableAbilities] - placements: Iterable[RequestQueryBuildingPlacement] - ignore_resource_requirements: bool - def __init__( - self, - pathing: Iterable[RequestQueryPathing] = ..., - abilities: Iterable[RequestQueryAvailableAbilities] = ..., - placements: Iterable[RequestQueryBuildingPlacement] = ..., - ignore_resource_requirements: bool = ..., - ) -> None: ... - -class ResponseQuery(Message): - pathing: Iterable[ResponseQueryPathing] - abilities: Iterable[ResponseQueryAvailableAbilities] - placements: Iterable[ResponseQueryBuildingPlacement] - def __init__( - self, - pathing: Iterable[ResponseQueryPathing] = ..., - abilities: Iterable[ResponseQueryAvailableAbilities] = ..., - placements: Iterable[ResponseQueryBuildingPlacement] = ..., - ) -> None: ... - -class RequestQueryPathing(Message): - start_pos: Point2D - unit_tag: int - end_pos: Point2D - def __init__( - self, - start_pos: Point2D = ..., - unit_tag: int = ..., - end_pos: Point2D = ..., - ) -> None: ... - -class ResponseQueryPathing(Message): - distance: float - def __init__(self, distance: float = ...) -> None: ... - -class RequestQueryAvailableAbilities(Message): - unit_tag: int - def __init__(self, unit_tag: int = ...) -> None: ... - -class ResponseQueryAvailableAbilities(Message): - abilities: Iterable[AvailableAbility] - unit_tag: int - unit_type_id: int - def __init__( - self, - abilities: Iterable[AvailableAbility] = ..., - unit_tag: int = ..., - unit_type_id: int = ..., - ) -> None: ... - -class RequestQueryBuildingPlacement(Message): - ability_id: int - target_pos: Point2D - placing_unit_tag: int - def __init__( - self, - ability_id: int = ..., - target_pos: Point2D = ..., - placing_unit_tag: int = ..., - ) -> None: ... - -class ResponseQueryBuildingPlacement(Message): - result: int - def __init__(self, result: int = ...) -> None: ... diff --git a/s2clientprotocol/raw_pb2.pyi b/s2clientprotocol/raw_pb2.pyi deleted file mode 100644 index 34d89c6d..00000000 --- a/s2clientprotocol/raw_pb2.pyi +++ /dev/null @@ -1,272 +0,0 @@ -from collections.abc import Iterable -from enum import Enum - -from google.protobuf.message import Message - -from .common_pb2 import ImageData, Point, Point2D, RectangleI, Size2DI - -class StartRaw(Message): - map_size: Size2DI - pathing_grid: ImageData - terrain_height: ImageData - placement_grid: ImageData - playable_area: RectangleI - start_locations: Iterable[Point2D] - def __init__( - self, - map_size: Size2DI = ..., - pathing_grid: ImageData = ..., - terrain_height: ImageData = ..., - placement_grid: ImageData = ..., - playable_area: RectangleI = ..., - start_locations: Iterable[Point2D] = ..., - ) -> None: ... - -class ObservationRaw(Message): - player: PlayerRaw - units: Iterable[Unit] - map_state: MapState - event: Event - effects: Iterable[Effect] - radar: Iterable[RadarRing] - def __init__( - self, - player: PlayerRaw = ..., - units: Iterable[Unit] = ..., - map_state: MapState = ..., - event: Event = ..., - effects: Iterable[Effect] = ..., - radar: Iterable[RadarRing] = ..., - ) -> None: ... - -class RadarRing(Message): - pos: Point - radius: float - def __init__(self, pos: Point = ..., radius: float = ...) -> None: ... - -class PowerSource(Message): - pos: Point - radius: float - tag: int - def __init__(self, pos: Point = ..., radius: float = ..., tag: int = ...) -> None: ... - -class PlayerRaw(Message): - power_sources: Iterable[PowerSource] - camera: Point - upgrade_ids: Iterable[int] - def __init__( - self, - power_sources: Iterable[PowerSource] = ..., - camera: Point = ..., - upgrade_ids: Iterable[int] = ..., - ) -> None: ... - -class UnitOrder(Message): - ability_id: int - target_world_space_pos: Point - target_unit_tag: int - progress: float - def __init__( - self, - ability_id: int = ..., - target_world_space_pos: Point = ..., - target_unit_tag: int = ..., - progress: float = ..., - ) -> None: ... - -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 - -class PassengerUnit(Message): - tag: int - health: float - health_max: float - shield: float - shield_max: float - energy: float - energy_max: float - unit_type: int - def __init__( - self, - tag: int = ..., - health: float = ..., - health_max: float = ..., - shield: float = ..., - shield_max: float = ..., - energy: float = ..., - energy_max: float = ..., - unit_type: int = ..., - ) -> None: ... - -class RallyTarget(Message): - point: Point - tag: int - def __init__(self, point: Point = ..., tag: int = ...) -> None: ... - -class Unit(Message): - display_type: int - alliance: int - tag: int - unit_type: int - owner: int - pos: Point - facing: float - radius: float - build_progress: float - cloak: int - buff_ids: Iterable[int] - detect_range: float - radar_range: float - is_selected: bool - is_on_screen: bool - is_blip: bool - is_powered: bool - is_active: bool - attack_upgrade_level: int - armor_upgrade_level: int - shield_upgrade_level: int - health: float - health_max: float - shield: float - shield_max: float - energy: float - energy_max: float - mineral_contents: int - vespene_contents: int - is_flying: bool - is_burrowed: bool - is_hallucination: bool - orders: Iterable[UnitOrder] - add_on_tag: int - passengers: Iterable[PassengerUnit] - cargo_space_taken: int - cargo_space_max: int - assigned_harvesters: int - ideal_harvesters: int - weapon_cooldown: float - engaged_target_tag: int - buff_duration_remain: int - buff_duration_max: int - rally_targets: Iterable[RallyTarget] - def __init__( - self, - display_type: int = ..., - alliance: int = ..., - tag: int = ..., - unit_type: int = ..., - owner: int = ..., - pos: Point = ..., - facing: float = ..., - radius: float = ..., - build_progress: float = ..., - cloak: int = ..., - buff_ids: Iterable[int] = ..., - detect_range: float = ..., - radar_range: float = ..., - is_selected: bool = ..., - is_on_screen: bool = ..., - is_blip: bool = ..., - is_powered: bool = ..., - is_active: bool = ..., - attack_upgrade_level: int = ..., - armor_upgrade_level: int = ..., - shield_upgrade_level: int = ..., - health: float = ..., - health_max: float = ..., - shield: float = ..., - shield_max: float = ..., - energy: float = ..., - energy_max: float = ..., - mineral_contents: int = ..., - vespene_contents: int = ..., - is_flying: bool = ..., - is_burrowed: bool = ..., - is_hallucination: bool = ..., - orders: Iterable[UnitOrder] = ..., - add_on_tag: int = ..., - passengers: Iterable[PassengerUnit] = ..., - cargo_space_taken: int = ..., - cargo_space_max: int = ..., - assigned_harvesters: int = ..., - ideal_harvesters: int = ..., - weapon_cooldown: float = ..., - engaged_target_tag: int = ..., - buff_duration_remain: int = ..., - buff_duration_max: int = ..., - rally_targets: Iterable[RallyTarget] = ..., - ) -> None: ... - -class MapState(Message): - visibility: ImageData - creep: ImageData - def __init__(self, visibility: ImageData = ..., creep: ImageData = ...) -> None: ... - -class Event(Message): - dead_units: Iterable[int] - def __init__(self, dead_units: Iterable[int] = ...) -> None: ... - -class Effect(Message): - effect_id: int - pos: Iterable[Point2D] - alliance: int - owner: int - radius: float - def __init__( - self, - effect_id: int = ..., - pos: Iterable[Point2D] = ..., - alliance: int = ..., - owner: int = ..., - radius: float = ..., - ) -> None: ... - -class ActionRaw(Message): - unit_command: ActionRawUnitCommand - camera_move: ActionRawCameraMove - toggle_autocast: ActionRawToggleAutocast - def __init__( - self, - unit_command: ActionRawUnitCommand = ..., - camera_move: ActionRawCameraMove = ..., - toggle_autocast: ActionRawToggleAutocast = ..., - ) -> None: ... - -class ActionRawUnitCommand(Message): - ability_id: int - target_world_space_pos: Point2D - target_unit_tag: int - unit_tags: Iterable[int] - queue_command: bool - def __init__( - self, - ability_id: int = ..., - target_world_space_pos: Point2D = ..., - target_unit_tag: int = ..., - unit_tags: Iterable[int] = ..., - queue_command: bool = ..., - ) -> None: ... - -class ActionRawCameraMove(Message): - center_world_space: Point - def __init__(self, center_world_space: Point = ...) -> None: ... - -class ActionRawToggleAutocast(Message): - ability_id: int - unit_tags: Iterable[int] - def __init__(self, ability_id: int = ..., unit_tags: Iterable[int] = ...) -> None: ... diff --git a/s2clientprotocol/sc2api_pb2.pyi b/s2clientprotocol/sc2api_pb2.pyi deleted file mode 100644 index 67574e00..00000000 --- a/s2clientprotocol/sc2api_pb2.pyi +++ /dev/null @@ -1,748 +0,0 @@ -from __future__ import annotations - -from collections.abc import Iterable -from enum import Enum - -from google.protobuf.message import Message - -from s2clientprotocol.spatial_pb2 import ActionSpatial, ObservationFeatureLayer, ObservationRender - -from .common_pb2 import AvailableAbility, Point2D, Size2DI -from .data_pb2 import AbilityData, BuffData, EffectData, UnitTypeData, UpgradeData -from .debug_pb2 import DebugCommand -from .query_pb2 import RequestQuery, ResponseQuery -from .raw_pb2 import ActionRaw, ObservationRaw, StartRaw -from .score_pb2 import Score -from .ui_pb2 import ActionUI, ObservationUI - -class Request(Message): - create_game: RequestCreateGame - join_game: RequestJoinGame - restart_game: RequestRestartGame - start_replay: RequestStartReplay - leave_game: RequestLeaveGame - quick_save: RequestQuickSave - quick_load: RequestQuickLoad - quit: RequestQuit - game_info: RequestGameInfo - observation: RequestObservation - action: RequestAction - obs_action: RequestObserverAction - step: RequestStep - data: RequestData - query: RequestQuery - save_replay: RequestSaveReplay - map_command: RequestMapCommand - replay_info: RequestReplayInfo - available_maps: RequestAvailableMaps - save_map: RequestSaveMap - ping: RequestPing - debug: RequestDebug - id: int - def __init__( - self, - create_game: RequestCreateGame = ..., - join_game: RequestJoinGame = ..., - restart_game: RequestRestartGame = ..., - start_replay: RequestStartReplay = ..., - leave_game: RequestLeaveGame = ..., - quick_save: RequestQuickSave = ..., - quick_load: RequestQuickLoad = ..., - quit: RequestQuit = ..., - game_info: RequestGameInfo = ..., - observation: RequestObservation = ..., - action: RequestAction = ..., - obs_action: RequestObserverAction = ..., - step: RequestStep = ..., - data: RequestData = ..., - query: RequestQuery = ..., - save_replay: RequestSaveReplay = ..., - map_command: RequestMapCommand = ..., - replay_info: RequestReplayInfo = ..., - available_maps: RequestAvailableMaps = ..., - save_map: RequestSaveMap = ..., - ping: RequestPing = ..., - debug: RequestDebug = ..., - id: int = ..., - ) -> None: ... - -class Response(Message): - create_game: ResponseCreateGame - join_game: ResponseJoinGame - restart_game: ResponseRestartGame - start_replay: ResponseStartReplay - leave_game: ResponseLeaveGame - quick_save: ResponseQuickSave - quick_load: ResponseQuickLoad - quit: ResponseQuit - game_info: ResponseGameInfo - observation: ResponseObservation - action: ResponseAction - obs_action: ResponseObserverAction - step: ResponseStep - data: ResponseData - query: ResponseQuery - save_replay: ResponseSaveReplay - replay_info: ResponseReplayInfo - available_maps: ResponseAvailableMaps - save_map: ResponseSaveMap - map_command: ResponseMapCommand - ping: ResponsePing - debug: ResponseDebug - id: int - error: Iterable[str] - status: int - def __init__( - self, - create_game: ResponseCreateGame = ..., - join_game: ResponseJoinGame = ..., - restart_game: ResponseRestartGame = ..., - start_replay: ResponseStartReplay = ..., - leave_game: ResponseLeaveGame = ..., - quick_save: ResponseQuickSave = ..., - quick_load: ResponseQuickLoad = ..., - quit: ResponseQuit = ..., - game_info: ResponseGameInfo = ..., - observation: ResponseObservation = ..., - action: ResponseAction = ..., - obs_action: ResponseObserverAction = ..., - step: ResponseStep = ..., - data: ResponseData = ..., - query: ResponseQuery = ..., - save_replay: ResponseSaveReplay = ..., - replay_info: ResponseReplayInfo = ..., - available_maps: ResponseAvailableMaps = ..., - save_map: ResponseSaveMap = ..., - map_command: ResponseMapCommand = ..., - ping: ResponsePing = ..., - debug: ResponseDebug = ..., - id: int = ..., - error: Iterable[str] = ..., - status: int = ..., - ) -> None: ... - -class Status(Enum): - launched: int - init_game: int - in_game: int - in_replay: int - ended: int - quit: int - unknown: int - -class RequestCreateGame(Message): - local_map: LocalMap - battlenet_map_name: str - player_setup: Iterable[PlayerSetup] - disable_fog: bool - random_seed: int - realtime: bool - def __init__( - self, - local_map: LocalMap = ..., - battlenet_map_name: str = ..., - player_setup: Iterable[PlayerSetup] = ..., - disable_fog: bool = ..., - random_seed: int = ..., - realtime: bool = ..., - ) -> None: ... - -class LocalMap(Message): - map_path: str - map_data: bytes - def __init__(self, map_path: str = ..., map_data: bytes = ...) -> None: ... - -class ResponseCreateGame(Message): - class Error(Enum): - MissingMap: int - InvalidMapPath: int - InvalidMapData: int - InvalidMapName: int - InvalidMapHandle: int - MissingPlayerSetup: int - InvalidPlayerSetup: int - MultiplayerUnsupported: int - - error: int - error_details: str - def __init__(self, error: int = ..., error_details: str = ...) -> None: ... - -class RequestJoinGame(Message): - race: int - observed_player_id: int - options: InterfaceOptions - server_ports: PortSet - client_ports: Iterable[PortSet] - shared_port: int - player_name: str - host_ip: str - def __init__( - self, - race: int = ..., - observed_player_id: int = ..., - options: InterfaceOptions = ..., - server_ports: PortSet = ..., - client_ports: Iterable[PortSet] = ..., - shared_port: int = ..., - player_name: str = ..., - host_ip: str = ..., - ) -> None: ... - -class PortSet(Message): - game_port: int - base_port: int - def __init__(self, game_port: int = ..., base_port: int = ...) -> None: ... - -class ResponseJoinGame(Message): - class Error(Enum): - MissingParticipation: int - InvalidObservedPlayerId: int - MissingOptions: int - MissingPorts: int - GameFull: int - LaunchError: int - FeatureUnsupported: int - NoSpaceForUser: int - MapDoesNotExist: int - CannotOpenMap: int - ChecksumError: int - NetworkError: int - OtherError: int - - player_id: int - error: int - error_details: str - def __init__(self, player_id: int = ..., error: int = ..., error_details: str = ...) -> None: ... - -class RequestRestartGame(Message): - def __init__(self) -> None: ... - -class ResponseRestartGame(Message): - class Error(Enum): - LaunchError: int - - error: int - error_details: str - need_hard_reset: bool - def __init__(self, error: int = ..., error_details: str = ..., need_hard_reset: bool = ...) -> None: ... - -class RequestStartReplay(Message): - replay_path: str - replay_data: bytes - map_data: bytes - observed_player_id: int - options: InterfaceOptions - disable_fog: bool - realtime: bool - record_replay: bool - def __init__( - self, - replay_path: str = ..., - replay_data: bytes = ..., - map_data: bytes = ..., - observed_player_id: int = ..., - options: InterfaceOptions = ..., - disable_fog: bool = ..., - realtime: bool = ..., - record_replay: bool = ..., - ) -> None: ... - -class ResponseStartReplay(Message): - class Error(Enum): - MissingReplay: int - InvalidReplayPath: int - InvalidReplayData: int - InvalidMapData: int - InvalidObservedPlayerId: int - MissingOptions: int - LaunchError: int - - error: int - error_details: str - def __init__(self, error: int = ..., error_details: str = ...) -> None: ... - -class RequestMapCommand(Message): - trigger_cmd: str - def __init__(self, trigger_cmd: str = ...) -> None: ... - -class ResponseMapCommand(Message): - class Error(Enum): - NoTriggerError: int - - error: int - error_details: str - def __init__(self, error: int = ..., error_details: str = ...) -> None: ... - -class RequestLeaveGame(Message): - def __init__(self) -> None: ... - -class ResponseLeaveGame(Message): - def __init__(self) -> None: ... - -class RequestQuickSave(Message): - def __init__(self) -> None: ... - -class ResponseQuickSave(Message): - def __init__(self) -> None: ... - -class RequestQuickLoad(Message): - def __init__(self) -> None: ... - -class ResponseQuickLoad(Message): - def __init__(self) -> None: ... - -class RequestQuit(Message): - def __init__(self) -> None: ... - -class ResponseQuit(Message): - def __init__(self) -> None: ... - -class RequestGameInfo(Message): - def __init__(self) -> None: ... - -class ResponseGameInfo(Message): - map_name: str - mod_names: Iterable[str] - local_map_path: str - player_info: Iterable[PlayerInfo] - start_raw: StartRaw - options: InterfaceOptions - def __init__( - self, - map_name: str = ..., - mod_names: Iterable[str] = ..., - local_map_path: str = ..., - player_info: Iterable[PlayerInfo] = ..., - start_raw: StartRaw = ..., - options: InterfaceOptions = ..., - ) -> None: ... - -class RequestObservation(Message): - disable_fog: bool - game_loop: int - def __init__(self, disable_fog: bool = ..., game_loop: int = ...) -> None: ... - -class ResponseObservation(Message): - actions: Iterable[Action] - action_errors: Iterable[ActionError] - observation: Observation - player_result: Iterable[PlayerResult] - chat: Iterable[ChatReceived] - def __init__( - self, - actions: Iterable[Action] = ..., - action_errors: Iterable[ActionError] = ..., - observation: Observation = ..., - player_result: Iterable[PlayerResult] = ..., - chat: Iterable[ChatReceived] = ..., - ) -> None: ... - -class ChatReceived(Message): - player_id: int - message: str - def __init__(self, player_id: int = ..., message: str = ...) -> None: ... - -class RequestAction(Message): - actions: Iterable[Action] - def __init__(self, actions: Iterable[Action] = ...) -> None: ... - -class ResponseAction(Message): - result: Iterable[int] - def __init__(self, result: Iterable[int] = ...) -> None: ... - -class RequestObserverAction(Message): - actions: Iterable[ObserverAction] - def __init__(self, actions: Iterable[ObserverAction] = ...) -> None: ... - -class ResponseObserverAction(Message): - def __init__(self) -> None: ... - -class RequestStep(Message): - count: int - def __init__(self, count: int = ...) -> None: ... - -class ResponseStep(Message): - simulation_loop: int - def __init__(self, simulation_loop: int = ...) -> None: ... - -class RequestData(Message): - ability_id: bool - unit_type_id: bool - upgrade_id: bool - buff_id: bool - effect_id: bool - def __init__( - self, - ability_id: bool = ..., - unit_type_id: bool = ..., - upgrade_id: bool = ..., - buff_id: bool = ..., - effect_id: bool = ..., - ) -> None: ... - -class ResponseData(Message): - abilities: Iterable[AbilityData] - units: Iterable[UnitTypeData] - upgrades: Iterable[UpgradeData] - buffs: Iterable[BuffData] - effects: Iterable[EffectData] - def __init__( - self, - abilities: Iterable[AbilityData] = ..., - units: Iterable[UnitTypeData] = ..., - upgrades: Iterable[UpgradeData] = ..., - buffs: Iterable[BuffData] = ..., - effects: Iterable[EffectData] = ..., - ) -> None: ... - -class RequestSaveReplay(Message): - def __init__(self) -> None: ... - -class ResponseSaveReplay(Message): - data: bytes - def __init__(self, data: bytes = ...) -> None: ... - -class RequestReplayInfo(Message): - replay_path: str - replay_data: bytes - download_data: bool - def __init__( - self, - replay_path: str = ..., - replay_data: bytes = ..., - download_data: bool = ..., - ) -> None: ... - -class PlayerInfoExtra(Message): - player_info: PlayerInfo - player_result: PlayerResult - player_mmr: int - player_apm: int - def __init__( - self, - player_info: PlayerInfo = ..., - player_result: PlayerResult = ..., - player_mmr: int = ..., - player_apm: int = ..., - ) -> None: ... - -class ResponseReplayInfo(Message): - class Error(Enum): - MissingReplay: int - InvalidReplayPath: int - InvalidReplayData: int - ParsingError: int - DownloadError: int - - map_name: str - local_map_path: str - player_info: Iterable[PlayerInfoExtra] - game_duration_loops: int - game_duration_seconds: float - game_version: str - data_version: str - data_build: int - base_build: int - error: int - error_details: str - def __init__( - self, - map_name: str = ..., - local_map_path: str = ..., - player_info: Iterable[PlayerInfoExtra] = ..., - game_duration_loops: int = ..., - game_duration_seconds: float = ..., - game_version: str = ..., - data_version: str = ..., - data_build: int = ..., - base_build: int = ..., - error: int = ..., - error_details: str = ..., - ) -> None: ... - -class RequestAvailableMaps(Message): - def __init__(self) -> None: ... - -class ResponseAvailableMaps(Message): - local_map_paths: Iterable[str] - battlenet_map_names: Iterable[str] - def __init__(self, local_map_paths: Iterable[str] = ..., battlenet_map_names: Iterable[str] = ...) -> None: ... - -class RequestSaveMap(Message): - map_path: str - map_data: bytes - def __init__(self, map_path: str = ..., map_data: bytes = ...) -> None: ... - -class ResponseSaveMap(Message): - class Error(Enum): - InvalidMapData: int - - error: int - def __init__(self, error: int = ...) -> None: ... - -class RequestPing(Message): - def __init__(self) -> None: ... - -class ResponsePing(Message): - game_version: str - data_version: str - data_build: int - base_build: int - def __init__( - self, - game_version: str = ..., - data_version: str = ..., - data_build: int = ..., - base_build: int = ..., - ) -> None: ... - -class RequestDebug(Message): - debug: Iterable[DebugCommand] - def __init__(self, debug: Iterable[DebugCommand] = ...) -> None: ... - -class ResponseDebug(Message): - def __init__(self) -> None: ... - -class Difficulty(Enum): - VeryEasy: int - Easy: int - Medium: int - MediumHard: int - Hard: int - Harder: int - VeryHard: int - CheatVision: int - CheatMoney: int - CheatInsane: int - -class PlayerType(Enum): - Participant: int - Computer: int - Observer: int - -class AIBuild(Enum): - RandomBuild: int - Rush: int - Timing: int - Power: int - Macro: int - Air: int - -class PlayerSetup(Message): - type: int - race: int - difficulty: int - player_name: str - ai_build: int - def __init__( - self, - type: int = ..., - race: int = ..., - difficulty: int = ..., - player_name: str = ..., - ai_build: int = ..., - ) -> None: ... - -class SpatialCameraSetup(Message): - resolution: Size2DI - minimap_resolution: Size2DI - width: float - crop_to_playable_area: bool - allow_cheating_layers: bool - def __init__( - self, - resolution: Size2DI = ..., - minimap_resolution: Size2DI = ..., - width: float = ..., - crop_to_playable_area: bool = ..., - allow_cheating_layers: bool = ..., - ) -> None: ... - -class InterfaceOptions(Message): - raw: bool - score: bool - feature_layer: SpatialCameraSetup - render: SpatialCameraSetup - show_cloaked: bool - show_burrowed_shadows: bool - show_placeholders: bool - raw_affects_selection: bool - raw_crop_to_playable_area: bool - def __init__( - self, - raw: bool = ..., - score: bool = ..., - feature_layer: SpatialCameraSetup = ..., - render: SpatialCameraSetup = ..., - show_cloaked: bool = ..., - show_burrowed_shadows: bool = ..., - show_placeholders: bool = ..., - raw_affects_selection: bool = ..., - raw_crop_to_playable_area: bool = ..., - ) -> None: ... - -class PlayerInfo(Message): - player_id: int - type: int - race_requested: int - race_actual: int - difficulty: int - ai_build: int - player_name: str - def __init__( - self, - player_id: int = ..., - type: int = ..., - race_requested: int = ..., - race_actual: int = ..., - difficulty: int = ..., - ai_build: int = ..., - player_name: str = ..., - ) -> None: ... - -class PlayerCommon(Message): - player_id: int - minerals: int - vespene: int - food_cap: int - food_used: int - food_army: int - food_workers: int - idle_worker_count: int - army_count: int - warp_gate_count: int - larva_count: int - def __init__( - self, - player_id: int = ..., - minerals: int = ..., - vespene: int = ..., - food_cap: int = ..., - food_used: int = ..., - food_army: int = ..., - food_workers: int = ..., - idle_worker_count: int = ..., - army_count: int = ..., - warp_gate_count: int = ..., - larva_count: int = ..., - ) -> None: ... - -class Observation(Message): - game_loop: int - player_common: PlayerCommon - alerts: Iterable[int] - abilities: Iterable[AvailableAbility] - score: Score - raw_data: ObservationRaw - feature_layer_data: ObservationFeatureLayer - render_data: ObservationRender - ui_data: ObservationUI - def __init__( - self, - game_loop: int = ..., - player_common: PlayerCommon = ..., - alerts: Iterable[int] = ..., - abilities: Iterable[AvailableAbility] = ..., - score: Score = ..., - raw_data: ObservationRaw = ..., - feature_layer_data: ObservationFeatureLayer = ..., - render_data: ObservationRender = ..., - ui_data: ObservationUI = ..., - ) -> None: ... - -class Action(Message): - action_raw: ActionRaw - action_feature_layer: ActionSpatial - action_render: ActionSpatial - action_ui: ActionUI - action_chat: ActionChat - game_loop: int - def __init__( - self, - action_raw: ActionRaw = ..., - action_feature_layer: ActionSpatial = ..., - action_render: ActionSpatial = ..., - action_ui: ActionUI = ..., - action_chat: ActionChat = ..., - game_loop: int = ..., - ) -> None: ... - -class Channel(Enum): - Broadcast: int - Team: int - -class ActionChat(Message): - channel: int - message: str - def __init__(self, channel: int = ..., message: str = ...) -> None: ... - -class ActionError(Message): - unit_tag: int - ability_id: int - result: int - def __init__(self, unit_tag: int = ..., ability_id: int = ..., result: int = ...) -> None: ... - -class ObserverAction(Message): - player_perspective: ActionObserverPlayerPerspective - camera_move: ActionObserverCameraMove - camera_follow_player: ActionObserverCameraFollowPlayer - camera_follow_units: ActionObserverCameraFollowUnits - def __init__( - self, - player_perspective: ActionObserverPlayerPerspective = ..., - camera_move: ActionObserverCameraMove = ..., - camera_follow_player: ActionObserverCameraFollowPlayer = ..., - camera_follow_units: ActionObserverCameraFollowUnits = ..., - ) -> None: ... - -class ActionObserverPlayerPerspective(Message): - player_id: int - def __init__(self, player_id: int = ...) -> None: ... - -class ActionObserverCameraMove(Message): - world_pos: Point2D - distance: float - def __init__(self, world_pos: Point2D = ..., distance: float = ...) -> None: ... - -class ActionObserverCameraFollowPlayer(Message): - player_id: int - def __init__(self, player_id: int = ...) -> None: ... - -class ActionObserverCameraFollowUnits(Message): - unit_tags: Iterable[int] - def __init__(self, unit_tags: Iterable[int] = ...) -> None: ... - -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 Result(Enum): - Victory: int - Defeat: int - Tie: int - Undecided: int - -class PlayerResult(Message): - player_id: int - result: int - def __init__(self, player_id: int = ..., result: int = ...) -> None: ... diff --git a/s2clientprotocol/score_pb2.pyi b/s2clientprotocol/score_pb2.pyi deleted file mode 100644 index 88c47391..00000000 --- a/s2clientprotocol/score_pb2.pyi +++ /dev/null @@ -1,107 +0,0 @@ -from __future__ import annotations - -from enum import Enum - -from google.protobuf.message import Message - -class ScoreType(Enum): - Curriculum: int - Melee: int - -class Score(Message): - score_type: int - score: int - score_details: ScoreDetails - def __init__( - self, - score_type: int = ..., - score: int = ..., - score_details: ScoreDetails = ..., - ) -> None: ... - -class CategoryScoreDetails(Message): - none: float - army: float - economy: float - technology: float - upgrade: float - def __init__( - self, - none: float = ..., - army: float = ..., - economy: float = ..., - technology: float = ..., - upgrade: float = ..., - ) -> None: ... - -class VitalScoreDetails(Message): - life: float - shields: float - energy: float - def __init__( - self, - life: float = ..., - shields: float = ..., - energy: float = ..., - ) -> None: ... - -class ScoreDetails(Message): - idle_production_time: float - idle_worker_time: float - total_value_units: float - total_value_structures: float - killed_value_units: float - killed_value_structures: float - collected_minerals: float - collected_vespene: float - collection_rate_minerals: float - collection_rate_vespene: float - spent_minerals: float - spent_vespene: float - food_used: CategoryScoreDetails - killed_minerals: CategoryScoreDetails - killed_vespene: CategoryScoreDetails - lost_minerals: CategoryScoreDetails - lost_vespene: CategoryScoreDetails - friendly_fire_minerals: CategoryScoreDetails - friendly_fire_vespene: CategoryScoreDetails - used_minerals: CategoryScoreDetails - used_vespene: CategoryScoreDetails - total_used_minerals: CategoryScoreDetails - total_used_vespene: CategoryScoreDetails - total_damage_dealt: VitalScoreDetails - total_damage_taken: VitalScoreDetails - total_healed: VitalScoreDetails - current_apm: float - current_effective_apm: float - def __init__( - self, - idle_production_time: float = ..., - idle_worker_time: float = ..., - total_value_units: float = ..., - total_value_structures: float = ..., - killed_value_units: float = ..., - killed_value_structures: float = ..., - collected_minerals: float = ..., - collected_vespene: float = ..., - collection_rate_minerals: float = ..., - collection_rate_vespene: float = ..., - spent_minerals: float = ..., - spent_vespene: float = ..., - food_used: CategoryScoreDetails = ..., - killed_minerals: CategoryScoreDetails = ..., - killed_vespene: CategoryScoreDetails = ..., - lost_minerals: CategoryScoreDetails = ..., - lost_vespene: CategoryScoreDetails = ..., - friendly_fire_minerals: CategoryScoreDetails = ..., - friendly_fire_vespene: CategoryScoreDetails = ..., - used_minerals: CategoryScoreDetails = ..., - used_vespene: CategoryScoreDetails = ..., - total_used_minerals: CategoryScoreDetails = ..., - total_used_vespene: CategoryScoreDetails = ..., - total_damage_dealt: VitalScoreDetails = ..., - total_damage_taken: VitalScoreDetails = ..., - total_healed: VitalScoreDetails = ..., - current_apm: float = ..., - current_effective_apm: float = ..., - ) -> None: ... diff --git a/s2clientprotocol/spatial_pb2.pyi b/s2clientprotocol/spatial_pb2.pyi deleted file mode 100644 index a1b72a29..00000000 --- a/s2clientprotocol/spatial_pb2.pyi +++ /dev/null @@ -1,154 +0,0 @@ -from __future__ import annotations - -from collections.abc import Iterable -from enum import Enum - -from google.protobuf.message import Message - -from .common_pb2 import ImageData, PointI, RectangleI - -class ObservationFeatureLayer(Message): - renders: FeatureLayers - minimap_renders: FeatureLayersMinimap - def __init__( - self, - renders: FeatureLayers = ..., - minimap_renders: FeatureLayersMinimap = ..., - ) -> None: ... - -class FeatureLayers(Message): - height_map: ImageData - visibility_map: ImageData - creep: ImageData - power: ImageData - player_id: ImageData - unit_type: ImageData - selected: ImageData - unit_hit_points: ImageData - unit_hit_points_ratio: ImageData - unit_energy: ImageData - unit_energy_ratio: ImageData - unit_shields: ImageData - unit_shields_ratio: ImageData - player_relative: ImageData - unit_density_aa: ImageData - unit_density: ImageData - effects: ImageData - hallucinations: ImageData - cloaked: ImageData - blip: ImageData - buffs: ImageData - buff_duration: ImageData - active: ImageData - build_progress: ImageData - buildable: ImageData - pathable: ImageData - placeholder: ImageData - def __init__( - self, - height_map: ImageData = ..., - visibility_map: ImageData = ..., - creep: ImageData = ..., - power: ImageData = ..., - player_id: ImageData = ..., - unit_type: ImageData = ..., - selected: ImageData = ..., - unit_hit_points: ImageData = ..., - unit_hit_points_ratio: ImageData = ..., - unit_energy: ImageData = ..., - unit_energy_ratio: ImageData = ..., - unit_shields: ImageData = ..., - unit_shields_ratio: ImageData = ..., - player_relative: ImageData = ..., - unit_density_aa: ImageData = ..., - unit_density: ImageData = ..., - effects: ImageData = ..., - hallucinations: ImageData = ..., - cloaked: ImageData = ..., - blip: ImageData = ..., - buffs: ImageData = ..., - buff_duration: ImageData = ..., - active: ImageData = ..., - build_progress: ImageData = ..., - buildable: ImageData = ..., - pathable: ImageData = ..., - placeholder: ImageData = ..., - ) -> None: ... - -class FeatureLayersMinimap(Message): - height_map: ImageData - visibility_map: ImageData - creep: ImageData - camera: ImageData - player_id: ImageData - player_relative: ImageData - selected: ImageData - alerts: ImageData - buildable: ImageData - pathable: ImageData - unit_type: ImageData - def __init__( - self, - height_map: ImageData = ..., - visibility_map: ImageData = ..., - creep: ImageData = ..., - camera: ImageData = ..., - player_id: ImageData = ..., - player_relative: ImageData = ..., - selected: ImageData = ..., - alerts: ImageData = ..., - buildable: ImageData = ..., - pathable: ImageData = ..., - unit_type: ImageData = ..., - ) -> None: ... - -class ObservationRender(Message): - map: ImageData - minimap: ImageData - def __init__(self, map: ImageData = ..., minimap: ImageData = ...) -> None: ... - -class ActionSpatial(Message): - unit_command: ActionSpatialUnitCommand - camera_move: ActionSpatialCameraMove - unit_selection_point: ActionSpatialUnitSelectionPoint - unit_selection_rect: ActionSpatialUnitSelectionRect - def __init__( - self, - unit_command: ActionSpatialUnitCommand = ..., - camera_move: ActionSpatialCameraMove = ..., - unit_selection_point: ActionSpatialUnitSelectionPoint = ..., - unit_selection_rect: ActionSpatialUnitSelectionRect = ..., - ) -> None: ... - -class ActionSpatialUnitCommand(Message): - ability_id: int - target_screen_coord: PointI - target_minimap_coord: PointI - queue_command: bool - def __init__( - self, - ability_id: int = ..., - target_screen_coord: PointI = ..., - target_minimap_coord: PointI = ..., - queue_command: bool = ..., - ) -> None: ... - -class ActionSpatialCameraMove(Message): - center_minimap: PointI - def __init__(self, center_minimap: PointI = ...) -> None: ... - -class Type(Enum): - Select: int - Toggle: int - AllType: int - AddAllType: int - -class ActionSpatialUnitSelectionPoint(Message): - selection_screen_coord: PointI - type: int - def __init__(self, selection_screen_coord: PointI = ..., type: int = ...) -> None: ... - -class ActionSpatialUnitSelectionRect(Message): - selection_screen_coord: Iterable[RectangleI] - selection_add: bool - def __init__(self, selection_screen_coord: Iterable[RectangleI] = ..., selection_add: bool = ...) -> None: ... diff --git a/s2clientprotocol/ui_pb2.pyi b/s2clientprotocol/ui_pb2.pyi deleted file mode 100644 index dbf39f3b..00000000 --- a/s2clientprotocol/ui_pb2.pyi +++ /dev/null @@ -1,184 +0,0 @@ -from __future__ import annotations - -from collections.abc import Iterable -from enum import Enum - -from google.protobuf.message import Message - -class ObservationUI(Message): - groups: Iterable[ControlGroup] - single: SinglePanel - multi: MultiPanel - cargo: CargoPanel - production: ProductionPanel - def __init__( - self, - groups: Iterable[ControlGroup] = ..., - single: SinglePanel = ..., - multi: MultiPanel = ..., - cargo: CargoPanel = ..., - production: ProductionPanel = ..., - ) -> None: ... - -class ControlGroup(Message): - control_group_index: int - leader_unit_type: int - count: int - def __init__( - self, - control_group_index: int = ..., - leader_unit_type: int = ..., - count: int = ..., - ) -> None: ... - -class UnitInfo(Message): - unit_type: int - player_relative: int - health: int - shields: int - energy: int - transport_slots_taken: int - build_progress: float - add_on: UnitInfo - max_health: int - max_shields: int - max_energy: int - def __init__( - self, - unit_type: int = ..., - player_relative: int = ..., - health: int = ..., - shields: int = ..., - energy: int = ..., - transport_slots_taken: int = ..., - build_progress: float = ..., - add_on: UnitInfo = ..., - max_health: int = ..., - max_shields: int = ..., - max_energy: int = ..., - ) -> None: ... - -class SinglePanel(Message): - unit: UnitInfo - attack_upgrade_level: int - armor_upgrade_level: int - shield_upgrade_level: int - buffs: Iterable[int] - def __init__( - self, - unit: UnitInfo = ..., - attack_upgrade_level: int = ..., - armor_upgrade_level: int = ..., - shield_upgrade_level: int = ..., - buffs: Iterable[int] = ..., - ) -> None: ... - -class MultiPanel(Message): - units: Iterable[UnitInfo] - def __init__(self, units: Iterable[UnitInfo] = ...) -> None: ... - -class CargoPanel(Message): - unit: UnitInfo - passengers: Iterable[UnitInfo] - slots_available: int - def __init__( - self, - unit: UnitInfo = ..., - passengers: Iterable[UnitInfo] = ..., - slots_available: int = ..., - ) -> None: ... - -class BuildItem(Message): - ability_id: int - build_progress: float - def __init__(self, ability_id: int = ..., build_progress: float = ...) -> None: ... - -class ProductionPanel(Message): - unit: UnitInfo - build_queue: Iterable[UnitInfo] - production_queue: Iterable[BuildItem] - def __init__( - self, - unit: UnitInfo = ..., - build_queue: Iterable[UnitInfo] = ..., - production_queue: Iterable[BuildItem] = ..., - ) -> None: ... - -class ActionUI(Message): - control_group: ActionControlGroup - select_army: ActionSelectArmy - select_warp_gates: ActionSelectWarpGates - select_larva: ActionSelectLarva - select_idle_worker: ActionSelectIdleWorker - multi_panel: ActionMultiPanel - cargo_panel: ActionCargoPanelUnload - production_panel: ActionProductionPanelRemoveFromQueue - toggle_autocast: ActionToggleAutocast - def __init__( - self, - control_group: ActionControlGroup = ..., - select_army: ActionSelectArmy = ..., - select_warp_gates: ActionSelectWarpGates = ..., - select_larva: ActionSelectLarva = ..., - select_idle_worker: ActionSelectIdleWorker = ..., - multi_panel: ActionMultiPanel = ..., - cargo_panel: ActionCargoPanelUnload = ..., - production_panel: ActionProductionPanelRemoveFromQueue = ..., - toggle_autocast: ActionToggleAutocast = ..., - ) -> None: ... - -class ControlGroupAction(Enum): - Recall: int - Set: int - Append: int - SetAndSteal: int - AppendAndSteal: int - -class ActionControlGroup(Message): - action: int - control_group_index: int - def __init__(self, action: int = ..., control_group_index: int = ...) -> None: ... - -class ActionSelectArmy(Message): - selection_add: bool - def __init__(self, selection_add: bool = ...) -> None: ... - -class ActionSelectWarpGates(Message): - selection_add: bool - def __init__(self, selection_add: bool = ...) -> None: ... - -class ActionSelectLarva(Message): - def __init__(self) -> None: ... - -class ActionSelectIdleWorker(Message): - class Type(Enum): - Set: int - Add: int - All: int - AddAll: int - - type: int - def __init__(self, type: int = ...) -> None: ... - -class ActionMultiPanel(Message): - class Type(Enum): - SingleSelect: int - DeselectUnit: int - SelectAllOfType: int - DeselectAllOfType: int - - type: int - unit_index: int - def __init__(self, type: int = ..., unit_index: int = ...) -> None: ... - -class ActionCargoPanelUnload(Message): - unit_index: int - def __init__(self, unit_index: int = ...) -> None: ... - -class ActionProductionPanelRemoveFromQueue(Message): - unit_index: int - def __init__(self, unit_index: int = ...) -> None: ... - -class ActionToggleAutocast(Message): - ability_id: int - def __init__(self, ability_id: int = ...) -> None: ... diff --git a/sc2/action.py b/sc2/action.py index b43124ae..019f3803 100644 --- a/sc2/action.py +++ b/sc2/action.py @@ -33,7 +33,10 @@ def combine_actions(action_iter: list[UnitCommand]): if combineable: # Combine actions with no target, e.g. lift, burrowup, burrowdown, siege, unsiege, uproot spines cmd = raw_pb.ActionRawUnitCommand( - ability_id=ability.value, unit_tags={u.unit.tag for u in items}, queue_command=queue + ability_id=ability.value, + # pyrefly: ignore + unit_tags={u.unit.tag for u in items}, + queue_command=queue, ) # Combine actions with target point, e.g. attack_move or move commands on a position if isinstance(target, Point2): @@ -58,13 +61,17 @@ def combine_actions(action_iter: list[UnitCommand]): if target is None: for u in items: cmd = raw_pb.ActionRawUnitCommand( - ability_id=ability.value, unit_tags={u.unit.tag}, queue_command=queue + ability_id=ability.value, + # pyrefly: ignore + unit_tags={u.unit.tag}, + queue_command=queue, ) yield raw_pb.ActionRaw(unit_command=cmd) elif isinstance(target, Point2): for u in items: cmd = raw_pb.ActionRawUnitCommand( ability_id=ability.value, + # pyrefly: ignore unit_tags={u.unit.tag}, queue_command=queue, target_world_space_pos=target.as_Point2D, @@ -74,6 +81,7 @@ def combine_actions(action_iter: list[UnitCommand]): for u in items: cmd = raw_pb.ActionRawUnitCommand( ability_id=ability.value, + # pyrefly: ignore unit_tags={u.unit.tag}, queue_command=queue, target_unit_tag=target.tag, diff --git a/sc2/bot_ai.py b/sc2/bot_ai.py index d98e72a0..f16d8596 100644 --- a/sc2/bot_ai.py +++ b/sc2/bot_ai.py @@ -1,4 +1,3 @@ -# pyre-ignore-all-errors[6, 16] from __future__ import annotations import math @@ -71,7 +70,6 @@ def step_time(self) -> tuple[float, float, float, float]: self._last_step_step_time * 1000, ) - # pyre-ignore[11] def alert(self, alert_code: Alert) -> bool: """ Check if alert is triggered in the current step. @@ -252,24 +250,24 @@ async def get_next_expansion(self) -> Point2 | None: """Find next expansion location.""" closest = None - distance = math.inf - for el in self.expansion_locations_list: + best_distance = math.inf + start_position = self.game_info.player_start_location + for position in self.expansion_locations_list: def is_near_to_expansion(t): - return t.distance_to(el) < self.EXPANSION_GAP_THRESHOLD + return t.distance_to(position) < self.EXPANSION_GAP_THRESHOLD if any(map(is_near_to_expansion, self.townhalls)): # already taken continue - startp = self.game_info.player_start_location - d = await self.client.query_pathing(startp, el) - if d is None: + distance = await self.client.query_pathing(start_position, position) + if distance is None: continue - if d < distance: - distance = d - closest = el + if distance < best_distance: + best_distance = distance + closest = position return closest @@ -494,7 +492,11 @@ def calculate_cost(self, item_id: UnitTypeId | UpgradeId | AbilityId) -> Cost: return self.calculate_unit_value(UnitTypeId.ARCHON) unit_data = self.game_data.units[item_id.value] # Cost of morphs is automatically correctly calculated by 'calculate_ability_cost' - return self.game_data.calculate_ability_cost(unit_data.creation_ability.exact_id) + creation_ability = unit_data.creation_ability + if creation_ability is None: + logger.error(f"Unknown creation_ability in calculate_cost for item_id: {item_id}") + return Cost(0, 0) + return self.game_data.calculate_ability_cost(creation_ability.exact_id) if isinstance(item_id, UpgradeId): cost = self.game_data.upgrades[item_id.value].cost @@ -568,11 +570,18 @@ async def can_cast( ability_target: int = self.game_data.abilities[ability_id.value]._proto.target # Check if target is in range (or is a self cast like stimpack) if ( + # Can't replace 1 with "Target.None.value" because ".None" doesn't seem to be a valid enum name ability_target == 1 or ability_target == Target.PointOrNone.value - and isinstance(target, Point2) - and unit.distance_to(target) <= unit.radius + target.radius + cast_range - ): # cant replace 1 with "Target.None.value" because ".None" doesnt seem to be a valid enum name + and ( + # Target is unit + isinstance(target, Unit) + and unit.distance_to(target) <= unit.radius + target.radius + cast_range + # Target is position + or isinstance(target, Point2) + and unit.distance_to(target) <= unit.radius + cast_range + ) + ): return True # Check if able to use ability on a unit if ( @@ -622,8 +631,12 @@ def select_build_worker(self, pos: Unit | Point2, force: bool = False) -> Unit | async def can_place_single(self, building: AbilityId | UnitTypeId, position: Point2) -> bool: """Checks the placement for only one position.""" if isinstance(building, UnitTypeId): - creation_ability = self.game_data.units[building.value].creation_ability.id - return (await self.client._query_building_placement_fast(creation_ability, [position]))[0] + creation_ability = self.game_data.units[building.value].creation_ability + if creation_ability is None: + logger.error(f"Unknown creation_ability in can_place_single for building: {building}") + return False + creation_ability_id = creation_ability.id + return (await self.client._query_building_placement_fast(creation_ability_id, [position]))[0] return (await self.client._query_building_placement_fast(building, [position]))[0] async def can_place(self, building: AbilityData | AbilityId | UnitTypeId, positions: list[Point2]) -> list[bool]: @@ -639,17 +652,18 @@ async def can_place(self, building: AbilityData | AbilityId | UnitTypeId, positi :param building: :param position:""" - building_type = type(building) - assert type(building) in {AbilityData, AbilityId, UnitTypeId}, f"{building}, {building_type}" - if building_type == UnitTypeId: - building = self.game_data.units[building.value].creation_ability.id - elif building_type == AbilityData: + if isinstance(building, UnitTypeId): + creation_ability = self.game_data.units[building.value].creation_ability + if creation_ability is None: + return [False for _ in positions] + building = creation_ability.id + elif isinstance(building, AbilityData): warnings.warn( "Using AbilityData is deprecated and may be removed soon. Please use AbilityId or UnitTypeId instead.", DeprecationWarning, stacklevel=2, ) - building = building_type.id + building = building.id if isinstance(positions, (Point2, tuple)): warnings.warn( @@ -657,6 +671,7 @@ async def can_place(self, building: AbilityData | AbilityId | UnitTypeId, positi DeprecationWarning, stacklevel=2, ) + # pyrefly: ignore return await self.can_place_single(building, positions) assert isinstance(positions, list), f"Expected an iterable (list, tuple), but was: {positions}" assert isinstance(positions[0], Point2), ( @@ -692,7 +707,10 @@ async def find_placement( assert isinstance(near, Point2), f"{near} is no Point2 object" if isinstance(building, UnitTypeId): - building = self.game_data.units[building.value].creation_ability.id + creation_ability = self.game_data.units[building.value].creation_ability + if creation_ability is None: + return None + building = creation_ability.id if await self.can_place_single(building, near) and ( not addon_place or await self.can_place_single(AbilityId.TERRANBUILD_SUPPLYDEPOT, near.offset((2.5, -0.5))) @@ -751,10 +769,13 @@ def already_pending_upgrade(self, upgrade_type: UpgradeId) -> float: assert isinstance(upgrade_type, UpgradeId), f"{upgrade_type} is no UpgradeId" if upgrade_type in self.state.upgrades: return 1 - creationAbilityID = self.game_data.upgrades[upgrade_type.value].research_ability.exact_id + research_ability = self.game_data.upgrades[upgrade_type.value].research_ability + if research_ability is None: + return 0 + creation_ability_id = research_ability.exact_id for structure in self.structures.filter(lambda unit: unit.is_ready): for order in structure.orders: - if order.ability.exact_id == creationAbilityID: + if order.ability.exact_id == creation_ability_id: return order.progress return 0 @@ -800,7 +821,7 @@ def structure_type_build_progress(self, structure_type: UnitTypeId | int) -> flo # SUPPLYDEPOTDROP is not in self.game_data.units, so bot_ai should not check the build progress via creation ability (worker abilities) if structure_type_value not in self.game_data.units: return max((s.build_progress for s in self.structures if s._proto.unit_type in equiv_values), default=0) - creation_ability_data: AbilityData = self.game_data.units[structure_type_value].creation_ability + creation_ability_data = self.game_data.units[structure_type_value].creation_ability if creation_ability_data is None: return 0 creation_ability: AbilityId = creation_ability_data.exact_id @@ -865,24 +886,30 @@ def already_pending(self, unit_type: UpgradeId | UnitTypeId) -> float: """ if isinstance(unit_type, UpgradeId): return self.already_pending_upgrade(unit_type) - try: - ability = self.game_data.units[unit_type.value].creation_ability.exact_id - except AttributeError: - if unit_type in CREATION_ABILITY_FIX: - # Hotfix for checking pending archons - if unit_type == UnitTypeId.ARCHON: - return self._abilities_count_and_build_progress[0][AbilityId.ARCHON_WARP_TARGET] / 2 - # Hotfix for rich geysirs - return self._abilities_count_and_build_progress[0][CREATION_ABILITY_FIX[unit_type]] - logger.error(f"Uncaught UnitTypeId: {unit_type}") + + if unit_type in CREATION_ABILITY_FIX: + # Hotfix for checking pending archons and other abilities + if unit_type == UnitTypeId.ARCHON: + return self._abilities_count_and_build_progress[0][AbilityId.ARCHON_WARP_TARGET] / 2 + # Hotfix for rich geysirs + return self._abilities_count_and_build_progress[0][CREATION_ABILITY_FIX[unit_type]] + + creation_ability = self.game_data.units[unit_type.value].creation_ability + if creation_ability is None: + logger.error(f"Unknown creation_ability in already_pending for unit_type: {unit_type}") return 0 - return self._abilities_count_and_build_progress[0][ability] + ability_id = creation_ability.exact_id + return self._abilities_count_and_build_progress[0][ability_id] def worker_en_route_to_build(self, unit_type: UnitTypeId) -> float: """This function counts how many workers are on the way to start the construction a building. :param unit_type:""" - ability = self.game_data.units[unit_type.value].creation_ability.exact_id + creation_ability = self.game_data.units[unit_type.value].creation_ability + if creation_ability is None: + logger.error(f"Unknown creation_ability in worker_en_route_to_build for unit_type: {unit_type}") + return 0 + ability = creation_ability.exact_id return self._worker_orders[ability] @property_cache_once_per_frame @@ -896,7 +923,7 @@ def structures_without_construction_SCVs(self) -> Units: continue for order in worker.orders: # When a construction is resumed, the worker.orders[0].target is the tag of the structure, else it is a Point2 - worker_targets.add(order.target) + worker_targets.add(order.target) # pyrefly: ignore return self.structures.filter( lambda structure: structure.build_progress < 1 # Redundant check? @@ -930,15 +957,15 @@ async def build( assert isinstance(near, (Unit, Point2)) if not self.can_afford(building): return False - p = None + position = None gas_buildings = {UnitTypeId.EXTRACTOR, UnitTypeId.ASSIMILATOR, UnitTypeId.REFINERY} if isinstance(near, Unit) and building not in gas_buildings: near = near.position if isinstance(near, Point2): near = near.to2 if isinstance(near, Point2): - p = await self.find_placement(building, near, max_distance, random_alternative, placement_step) - if p is None: + position = await self.find_placement(building, near, max_distance, random_alternative, placement_step) + if position is None: return False builder = build_worker or self.select_build_worker(near) if builder is None: @@ -947,7 +974,8 @@ async def build( assert isinstance(near, Unit) builder.build_gas(near) return True - self.do(builder.build(building, p), subtract_cost=True, ignore_warning=True) + # pyrefly: ignore + self.do(builder.build(building, position), subtract_cost=True, ignore_warning=True) return True def train( @@ -1056,6 +1084,7 @@ def train( else: # Normal train a unit from larva or inside a structure successfully_trained = self.do( + # pyrefly: ignore structure.train(unit_type), subtract_cost=True, subtract_supply=True, @@ -1079,6 +1108,7 @@ def train( trained_amount += 1 # With one command queue=False and one queue=True, you can queue 2 marines in a reactored barracks in one frame successfully_trained = self.do( + # pyrefly: ignore structure.train(unit_type, queue=True), subtract_cost=True, subtract_supply=True, @@ -1125,7 +1155,8 @@ def research(self, upgrade_type: UpgradeId) -> bool: return False research_structure_type: UnitTypeId = UPGRADE_RESEARCHED_FROM[upgrade_type] - # pyre-ignore[9] + + # pyrefly: ignore required_tech_building: UnitTypeId | None = RESEARCH_INFO[research_structure_type][upgrade_type].get( "required_building", None ) @@ -1168,6 +1199,7 @@ def research(self, upgrade_type: UpgradeId) -> bool: ): # Can_afford check was already done earlier in this function successful_action: bool = self.do( + # pyrefly: ignore structure.research(upgrade_type), subtract_cost=True, ignore_warning=True, @@ -1368,7 +1400,6 @@ async def on_step(self, iteration: int): """ raise NotImplementedError - # pyre-ignore[11] async def on_end(self, game_result: Result) -> None: """Override this in your bot class. This function is called at the end of a game. Unsure if this function will be called on the laddermanager client as the bot process may forcefully be terminated. diff --git a/sc2/bot_ai_internal.py b/sc2/bot_ai_internal.py index d2bde3f7..056fa2a6 100644 --- a/sc2/bot_ai_internal.py +++ b/sc2/bot_ai_internal.py @@ -1,4 +1,3 @@ -# pyre-ignore-all-errors[6, 16, 29] from __future__ import annotations import itertools @@ -45,16 +44,17 @@ if TYPE_CHECKING: from sc2.client import Client from sc2.game_info import GameInfo + from sc2.bot_ai import BotAI class BotAIInternal(ABC): """Base class for bots.""" - def __init__(self) -> None: + def __init__(self: BotAI) -> None: self._initialize_variables() @final - def _initialize_variables(self) -> None: + def _initialize_variables(self: BotAI) -> None: """Called from main.py internally""" self.cache: dict[str, Any] = {} # Specific opponent bot ID used in sc2ai ladder games http://sc2ai.net/ and on ai arena https://aiarena.net @@ -103,8 +103,9 @@ def _initialize_variables(self) -> None: self.warp_gate_count: int = 0 self.actions: list[UnitCommand] = [] self.blips: set[Blip] = set() - # pyre-ignore[11] - self.race: Race | None = None + + # Will be set on AbstractPlayer init + self.race: Race = None # pyrefly: ignore self.enemy_race: Race | None = None self._generated_frame = -100 self._units_created: Counter = Counter() @@ -162,7 +163,7 @@ def _client(self) -> Client: @final @property_cache_once_per_frame - def expansion_locations(self) -> dict[Point2, Units]: + def expansion_locations(self: BotAI) -> dict[Point2, Units]: """Same as the function above.""" assert self._expansion_positions_list, ( "self._find_expansion_locations() has not been run yet, so accessing the list of expansion locations is pointless." @@ -191,7 +192,8 @@ def _cluster_center(self, group: list[Unit]) -> Point2: if not group: raise ValueError("Cannot calculate center of empty group") - total_x = total_y = 0 + total_x: float = 0 + total_y: float = 0 for unit in group: total_x += unit.position.x total_y += unit.position.y @@ -354,7 +356,7 @@ def _find_expansion_locations(self) -> None: # Distance offsets we apply to center of each resource group to find expansion position offset_range: int = 7 - offsets = [ + offsets: list[tuple[float, float]] = [ (x, y) for x, y in itertools.product(range(-offset_range, offset_range + 1), repeat=2) if 4 < math.hypot(x, y) <= 8 @@ -437,17 +439,20 @@ def _abilities_count_and_build_progress(self) -> tuple[Counter[AbilityId], dict[ if unit.type_id in CREATION_ABILITY_FIX: if unit.type_id == UnitTypeId.ARCHON: # Hotfix for archons in morph state - creation_ability = AbilityId.ARCHON_WARP_TARGET - abilities_amount[creation_ability] += 2 + creation_ability_id = AbilityId.ARCHON_WARP_TARGET + abilities_amount[creation_ability_id] += 2 else: # Hotfix for rich geysirs - creation_ability = CREATION_ABILITY_FIX[unit.type_id] - abilities_amount[creation_ability] += 1 + creation_ability_id = CREATION_ABILITY_FIX[unit.type_id] + abilities_amount[creation_ability_id] += 1 else: - creation_ability: AbilityId = self.game_data.units[unit.type_id.value].creation_ability.exact_id - abilities_amount[creation_ability] += 1 - max_build_progress[creation_ability] = max( - max_build_progress.get(creation_ability, 0), unit.build_progress + creation_ability = self.game_data.units[unit.type_id.value].creation_ability + if creation_ability is None: + continue + creation_ability_id = creation_ability.exact_id + abilities_amount[creation_ability_id] += 1 + max_build_progress[creation_ability_id] = max( + max_build_progress.get(creation_ability_id, 0), unit.build_progress ) return abilities_amount, max_build_progress @@ -473,7 +478,7 @@ def _worker_orders(self) -> Counter[AbilityId]: @final def do( - self, + self: BotAI, action: UnitCommand, subtract_cost: bool = False, subtract_supply: bool = False, @@ -542,7 +547,7 @@ def do( return True @final - async def synchronous_do(self, action: UnitCommand): + async def synchronous_do(self: BotAI, action: UnitCommand): """ Not recommended. Use self.do instead to reduce lag. This function is only useful for realtime=True in the first frame of the game to instantly produce a worker @@ -554,7 +559,7 @@ async def synchronous_do(self, action: UnitCommand): if not self.can_afford(action.ability): logger.warning(f"Cannot afford action {action}") return ActionResult.Error - r = await self.client.actions(action) + r: ActionResult = await self.client.actions(action) # pyrefly: ignore if not r: # success cost = self.game_data.calculate_ability_cost(action.ability) self.minerals -= cost.minerals @@ -594,11 +599,14 @@ def prevent_double_actions(action: UnitCommand) -> bool: # Different action, return True return True with suppress(AttributeError): - if current_action.target == action.target.tag: + if current_action.target == action.target.tag: # pyrefly: ignore # Same action, remove action if same target unit return False with suppress(AttributeError): - if action.target.x == current_action.target.x and action.target.y == current_action.target.y: + if ( + # pyrefly: ignore + action.target.x == current_action.target.x and action.target.y == current_action.target.y + ): # Same action, remove action if same target position return False return True @@ -649,7 +657,7 @@ def _prepare_first_step(self) -> None: self._time_before_step: float = time.perf_counter() @final - def _prepare_step(self, state: GameState, proto_game_info: sc_pb.Response) -> None: + def _prepare_step(self: BotAI, state: GameState, proto_game_info: sc_pb.Response) -> None: """ :param state: :param proto_game_info: @@ -690,7 +698,7 @@ def _prepare_step(self, state: GameState, proto_game_info: sc_pb.Response) -> No self.enemy_race = Race(self.all_enemy_units.first.race) @final - def _prepare_units(self) -> None: + def _prepare_units(self: BotAI) -> None: # Set of enemy units detected by own sensor tower, as blips have less unit information than normal visible units self.blips: set[Blip] = set() self.all_units: Units = Units([], self) @@ -816,7 +824,7 @@ async def _after_step(self) -> int: return self.state.game_loop @final - async def _advance_steps(self, steps: int) -> None: + async def _advance_steps(self: BotAI, steps: int) -> None: """Advances the game loop by amount of 'steps'. This function is meant to be used as a debugging and testing tool only. If you are using this, please be aware of the consequences, e.g. 'self.units' will be filled with completely new data.""" await self._after_step() @@ -829,7 +837,7 @@ async def _advance_steps(self, steps: int) -> None: await self.issue_events() @final - async def issue_events(self) -> None: + async def issue_events(self: BotAI) -> None: """This function will be automatically run from main.py and triggers the following functions: - on_unit_created - on_unit_destroyed @@ -844,7 +852,7 @@ async def issue_events(self) -> None: await self._issue_vision_events() @final - async def _issue_unit_added_events(self) -> None: + async def _issue_unit_added_events(self: BotAI) -> None: for unit in self.units: if unit.tag not in self._units_previous_map and unit.tag not in self._unit_tags_seen_this_game: self._unit_tags_seen_this_game.add(unit.tag) @@ -861,14 +869,14 @@ async def _issue_unit_added_events(self) -> None: await self.on_unit_type_changed(unit, previous_frame_unit.type_id) @final - async def _issue_upgrade_events(self) -> None: + async def _issue_upgrade_events(self: BotAI) -> None: difference = self.state.upgrades - self._previous_upgrades for upgrade_completed in difference: await self.on_upgrade_complete(upgrade_completed) self._previous_upgrades = self.state.upgrades @final - async def _issue_building_events(self) -> None: + async def _issue_building_events(self: BotAI) -> None: for structure in self.structures: if structure.tag not in self._structures_previous_map: if structure.build_progress < 1: @@ -900,7 +908,7 @@ async def _issue_building_events(self) -> None: await self.on_building_construction_complete(structure) @final - async def _issue_vision_events(self) -> None: + async def _issue_vision_events(self: BotAI) -> None: # Call events for enemy unit entered vision for enemy_unit in self.enemy_units: if enemy_unit.tag not in self._enemy_units_previous_map: @@ -918,7 +926,7 @@ async def _issue_vision_events(self) -> None: await self.on_enemy_unit_left_vision(enemy_structure_tag) @final - async def _issue_unit_dead_events(self) -> None: + async def _issue_unit_dead_events(self: BotAI) -> None: for unit_tag in self.state.dead_units & set(self._all_units_previous_map): await self.on_unit_destroyed(unit_tag) diff --git a/sc2/cache.py b/sc2/cache.py index b1682fc5..ad06ce99 100644 --- a/sc2/cache.py +++ b/sc2/cache.py @@ -32,17 +32,18 @@ class property_cache_once_per_frame(property): # noqa: N801 def __init__(self, func: Callable[[BotAI], T], name: str | None = None) -> None: self.__name__ = name or func.__name__ self.__frame__ = f"__frame__{self.__name__}" + # pyrefly: ignore self.func = func def __set__(self, obj: BotAI, value: T) -> None: obj.cache[self.__name__] = value - # pyre-ignore[16] + obj.cache[self.__frame__] = obj.state.game_loop # pyre-fixme[34] def __get__(self, obj: BotAI, _type=None) -> T: value = obj.cache.get(self.__name__, None) - # pyre-ignore[16] + bot_frame = obj.state.game_loop if value is None or obj.cache[self.__frame__] < bot_frame: value = self.func(obj) diff --git a/sc2/client.py b/sc2/client.py index 8971b86f..79a0937b 100644 --- a/sc2/client.py +++ b/sc2/client.py @@ -1,9 +1,8 @@ -# pyre-ignore-all-errors[6, 9, 16, 29, 58] from __future__ import annotations from collections.abc import Iterable from pathlib import Path -from typing import Any +from typing import Any, Literal from aiohttp import ClientWebSocketResponse from loguru import logger @@ -37,8 +36,10 @@ def __init__(self, ws: ClientWebSocketResponse, save_replay_path: str | None = N # How many frames will be waited between iterations before the next one is called self.game_step: int = 4 self.save_replay_path: str | None = save_replay_path - self._player_id = None - self._game_result = None + # The following will be set on join_game() + self._player_id: int = None # pyrefly: ignore + # The following will be set on leave() + self._game_result: dict[int, Result] = None # pyrefly: ignore # Store a hash value of all the debug requests to prevent sending the same ones again if they haven't changed last frame self._debug_hash_tuple_last_iteration: tuple[int, int, int, int] = (0, 0, 0, 0) self._debug_draw_last_frame = False @@ -89,26 +90,26 @@ async def join_game( if race is None: assert isinstance(observed_player_id, int), f"observed_player_id is of type {type(observed_player_id)}" # join as observer - req = sc_pb.RequestJoinGame(observed_player_id=observed_player_id, options=ifopts) + request = sc_pb.RequestJoinGame(observed_player_id=observed_player_id, options=ifopts) else: assert isinstance(race, Race) - req = sc_pb.RequestJoinGame(race=race.value, options=ifopts) + request = sc_pb.RequestJoinGame(race=race.value, options=ifopts) # pyrefly: ignore[bad-argument-type] if portconfig: - req.server_ports.game_port = portconfig.server[0] - req.server_ports.base_port = portconfig.server[1] + request.server_ports.game_port = portconfig.server[0] + request.server_ports.base_port = portconfig.server[1] for ppc in portconfig.players: - p = req.client_ports.add() + p = request.client_ports.add() # pyrefly: ignore p.game_port = ppc[0] p.base_port = ppc[1] if name is not None: assert isinstance(name, str), f"name is of type {type(name)}" - req.player_name = name + request.player_name = name - result = await self._execute(join_game=req) - self._game_result = None + result = await self._execute(join_game=request) + self._game_result = None # pyrefly: ignore self._player_id = result.join_game.player_id return result.join_game.player_id @@ -150,7 +151,7 @@ async def observation(self, game_loop: int | None = None): result = await self._execute(observation=sc_pb.RequestObservation()) assert result.observation.player_result - player_id_to_result = {} + player_id_to_result = dict[int, Result]() for pr in result.observation.player_result: player_id_to_result[pr.player_id] = Result(pr.result) self._game_result = player_id_to_result @@ -211,16 +212,21 @@ async def actions(self, actions: list[UnitCommand], return_successes: bool = Fal # On realtime=True, might get an error here: sc2.protocol.ProtocolError: ['Not in a game'] try: - res = await self._execute( - action=sc_pb.RequestAction(actions=(sc_pb.Action(action_raw=a) for a in combine_actions(actions))) + response = await self._execute( + action=sc_pb.RequestAction( + # pyrefly: ignore + actions=(sc_pb.Action(action_raw=action) for action in combine_actions(actions)) + ) ) except ProtocolError: return [] if return_successes: - return [ActionResult(r) for r in res.action.result] - return [ActionResult(r) for r in res.action.result if ActionResult(r) != ActionResult.Success] + return [ActionResult(result) for result in response.action.result] + return [ + ActionResult(result) for result in response.action.result if ActionResult(result) != ActionResult.Success + ] - async def query_pathing(self, start: Unit | Point2 | Point3, end: Point2 | Point3) -> int | float | None: + async def query_pathing(self, start: Unit | Point2 | Point3, end: Point2 | Point3) -> float | None: """Caution: returns "None" when path not found Try to combine queries with the function below because the pathing query is generally slow. @@ -238,25 +244,25 @@ async def query_pathing(self, start: Unit | Point2 | Point3, end: Point2 | Point return None return distance - async def query_pathings(self, zipped_list: list[list[Unit | Point2 | Point3]]) -> list[float]: + async def query_pathings(self, zipped_list: list[tuple[Unit | Point2 | Point3, Point2 | Point3]]) -> list[float]: """Usage: await self.query_pathings([[unit1, target2], [unit2, target2]]) -> returns [distance1, distance2] Caution: returns 0 when path not found :param zipped_list: """ - assert zipped_list, "No zipped_list" - assert isinstance(zipped_list, list), f"{type(zipped_list)}" - assert isinstance(zipped_list[0], list), f"{type(zipped_list[0])}" - assert len(zipped_list[0]) == 2, f"{len(zipped_list[0])}" - assert isinstance(zipped_list[0][0], (Point2, Unit)), f"{type(zipped_list[0][0])}" - assert isinstance(zipped_list[0][1], Point2), f"{type(zipped_list[0][1])}" - if isinstance(zipped_list[0][0], Point2): - path = ( - query_pb.RequestQueryPathing(start_pos=p1.as_Point2D, end_pos=p2.as_Point2D) for p1, p2 in zipped_list + assert zipped_list, "No entry in zipped_list" + path = ( + query_pb.RequestQueryPathing( + # pyrefly: ignore + unit_tag=p1.tag if isinstance(p1, Unit) else None, + # pyrefly: ignore + start_pos=None if isinstance(p1, Unit) else p1.as_Point2D, + end_pos=p2.as_Point2D, ) - else: - path = (query_pb.RequestQueryPathing(unit_tag=p1.tag, end_pos=p2.as_Point2D) for p1, p2 in zipped_list) + for p1, p2 in zipped_list + ) + # pyrefly: ignore results = await self._execute(query=query_pb.RequestQuery(pathing=path)) return [float(d.distance) for d in results.query.pathing] @@ -272,6 +278,7 @@ async def _query_building_placement_fast( """ result = await self._execute( query=query_pb.RequestQuery( + # pyrefly: ignore placements=( query_pb.RequestQueryBuildingPlacement(ability_id=ability.value, target_pos=position.as_Point2D) for position in positions @@ -297,6 +304,7 @@ async def query_building_placement( assert isinstance(ability, AbilityData) result = await self._execute( query=query_pb.RequestQuery( + # pyrefly: ignore placements=( query_pb.RequestQueryBuildingPlacement(ability_id=ability.id.value, target_pos=position.as_Point2D) for position in positions @@ -320,13 +328,14 @@ async def query_available_abilities( assert units result = await self._execute( query=query_pb.RequestQuery( + # pyrefly: ignore abilities=(query_pb.RequestQueryAvailableAbilities(unit_tag=unit.tag) for unit in units), ignore_resource_requirements=ignore_resource_requirements, ) ) """ Fix for bots that only query a single unit, may be removed soon """ if not input_was_a_list: - # pyre-fixme[7] + # pyrefly: ignore return [[AbilityId(a.ability_id) for a in b.abilities] for b in result.query.abilities][0] return [[AbilityId(a.ability_id) for a in b.abilities] for b in result.query.abilities] @@ -334,9 +343,9 @@ async def query_available_abilities_with_tag( self, units: list[Unit] | Units, ignore_resource_requirements: bool = False ) -> dict[int, set[AbilityId]]: """Query abilities of multiple units""" - result = await self._execute( query=query_pb.RequestQuery( + # pyrefly: ignore abilities=(query_pb.RequestQueryAvailableAbilities(unit_tag=unit.tag) for unit in units), ignore_resource_requirements=ignore_resource_requirements, ) @@ -348,7 +357,11 @@ async def chat_send(self, message: str, team_only: bool) -> None: ch = ChatChannel.Team if team_only else ChatChannel.Broadcast await self._execute( action=sc_pb.RequestAction( - actions=[sc_pb.Action(action_chat=sc_pb.ActionChat(channel=ch.value, message=message))] + actions=[ + sc_pb.Action( + action_chat=sc_pb.ActionChat(channel=ch.value, message=message) # type: ignore[bad-argument-type] + ) + ] ) ) @@ -368,7 +381,9 @@ async def toggle_autocast(self, units: list[Unit] | Units, ability: AbilityId) - sc_pb.Action( action_raw=raw_pb.ActionRaw( toggle_autocast=raw_pb.ActionRawToggleAutocast( - ability_id=ability.value, unit_tags=(u.tag for u in units) + ability_id=ability.value, + # pyrefly: ignore + unit_tags=(u.tag for u in units), ) ) ) @@ -376,22 +391,18 @@ async def toggle_autocast(self, units: list[Unit] | Units, ability: AbilityId) - ) ) - async def debug_create_unit(self, unit_spawn_commands: list[list[UnitTypeId | int | Point2 | Point3]]) -> None: + async def debug_create_unit( + self, unit_spawn_commands: list[tuple[UnitTypeId, int, Point2 | Point3, Literal[1, 2]]] + ) -> None: """Usage example (will spawn 5 marines in the center of the map for player ID 1): await self._client.debug_create_unit([[UnitTypeId.MARINE, 5, self._game_info.map_center, 1]]) :param unit_spawn_commands:""" - assert isinstance(unit_spawn_commands, list) - assert unit_spawn_commands - assert isinstance(unit_spawn_commands[0], list) - assert len(unit_spawn_commands[0]) == 4 - assert isinstance(unit_spawn_commands[0][0], UnitTypeId) - assert unit_spawn_commands[0][1] > 0 # careful, in realtime=True this function may create more units - assert isinstance(unit_spawn_commands[0][2], (Point2, Point3)) - assert 1 <= unit_spawn_commands[0][3] <= 2 + assert unit_spawn_commands, "List is empty" await self._execute( debug=sc_pb.RequestDebug( + # pyrefly: ignore debug=( debug_pb.DebugCommand( create_unit=debug_pb.DebugCreateUnit( @@ -417,6 +428,7 @@ async def debug_kill_unit(self, unit_tags: Unit | Units | list[int] | set[int]) assert unit_tags await self._execute( + # pyrefly: ignore debug=sc_pb.RequestDebug(debug=[debug_pb.DebugCommand(kill_unit=debug_pb.DebugKillUnit(tag=unit_tags))]) ) @@ -636,15 +648,19 @@ async def _send_debug(self) -> None: debug=[ debug_pb.DebugCommand( draw=debug_pb.DebugDraw( + # pyrefly: ignore text=[text.to_proto() for text in self._debug_texts] if self._debug_texts else None, + # pyrefly: ignore lines=[line.to_proto() for line in self._debug_lines] if self._debug_lines else None, + # pyrefly: ignore boxes=[box.to_proto() for box in self._debug_boxes] if self._debug_boxes else None, + # pyrefly: ignore spheres=[sphere.to_proto() for sphere in self._debug_spheres] if self._debug_spheres else None, @@ -666,6 +682,7 @@ async def _send_debug(self) -> None: await self._execute( debug=sc_pb.RequestDebug( debug=[ + # pyrefly: ignore debug_pb.DebugCommand(draw=debug_pb.DebugDraw(text=None, lines=None, boxes=None, spheres=None)) ] ) @@ -700,7 +717,9 @@ async def debug_set_unit_value( debug=( debug_pb.DebugCommand( unit_value=debug_pb.DebugSetUnitValue( - unit_value=unit_value, value=float(value), unit_tag=unit_tag + unit_value=unit_value, # pyrefly: ignore[bad-argument-type] + value=float(value), + unit_tag=unit_tag, ) ) for unit_tag in unit_tags @@ -713,57 +732,88 @@ async def debug_hang(self, delay_in_seconds: float) -> None: delay_in_ms = int(round(delay_in_seconds * 1000)) await self._execute( debug=sc_pb.RequestDebug( - debug=[debug_pb.DebugCommand(test_process=debug_pb.DebugTestProcess(test=1, delay_ms=delay_in_ms))] + debug=[ + debug_pb.DebugCommand( + test_process=debug_pb.DebugTestProcess( + test=1, # pyrefly: ignore + delay_ms=delay_in_ms, + ) + ) + ] ) ) async def debug_show_map(self) -> None: """Reveals the whole map for the bot. Using it a second time disables it again.""" - await self._execute(debug=sc_pb.RequestDebug(debug=[debug_pb.DebugCommand(game_state=1)])) + await self._execute( + debug=sc_pb.RequestDebug(debug=[debug_pb.DebugCommand(game_state=1)]) # pyrefly: ignore[bad-argument-type] + ) async def debug_control_enemy(self) -> None: """Allows control over enemy units and structures similar to team games control - does not allow the bot to spend the opponent's ressources. Using it a second time disables it again.""" - await self._execute(debug=sc_pb.RequestDebug(debug=[debug_pb.DebugCommand(game_state=2)])) + await self._execute( + debug=sc_pb.RequestDebug(debug=[debug_pb.DebugCommand(game_state=2)]) # pyrefly: ignore[bad-argument-type] + ) async def debug_food(self) -> None: """Should disable food usage (does not seem to work?). Using it a second time disables it again.""" - await self._execute(debug=sc_pb.RequestDebug(debug=[debug_pb.DebugCommand(game_state=3)])) + await self._execute( + debug=sc_pb.RequestDebug(debug=[debug_pb.DebugCommand(game_state=3)]) # pyrefly: ignore[bad-argument-type] + ) async def debug_free(self) -> None: """Units, structures and upgrades are free of mineral and gas cost. Using it a second time disables it again.""" - await self._execute(debug=sc_pb.RequestDebug(debug=[debug_pb.DebugCommand(game_state=4)])) + await self._execute( + debug=sc_pb.RequestDebug(debug=[debug_pb.DebugCommand(game_state=4)]) # pyrefly: ignore[bad-argument-type] + ) async def debug_all_resources(self) -> None: """Gives 5000 minerals and 5000 vespene to the bot.""" - await self._execute(debug=sc_pb.RequestDebug(debug=[debug_pb.DebugCommand(game_state=5)])) + await self._execute( + debug=sc_pb.RequestDebug(debug=[debug_pb.DebugCommand(game_state=5)]) # pyrefly: ignore[bad-argument-type] + ) async def debug_god(self) -> None: """Your units and structures no longer take any damage. Using it a second time disables it again.""" - await self._execute(debug=sc_pb.RequestDebug(debug=[debug_pb.DebugCommand(game_state=6)])) + await self._execute( + debug=sc_pb.RequestDebug(debug=[debug_pb.DebugCommand(game_state=6)]) # pyrefly: ignore[bad-argument-type] + ) async def debug_minerals(self) -> None: """Gives 5000 minerals to the bot.""" - await self._execute(debug=sc_pb.RequestDebug(debug=[debug_pb.DebugCommand(game_state=7)])) + await self._execute( + debug=sc_pb.RequestDebug(debug=[debug_pb.DebugCommand(game_state=7)]) # pyrefly: ignore[bad-argument-type] + ) async def debug_gas(self) -> None: """Gives 5000 vespene to the bot. This does not seem to be working.""" - await self._execute(debug=sc_pb.RequestDebug(debug=[debug_pb.DebugCommand(game_state=8)])) + await self._execute( + debug=sc_pb.RequestDebug(debug=[debug_pb.DebugCommand(game_state=8)]) # pyrefly: ignore[bad-argument-type] + ) async def debug_cooldown(self) -> None: """Disables cooldowns of unit abilities for the bot. Using it a second time disables it again.""" - await self._execute(debug=sc_pb.RequestDebug(debug=[debug_pb.DebugCommand(game_state=9)])) + await self._execute( + debug=sc_pb.RequestDebug(debug=[debug_pb.DebugCommand(game_state=9)]) # pyrefly: ignore[bad-argument-type] + ) async def debug_tech_tree(self) -> None: """Removes all tech requirements (e.g. can build a factory without having a barracks). Using it a second time disables it again.""" - await self._execute(debug=sc_pb.RequestDebug(debug=[debug_pb.DebugCommand(game_state=10)])) + await self._execute( + debug=sc_pb.RequestDebug(debug=[debug_pb.DebugCommand(game_state=10)]) # pyrefly: ignore[bad-argument-type] + ) async def debug_upgrade(self) -> None: """Researches all currently available upgrades. E.g. using it once unlocks combat shield, stimpack and 1-1. Using it a second time unlocks 2-2 and all other upgrades stay researched.""" - await self._execute(debug=sc_pb.RequestDebug(debug=[debug_pb.DebugCommand(game_state=11)])) + await self._execute( + debug=sc_pb.RequestDebug(debug=[debug_pb.DebugCommand(game_state=11)]) # pyrefly: ignore[bad-argument-type] + ) async def debug_fast_build(self) -> None: """Sets the build time of units and structures and upgrades to zero. Using it a second time disables it again.""" - await self._execute(debug=sc_pb.RequestDebug(debug=[debug_pb.DebugCommand(game_state=12)])) + await self._execute( + debug=sc_pb.RequestDebug(debug=[debug_pb.DebugCommand(game_state=12)]) # pyrefly: ignore[bad-argument-type] + ) async def quick_save(self) -> None: """Saves the current game state to an in-memory bookmark. @@ -787,12 +837,12 @@ def to_debug_color(color: tuple[float, float, float] | list[float] | Point3 | No return debug_pb.Color(r=255, g=255, b=255) # Need to check if not of type Point3 because Point3 inherits from tuple if isinstance(color, (tuple, list)) or isinstance(color, Point3) and len(color) == 3: - return debug_pb.Color(r=color[0], g=color[1], b=color[2]) + return debug_pb.Color(r=int(color[0]), g=int(color[1]), b=int(color[2])) # In case color is of type Point3 r = getattr(color, "r", getattr(color, "x", 255)) g = getattr(color, "g", getattr(color, "y", 255)) b = getattr(color, "b", getattr(color, "z", 255)) - # pyre-ignore[20] + if max(r, g, b) <= 1: r *= 255 g *= 255 @@ -819,6 +869,7 @@ def to_proto(self): color=self.to_debug_color(self._color), text=self._text, virtual_pos=self._start_point.to3.as_Point, + # pyrefly: ignore world_pos=None, size=self._font_size, ) @@ -830,20 +881,21 @@ def __hash__(self) -> int: class DrawItemWorldText(DrawItem): def __init__( self, - start_point: Point3 = None, - color: tuple[float, float, float] | list[float] | Point3 | None = None, + start_point: Point3, + color: tuple[float, float, float] | list[float] | Point3 | None, text: str = "", font_size: int = 8, ) -> None: - self._start_point: Point3 = start_point - self._color: Point3 = color - self._text: str = text - self._font_size: int = font_size + self._start_point = start_point + self._color = color + self._text = text + self._font_size = font_size def to_proto(self): return debug_pb.DebugText( color=self.to_debug_color(self._color), text=self._text, + # pyrefly: ignore virtual_pos=None, world_pos=self._start_point.as_Point, size=self._font_size, @@ -856,13 +908,13 @@ def __hash__(self) -> int: class DrawItemLine(DrawItem): def __init__( self, - start_point: Point3 = None, - end_point: Point3 = None, + start_point: Point3, + end_point: Point3, color: tuple[float, float, float] | list[float] | Point3 | None = None, ) -> None: - self._start_point: Point3 = start_point - self._end_point: Point3 = end_point - self._color: Point3 = color + self._start_point = start_point + self._end_point = end_point + self._color = color def to_proto(self): return debug_pb.DebugLine( @@ -877,13 +929,13 @@ def __hash__(self) -> int: class DrawItemBox(DrawItem): def __init__( self, - start_point: Point3 = None, - end_point: Point3 = None, + start_point: Point3, + end_point: Point3, color: tuple[float, float, float] | list[float] | Point3 | None = None, ) -> None: - self._start_point: Point3 = start_point - self._end_point: Point3 = end_point - self._color: Point3 = color + self._start_point = start_point + self._end_point = end_point + self._color = color def to_proto(self): return debug_pb.DebugBox( @@ -899,13 +951,13 @@ def __hash__(self) -> int: class DrawItemSphere(DrawItem): def __init__( self, - start_point: Point3 = None, - radius: float = None, + start_point: Point3, + radius: float, color: tuple[float, float, float] | list[float] | Point3 | None = None, ) -> None: - self._start_point: Point3 = start_point - self._radius: float = radius - self._color: Point3 = color + self._start_point = start_point + self._radius = radius + self._color = color def to_proto(self): return debug_pb.DebugSphere( diff --git a/sc2/constants.py b/sc2/constants.py index 6478ff01..a3be87c3 100644 --- a/sc2/constants.py +++ b/sc2/constants.py @@ -1,4 +1,3 @@ -# pyre-ignore-all-errors[16] from __future__ import annotations from collections import defaultdict @@ -494,7 +493,7 @@ def return_NOTAUNIT() -> UnitTypeId: UnitTypeId.EXTRACTOR, UnitTypeId.EXTRACTORRICH, } -# pyre-ignore[11] + DAMAGE_BONUS_PER_UPGRADE: dict[UnitTypeId, dict[int, Any]] = { # # Protoss diff --git a/sc2/controller.py b/sc2/controller.py index e068aa3f..a8913be7 100644 --- a/sc2/controller.py +++ b/sc2/controller.py @@ -26,18 +26,26 @@ def running(self) -> bool: async def create_game(self, game_map, players, realtime: bool, random_seed=None, disable_fog=None): req = sc_pb.RequestCreateGame( - local_map=sc_pb.LocalMap(map_path=str(game_map.relative_path)), realtime=realtime, disable_fog=disable_fog + local_map=sc_pb.LocalMap(map_path=str(game_map.relative_path)), + realtime=realtime, + # pyrefly: ignore + disable_fog=disable_fog, ) if random_seed is not None: req.random_seed = random_seed for player in players: + # pyrefly: ignore p = req.player_setup.add() p.type = player.type.value if isinstance(player, Computer): + # pyrefly: ignore[bad-assignment] p.race = player.race.value + # pyrefly: ignore[bad-assignment] p.difficulty = player.difficulty.value - p.ai_build = player.ai_build.value + if player.ai_build is not None: + # pyrefly: ignore[bad-assignment] + p.ai_build = player.ai_build.value logger.info("Creating new game") logger.info(f"Map: {game_map.name}") diff --git a/sc2/data.py b/sc2/data.py index d376138b..e36bddd0 100644 --- a/sc2/data.py +++ b/sc2/data.py @@ -1,4 +1,3 @@ -# pyre-ignore-all-errors[16, 19] """For the list of enums, see here https://github.com/Blizzard/s2client-proto/tree/bff45dae1fc685e6acbaae084670afb7d1c0832c/s2clientprotocol @@ -38,7 +37,7 @@ ActionResult = enum.Enum("ActionResult", error_pb.ActionResult.items()) -# pyre-ignore[11] + race_worker: dict[Race, UnitTypeId] = { Race.Protoss: UnitTypeId.PROBE, Race.Terran: UnitTypeId.SCV, diff --git a/sc2/expiring_dict.py b/sc2/expiring_dict.py index 7c6cc2c0..d80ffa8a 100644 --- a/sc2/expiring_dict.py +++ b/sc2/expiring_dict.py @@ -1,4 +1,3 @@ -# pyre-ignore-all-errors[14, 15, 58] from __future__ import annotations from collections import OrderedDict @@ -42,7 +41,6 @@ def __init__(self, bot: BotAI, max_age_frames: int = 1) -> None: @property def frame(self) -> int: - # pyre-ignore[16] return self.bot.state.game_loop def __contains__(self, key: Hashable) -> bool: @@ -75,7 +73,7 @@ def __setitem__(self, key: Hashable, value: Any) -> None: def __repr__(self) -> str: """Printable version of the dict instead of getting memory adress""" - print_list = [] + print_list: list[str] = [] with self.lock: for key, value in OrderedDict.items(self): if self.frame - value[1] < self.max_age: diff --git a/sc2/game_data.py b/sc2/game_data.py index 4be84ee2..4283892c 100644 --- a/sc2/game_data.py +++ b/sc2/game_data.py @@ -1,4 +1,3 @@ -# pyre-ignore-all-errors[29] from __future__ import annotations from bisect import bisect_left @@ -49,7 +48,6 @@ def calculate_ability_cost(self, ability: AbilityData | AbilityId | UnitCommand) if not AbilityData.id_exists(unit.creation_ability.id.value): continue - # pyre-ignore[16] if unit.creation_ability.is_free_morph: continue @@ -265,9 +263,7 @@ def morph_cost(self) -> Cost | None: self._game_data.units[tech_alias.value].cost.minerals for tech_alias in self.tech_alias ) tech_alias_cost_vespene = max( - self._game_data.units[tech_alias.value].cost.vespene - # pyre-ignore[16] - for tech_alias in self.tech_alias + self._game_data.units[tech_alias.value].cost.vespene for tech_alias in self.tech_alias ) return Cost( self._proto.mineral_cost - tech_alias_cost_minerals, diff --git a/sc2/game_info.py b/sc2/game_info.py index aab025d5..f68daf95 100644 --- a/sc2/game_info.py +++ b/sc2/game_info.py @@ -1,4 +1,3 @@ -# pyre-ignore-all-errors[6, 11, 16, 58] from __future__ import annotations import heapq @@ -31,7 +30,7 @@ def y_offset(self) -> float: return 0.5 @cached_property - def _height_map(self): + def _height_map(self) -> PixelMap: return self.game_info.terrain_height @cached_property @@ -146,6 +145,7 @@ def barracks_can_fit_addon(self) -> bool: """Test if a barracks can fit an addon at natural ramp""" # https://i.imgur.com/4b2cXHZ.png if len(self.upper2_for_ramp_wall) == 2: + # pyrefly: ignore return self.barracks_in_middle.x + 1 > max(self.corner_depots, key=lambda depot: depot.x).x raise Exception("Not implemented. Trying to access a ramp that has a wrong amount of upper points.") @@ -173,8 +173,9 @@ def protoss_wall_pylon(self) -> Point2 | None: raise Exception("Not implemented. Trying to access a ramp that has a wrong amount of upper points.") middle = self.depot_in_middle # direction up the ramp + # pyrefly: ignore direction = self.barracks_in_middle.negative_offset(middle) - # pyre-ignore[7] + return middle + 6 * direction @cached_property @@ -188,12 +189,14 @@ def protoss_wall_buildings(self) -> frozenset[Point2]: if len(self.upper2_for_ramp_wall) == 2: middle = self.depot_in_middle # direction up the ramp + # pyrefly: ignore direction = self.barracks_in_middle.negative_offset(middle) # sort depots based on distance to start to get wallin orientation sorted_depots = sorted( self.corner_depots, key=lambda depot: depot.distance_to(self.game_info.player_start_location) ) wall1: Point2 = sorted_depots[1].offset(direction) + # pyrefly: ignore wall2 = middle + direction + (middle - wall1) / 1.5 return frozenset([wall1, wall2]) @@ -211,6 +214,7 @@ def protoss_wall_warpin(self) -> Point2 | None: raise Exception("Not implemented. Trying to access a ramp that has a wrong amount of upper points.") middle = self.depot_in_middle # direction up the ramp + # pyrefly: ignore direction = self.barracks_in_middle.negative_offset(middle) # sort depots based on distance to start to get wallin orientation sorted_depots = sorted(self.corner_depots, key=lambda x: x.distance_to(self.game_info.player_start_location)) @@ -223,7 +227,7 @@ def __init__(self, proto: sc2api_pb2.ResponseGameInfo) -> None: self.players: list[Player] = [Player.from_proto(p) for p in self._proto.player_info] self.map_name: str = self._proto.map_name self.local_map_path: str = self._proto.local_map_path - # pyre-ignore[8] + self.map_size: Size = Size.from_proto(self._proto.start_raw.map_size) # self.pathing_grid[point]: if 0, point is not pathable, if 1, point is pathable @@ -234,9 +238,9 @@ def __init__(self, proto: sc2api_pb2.ResponseGameInfo) -> None: self.placement_grid: PixelMap = PixelMap(self._proto.start_raw.placement_grid, in_bits=True) self.playable_area = Rect.from_proto(self._proto.start_raw.playable_area) self.map_center = self.playable_area.center - # pyre-ignore[8] + # pyrefly: ignore self.map_ramps: list[Ramp] = None # Filled later by BotAI._prepare_first_step - # pyre-ignore[8] + # pyrefly: ignore self.vision_blockers: frozenset[Point2] = None # Filled later by BotAI._prepare_first_step self.player_races: dict[int, int] = { p.player_id: p.race_actual or p.race_requested for p in self._proto.player_info @@ -244,7 +248,7 @@ def __init__(self, proto: sc2api_pb2.ResponseGameInfo) -> None: self.start_locations: list[Point2] = [ Point2.from_proto(sl).round(decimals=1) for sl in self._proto.start_raw.start_locations ] - # pyre-ignore[8] + # pyrefly: ignore self.player_start_location: Point2 = None # Filled later by BotAI._prepare_first_step def _find_ramps_and_vision_blockers(self) -> tuple[list[Ramp], frozenset[Point2]]: @@ -270,10 +274,10 @@ def equal_height_around(tile): # divide points into ramp points and vision blockers ramp_points = [point for point in points if not equal_height_around(point)] vision_blockers = frozenset(point for point in points if equal_height_around(point)) - ramps = [Ramp(group, self) for group in self._find_groups(ramp_points)] + ramps = [Ramp(frozenset(group), self) for group in self._find_groups(ramp_points)] return ramps, vision_blockers - def _find_groups(self, points: frozenset[Point2], minimum_points_per_group: int = 8) -> Iterable[frozenset[Point2]]: + def _find_groups(self, points: Iterable[Point2], minimum_points_per_group: int = 8) -> Iterable[frozenset[Point2]]: """ From a set of points, this function will try to group points together by painting clusters of points in a rectangular map using flood fill algorithm. @@ -287,6 +291,7 @@ def _find_groups(self, points: frozenset[Point2], minimum_points_per_group: int picture: list[list[int]] = [[-2 for _ in range(map_width)] for _ in range(map_height)] def paint(pt: Point2) -> None: + # pyrefly: ignore picture[pt.y][pt.x] = current_color nearby: list[tuple[int, int]] = [(a, b) for a in [-1, 0, 1] for b in [-1, 0, 1] if a != 0 or b != 0] @@ -310,6 +315,7 @@ def paint(pt: Point2) -> None: # Do we ever reach out of map bounds? if not (0 <= px < map_width and 0 <= py < map_height): continue + # pyrefly: ignore if picture[py][px] != NOT_COLORED_YET: continue point: Point2 = Point2((px, py)) diff --git a/sc2/game_state.py b/sc2/game_state.py index 7ff4bb89..fd7af3db 100644 --- a/sc2/game_state.py +++ b/sc2/game_state.py @@ -1,9 +1,9 @@ -# pyre-ignore-all-errors[11, 16] from __future__ import annotations from dataclasses import dataclass from functools import cached_property from itertools import chain +from s2clientprotocol.raw_pb2 import Effect, Unit from loguru import logger @@ -46,7 +46,7 @@ def is_visible(self) -> bool: return self._proto.display_type == DisplayType.Visible.value @property - def alliance(self) -> Alliance: + def alliance(self) -> int: return self._proto.alliance @property @@ -97,7 +97,7 @@ def __init__(self, proto: raw_pb2.Effect | raw_pb2.Unit, fake: bool = False) -> :param proto: :param fake: """ - self._proto = proto + self._proto: Effect | Unit = proto self.fake = fake @property @@ -133,7 +133,7 @@ def owner(self) -> int: @property def radius(self) -> float: - if self.fake: + if isinstance(self._proto, Unit): return FakeEffectRadii[self._proto.unit_type] return self._proto.radius @@ -149,6 +149,8 @@ class ChatMessage: @dataclass class AbilityLookupTemplateClass: + ability_id: int + @property def exact_id(self) -> AbilityId: return AbilityId(self.ability_id) @@ -164,7 +166,6 @@ def generic_id(self) -> AbilityId: @dataclass class ActionRawUnitCommand(AbilityLookupTemplateClass): game_loop: int - ability_id: int unit_tags: list[int] queue_command: bool target_world_space_pos: Point2 | None @@ -174,7 +175,6 @@ class ActionRawUnitCommand(AbilityLookupTemplateClass): @dataclass class ActionRawToggleAutocast(AbilityLookupTemplateClass): game_loop: int - ability_id: int unit_tags: list[int] @@ -185,7 +185,6 @@ class ActionRawCameraMove: @dataclass class ActionError(AbilityLookupTemplateClass): - ability_id: int unit_tag: int # See here for the codes of 'result': https://github.com/Blizzard/s2client-proto/blob/01ab351e21c786648e4c6693d4aad023a176d45c/s2clientprotocol/error.proto#L6 result: int @@ -212,7 +211,7 @@ def __init__( 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) + self.psionic_matrix: PsionicMatrix = PsionicMatrix.from_proto(list(self.observation_raw.player.power_sources)) # 22.4 per second on faster game speed self.game_loop: int = self.observation.game_loop @@ -259,7 +258,7 @@ def alerts(self) -> list[int]: """ if self.previous_observation is not None: return list(chain(self.previous_observation.observation.alerts, self.observation.alerts)) - return self.observation.alerts + return list(self.observation.alerts) @cached_property def actions(self) -> list[ActionRawUnitCommand | ActionRawToggleAutocast | ActionRawCameraMove]: @@ -283,7 +282,7 @@ def actions(self) -> list[ActionRawUnitCommand | ActionRawToggleAutocast | Actio ActionRawUnitCommand( game_loop, raw_unit_command.ability_id, - raw_unit_command.unit_tags, + list(raw_unit_command.unit_tags), raw_unit_command.queue_command, Point2.from_proto(raw_unit_command.target_world_space_pos), ) @@ -294,7 +293,7 @@ def actions(self) -> list[ActionRawUnitCommand | ActionRawToggleAutocast | Actio ActionRawUnitCommand( game_loop, raw_unit_command.ability_id, - raw_unit_command.unit_tags, + list(raw_unit_command.unit_tags), raw_unit_command.queue_command, None, raw_unit_command.target_unit_tag, @@ -307,7 +306,7 @@ def actions(self) -> list[ActionRawUnitCommand | ActionRawToggleAutocast | Actio ActionRawToggleAutocast( game_loop, raw_toggle_autocast_action.ability_id, - raw_toggle_autocast_action.unit_tags, + list(raw_toggle_autocast_action.unit_tags), ) ) else: @@ -321,7 +320,7 @@ def actions_unit_commands(self) -> list[ActionRawUnitCommand]: List of successful unit actions since last frame. See https://github.com/Blizzard/s2client-proto/blob/01ab351e21c786648e4c6693d4aad023a176d45c/s2clientprotocol/raw.proto#L185-L193 """ - # pyre-ignore[7] + return list(filter(lambda action: isinstance(action, ActionRawUnitCommand), self.actions)) @cached_property @@ -330,7 +329,7 @@ def actions_toggle_autocast(self) -> list[ActionRawToggleAutocast]: List of successful autocast toggle actions since last frame. See https://github.com/Blizzard/s2client-proto/blob/01ab351e21c786648e4c6693d4aad023a176d45c/s2clientprotocol/raw.proto#L199-L202 """ - # pyre-ignore[7] + return list(filter(lambda action: isinstance(action, ActionRawToggleAutocast), self.actions)) @cached_property diff --git a/sc2/generate_ids.py b/sc2/generate_ids.py index 1fd4e7d6..af0b5fe1 100644 --- a/sc2/generate_ids.py +++ b/sc2/generate_ids.py @@ -25,7 +25,7 @@ def __init__( self.game_version = game_version self.verbose = verbose - self.HEADER = f"""# pyre-ignore-all-errors[14] + self.HEADER = f""" from __future__ import annotations # DO NOT EDIT! # This file was automatically generated by "{Path(__file__).name}" @@ -194,8 +194,8 @@ def _missing_(cls, value: int) -> {class_name}: f.write(f'ID_VERSION_STRING = "{self.game_version}"\n') def update_ids_from_stableid_json(self) -> None: - if self.game_version is None or ID_VERSION_STRING is None or self.game_version != ID_VERSION_STRING: - if self.verbose and self.game_version is not None and ID_VERSION_STRING is not None: + if self.game_version is None or self.game_version != ID_VERSION_STRING: + if self.verbose and self.game_version is not None: logger.info( f"Game version is different (Old: {self.game_version}, new: {ID_VERSION_STRING}. Updating ids to match game version" ) diff --git a/sc2/ids/__init__.py b/sc2/ids/__init__.py index 24b6bc2a..a69ff863 100644 --- a/sc2/ids/__init__.py +++ b/sc2/ids/__init__.py @@ -1,4 +1,3 @@ -# pyre-ignore-all-errors[14] from __future__ import annotations # DO NOT EDIT! diff --git a/sc2/ids/ability_id.py b/sc2/ids/ability_id.py index 955be493..d895fa90 100644 --- a/sc2/ids/ability_id.py +++ b/sc2/ids/ability_id.py @@ -1,4 +1,3 @@ -# pyre-ignore-all-errors[14] from __future__ import annotations # DO NOT EDIT! diff --git a/sc2/ids/buff_id.py b/sc2/ids/buff_id.py index 5a7345a8..632965b1 100644 --- a/sc2/ids/buff_id.py +++ b/sc2/ids/buff_id.py @@ -1,4 +1,3 @@ -# pyre-ignore-all-errors[14] from __future__ import annotations # DO NOT EDIT! diff --git a/sc2/ids/effect_id.py b/sc2/ids/effect_id.py index 77aea24b..7bef1546 100644 --- a/sc2/ids/effect_id.py +++ b/sc2/ids/effect_id.py @@ -1,4 +1,3 @@ -# pyre-ignore-all-errors[14] from __future__ import annotations # DO NOT EDIT! diff --git a/sc2/ids/unit_typeid.py b/sc2/ids/unit_typeid.py index ec74ebe8..ab0facdf 100644 --- a/sc2/ids/unit_typeid.py +++ b/sc2/ids/unit_typeid.py @@ -1,4 +1,3 @@ -# pyre-ignore-all-errors[14] from __future__ import annotations # DO NOT EDIT! diff --git a/sc2/ids/upgrade_id.py b/sc2/ids/upgrade_id.py index 4be6cbfe..0f411d5e 100644 --- a/sc2/ids/upgrade_id.py +++ b/sc2/ids/upgrade_id.py @@ -1,4 +1,3 @@ -# pyre-ignore-all-errors[14] from __future__ import annotations # DO NOT EDIT! diff --git a/sc2/main.py b/sc2/main.py index 8d07314e..cb18c0d3 100644 --- a/sc2/main.py +++ b/sc2/main.py @@ -1,4 +1,3 @@ -# pyre-ignore-all-errors[6, 11, 16, 21, 29] from __future__ import annotations import asyncio @@ -25,7 +24,7 @@ from sc2.game_state import GameState from sc2.maps import Map from sc2.observer_ai import ObserverAI -from sc2.player import AbstractPlayer, Bot, BotProcess, Human +from sc2.player import AbstractPlayer, Bot, BotProcess, Computer, Human from sc2.portconfig import Portconfig from sc2.protocol import ConnectionAlreadyClosedError, ProtocolError from sc2.proxy import Proxy @@ -56,7 +55,12 @@ class GameMatch: def __post_init__(self) -> None: # avoid players sharing names - if len(self.players) > 1 and self.players[0].name is not None and self.players[0].name == self.players[1].name: + if ( + len(self.players) > 1 + and self.players[0].name is not None + and self.players[1].name is not None + and self.players[0].name == self.players[1].name + ): self.players[1].name += "2" if self.sc2_config is not None: @@ -107,7 +111,8 @@ async def _play_game_human(client, player_id, realtime, game_time_limit): async def _play_game_ai( client: Client, player_id: int, ai: BotAI, realtime: bool, game_time_limit: int | None ) -> Result: - gs: GameState | None = None + # pyrefly: ignore + gs: GameState = None async def initialize_first_step() -> Result | None: nonlocal gs @@ -205,10 +210,10 @@ async def run_bot_iteration(iteration: int): async def _play_game( - player: AbstractPlayer, + player: Human | Bot, client: Client, realtime: bool, - portconfig: Portconfig, + portconfig: Portconfig | None = None, game_time_limit: int | None = None, rgb_render_config: dict[str, Any] | None = None, ) -> Result: @@ -336,7 +341,7 @@ async def _setup_host_game( async def _host_game( map_settings: Map, - players: list[AbstractPlayer], + players: list[Human | Bot | Computer] | list[Human | Bot], realtime: bool = False, portconfig: Portconfig | None = None, save_replay_as: str | None = None, @@ -349,6 +354,7 @@ async def _host_game( assert players, "Can't create a game without players" assert any((isinstance(p, (Human, Bot))) for p in players) + assert isinstance(players[0], (Human, Bot)), "First player needs to be a Human or a Bot" async with SC2Process( fullscreen=players[0].fullscreen, render=rgb_render_config is not None, sc2_version=sc2_version @@ -359,7 +365,7 @@ async def _host_game( server, map_settings, players, realtime, random_seed, disable_fog, save_replay_as ) # Bot can decide if it wants to launch with 'raw_affects_selection=True' - if not isinstance(players[0], Human) and getattr(players[0].ai, "raw_affects_selection", None) is not None: + if isinstance(players[0], Bot) and getattr(players[0].ai, "raw_affects_selection", None) is not None: client.raw_affects_selection = players[0].ai.raw_affects_selection result = await _play_game(players[0], client, realtime, portconfig, game_time_limit, rgb_render_config) @@ -378,7 +384,7 @@ async def _host_game_aiter( map_settings, players, realtime, - portconfig=None, + portconfig, save_replay_as=None, game_time_limit=None, ): @@ -417,9 +423,9 @@ def _host_game_iter(*args, **kwargs): async def _join_game( - players: list[AbstractPlayer], - realtime: bool = False, - portconfig: Portconfig | None = None, + players: list[Human | Bot], + realtime: bool, + portconfig: Portconfig, save_replay_as: str | None = None, game_time_limit: int | None = None, sc2_version: str | None = None, @@ -465,6 +471,7 @@ def get_replay_version(replay_path: str | Path) -> tuple[str, str]: replay_io.write(replay_data) replay_io.seek(0) archive = mpyq.MPQArchive(replay_io).extract() + # pyrefly: ignore metadata = json.loads(archive[b"replay.gamemetadata.json"].decode("utf-8")) return metadata["BaseBuild"], metadata["DataVersion"] @@ -472,7 +479,7 @@ def get_replay_version(replay_path: str | Path) -> tuple[str, str]: # TODO Deprecate run_game function in favor of run_multiple_games def run_game( map_settings: Map, - players: list[AbstractPlayer], + players: list[Human | Bot | Computer], realtime: bool, portconfig: Portconfig | None = None, save_replay_as: str | None = None, @@ -486,14 +493,16 @@ def run_game( Returns a single Result enum if the game was against the built-in computer. Returns a list of two Result enums if the game was "Human vs Bot" or "Bot vs Bot". """ + result: Result | list[Result | None] if sum(isinstance(p, (Human, Bot)) for p in players) > 1: portconfig = Portconfig() + players_non_computer: list[Human | Bot] = [p for p in players if isinstance(p, (Human, Bot))] async def run_host_and_join(): return await asyncio.gather( _host_game( map_settings, - players, + players_non_computer, realtime=realtime, portconfig=portconfig, save_replay_as=save_replay_as, @@ -504,7 +513,7 @@ async def run_host_and_join(): disable_fog=disable_fog, ), _join_game( - players, + players_non_computer, realtime=realtime, portconfig=portconfig, save_replay_as=save_replay_as, @@ -514,11 +523,12 @@ async def run_host_and_join(): return_exceptions=True, ) - result: list[Result] = asyncio.run(run_host_and_join()) + # pyrefly: ignore + result = asyncio.run(run_host_and_join()) assert isinstance(result, list) assert all(isinstance(r, Result) for r in result) else: - result: Result = asyncio.run( + result = asyncio.run( _host_game( map_settings, players, @@ -551,9 +561,9 @@ def run_replay(ai: ObserverAI, replay_path: Path | str, realtime: bool = False, async def play_from_websocket( ws_connection: str | ClientWebSocketResponse, - player: AbstractPlayer, - realtime: bool = False, - portconfig: Portconfig | None = None, + player: Human | Bot, + realtime: bool, + portconfig: Portconfig, save_replay_as: str | None = None, game_time_limit: int | None = None, should_close: bool = True, @@ -569,6 +579,7 @@ async def play_from_websocket( try: if isinstance(ws_connection, str): session = ClientSession() + # pyrefly: ignore ws_connection = await session.ws_connect(ws_connection, timeout=120) should_close = True client = Client(ws_connection) @@ -592,7 +603,7 @@ async def run_match(controllers: list[Controller], match: GameMatch, close_ws: b # Setup portconfig beforehand, so all players use the same ports startport = None - portconfig = None + portconfig: Portconfig = None # pyrefly: ignore if match.needed_sc2_count > 1: if any(isinstance(player, BotProcess) for player in match.players): portconfig = Portconfig.contiguous_ports() @@ -624,12 +635,12 @@ async def run_match(controllers: list[Controller], match: GameMatch, close_ws: b async_results = await asyncio.gather(*coros, return_exceptions=True) - if not isinstance(async_results, list): - async_results = [async_results] for i, a in enumerate(async_results): if isinstance(a, Exception): logger.error(f"Exception[{a}] thrown by {[p for p in match.players if p.needs_sc2][i]}") + # TODO async_results may contain exceptions + # pyrefly: ignore return process_results(match.players, async_results) @@ -644,9 +655,10 @@ def process_results(players: list[AbstractPlayer], async_results: list[Result]) else: result[player] = Result.Undecided i += 1 - else: # computer + else: + # Computer other_result = async_results[0] - result[player] = None + result[player] = Result.Undecided if other_result in opp_res: result[player] = opp_res[other_result] @@ -659,12 +671,14 @@ async def maintain_SCII_count(count: int, controllers: list[Controller], proc_ar if controllers: to_remove = [] alive = await asyncio.wait_for( - asyncio.gather(*(c.ping() for c in controllers if not c._ws.closed), return_exceptions=True), timeout=20 + # pyrefly: ignore + asyncio.gather(*(c.ping() for c in controllers if not c._ws.closed), return_exceptions=True), + timeout=20, ) i = 0 # for alive for controller in controllers: if controller._ws.closed: - if not controller._process._session.closed: + if controller._process._session is not None and not controller._process._session.closed: await controller._process._session.close() to_remove.append(controller) else: @@ -698,12 +712,14 @@ async def maintain_SCII_count(count: int, controllers: list[Controller], proc_ar else: # Doesnt seem to work on linux: starting 2 clients nearly at the same time new_controllers = await asyncio.wait_for( + # pyrefly: ignore asyncio.gather(*[sc.__aenter__() for sc in extra], return_exceptions=True), timeout=50, ) controllers.extend(c for c in new_controllers if isinstance(c, Controller)) if len(controllers) == count: + # pyrefly: ignore await asyncio.wait_for(asyncio.gather(*(c.ping() for c in controllers)), timeout=20) break extra = [ @@ -739,8 +755,8 @@ async def a_run_multiple_games(matches: list[GameMatch]) -> list[dict[AbstractPl if not matches: return [] - results = [] - controllers = [] + results: list[dict[AbstractPlayer, Result]] = [] + controllers: list[Controller] = [] for m in matches: result = None dont_restart = m.needed_sc2_count == 2 @@ -755,7 +771,8 @@ async def a_run_multiple_games(matches: list[GameMatch]) -> list[dict[AbstractPl finally: if dont_restart: # Keeping them alive after a non-computer match can cause crashes await maintain_SCII_count(0, controllers, m.sc2_config) - results.append(result) + if result is not None: + results.append(result) KillSwitch.kill_all() return results @@ -772,7 +789,7 @@ async def a_run_multiple_games_nokill(matches: list[GameMatch]) -> list[dict[Abs return [] # Start the matches - results = [] + results: list[dict[AbstractPlayer, Result]] = [] controllers: list[Controller] = [] for m in matches: logger.info(f"Starting match {1 + len(results)} / {len(matches)}: {m}") @@ -795,10 +812,11 @@ async def a_run_multiple_games_nokill(matches: list[GameMatch]) -> list[dict[Abs logger.exception(f"Caught unknown exception: {e}") if not (isinstance(e, ProtocolError) and e.is_game_over_error): logger.info(f"controller {c.__dict__} threw {e}") - - results.append(result) + if result is not None: + results.append(result) # Fire the killswitch manually, instead of letting the winning player fire it. + # pyrefly: ignore await asyncio.wait_for(asyncio.gather(*(c._process._close_connection() for c in controllers)), timeout=50) KillSwitch.kill_all() signal.signal(signal.SIGINT, signal.SIG_DFL) diff --git a/sc2/observer_ai.py b/sc2/observer_ai.py index 7cd23c99..814fe455 100644 --- a/sc2/observer_ai.py +++ b/sc2/observer_ai.py @@ -1,4 +1,3 @@ -# pyre-ignore-all-errors[6, 11, 16] """ This class is very experimental and probably not up to date and needs to be refurbished. If it works, you can watch replays with it. diff --git a/sc2/paths.py b/sc2/paths.py index 10ec26bb..86eac639 100644 --- a/sc2/paths.py +++ b/sc2/paths.py @@ -55,7 +55,7 @@ def platform_detect(): return pf -PF = platform_detect() +PF: str = platform_detect() def get_home(): @@ -68,12 +68,12 @@ def get_home(): def get_user_sc2_install(): """Attempts to find a user's SC2 install if their OS has ExecuteInfo.txt""" if USERPATH[PF]: - einfo = str(get_home() / Path(USERPATH[PF])) + einfo = str(get_home() / Path(USERPATH[PF])) # pyrefly: ignore if Path(einfo).is_file(): with Path(einfo).open() as f: content = f.read() if content: - base = re.search(r" = (.*)Versions", content).group(1) + base = re.search(r" = (.*)Versions", content).group(1) # pyrefly: ignore if PF in {"WSL1", "WSL2"}: base = str(wsl.win_path_to_wsl_path(base)) @@ -88,8 +88,9 @@ def get_env() -> None: def get_runner_args(cwd): - if "WINE" in os.environ: - runner_file = Path(os.environ.get("WINE")) + wine_path = os.environ.get("WINE") + if wine_path is not None: + runner_file = Path(wine_path) runner_file = runner_file if runner_file.is_file() else runner_file / "wine" """ TODO Is converting linux path really necessary? @@ -133,16 +134,16 @@ def __setup(cls): try: base = os.environ.get("SC2PATH") or get_user_sc2_install() or BASEDIR[PF] - cls.BASE = Path(base).expanduser() + cls.BASE = Path(base).expanduser() # pyrefly: ignore cls.EXECUTABLE = latest_executeble(cls.BASE / "Versions") - cls.CWD = cls.BASE / CWD[PF] if CWD[PF] else None + cls.CWD = cls.BASE / CWD[PF] if CWD[PF] else None # pyrefly: ignore - cls.REPLAYS = cls.BASE / "Replays" + cls.REPLAYS = cls.BASE / "Replays" # pyrefly: ignore if (cls.BASE / "maps").exists(): - cls.MAPS = cls.BASE / "maps" + cls.MAPS = cls.BASE / "maps" # pyrefly: ignore else: - cls.MAPS = cls.BASE / "Maps" + cls.MAPS = cls.BASE / "Maps" # pyrefly: ignore except FileNotFoundError as e: logger.critical(f"SC2 installation not found: File '{e.filename}' does not exist.") sys.exit(1) diff --git a/sc2/pixel_map.py b/sc2/pixel_map.py index c6925d80..4b4af8f0 100644 --- a/sc2/pixel_map.py +++ b/sc2/pixel_map.py @@ -6,7 +6,7 @@ import numpy as np from s2clientprotocol.common_pb2 import ImageData -from sc2.position import Point2 +from sc2.position import Point2, _PointLike class PixelMap: @@ -43,13 +43,14 @@ def bits_per_pixel(self) -> int: def bytes_per_pixel(self) -> int: return self._proto.bits_per_pixel // 8 - def __getitem__(self, pos: tuple[int, int]) -> int: + def __getitem__(self, pos: _PointLike) -> int: """Example usage: is_pathable = self._game_info.pathing_grid[Point2((20, 20))] != 0""" assert 0 <= pos[0] < self.width, f"x is {pos[0]}, self.width is {self.width}" assert 0 <= pos[1] < self.height, f"y is {pos[1]}, self.height is {self.height}" + # pyrefly: ignore return int(self.data_numpy[pos[1], pos[0]]) - def __setitem__(self, pos: tuple[int, int], value: int) -> None: + def __setitem__(self, pos: _PointLike, value: int) -> None: """Example usage: self._game_info.pathing_grid[Point2((20, 20))] = 255""" assert 0 <= pos[0] < self.width, f"x is {pos[0]}, self.width is {self.width}" assert 0 <= pos[1] < self.height, f"y is {pos[1]}, self.height is {self.height}" @@ -57,6 +58,7 @@ def __setitem__(self, pos: tuple[int, int], value: int) -> None: f"value is {value}, it should be between 0 and {254 * self._in_bits + 1}" ) assert isinstance(value, int), f"value is of type {type(value)}, it should be an integer" + # pyrefly: ignore self.data_numpy[pos[1], pos[0]] = value def is_set(self, p: tuple[int, int]) -> bool: @@ -70,7 +72,8 @@ def copy(self) -> PixelMap: def flood_fill(self, start_point: Point2, pred: Callable[[int], bool]) -> set[Point2]: nodes: set[Point2] = set() - queue: list[Point2] = [start_point] + # pyrefly: ignore + queue: list[tuple[int, int]] = [start_point] while queue: x, y = queue.pop() @@ -83,7 +86,7 @@ def flood_fill(self, start_point: Point2, pred: Callable[[int], bool]) -> set[Po if pred(self[x, y]): nodes.add(Point2((x, y))) - queue += [Point2((x + a, y + b)) for a in [-1, 0, 1] for b in [-1, 0, 1] if not (a == 0 and b == 0)] + queue += [(x + a, y + b) for a in [-1, 0, 1] for b in [-1, 0, 1] if not (a == 0 and b == 0)] return nodes def flood_fill_all(self, pred: Callable[[int], bool]) -> set[frozenset[Point2]]: diff --git a/sc2/player.py b/sc2/player.py index bd1410a5..e624d41c 100644 --- a/sc2/player.py +++ b/sc2/player.py @@ -1,4 +1,3 @@ -# pyre-ignore-all-errors[6, 11, 16, 29] from __future__ import annotations from abc import ABC @@ -68,7 +67,7 @@ def __init__(self, race: Race, ai: BotAI, name: str | None = None, fullscreen: b """ assert isinstance(ai, BotAI) or ai is None, f"ai is of type {type(ai)}, inherit BotAI from bot_ai.py" super().__init__(PlayerType.Participant, race, name=name, fullscreen=fullscreen) - self.ai = ai + self.ai: BotAI = ai def __str__(self) -> str: if self.name is not None: @@ -83,7 +82,9 @@ def __init__( super().__init__(PlayerType.Computer, race, difficulty=difficulty, ai_build=ai_build) def __str__(self) -> str: - return f"Computer {self.difficulty._name_}({self.race._name_}, {self.ai_build.name})" + if self.ai_build is not None: + return f"Computer {self.difficulty._name_}({self.race._name_}, {self.ai_build.name})" + return f"Computer {self.difficulty._name_}({self.race._name_})" class Observer(AbstractPlayer): @@ -99,7 +100,8 @@ def __init__( self, player_id: int, p_type: PlayerType, - requested_race: Race, + # None in case of observer + requested_race: Race | None, difficulty: Difficulty | None = None, actual_race: Race | None = None, name: str | None = None, diff --git a/sc2/portconfig.py b/sc2/portconfig.py index 9041b90f..ca022c16 100644 --- a/sc2/portconfig.py +++ b/sc2/portconfig.py @@ -26,20 +26,20 @@ class Portconfig: """ def __init__( - self, guests: int = 1, server_ports: list[int] | None = None, player_ports: list[int] | None = None + self, guests: int = 1, server_ports: list[int] | None = None, player_ports: list[list[int]] | None = None ) -> None: self.shared = None self._picked_ports: list[int] = [] if server_ports: - self.server = server_ports + self.server: list[int] = server_ports else: self.server = [portpicker.pick_unused_port() for _ in range(2)] self._picked_ports.extend(self.server) if player_ports: - self.players = player_ports + self.players: list[list[int]] = player_ports else: self.players = [[portpicker.pick_unused_port() for _ in range(2)] for _ in range(guests)] - self._picked_ports.extend(port for player in self.players for port in player) + self._picked_ports.extend([port for player in self.players for port in player]) def clean(self) -> None: while self._picked_ports: diff --git a/sc2/position.py b/sc2/position.py index f3d9bd70..9ba15f93 100644 --- a/sc2/position.py +++ b/sc2/position.py @@ -43,8 +43,9 @@ def distance_to(self, target: _PosLike) -> float: """Calculate a single distance from a point or unit to another point or unit :param target:""" - p: tuple[float, ...] = target if isinstance(target, tuple) else target.position - return math.hypot(self[0] - p[0], self[1] - p[1]) + # pyrefly: ignore + position: tuple[float, ...] = target if isinstance(target, tuple) else target.position + return math.hypot(self[0] - position[0], self[1] - position[1]) def distance_to_point2(self, p: _PointLike) -> float: """Same as the function above, but should be a bit faster because of the dropped asserts @@ -82,6 +83,7 @@ def distance_to_closest(self, ps: Iterable[_TPosLike]) -> float: assert ps, "ps is empty" closest_distance = math.inf for p in ps: + # pyrefly: ignore p2: tuple[float, ...] = p if isinstance(p, tuple) else p.position distance = self.distance_to_point2(p2) if distance <= closest_distance: @@ -103,6 +105,7 @@ def distance_to_furthest(self, ps: Iterable[_PosLike]) -> float: assert ps, "ps is empty" furthest_distance = -math.inf for p in ps: + # pyrefly: ignore p2: tuple[float, ...] = p if isinstance(p, tuple) else p.position distance = self.distance_to_point2(p2) if distance >= furthest_distance: @@ -130,6 +133,7 @@ def towards(self: T, p: _PosLike, distance: float = 1, limit: bool = False) -> T :param distance: :param limit: """ + # pyrefly: ignore p2: tuple[float, ...] = p if isinstance(p, tuple) else p.position # assert self != p, f"self is {self}, p is {p}" # TODO test and fix this if statement @@ -284,7 +288,7 @@ def neighbors8(self: T) -> set[T]: def negative_offset(self: T, other: Point2) -> T: return self.__class__((self[0] - other[0], self[1] - other[1])) - def __add__(self, other: Point2) -> Point2: # pyright: ignore[reportIncompatibleMethodOverride] + def __add__(self, other: Point2) -> Point2: return self.offset(other) def __sub__(self, other: Point2) -> Point2: @@ -299,12 +303,12 @@ def __abs__(self) -> float: def __bool__(self) -> bool: return self[0] != 0 or self[1] != 0 - def __mul__(self, other: _PointLike | float) -> Point2: # pyright: ignore[reportIncompatibleMethodOverride] + def __mul__(self, other: _PointLike | float) -> Point2: if isinstance(other, (int, float)): return Point2((self[0] * other, self[1] * other)) return Point2((self[0] * other[0], self[1] * other[1])) - def __rmul__(self, other: _PointLike | float) -> Point2: # pyright: ignore[reportIncompatibleMethodOverride] + def __rmul__(self, other: _PointLike | float) -> Point2: return self.__mul__(other) def __truediv__(self, other: float | Point2) -> Point2: @@ -338,7 +342,7 @@ def center(points: list[Point2]) -> Point2: class Point3(Point2): @classmethod - def from_proto(cls, data: common_pb.Point | Point3) -> Point3: # pyright: ignore[reportIncompatibleMethodOverride] + def from_proto(cls, data: common_pb.Point | Point3) -> Point3: """ :param data: """ @@ -387,7 +391,7 @@ def height(self) -> float: class Rect(Point2): @classmethod - def from_proto(cls, data: common_pb.RectangleI) -> Rect: # pyright: ignore[reportIncompatibleMethodOverride] + def from_proto(cls, data: common_pb.RectangleI) -> Rect: """ :param data: """ @@ -425,7 +429,7 @@ def size(self) -> Size: return Size((self[2], self[3])) @property - def center(self) -> Point2: # pyright: ignore[reportIncompatibleMethodOverride] + def center(self) -> Point2: return Point2((self.x + self.width / 2, self.y + self.height / 2)) def offset(self, p: _PointLike) -> Rect: diff --git a/sc2/protocol.py b/sc2/protocol.py index 2722abe0..d2fae1aa 100644 --- a/sc2/protocol.py +++ b/sc2/protocol.py @@ -113,7 +113,7 @@ async def _execute(self, debug: sc_pb.RequestDebug) -> sc_pb.Response: ... async def _execute(self, **kwargs) -> sc_pb.Response: assert len(kwargs) == 1, "Only one request allowed by the API" - response = await self.__request(sc_pb.Request(**kwargs)) + response: sc_pb.Response = await self.__request(sc_pb.Request(**kwargs)) new_status = Status(response.status) if new_status != self._status: diff --git a/sc2/proxy.py b/sc2/proxy.py index 340570cd..4ea563f5 100644 --- a/sc2/proxy.py +++ b/sc2/proxy.py @@ -1,10 +1,10 @@ -# pyre-ignore-all-errors[16, 29] from __future__ import annotations import asyncio import os import platform import subprocess +import sys import time import traceback from pathlib import Path @@ -59,7 +59,8 @@ async def parse_request(self, msg) -> None: elif self.controller._status == Status.ended: await self.get_response() elif request.HasField("join_game") and not request.join_game.HasField("player_name"): - request.join_game.player_name = self.player.name + if self.player.name is not None: + request.join_game.player_name = self.player.name await self.controller._ws.send_bytes(request.SerializeToString()) # TODO Catching too general exception Exception (broad-except) @@ -175,8 +176,9 @@ async def play_with_proxy(self, startport): subproc_args = {"cwd": str(self.player.path), "stderr": subprocess.STDOUT} if platform.system() == "Linux": + # pyrefly: ignore subproc_args["preexec_fn"] = os.setpgrp - elif platform.system() == "Windows": + elif platform.system() == "Windows" and sys.platform == "win32": subproc_args["creationflags"] = subprocess.CREATE_NEW_PROCESS_GROUP player_command_line = self.player.cmd_line(self.port, startport, self.controller._process._host, self.realtime) diff --git a/sc2/renderer.py b/sc2/renderer.py index 17e3599e..7b7d1165 100644 --- a/sc2/renderer.py +++ b/sc2/renderer.py @@ -1,5 +1,6 @@ from __future__ import annotations + import datetime from typing import TYPE_CHECKING @@ -9,23 +10,26 @@ if TYPE_CHECKING: from sc2.client import Client + from pyglet.image import ImageData + from pyglet.text import Label + from pyglet.window import Window class Renderer: def __init__(self, client: Client, map_size: tuple[float, float], minimap_size: tuple[float, float]) -> None: self._client = client - self._window = None + self._window: Window = None # pyrefly: ignore self._map_size = map_size - self._map_image = None + self._map_image: ImageData = None # pyrefly: ignore self._minimap_size = minimap_size - self._minimap_image = None + self._minimap_image: ImageData = None # pyrefly: ignore self._mouse_x, self._mouse_y = None, None - self._text_supply = None - self._text_vespene = None - self._text_minerals = None - self._text_score = None - self._text_time = None + self._text_supply: Label = None # pyrefly: ignore + self._text_vespene: Label = None # pyrefly: ignore + self._text_minerals: Label = None # pyrefly: ignore + self._text_score: Label = None # pyrefly: ignore + self._text_time: Label = None # pyrefly: ignore async def render(self, observation: ResponseObservation) -> None: render_data = observation.observation.render_data @@ -47,11 +51,11 @@ async def render(self, observation: ResponseObservation) -> None: from pyglet.window import Window self._window = Window(width=map_width, height=map_height) - # pyre-fixme[16] + # pyrefly: ignore self._window.on_mouse_press = self._on_mouse_press - # pyre-fixme[16] + # pyrefly: ignore self._window.on_mouse_release = self._on_mouse_release - # pyre-fixme[16] + # pyrefly: ignore self._window.on_mouse_drag = self._on_mouse_drag self._map_image = ImageData(map_width, map_height, "RGB", map_data, map_pitch) self._minimap_image = ImageData(minimap_width, minimap_height, "RGB", minimap_data, minimap_pitch) @@ -114,6 +118,7 @@ async def render(self, observation: ResponseObservation) -> None: self._text_vespene.text = str(observation.observation.player_common.vespene) self._text_minerals.text = str(observation.observation.player_common.minerals) if observation.observation.HasField("score"): + # pyrefly: ignore self._text_score.text = f"{score_pb._SCORE_SCORETYPE.values_by_number[observation.observation.score.score_type].name} score: {observation.observation.score.score}" await self._update_window() diff --git a/sc2/sc2process.py b/sc2/sc2process.py index 391d30b1..0017c01d 100644 --- a/sc2/sc2process.py +++ b/sc2/sc2process.py @@ -132,9 +132,9 @@ def versions(self): def find_data_hash(self, target_sc2_version: str) -> str | None: """Returns the data hash from the matching version string.""" - version: dict for version in self.versions: if version["label"] == target_sc2_version: + # pyrefly: ignore return version["data-hash"] return None @@ -154,7 +154,7 @@ def _launch(self): else: executable = str(Paths.EXECUTABLE) - if self._port is None: + if self._port == -1: self._port = portpicker.pick_unused_port() self._used_portpicker = True args = paths.get_runner_args(Paths.CWD) + [ @@ -222,6 +222,7 @@ async def _connect(self) -> ClientWebSocketResponse: await asyncio.sleep(1) try: self._session = aiohttp.ClientSession() + # pyrefly: ignore ws = await self._session.ws_connect(self.ws_url, timeout=120) # FIXME fix deprecation warning in for future aiohttp version # ws = await self._session.ws_connect( @@ -229,8 +230,9 @@ async def _connect(self) -> ClientWebSocketResponse: # ) logger.debug("Websocket connection ready") return ws - except aiohttp.client_exceptions.ClientConnectorError: - await self._session.close() + except aiohttp.ClientConnectorError: + if self._session is not None: + await self._session.close() if i > 15: logger.debug("Connection refused (startup not complete (yet))") @@ -266,6 +268,7 @@ def _clean(self, verbose: bool = True) -> None: self._process.wait() logger.error("KILLED") # Try to kill wineserver on linux + # pyrefly: ignore if paths.PF in {"Linux", "WineLinux"}: # Command wineserver not detected with suppress(FileNotFoundError), subprocess.Popen(["wineserver", "-k"]) as p: @@ -278,6 +281,6 @@ def _clean(self, verbose: bool = True) -> None: self._ws = None if self._used_portpicker and self._port is not None: portpicker.return_port(self._port) - self._port = None + self._port = -1 if verbose: logger.info("Cleanup complete") diff --git a/sc2/score.py b/sc2/score.py index aba9c8ff..82e8a823 100644 --- a/sc2/score.py +++ b/sc2/score.py @@ -13,7 +13,7 @@ def __init__(self, proto: score_pb2.Score) -> None: self._proto = proto.score_details @property - def summary(self) -> list[list[int | float]]: + def summary(self) -> list[list[float]]: """ TODO this is super ugly, how can we improve this summary? Print summary to file with: @@ -105,6 +105,7 @@ def summary(self) -> list[list[int | float]]: "current_apm", "current_effective_apm", ] + # pyrefly: ignore return [[value, getattr(self, value)] for value in values] @property diff --git a/sc2/unit.py b/sc2/unit.py index 236116f9..73c519b0 100644 --- a/sc2/unit.py +++ b/sc2/unit.py @@ -1,4 +1,3 @@ -# pyre-ignore-all-errors[11, 16, 29] from __future__ import annotations import math @@ -53,7 +52,7 @@ UNIT_PHOTONCANNON, transforming, ) -from sc2.data import Alliance, Attribute, CloakState, Race, Target, race_gas, warpgate_abilities +from sc2.data import Attribute, CloakState, Race, Target, race_gas, warpgate_abilities from sc2.ids.ability_id import AbilityId from sc2.ids.buff_id import BuffId from sc2.ids.unit_typeid import UnitTypeId @@ -63,7 +62,6 @@ if TYPE_CHECKING: from sc2.bot_ai import BotAI - from sc2.bot_ai_internal import BotAIInternal from sc2.game_data import AbilityData, UnitTypeData @@ -109,7 +107,7 @@ class Unit(HasPosition2D): def __init__( self, proto_data: raw_pb2.Unit, - bot_object: BotAI | BotAIInternal, + bot_object: BotAI, distance_calculation_index: int = -1, base_build: int = -1, ) -> None: @@ -287,7 +285,7 @@ def air_range(self) -> float: return 0 @cached_property - def bonus_damage(self) -> tuple[int, str] | None: + def bonus_damage(self) -> tuple[float, str] | None: """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). @@ -472,7 +470,8 @@ def is_snapshot(self) -> bool: 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 + # pyrefly: ignore + position: tuple[int, int] = self.position.rounded return self._bot_object.state.visibility.data_numpy[position[1], position[0]] != 2 @cached_property @@ -504,7 +503,7 @@ def is_placeholder(self) -> bool: return self._proto.display_type == IS_PLACEHOLDER @property - def alliance(self) -> Alliance: + def alliance(self) -> int: """Returns the team the unit belongs to.""" return self._proto.alliance @@ -529,7 +528,7 @@ def position_tuple(self) -> tuple[float, float]: return self._proto.pos.x, self._proto.pos.y @cached_property - def position(self) -> Point2: # pyright: ignore[reportIncompatibleMethodOverride] + def position(self) -> Point2: """Returns the 2d position of the unit.""" return Point2.from_proto(self._proto.pos) @@ -594,9 +593,7 @@ def in_ability_cast_range(self, ability_id: AbilityId, target: Unit | Point2, bo <= (cast_range + self.radius + target.radius + bonus_distance) ** 2 ) # For casting abilities on the ground, like queen creep tumor, ravager bile, HT storm - if ability_target_type in {Target.Point.value, Target.PointOrUnit.value} and isinstance( - target, (Point2, tuple) - ): + if ability_target_type in {Target.Point.value, Target.PointOrUnit.value} and isinstance(target, Point2): return ( self._bot_object._distance_pos_to_pos(self.position_tuple, target) <= cast_range + self.radius + bonus_distance @@ -1028,6 +1025,7 @@ def buff_duration_max(self) -> int: def orders(self) -> list[UnitOrder]: """Returns the a list of the current orders.""" # TODO: add examples on how to use unit orders + # pyrefly: ignore return [UnitOrder.from_proto(order, self._bot_object) for order in self._proto.orders] @cached_property @@ -1154,6 +1152,7 @@ def add_on_position(self) -> Point2: @cached_property def passengers(self) -> set[Unit]: """Returns the units inside a Bunker, CommandCenter, PlanetaryFortress, Medivac, Nydus, Overlord or WarpPrism.""" + # pyrefly: ignore return {Unit(unit, self._bot_object) for unit in self._proto.passengers} @cached_property @@ -1259,8 +1258,12 @@ def train( :param queue: :param can_afford_check: """ + creation_ability = self._bot_object.game_data.units[unit.value].creation_ability + if creation_ability is None: + return False + return self( - self._bot_object.game_data.units[unit.value].creation_ability.id, + creation_ability.id, queue=queue, subtract_cost=True, can_afford_check=can_afford_check, @@ -1279,7 +1282,7 @@ def build( SCV.build(COMMANDCENTER, position) hatchery.build(UnitTypeId.LAIR) # Target for refinery, assimilator and extractor needs to be the vespene geysir unit, not its position - SCV.build(REFINERY, target_vespene_geysir) + SCV.build(REFINERY, target_vespene_geyser) :param unit: :param position: @@ -1290,8 +1293,11 @@ def build( assert isinstance(position, Unit), ( "When building the gas structure, the target needs to be a unit (the vespene geysir) not the position of the vespene geysir." ) + creation_ability = self._bot_object.game_data.units[unit.value].creation_ability + if creation_ability is None: + return False return self( - self._bot_object.game_data.units[unit.value].creation_ability.id, + creation_ability.id, target=position, queue=queue, subtract_cost=True, @@ -1308,7 +1314,7 @@ def build_gas( Usage:: # Target for refinery, assimilator and extractor needs to be the vespene geysir unit, not its position - SCV.build_gas(target_vespene_geysir) + SCV.build_gas(target_vespene_geyser) :param target_geysir: :param queue: @@ -1318,8 +1324,11 @@ def build_gas( assert isinstance(target_geysir, Unit), ( "When building the gas structure, the target needs to be a unit (the vespene geysir) not the position of the vespene geysir." ) + creation_ability = self._bot_object.game_data.units[gas_structure_type_id.value].creation_ability + if creation_ability is None: + return False return self( - self._bot_object.game_data.units[gas_structure_type_id.value].creation_ability.id, + creation_ability.id, target=target_geysir, queue=queue, subtract_cost=True, @@ -1339,8 +1348,11 @@ def research( :param queue: :param can_afford_check: """ + research_ability = self._bot_object.game_data.upgrades[upgrade.value].research_ability + if research_ability is None: + return False return self( - self._bot_object.game_data.upgrades[upgrade.value].research_ability.exact_id, + research_ability.exact_id, queue=queue, subtract_cost=True, can_afford_check=can_afford_check, @@ -1358,9 +1370,11 @@ def warp_in( :param queue: :param can_afford_check: """ - normal_creation_ability = self._bot_object.game_data.units[unit.value].creation_ability.id + creation_ability = self._bot_object.game_data.units[unit.value].creation_ability + if creation_ability is None: + return False return self( - warpgate_abilities[normal_creation_ability], + warpgate_abilities[creation_ability.id], target=position, subtract_cost=True, subtract_supply=True, diff --git a/sc2/units.py b/sc2/units.py index 1871dfc6..3e58b1d2 100644 --- a/sc2/units.py +++ b/sc2/units.py @@ -1,4 +1,3 @@ -# pyre-ignore-all-errors[14, 15, 16] from __future__ import annotations import random diff --git a/sc2/versions.py b/sc2/versions.py index 96c2f35f..5292f233 100644 --- a/sc2/versions.py +++ b/sc2/versions.py @@ -1,4 +1,6 @@ -VERSIONS = [ +from __future__ import annotations + +VERSIONS: list[dict[str, int | str]] = [ { "base-version": 52910, "data-hash": "8D9FEF2E1CF7C6C9CBE4FBCA830DDE1C", diff --git a/sc2/wsl.py b/sc2/wsl.py index 4f2a3cdd..dcdfecec 100644 --- a/sc2/wsl.py +++ b/sc2/wsl.py @@ -93,10 +93,11 @@ def detect() -> str | None: # Unix-style newlines for safety's sake. lines = re.sub(r"\000|\r", "", wsl_proc.stdout.decode("utf-8")).split("\n") - def line_has_proc(ln): - return re.search("^\\s*[*]?\\s+" + wsl_name, ln) + def line_has_proc(ln: str): + if wsl_name is not None: + return re.search("^\\s*[*]?\\s+" + wsl_name, ln) - def line_version(ln): + def line_version(ln: str): return re.sub("^.*\\s+(\\d+)\\s*$", "\\1", ln) versions = [line_version(ln) for ln in lines if line_has_proc(ln)] diff --git a/test/autotest_bot.py b/test/autotest_bot.py index a6a0dc23..7d3ebbbf 100644 --- a/test/autotest_bot.py +++ b/test/autotest_bot.py @@ -216,7 +216,7 @@ async def test_botai_actions4(self): async def test_botai_actions5(self): # Test BotAI action: self.expand_now() which tests for get_next_expansion, select_build_worker, can_place, find_placement, build and can_afford # Wait till worker has started construction of CC - while 1: + while True: if self.can_afford(UnitTypeId.COMMANDCENTER): await self.get_next_expansion() await self.expand_now() @@ -241,9 +241,9 @@ async def test_botai_actions6(self): # Test if reaper grenade shows up in effects center = self.game_info.map_center - while 1: + while True: if self.units(UnitTypeId.REAPER).amount < 10: - await self.client.debug_create_unit([[UnitTypeId.REAPER, 10, center, 1]]) + await self.client.debug_create_unit([(UnitTypeId.REAPER, 10, center, 1)]) for reaper in self.units(UnitTypeId.REAPER): reaper(AbilityId.KD8CHARGE_KD8CHARGE, center) @@ -266,9 +266,9 @@ async def test_botai_actions6(self): async def test_botai_actions7(self): # Test ravager effects center = self.game_info.map_center - while 1: + while True: if self.units(UnitTypeId.RAVAGER).amount < 10: - await self.client.debug_create_unit([[UnitTypeId.RAVAGER, 10, center, 1]]) + await self.client.debug_create_unit([(UnitTypeId.RAVAGER, 10, center, 1)]) for ravager in self.units(UnitTypeId.RAVAGER): ravager(AbilityId.EFFECT_CORROSIVEBILE, center) @@ -291,15 +291,15 @@ async def test_botai_actions8(self): # Test if train function works on hatchery, lair, hive center = self.game_info.map_center if not self.structures(UnitTypeId.HIVE): - await self.client.debug_create_unit([[UnitTypeId.HIVE, 1, center, 1]]) + await self.client.debug_create_unit([(UnitTypeId.HIVE, 1, center, 1)]) if not self.structures(UnitTypeId.LAIR): - await self.client.debug_create_unit([[UnitTypeId.LAIR, 1, center, 1]]) + await self.client.debug_create_unit([(UnitTypeId.LAIR, 1, center, 1)]) if not self.structures(UnitTypeId.HATCHERY): - await self.client.debug_create_unit([[UnitTypeId.HATCHERY, 1, center, 1]]) + await self.client.debug_create_unit([(UnitTypeId.HATCHERY, 1, center, 1)]) if not self.structures(UnitTypeId.SPAWNINGPOOL): - await self.client.debug_create_unit([[UnitTypeId.SPAWNINGPOOL, 1, center, 1]]) + await self.client.debug_create_unit([(UnitTypeId.SPAWNINGPOOL, 1, center, 1)]) - while 1: + while True: townhalls = self.structures.of_type({UnitTypeId.HIVE, UnitTypeId.LAIR, UnitTypeId.HATCHERY}) if townhalls.amount == 3 and self.minerals >= 450 and not self.already_pending(UnitTypeId.QUEEN): self.train(UnitTypeId.QUEEN, amount=3) @@ -324,14 +324,14 @@ async def test_botai_actions9(self): center = self.game_info.map_center await self.client.debug_create_unit( [ - [UnitTypeId.HIGHTEMPLAR, 1, center, 1], - [UnitTypeId.DARKTEMPLAR, 1, center + Point2((5, 0)), 1], + (UnitTypeId.HIGHTEMPLAR, 1, center, 1), + (UnitTypeId.DARKTEMPLAR, 1, center + Point2((5, 0)), 1), ] ) await self._advance_steps(4) assert self.already_pending(UnitTypeId.ARCHON) == 0 - while 1: + while True: for templar in self.units.of_type({UnitTypeId.HIGHTEMPLAR, UnitTypeId.DARKTEMPLAR}): templar(AbilityId.MORPH_ARCHON) @@ -365,7 +365,7 @@ async def test_botai_actions10(self): center = self.game_info.map_center target_amount = 400 - while 1: + while True: bane_nests = self.structures(UnitTypeId.BANELINGNEST) lings = self.units(UnitTypeId.ZERGLING) banes = self.units(UnitTypeId.BANELING) @@ -377,10 +377,10 @@ async def test_botai_actions10(self): # Spawn units if not bane_nests: - await self.client.debug_create_unit([[UnitTypeId.BANELINGNEST, 1, center, 1]]) + await self.client.debug_create_unit([(UnitTypeId.BANELINGNEST, 1, center, 1)]) current_amount = banes.amount + bane_cocoons.amount + lings.amount if current_amount < target_amount: - await self.client.debug_create_unit([[UnitTypeId.ZERGLING, target_amount - current_amount, center, 1]]) + await self.client.debug_create_unit([(UnitTypeId.ZERGLING, target_amount - current_amount, center, 1)]) if lings.amount >= target_amount and self.minerals >= 10_000 and self.vespene >= 10_000: for ling in lings: @@ -408,11 +408,11 @@ async def test_botai_actions11(self): map_center = self.game_info.map_center while not self.units(UnitTypeId.RAVEN): - await self.client.debug_create_unit([[UnitTypeId.RAVEN, 1, map_center, 1]]) + await self.client.debug_create_unit([(UnitTypeId.RAVEN, 1, map_center, 1)]) await self._advance_steps(2) while not self.enemy_units(UnitTypeId.INFESTOR): - await self.client.debug_create_unit([[UnitTypeId.INFESTOR, 1, map_center, 2]]) + await self.client.debug_create_unit([(UnitTypeId.INFESTOR, 1, map_center, 2)]) await self._advance_steps(2) raven = self.units(UnitTypeId.RAVEN)[0] @@ -421,7 +421,7 @@ async def test_botai_actions11(self): await self._advance_steps(4) enemy = self.enemy_units(UnitTypeId.INFESTOR)[0] - while 1: + while True: raven = self.units(UnitTypeId.RAVEN)[0] raven(AbilityId.EFFECT_ANTIARMORMISSILE, enemy) await self._advance_steps(2) @@ -440,7 +440,7 @@ async def test_botai_actions12(self): await self.client.debug_all_resources() await self._advance_steps(2) - while 1: + while True: # Once depot is under construction: debug kill scv -> advance simulation: should now match the test case if self.structures(UnitTypeId.SUPPLYDEPOT).not_ready.amount == 1: construction_scvs: Units = self.workers.filter(lambda worker: worker.is_constructing_scv) @@ -484,7 +484,6 @@ async def on_start(self): await self.client.debug_kill_unit(self.units) async def on_step(self, iteration: int): - # pyre-ignore[16] map_center = self.game_info.map_center enemies = self.enemy_units | self.enemy_structures if enemies: diff --git a/test/battery_overcharge_bot.py b/test/battery_overcharge_bot.py index d948244a..22bb9add 100644 --- a/test/battery_overcharge_bot.py +++ b/test/battery_overcharge_bot.py @@ -18,9 +18,9 @@ async def on_start(self): """Spawn requires structures.""" await self.client.debug_create_unit( [ - [UnitTypeId.PYLON, 1, self.start_location.towards(self.game_info.map_center, 5), 1], - [UnitTypeId.SHIELDBATTERY, 1, self.start_location.towards(self.game_info.map_center, 5), 1], - [UnitTypeId.CYBERNETICSCORE, 1, self.start_location.towards(self.game_info.map_center, 5), 1], + (UnitTypeId.PYLON, 1, self.start_location.towards(self.game_info.map_center, 5), 1), + (UnitTypeId.SHIELDBATTERY, 1, self.start_location.towards(self.game_info.map_center, 5), 1), + (UnitTypeId.CYBERNETICSCORE, 1, self.start_location.towards(self.game_info.map_center, 5), 1), ] ) diff --git a/test/benchmark_distance_two_points.py b/test/benchmark_distance_two_points.py index 9527a107..dbfe10af 100644 --- a/test/benchmark_distance_two_points.py +++ b/test/benchmark_distance_two_points.py @@ -1,4 +1,3 @@ -# pyre-ignore-all-errors[21] from __future__ import annotations import math @@ -47,7 +46,7 @@ def distance_numpy_linalg_norm(p1, p2): def distance_sum_squared_sqrt(p1, p2) -> int | float: """Distance calculation using numpy""" - return np.sqrt(np.sum((p1 - p2) ** 2)) + return np.sqrt(np.sum((p1 - p2) ** 2)) # pyrefly: ignore def distance_sum_squared(p1, p2) -> int | float: diff --git a/test/benchmark_distances_cdist.py b/test/benchmark_distances_cdist.py index fdcfd7b8..8d02d083 100644 --- a/test/benchmark_distances_cdist.py +++ b/test/benchmark_distances_cdist.py @@ -1,4 +1,3 @@ -# pyre-ignore-all-errors[21] import random import numpy as np @@ -57,7 +56,7 @@ def distance_matrix_scipy_cdist_mahalanobis(ps): def distance_matrix_scipy_cdist_matching(ps): # Calculate distances between each of the points - return cdist(ps, ps, "matching") + return cdist(ps, ps, "matching") # pyrefly: ignore # def distance_matrix_scipy_cdist_minkowski(ps): diff --git a/test/benchmark_distances_points_to_point.py b/test/benchmark_distances_points_to_point.py index cd36c8d8..f04d5849 100644 --- a/test/benchmark_distances_points_to_point.py +++ b/test/benchmark_distances_points_to_point.py @@ -1,4 +1,3 @@ -# pyre-ignore-all-errors[21] from __future__ import annotations import math diff --git a/test/benchmark_distances_units.py b/test/benchmark_distances_units.py index 11d81462..c281045a 100644 --- a/test/benchmark_distances_units.py +++ b/test/benchmark_distances_units.py @@ -1,4 +1,3 @@ -# pyre-ignore-all-errors[21] import math import random diff --git a/test/damagetest_bot.py b/test/damagetest_bot.py index 962c2bd8..6d0f8318 100644 --- a/test/damagetest_bot.py +++ b/test/damagetest_bot.py @@ -231,8 +231,6 @@ def do_some_unit_property_tests(attacker: Unit, defender: Unit): await self.clean_up_center() - attacker: Unit - defender: Unit for upgrade_level in upgrade_levels: if upgrade_level != 0: await self.client.debug_upgrade() @@ -252,7 +250,7 @@ def do_some_unit_property_tests(attacker: Unit, defender: Unit): # Spawn units await self.client.debug_create_unit( - [[attacker_type, 1, map_center, 1], [defender_type, 1, map_center, 2]] + [(attacker_type, 1, map_center, 1), (defender_type, 1, map_center, 2)] ) await self._advance_steps(1) @@ -292,8 +290,10 @@ def do_some_unit_property_tests(attacker: Unit, defender: Unit): await self._advance_steps(1) # Unsure why I have to recalculate this here again but it prevents a bug attacker, defender = get_attacker_and_defender() + # pyrefly: ignore expected_damage: float = max(expected_damage, attacker.calculate_damage_vs_target(defender)[0]) real_damage = math.ceil( + # pyrefly: ignore defender.health_max + defender.shield_max - defender.health - defender.shield ) # logger.info( @@ -304,6 +304,7 @@ def do_some_unit_property_tests(attacker: Unit, defender: Unit): f"Step limit reached. Test timed out for attacker {attacker_type} and defender {defender_type}" ) assert expected_damage == real_damage, ( + # pyrefly: ignore f"Expected damage does not match real damage: Unit type {attacker_type} (attack upgrade: {attacker.attack_upgrade_level}) deals {real_damage} damage against {defender_type} (armor upgrade: {defender.armor_upgrade_level} and shield upgrade: {defender.shield_upgrade_level}) but calculated damage was {expected_damage}, attacker weapons: \n{attacker._weapons}" ) @@ -321,7 +322,6 @@ async def on_start(self): await self.client.debug_kill_unit(self.units) async def on_step(self, iteration: int): - # pyre-ignore[16] map_center = self.game_info.map_center enemies = self.enemy_units | self.enemy_structures if enemies: diff --git a/test/generate_pickle_files_bot.py b/test/generate_pickle_files_bot.py index 3b4158ee..d8b3296d 100644 --- a/test/generate_pickle_files_bot.py +++ b/test/generate_pickle_files_bot.py @@ -1,4 +1,3 @@ -# pyre-ignore-all-errors[16] """ This "bot" will loop over several available ladder maps and generate the pickle file in the "/test/pickle_data/" subfolder. These will then be used to run tests from the test script "test_pickled_data.py" @@ -26,7 +25,7 @@ class ExporterBot(BotAI): def __init__(self): BotAI.__init__(self) - self.map_name: str = None + self.map_name: str = None # pyrefly: ignore async def on_step(self, iteration): pass @@ -45,7 +44,7 @@ def get_combat_file_path(self) -> Path: file_path = folder_path / subfolder_name / file_name return file_path - async def store_data_to_file(self, file_path: str): + async def store_data_to_file(self, file_path: Path): # Grab all raw data from observation raw_game_data = await self.client._execute( data=sc_pb.RequestData(ability_id=True, unit_type_id=True, upgrade_id=True, buff_id=True, effect_id=True) @@ -87,10 +86,10 @@ async def on_start(self): } # Create units for self - await self.client.debug_create_unit([[valid_unit, 1, self.start_location, 1] for valid_unit in valid_units]) + await self.client.debug_create_unit([(valid_unit, 1, self.start_location, 1) for valid_unit in valid_units]) # Create units for enemy await self.client.debug_create_unit( - [[valid_unit, 1, self.enemy_start_locations[0], 2] for valid_unit in valid_units] + [(valid_unit, 1, self.enemy_start_locations[0], 2) for valid_unit in valid_units] ) await self._advance_steps(2) diff --git a/test/queries_test_bot.py b/test/queries_test_bot.py index 72c3c7f8..bb029e84 100644 --- a/test/queries_test_bot.py +++ b/test/queries_test_bot.py @@ -1,4 +1,3 @@ -# pyre-ignore-all-errors[16] """ This testbot's purpose is to test the query behavior of the API. These query functions are: @@ -48,7 +47,7 @@ async def clear_map_center(self): map_center = self.game_info.map_center # Spawn observer to be able to see enemy invisible units - await self.client.debug_create_unit([[UnitTypeId.OBSERVER, 1, map_center, 1]]) + await self.client.debug_create_unit([(UnitTypeId.OBSERVER, 1, map_center, 1)]) await self._advance_steps(10) # Remove everything close to map center @@ -72,7 +71,7 @@ async def spawn_unit(self, unit_type: UnitTypeId | list[UnitTypeId]): if not isinstance(unit_type, list): unit_type = [unit_type] for i in unit_type: - await self.client.debug_create_unit([[i, 1, self.game_info.map_center, 1]]) + await self.client.debug_create_unit([(i, 1, self.game_info.map_center, 1)]) async def spawn_unit_enemy(self, unit_type: UnitTypeId | list[UnitTypeId]): await self._advance_steps(10) @@ -80,9 +79,9 @@ async def spawn_unit_enemy(self, unit_type: UnitTypeId | list[UnitTypeId]): unit_type = [unit_type] for i in unit_type: if i == UnitTypeId.CREEPTUMOR: - await self.client.debug_create_unit([[i, 1, self.game_info.map_center + Point2((5, 5)), 2]]) + await self.client.debug_create_unit([(i, 1, self.game_info.map_center + Point2((5, 5)), 2)]) else: - await self.client.debug_create_unit([[i, 1, self.game_info.map_center, 2]]) + await self.client.debug_create_unit([(i, 1, self.game_info.map_center, 2)]) async def run_can_place(self) -> bool: result = await self.can_place(AbilityId.TERRANBUILD_COMMANDCENTER, [self.game_info.map_center]) @@ -195,7 +194,7 @@ async def test_rally_points_with_rally_ability(self): map_center = self.game_info.map_center barracks_spawn_point = map_center.offset(Point2((10, 10))) await self.client.debug_create_unit( - [[UnitTypeId.BARRACKS, 2, barracks_spawn_point, 1], [UnitTypeId.FACTORY, 2, barracks_spawn_point, 1]] + [(UnitTypeId.BARRACKS, 2, barracks_spawn_point, 1), (UnitTypeId.FACTORY, 2, barracks_spawn_point, 1)] ) await self._advance_steps(10) @@ -221,7 +220,7 @@ async def test_rally_points_with_smart_ability(self): map_center = self.game_info.map_center barracks_spawn_point = map_center.offset(Point2((10, 10))) await self.client.debug_create_unit( - [[UnitTypeId.BARRACKS, 2, barracks_spawn_point, 1], [UnitTypeId.FACTORY, 2, barracks_spawn_point, 1]] + [(UnitTypeId.BARRACKS, 2, barracks_spawn_point, 1), (UnitTypeId.FACTORY, 2, barracks_spawn_point, 1)] ) await self._advance_steps(10) diff --git a/test/real_time_worker_production.py b/test/real_time_worker_production.py index d272e0ea..062362ce 100644 --- a/test/real_time_worker_production.py +++ b/test/real_time_worker_production.py @@ -68,16 +68,16 @@ async def on_step(self, iteration): continue if self.enemy_structures.closer_than(10, expansion_location): continue - await self.client.debug_create_unit([[UnitTypeId.NEXUS, 1, expansion_location, 1]]) + await self.client.debug_create_unit([(UnitTypeId.NEXUS, 1, expansion_location, 1)]) logger.info( f"{self.time_formatted} {self.state.game_loop} Spawning a nexus {self.supply_used} / {self.supply_cap}" ) made_nexus = True - break + continue # Spawn new pylon in map center if no more expansions are available if self.supply_left == 0 and not made_nexus: - await self.client.debug_create_unit([[UnitTypeId.PYLON, 1, self.game_info.map_center, 1]]) + await self.client.debug_create_unit([(UnitTypeId.PYLON, 1, self.game_info.map_center, 1)]) # Don't get disturbed during this test if self.enemy_units: @@ -92,7 +92,6 @@ async def on_building_construction_complete(self, unit: Unit): if unit.is_structure: unit(AbilityId.RALLY_WORKERS, self.mineral_field.closest_to(unit)) - # pyre-ignore[11] async def on_end(self, game_result: Result): global on_end_was_called on_end_was_called = True diff --git a/test/run_example_bots_vs_computer.py b/test/run_example_bots_vs_computer.py index 9cb15576..3291c13e 100644 --- a/test/run_example_bots_vs_computer.py +++ b/test/run_example_bots_vs_computer.py @@ -1,4 +1,3 @@ -# pyre-ignore-all-errors[16] """ This script makes sure to run all bots in the examples folder to check if they can launch. """ @@ -115,10 +114,9 @@ # Run example bots for bot_info in bot_infos: - # pyre-ignore[11] - bot_race: Race = bot_info["race"] - bot_path: str = bot_info["path"] - bot_class_name: str = bot_info["bot_class_name"] + bot_race: Race = bot_info["race"] # pyrefly: ignore + bot_path: str = bot_info["path"] # pyrefly: ignore + bot_class_name: str = bot_info["bot_class_name"] # pyrefly: ignore module = import_module(bot_path) bot_class: type[BotAI] = getattr(module, bot_class_name) diff --git a/test/run_example_bots_vs_each_other.py b/test/run_example_bots_vs_each_other.py index b70f3ccb..59aefe14 100644 --- a/test/run_example_bots_vs_each_other.py +++ b/test/run_example_bots_vs_each_other.py @@ -1,4 +1,3 @@ -# pyre-ignore-all-errors[16] """ This script makes sure to run all bots in the examples folder to check if they can launch against each other. """ @@ -96,16 +95,15 @@ # Run bots against each other for bot_info1, bot_info2 in combinations(bot_infos, 2): - # pyre-ignore[11] - bot_race1: Race = bot_info1["race"] - bot_path: str = bot_info1["path"] - bot_class_name: str = bot_info1["bot_class_name"] + bot_race1: Race = bot_info1["race"] # pyrefly: ignore + bot_path: str = bot_info1["path"] # pyrefly: ignore + bot_class_name: str = bot_info1["bot_class_name"] # pyrefly: ignore module = import_module(bot_path) bot_class1: type[BotAI] = getattr(module, bot_class_name) - bot_race2: Race = bot_info2["race"] - bot_path: str = bot_info2["path"] - bot_class_name: str = bot_info2["bot_class_name"] + bot_race2: Race = bot_info2["race"] # pyrefly: ignore + bot_path: str = bot_info2["path"] # pyrefly: ignore + bot_class_name: str = bot_info2["bot_class_name"] # pyrefly: ignore module = import_module(bot_path) bot_class2: type[BotAI] = getattr(module, bot_class_name) diff --git a/test/test_expiring_dict.py b/test/test_expiring_dict.py index 9fc6daa3..55324021 100644 --- a/test/test_expiring_dict.py +++ b/test/test_expiring_dict.py @@ -18,7 +18,7 @@ def increment(self, value=1): test_dict = {"hello": "its me mario", "does_this_work": "yes it works", "another_test": "yep this one also worked"} bot = BotAI() - test = ExpiringDict(bot, max_age_frames=10) + test = ExpiringDict(bot, max_age_frames=10) # pyrefly: ignore for key, value in test_dict.items(): test[key] = value @@ -50,8 +50,8 @@ def increment(self, value=1): assert key in test assert test[key] == item, (key, item) assert test.get(key) == item - assert test.get(key, with_age=True)[0] == item - assert test.get(key, with_age=True)[1] in {0, 1} + assert test.get(key, with_age=True)[0] == item # pyrefly: ignore + assert test.get(key, with_age=True)[1] in {0, 1} # pyrefly: ignore c = 0 for _key in test: @@ -65,7 +65,7 @@ def increment(self, value=1): # Update from another dict updater_dict = {"new_key": "my_new_value"} - test.update(updater_dict) + test.update(updater_dict) # pyrefly: ignore assert "does_this_work" in test assert "new_key" in test diff --git a/test/test_pickled_data.py b/test/test_pickled_data.py index 5e36038b..fac77b71 100644 --- a/test/test_pickled_data.py +++ b/test/test_pickled_data.py @@ -58,7 +58,7 @@ def build_bot_object_from_pickle_data(raw_game_data, raw_game_info, raw_observat game_info = GameInfo(raw_game_info.game_info) game_state = GameState(raw_observation) bot._initialize_variables() - client = Client(True) + client = Client(True) # pyrefly: ignore bot._prepare_start(client=client, player_id=1, game_info=game_info, game_data=game_data) bot._prepare_step(state=game_state, proto_game_info=raw_game_info) return bot @@ -121,7 +121,7 @@ def test_bot_ai(): assert bot.time == 0 assert bot.time_formatted in {"0:00", "00:00"} assert bot.start_location is None # Is populated by main.py - bot.game_info.player_start_location = bot.townhalls.random.position + bot.game_info.player_start_location = bot.townhalls.random.position # pyrefly: ignore assert bot.townhalls.random.position not in bot.enemy_start_locations assert bot.enemy_units == Units([], bot) assert bot.enemy_structures == Units([], bot) @@ -149,13 +149,13 @@ def test_bot_ai(): # Store old values for minerals, vespene old_values = bot.minerals, bot.vespene, bot.supply_cap, bot.supply_left, bot.supply_used - bot.vespene = 50 + bot.vespene = 50 # pyrefly: ignore assert bot.can_afford(UpgradeId.WARPGATERESEARCH) assert bot.can_afford(AbilityId.RESEARCH_WARPGATE) - bot.minerals = 150 - bot.supply_cap = 15 - bot.supply_left = -1 - bot.supply_used = 16 + bot.minerals = 150 # pyrefly: ignore + bot.supply_cap = 15 # pyrefly: ignore + bot.supply_left = -1 # pyrefly: ignore + bot.supply_used = 16 # pyrefly: ignore # Confirm that units that don't cost supply can be built while at negative supply using can_afford function assert bot.can_afford(UnitTypeId.GATEWAY) assert bot.can_afford(UnitTypeId.PYLON) @@ -163,6 +163,7 @@ def test_bot_ai(): assert bot.can_afford(UnitTypeId.BANELING) assert not bot.can_afford(UnitTypeId.ZERGLING) assert not bot.can_afford(UnitTypeId.MARINE) + # pyrefly: ignore bot.minerals, bot.vespene, bot.supply_cap, bot.supply_left, bot.supply_used = old_values worker = bot.workers.random @@ -288,13 +289,15 @@ def test_bot_ai(): def calc_cost(item_id) -> Cost: if isinstance(item_id, AbilityId): - # pyre-ignore[16] return bot.game_data.calculate_ability_cost(item_id) elif isinstance(item_id, UpgradeId): return bot.game_data.upgrades[item_id.value].cost elif isinstance(item_id, UnitTypeId): - creation_ability: AbilityId = bot.game_data.units[item_id.value].creation_ability.exact_id - return bot.game_data.calculate_ability_cost(creation_ability) + creation_ability = bot.game_data.units[item_id.value].creation_ability + if creation_ability is None: + return Cost(0, 0) + creation_ability_id = creation_ability.exact_id + return bot.game_data.calculate_ability_cost(creation_ability_id) return Cost(0, 0) def assert_cost(item_id, real_cost: Cost): @@ -487,11 +490,11 @@ def test_pixelmap(): pathing_grid: PixelMap = bot.game_info.pathing_grid assert pathing_grid.bits_per_pixel assert pathing_grid.bytes_per_pixel == pathing_grid.bits_per_pixel // 8 - assert not pathing_grid.is_set(Point2((0, 0))) - assert pathing_grid.is_empty(Point2((0, 0))) + assert not pathing_grid.is_set((0, 0)) + assert pathing_grid.is_empty((0, 0)) pathing_grid[Point2((0, 0))] = 123 - assert pathing_grid.is_set(Point2((0, 0))) - assert not pathing_grid.is_empty(Point2((0, 0))) + assert pathing_grid.is_set((0, 0)) + assert not pathing_grid.is_empty((0, 0)) pathing_grid.flood_fill_all(lambda i: True) pathing_grid.copy() pathing_grid.print() @@ -951,10 +954,12 @@ def test_exact_creation_ability(): UnitTypeId.REFINERYRICH, ]: with test_case.assertRaises(AttributeError): + # pyrefly: ignore _creation_ability = bot.game_data.units[unit_type.value].creation_ability.exact_id continue try: + # pyrefly: ignore _creation_ability = bot.game_data.units[unit_type.value].creation_ability.exact_id except AttributeError: if unit_type not in CREATION_ABILITY_FIX: @@ -971,12 +976,10 @@ def test_dicts(): bot: BotAI = get_map_specific_bot(random.choice(MAPS)) - unit_id: UnitTypeId - data: dict - for unit_id, data in RESEARCH_INFO.items(): + for data in RESEARCH_INFO.values(): upgrade_id: UpgradeId for upgrade_id, upgrade_data in data.items(): - research_ability_correct: AbilityId = upgrade_data["ability"] + research_ability_correct: AbilityId = upgrade_data["ability"] # pyrefly: ignore research_ability_data_from_api = bot.game_data.upgrades[upgrade_id.value].research_ability if research_ability_data_from_api is None: continue @@ -993,12 +996,12 @@ def test_dicts(): @given( - st.integers(min_value=-1e5, max_value=1e5), - st.integers(min_value=-1e5, max_value=1e5), - st.integers(min_value=-1e5, max_value=1e5), - st.integers(min_value=-1e5, max_value=1e5), - st.integers(min_value=-1e5, max_value=1e5), - st.integers(min_value=-1e5, max_value=1e5), + st.integers(min_value=-1e5, max_value=1e5), # pyrefly: ignore + st.integers(min_value=-1e5, max_value=1e5), # pyrefly: ignore + st.integers(min_value=-1e5, max_value=1e5), # pyrefly: ignore + st.integers(min_value=-1e5, max_value=1e5), # pyrefly: ignore + st.integers(min_value=-1e5, max_value=1e5), # pyrefly: ignore + st.integers(min_value=-1e5, max_value=1e5), # pyrefly: ignore ) @settings(max_examples=500) def test_position_pointlike(x1, y1, x2, y2, x3, y3): @@ -1060,10 +1063,10 @@ def test_position_pointlike(x1, y1, x2, y2, x3, y3): @given( - st.integers(min_value=-1e5, max_value=1e5), - st.integers(min_value=-1e5, max_value=1e5), - st.integers(min_value=-1e5, max_value=1e5), - st.integers(min_value=-1e5, max_value=1e5), + st.integers(min_value=-1e5, max_value=1e5), # pyrefly: ignore + st.integers(min_value=-1e5, max_value=1e5), # pyrefly: ignore + st.integers(min_value=-1e5, max_value=1e5), # pyrefly: ignore + st.integers(min_value=-1e5, max_value=1e5), # pyrefly: ignore ) @settings(max_examples=500) def test_position_point2(x1, y1, x2, y2): @@ -1118,9 +1121,9 @@ def test_position_point2(x1, y1, x2, y2): @given( - st.integers(min_value=-1e5, max_value=1e5), - st.integers(min_value=-1e5, max_value=1e5), - st.integers(min_value=-1e5, max_value=1e5), + st.integers(min_value=-1e5, max_value=1e5), # pyrefly: ignore + st.integers(min_value=-1e5, max_value=1e5), # pyrefly: ignore + st.integers(min_value=-1e5, max_value=1e5), # pyrefly: ignore ) @settings(max_examples=10) def test_position_point3(x1, y1, z1): @@ -1129,7 +1132,16 @@ def test_position_point3(x1, y1, z1): assert pos1.to3 == pos1 -@given(st.integers(min_value=-1e5, max_value=1e5), st.integers(min_value=-1e5, max_value=1e5)) +@given( + st.integers( + min_value=-1e5, # pyrefly: ignore + max_value=1e5, # pyrefly: ignore + ), + st.integers( + min_value=-1e5, # pyrefly: ignore + max_value=1e5, # pyrefly: ignore + ), +) @settings(max_examples=20) def test_position_size(w, h): size = Size((w, h)) @@ -1138,10 +1150,10 @@ def test_position_size(w, h): @given( - st.integers(min_value=-1e5, max_value=1e5), - st.integers(min_value=-1e5, max_value=1e5), - st.integers(min_value=-1e5, max_value=1e5), - st.integers(min_value=-1e5, max_value=1e5), + st.integers(min_value=-1e5, max_value=1e5), # pyrefly: ignore + st.integers(min_value=-1e5, max_value=1e5), # pyrefly: ignore + st.integers(min_value=-1e5, max_value=1e5), # pyrefly: ignore + st.integers(min_value=-1e5, max_value=1e5), # pyrefly: ignore ) @settings(max_examples=20) def test_position_rect(x, y, w, h): diff --git a/test/test_pickled_ramp.py b/test/test_pickled_ramp.py index 0695fe65..33161eff 100644 --- a/test/test_pickled_ramp.py +++ b/test/test_pickled_ramp.py @@ -14,7 +14,6 @@ from loguru import logger from sc2.game_info import Ramp -from sc2.ids.unit_typeid import UnitTypeId from sc2.position import Point2 from sc2.unit import Unit from sc2.units import Units @@ -25,6 +24,7 @@ def pytest_generate_tests(metafunc): idlist = [] argvalues = [] + argnames = [] for scenario in metafunc.cls.scenarios: idlist.append(scenario[0]) items = scenario[1].items() @@ -37,11 +37,11 @@ class TestClass: # Load all pickle files and convert them into bot objects from raw data (game_data, game_info, game_state) scenarios = [(map_path.name, {"map_path": map_path}) for map_path in MAPS] - MAPS_WITH_ODD_EXPANSION_COUNT: set[UnitTypeId] = {"Persephone AIE", "StargazersAIE", "Stasis LE"} + MAPS_WITH_ODD_EXPANSION_COUNT = {"Persephone AIE", "StargazersAIE", "Stasis LE"} def test_main_base_ramp(self, map_path: Path): bot = get_map_specific_bot(map_path) - # pyre-ignore[16] + bot.game_info.map_ramps, bot.game_info.vision_blockers = bot.game_info._find_ramps_and_vision_blockers() # Test if main ramp works for all spawns @@ -107,7 +107,7 @@ def test_bot_ai(self, map_path: Path): ) # On N player maps, it is expected that there are N*X bases because of symmetry, at least for maps designed for 1vs1 # Those maps in the list have an un-even expansion count - # pyre-ignore[16] + expect_even_expansion_count = 1 if bot.game_info.map_name in self.MAPS_WITH_ODD_EXPANSION_COUNT else 0 assert ( len(bot.expansion_locations_list) % (len(bot.enemy_start_locations) + 1) == expect_even_expansion_count @@ -120,7 +120,7 @@ def test_bot_ai(self, map_path: Path): for location in bot.enemy_start_locations: assert location in set(bot.expansion_locations_list), f"{location}, {bot.expansion_locations_list}" # Each expansion is supposed to have at least one geysir and 6-12 minerals - # pyre-ignore[16] + for expansion, resource_positions in bot.expansion_locations_dict.items(): assert isinstance(expansion, Point2) assert isinstance(resource_positions, Units) diff --git a/test/travis_test_script.py b/test/travis_test_script.py index 973141d6..5fde8cb5 100644 --- a/test/travis_test_script.py +++ b/test/travis_test_script.py @@ -49,34 +49,32 @@ # Break as the bot run was successful break - # pyre-ignore[16] - if process.returncode is not None: + if process is not None and process.returncode is not None and result is not None: # Reformat the output into a list - # pyre-ignore[16] - logger.info_output = result + linebreaks = [ - # pyre-ignore[16] - ["\r\n", logger.info_output.count("\r\n")], - ["\r", logger.info_output.count("\r")], - ["\n", logger.info_output.count("\n")], + ("\r\n", result.count("\r\n")), + ("\r", result.count("\r")), + ("\n", result.count("\n")), ] most_linebreaks_type = max(linebreaks, key=lambda x: x[1]) linebreak_type, linebreak_count = most_linebreaks_type - # pyre-ignore[16] - output_as_list = logger.info_output.split(linebreak_type) + + output_as_list = result.split(linebreak_type) logger.info("Travis test script, bot output:\r\n{}\r\nEnd of bot output".format("\r\n".join(output_as_list))) time_taken = time.time() - t0 # Bot was not successfully run in time, returncode will be None - if process.returncode is None or process.returncode != 0: + if process is not None and (process.returncode is None or process.returncode != 0): logger.info( f"Exiting with exit code 5, error: Attempted to launch script {sys.argv[1]} timed out after {time_taken} seconds. Retries completed: {i}" ) sys.exit(5) # process.returncode will always return 0 if the game was run successfully or if there was a python error (in this case it returns as defeat) - logger.info(f"Returncode: {process.returncode}") + if process is not None and process.returncode is not None: + logger.info(f"Returncode: {process.returncode}") logger.info(f"Game took {round(time.time() - t0, 1)} real time seconds") if process is not None and process.returncode == 0: for line in output_as_list: diff --git a/test/upgradestest_bot.py b/test/upgradestest_bot.py index 6267866b..5b0ae987 100644 --- a/test/upgradestest_bot.py +++ b/test/upgradestest_bot.py @@ -102,19 +102,21 @@ async def test_botai_actions1(self): if "TECHLAB" in structure_type.name: continue + # pyrefly: ignore structure_upgrade_types: dict[UpgradeId, dict[str, AbilityId]] = RESEARCH_INFO[structure_type] data: dict[str, AbilityId] for upgrade_id, data in structure_upgrade_types.items(): # Collect data to spawn - research_ability: AbilityId = data.get("ability", None) - requires_power: bool = data.get("requires_power", False) - required_building: UnitTypeId = data.get("required_building", None) + research_ability: AbilityId = data.get("ability", None) # pyrefly: ignore + requires_power: bool = data.get("requires_power", False) # pyrefly: ignore + required_building: UnitTypeId = data.get("required_building", None) # pyrefly: ignore # Prevent linux crash if ( research_ability.value not in self.game_data.abilities or upgrade_id.value not in self.game_data.upgrades or self.game_data.upgrades[upgrade_id.value].research_ability is None + # pyrefly: ignore or self.game_data.upgrades[upgrade_id.value].research_ability.exact_id != research_ability ): logger.info( @@ -130,7 +132,7 @@ async def test_botai_actions1(self): if required_building: spawn_structures.append(required_building) - await self.client.debug_create_unit([[structure, 1, map_center, 1] for structure in spawn_structures]) + await self.client.debug_create_unit([(structure, 1, map_center, 1) for structure in spawn_structures]) logger.info( f"Spawning {structure_type} to research upgrade {upgrade_id} via research ability {research_ability}" ) @@ -152,7 +154,7 @@ async def test_botai_actions1(self): assert self.structures(structure_type), f"Structure {structure_type} has not been spawned in time" # Try to research the upgrade - while 1: + while True: upgrader_structures: Units = self.structures(structure_type) # Upgrade has been researched, break if upgrader_structures: @@ -174,7 +176,6 @@ async def on_start(self): await self.client.debug_kill_unit(self.units) async def on_step(self, iteration: int): - # pyre-ignore[16] map_center = self.game_info.map_center enemies = self.enemy_units | self.enemy_structures if enemies: diff --git a/uv.lock b/uv.lock index 214a290d..22012738 100644 --- a/uv.lock +++ b/uv.lock @@ -305,7 +305,7 @@ dev = [ { name = "protobuf", specifier = ">=6,<8" }, { name = "pyglet", specifier = ">=2.0.20" }, { name = "pylint", specifier = ">=3.3.2" }, - { name = "pyrefly", specifier = ">=0.21.0" }, + { name = "pyrefly", specifier = ">=0.58.0" }, { name = "pytest", specifier = ">=8.3.4" }, { name = "pytest-asyncio", specifier = ">=0.25.0" }, { name = "pytest-benchmark", specifier = ">=5.1.0" }, @@ -2547,18 +2547,18 @@ wheels = [ [[package]] name = "pyrefly" -version = "0.21.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/46/3f/8a30ed93cb027a18080d7e670b2bbf14135b031fe74443eaa850494d9aa8/pyrefly-0.21.0.tar.gz", hash = "sha256:e05a083047dcba25e730c7e0c70b3dc48ba420f17ef73265f169bc95f487a99d", size = 1056016, upload-time = "2025-06-23T17:45:22.033Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/dd/5b1a4a3a713be65e2af02563f8baa70ea69d594d681cb1c36b38319eee90/pyrefly-0.21.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b44e6172421de12fa14d380bcd416fa64d474aec9f1829e6985b18471b1fcee1", size = 5820971, upload-time = "2025-06-23T17:45:04.928Z" }, - { url = "https://files.pythonhosted.org/packages/5b/40/df55322e761b798903c951ad5699585046f9e926e6b3b6686cb0056024a4/pyrefly-0.21.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:0d7ea3b86ac3c8680b290389ca3706cbbb046899247a963ac7384c57880e6f2f", size = 5404314, upload-time = "2025-06-23T17:45:07.007Z" }, - { url = "https://files.pythonhosted.org/packages/0b/a5/b3d526bf75ab8708cc85cd0db571af0179b566964fe2c9aae717f3a03090/pyrefly-0.21.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:101ecb84b0442e85a92bcb15a2dfd844f7a6abae8f980661849c71102447443e", size = 5611017, upload-time = "2025-06-23T17:45:08.989Z" }, - { url = "https://files.pythonhosted.org/packages/f6/02/4d4b0ddade7e2980a13e14062770535666a84dfab3293c33e154dfff6ae1/pyrefly-0.21.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b934004ddcdcbe55efeb732d0c0155a781a184e374fec9cd031e819ac1b92eff", size = 6285992, upload-time = "2025-06-23T17:45:11.991Z" }, - { url = "https://files.pythonhosted.org/packages/29/7c/2c3922ee3bdd82a827a893a80aeddf119e38ce8406035a6eda6ae480885e/pyrefly-0.21.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c66446bc5f7e912dab923adf93f636307c834ab53ddd9422e56d101910d960a", size = 6066455, upload-time = "2025-06-23T17:45:13.587Z" }, - { url = "https://files.pythonhosted.org/packages/e1/c8/c8d391f3535046cb79154d4dfcc01b0d022d5af2701627cbe9a518dd50bc/pyrefly-0.21.0-py3-none-win32.whl", hash = "sha256:a4cb8acf2dc831759cb43fa0326e07085cb21da7202212e70d9478bfc40b7a28", size = 5569924, upload-time = "2025-06-23T17:45:15.36Z" }, - { url = "https://files.pythonhosted.org/packages/85/79/58c94192acbc234ff9290c22063bf8f11d65a7b61eb61f9260fa6fb4b1f3/pyrefly-0.21.0-py3-none-win_amd64.whl", hash = "sha256:765d19f2b48d5dd3dae0752676e2d6e388025fa6337947031ff628160b8cf568", size = 5946646, upload-time = "2025-06-23T17:45:17.433Z" }, - { url = "https://files.pythonhosted.org/packages/75/6e/d476584e93e3c63609dde26a57bc23f732c22b0e9bd17462282268aec758/pyrefly-0.21.0-py3-none-win_arm64.whl", hash = "sha256:a3fc4fffb625a5610b68fc3bf07e4d33b5b1c279512e0251b3d3d4f7c3ed1541", size = 5595515, upload-time = "2025-06-23T17:45:19.4Z" }, +version = "0.58.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/bb/5611669a79967250e0654c0cffb7b0bb892b4263d5dec9b4345917edd9cd/pyrefly-0.58.0.tar.gz", hash = "sha256:4512b89cd8db95e8994537895ff41ad60e6211643442f8e33ed93bb59f88a256", size = 5357858, upload-time = "2026-03-25T01:21:01.8Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/fb/499e8efc0c3bc4e8f0e689922e616a8d406ff3297962c94b8053701b727f/pyrefly-0.58.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6521e3924d98c2fe118109de625c075d970f9d4a9682f11afa4f7c6b5f11ca1f", size = 12765022, upload-time = "2026-03-25T01:20:41.397Z" }, + { url = "https://files.pythonhosted.org/packages/b6/7f/25f329adfb191e78c22524858cce7d0e06d3961d44fe2ed5c302d3096dc2/pyrefly-0.58.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:bd52dd8f330e08f3829791b914f170769f97e8a0e377abf554710decbf799f20", size = 12276729, upload-time = "2026-03-25T01:20:43.815Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ca/4982e8e3a177ed30e7ec23f51a429c2319653dcebbe20bac55ef3f275f23/pyrefly-0.58.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2334bea7d7b43a4d9061cce8e48ee5da2de185511637ed691dca5d649633f514", size = 35369879, upload-time = "2026-03-25T01:20:46.48Z" }, + { url = "https://files.pythonhosted.org/packages/7f/56/02afb8f6584765d1fed08a81f0415504345229ff452384143864a8ed8062/pyrefly-0.58.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb7bf956beb01f2664a1b20ca7303dc617bc454457b869237945edd5a52fc39b", size = 38060979, upload-time = "2026-03-25T01:20:49.62Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ec/78ae846cba36a244db20d157948e6dd238713f63b812b701754e22fab4d5/pyrefly-0.58.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce58ec7d4a5bc4fdb4a8d90329259c15e221e840ce1da946c698c4954aa4098b", size = 40753678, upload-time = "2026-03-25T01:20:52.718Z" }, + { url = "https://files.pythonhosted.org/packages/98/ec/6d1461960bf2efbf7af679846edd29ff1fce9e20b4a653cb99338e1e847e/pyrefly-0.58.0-py3-none-win32.whl", hash = "sha256:43fa3b1abc0b55503c6238d09f333ce4f7a341cb9d5113b9d5e3089b8e0901b6", size = 11793278, upload-time = "2026-03-25T01:20:55.367Z" }, + { url = "https://files.pythonhosted.org/packages/3d/23/70c20ea234409e4ea3b4177ec6cdf221532fd34bfec012961972e2ab511f/pyrefly-0.58.0-py3-none-win_amd64.whl", hash = "sha256:5dbd270d6841ec7b772ee41dd2d83f376ad24b279b747ddb8c11550ad3648dde", size = 12626215, upload-time = "2026-03-25T01:20:57.329Z" }, + { url = "https://files.pythonhosted.org/packages/c3/20/fed053053785585cdd3a393aa3ab8e2e1f9c5078c16c9b659bb4b87ff22d/pyrefly-0.58.0-py3-none-win_arm64.whl", hash = "sha256:86425dfb43607027960ffed13722504992e83b45becbb83853d8f961c715dac2", size = 12124266, upload-time = "2026-03-25T01:20:59.641Z" }, ] [[package]]