diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a1e04705..473c2cdf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -184,28 +184,28 @@ jobs: - name: Run autotest_bot.py # Run bot and list resulting files (replay file, stable_id.json) run: | - docker run -i -d --name my_container $IMAGE_NAME + docker run -i -d --name my_container --env 'PYTHONPATH=/root/python-sc2' $IMAGE_NAME docker exec -i my_container bash -c "python test/travis_test_script.py test/autotest_bot.py" docker exec -i my_container bash -c "tree" docker rm -f my_container - name: Run upgradestest_bot.py run: | - docker run -i -d --name my_container $IMAGE_NAME + docker run -i -d --name my_container --env 'PYTHONPATH=/root/python-sc2' $IMAGE_NAME docker exec -i my_container bash -c "python test/travis_test_script.py test/upgradestest_bot.py" docker exec -i my_container bash -c "tree" docker rm -f my_container - name: Run damagetest_bot.py run: | - docker run -i -d --name my_container $IMAGE_NAME + docker run -i -d --name my_container --env 'PYTHONPATH=/root/python-sc2' $IMAGE_NAME docker exec -i my_container bash -c "python test/travis_test_script.py test/damagetest_bot.py" docker exec -i my_container bash -c "tree" docker rm -f my_container - name: Run queries_test_bot.py run: | - docker run -i -d --name my_container $IMAGE_NAME + docker run -i -d --name my_container --env 'PYTHONPATH=/root/python-sc2' $IMAGE_NAME docker exec -i my_container bash -c "python test/travis_test_script.py test/queries_test_bot.py" docker exec -i my_container bash -c "tree" docker rm -f my_container @@ -234,7 +234,7 @@ jobs: - name: Run example bots vs computer run: | - docker run -i -d --name my_container $IMAGE_NAME + docker run -i -d --name my_container --env 'PYTHONPATH=/root/python-sc2' $IMAGE_NAME docker exec -i my_container bash -c "python test/run_example_bots_vs_computer.py" docker exec -i my_container bash -c "tree" docker rm -f my_container @@ -266,7 +266,7 @@ jobs: # # - name: Run example bots vs each other # run: | - # docker run -i -d --name my_container $IMAGE_NAME + # docker run -i -d --name my_container --env 'PYTHONPATH=/root/python-sc2' $IMAGE_NAME # docker exec -i my_container bash -c "python test/run_example_bots_vs_each_other.py" # docker exec -i my_container bash -c "tree" # docker rm -f my_container @@ -294,11 +294,14 @@ jobs: run: | mkdir htmlcov docker run -i -d \ - --mount type=bind,source=$(pwd)/htmlcov,destination=/root/python-sc2/htmlcov \ + -v $(pwd)/htmlcov:/root/python-sc2/htmlcov \ --name my_container \ + --env 'PYTHONPATH=/root/python-sc2/' \ + --entrypoint /bin/bash \ $IMAGE_NAME echo "Install dev requirements because only non dev requirements exist in the docker image at the moment" - docker exec -i my_container bash -c "uv sync --frozen --no-cache --no-install-project" + docker exec -i my_container bash -c "pip install uv \ + && uv sync --frozen --no-cache --no-install-project" - name: Run coverage on tests run: docker exec -i my_container bash -c "uv run pytest --cov=./" diff --git a/.github/workflows/docker-ci.yml b/.github/workflows/docker-ci.yml index f84ae9a5..0ad46dcc 100644 --- a/.github/workflows/docker-ci.yml +++ b/.github/workflows/docker-ci.yml @@ -103,6 +103,7 @@ jobs: echo "Start container, override the default entrypoint" docker run -i -d \ --name test_container \ + --env 'PYTHONPATH=/root/python-sc2/' \ --entrypoint /bin/bash \ $IMAGE_NAME-v$VERSION_NUMBER-squashed echo "Install python-sc2" diff --git a/.gitignore b/.gitignore index 81e04f26..fd985aba 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,5 @@ mini_games/ /htmlcov /docs + +.pyre diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a00efede..a19b2939 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -32,3 +32,27 @@ repos: - id: python-no-log-warn # Enforce type annotation instead of comment annotation - id: python-use-type-annotations + +- repo: local + hooks: + # Autoformat code + - id: ruff-format-check + name: Check if files are formatted + stages: [push] + language: system + entry: uv run ruff format . --check --diff + pass_filenames: false + + - id: ruff-lint + name: Lint files + stages: [push] + language: system + entry: uv run ruff check . + pass_filenames: false + + - id: pyre + name: Static types checking with pyre + stages: [push] + language: system + entry: uv run pyre + pass_filenames: false diff --git a/.pyre_configuration b/.pyre_configuration new file mode 100644 index 00000000..6c670b47 --- /dev/null +++ b/.pyre_configuration @@ -0,0 +1,17 @@ +{ + "site_package_search_strategy": "pep561", + "source_directories": [ + { + "import_root": ".", + "source": "sc2" + }, + { + "import_root": ".", + "source": "examples" + }, + { + "import_root": ".", + "source": "test" + } + ] +} diff --git a/dockerfiles/test_docker_image.sh b/dockerfiles/test_docker_image.sh index 439a7899..4b203c2e 100644 --- a/dockerfiles/test_docker_image.sh +++ b/dockerfiles/test_docker_image.sh @@ -35,18 +35,19 @@ docker rm -f test_container # Start container, override the default entrypoint docker run -i -d \ --name test_container \ + --env 'PYTHONPATH=/root/python-sc2' \ --entrypoint /bin/bash \ $IMAGE_NAME +# Install requirements docker exec -i test_container mkdir -p /root/python-sc2 docker cp pyproject.toml test_container:/root/python-sc2/ 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 test test_container:/root/python-sc2/test -# Install python-sc2, via mount the python-sc2 folder will be available -docker exec -i test_container bash -c "pip install uv && cd python-sc2 && uv sync --no-cache --no-install-project" - # 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" diff --git a/docs_generate/conf.py b/docs_generate/conf.py index eb79b598..e728e151 100644 --- a/docs_generate/conf.py +++ b/docs_generate/conf.py @@ -13,9 +13,11 @@ import os import sys -sys.path.insert(0, os.path.abspath("..")) +sys.path.insert(0, os.path.abspath("..")) # noqa: PTH100 -import sphinx_rtd_theme # nopycln: import +import sphinx_rtd_theme + +sphinx_rtd_theme # Add statement to keep unused import # -- Project information ----------------------------------------------------- diff --git a/docs_generate/text_files/introduction.rst b/docs_generate/text_files/introduction.rst index 1daece06..33193fb9 100644 --- a/docs_generate/text_files/introduction.rst +++ b/docs_generate/text_files/introduction.rst @@ -79,10 +79,10 @@ Information about the enemy player:: self.enemy_structures: Units # Enemy spawn locations as a list of Point2 points - self.enemy_start_locations: List[Point2] + self.enemy_start_locations: list[Point2] # Enemy units that are inside your sensor tower range - self.blips: Set[Blip] + self.blips: set[Blip] # The enemy race. If the enemy chose random, this will stay at random forever self.enemy_race: Race @@ -98,7 +98,7 @@ Other information:: self.all_units: Units # All units combined: yours, enemy's and neutral # Locations of possible expansions - self.expansion_locations: Dict[Point2, Units] + self.expansion_locations: dict[Point2, Units] # Game data about units, abilities and upgrades (see game_data.py) self.game_data: GameData diff --git a/examples/arcade_bot.py b/examples/arcade_bot.py index 860e7ec8..32bbf22c 100644 --- a/examples/arcade_bot.py +++ b/examples/arcade_bot.py @@ -19,7 +19,8 @@ - Make marines constantly run if they have a ling/bane very close to them - Split marines before engaging """ -from typing import Union + +from __future__ import annotations from loguru import logger @@ -37,7 +38,6 @@ class MarineSplitChallenge(BotAI): - async def on_start(self): await self.chat_send("Edit this message for automatic chat commands.") self.client.game_step = 2 @@ -45,9 +45,7 @@ async def on_start(self): async def on_step(self, iteration): # do marine micro vs zerglings for unit in self.units(UnitTypeId.MARINE): - if self.enemy_units: - # attack (or move towards) zerglings / banelings if unit.weapon_cooldown <= self.client.game_step / 2: enemies_in_range = self.enemy_units.filter(unit.target_in_range) @@ -57,7 +55,8 @@ async def on_step(self, iteration): # Use stimpack if ( self.already_pending_upgrade(UpgradeId.STIMPACK) == 1 - and not unit.has_buff(BuffId.STIMPACK) and unit.health > 10 + and not unit.has_buff(BuffId.STIMPACK) + and unit.health > 10 ): unit(AbilityId.EFFECT_STIM) @@ -97,15 +96,17 @@ async def on_step(self, iteration): def position_around_unit( self, - pos: Union[Unit, Point2, Point3], + pos: Unit | Point2 | Point3, distance: int = 1, step_size: int = 1, exclude_out_of_bounds: bool = True, ): + # pyre-ignore[16] pos = pos.position.rounded positions = { pos.offset(Point2((x, y))) - for x in range(-distance, distance + 1, step_size) for y in range(-distance, distance + 1, step_size) + for x in range(-distance, distance + 1, step_size) + for y in range(-distance, distance + 1, step_size) if (x, y) != (0, 0) } # filter positions outside map size @@ -113,6 +114,7 @@ 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 d6e8d6a7..73d69fa7 100644 --- a/examples/bot_vs_bot.py +++ b/examples/bot_vs_bot.py @@ -1,7 +1,8 @@ """ This script shows how to let two custom bots play against each other. """ -from typing import List + +from __future__ import annotations from loguru import logger @@ -14,7 +15,7 @@ def main_old(): - result: List[Result] = run_game( + result: list[Result] = run_game( maps.get("AcropolisLE"), [ Bot(Race.Protoss, WarpGateBot()), diff --git a/examples/competitive/__init__.py b/examples/competitive/__init__.py index 34aa07e2..28a10595 100644 --- a/examples/competitive/__init__.py +++ b/examples/competitive/__init__.py @@ -1,4 +1,3 @@ -# pylint: disable=W0212 import argparse import asyncio @@ -7,7 +6,7 @@ import sc2 from sc2.client import Client -from sc2.protocol import ConnectionAlreadyClosed +from sc2.protocol import ConnectionAlreadyClosedError # Run ladder game @@ -26,10 +25,7 @@ def run_ladder_game(bot): parser.add_argument("--RealTime", action="store_true", help="Real time flag") args, _unknown = parser.parse_known_args() - if args.LadderServer is None: - host = "127.0.0.1" - else: - host = args.LadderServer + host = "127.0.0.1" if args.LadderServer is None else args.LadderServer host_port = args.GamePort lan_port = args.StartPort @@ -68,7 +64,7 @@ async def join_ladder_game(host, port, players, realtime, portconfig, save_repla await client.save_replay(save_replay_as) # await client.leave() # await client.quit() - except ConnectionAlreadyClosed: + except ConnectionAlreadyClosedError: logger.error("Connection was closed before the game ended") return None finally: diff --git a/examples/competitive/bot.py b/examples/competitive/bot.py index e736b44e..5170635a 100644 --- a/examples/competitive/bot.py +++ b/examples/competitive/bot.py @@ -3,7 +3,6 @@ class CompetitiveBot(BotAI): - async def on_start(self): print("Game started") # Do things here before the game starts @@ -12,6 +11,7 @@ async def on_step(self, iteration): # 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 e7a983e8..10984103 100644 --- a/examples/competitive/run.py +++ b/examples/competitive/run.py @@ -1,4 +1,4 @@ -# pylint: disable=E0401 +# pyre-ignore-all-errors[16, 21] import sys from __init__ import run_ladder_game diff --git a/examples/distributed_workers.py b/examples/distributed_workers.py index 724cc392..95d3d4af 100644 --- a/examples/distributed_workers.py +++ b/examples/distributed_workers.py @@ -1,3 +1,4 @@ +# pyre-ignore-all-errors[16] from sc2 import maps from sc2.bot_ai import BotAI from sc2.data import Difficulty, Race @@ -7,7 +8,6 @@ class TerranBot(BotAI): - async def on_step(self, iteration): await self.distribute_workers() await self.build_supply() diff --git a/examples/observer_easy_vs_easy.py b/examples/observer_easy_vs_easy.py index f4a10558..aa9f944b 100644 --- a/examples/observer_easy_vs_easy.py +++ b/examples/observer_easy_vs_easy.py @@ -8,8 +8,7 @@ def main(): run_game( maps.get("Abyssal Reef LE"), - [Bot(Race.Protoss, CannonRushBot()), - Computer(Race.Protoss, Difficulty.Medium)], + [Bot(Race.Protoss, CannonRushBot()), Computer(Race.Protoss, Difficulty.Medium)], realtime=True, ) diff --git a/examples/protoss/cannon_rush.py b/examples/protoss/cannon_rush.py index 6a7c5aa6..2d287202 100644 --- a/examples/protoss/cannon_rush.py +++ b/examples/protoss/cannon_rush.py @@ -9,8 +9,6 @@ class CannonRushBot(BotAI): - - # pylint: disable=R0912 async def on_step(self, iteration): if iteration == 0: await self.chat_send("(probe)(pylon)(cannon)(cannon)(gg)") @@ -64,8 +62,7 @@ async def on_step(self, iteration): def main(): run_game( maps.get("(2)CatalystLE"), - [Bot(Race.Protoss, CannonRushBot(), name="CheeseCannon"), - Computer(Race.Protoss, Difficulty.Medium)], + [Bot(Race.Protoss, CannonRushBot(), name="CheeseCannon"), Computer(Race.Protoss, Difficulty.Medium)], realtime=False, ) diff --git a/examples/protoss/find_adept_shades.py b/examples/protoss/find_adept_shades.py index f1f4fde0..8b136cfc 100644 --- a/examples/protoss/find_adept_shades.py +++ b/examples/protoss/find_adept_shades.py @@ -10,9 +10,7 @@ from sc2.position import Point2 -# pylint: disable=W0231 class FindAdeptShadesBot(BotAI): - def __init__(self): self.shaded = False self.shades_mapping = {} @@ -28,6 +26,7 @@ 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: @@ -39,6 +38,7 @@ 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,6 +53,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 @@ -60,8 +61,7 @@ async def on_step(self, iteration: int): def main(): run_game( maps.get("(2)CatalystLE"), - [Bot(Race.Protoss, FindAdeptShadesBot()), - Computer(Race.Protoss, Difficulty.Medium)], + [Bot(Race.Protoss, FindAdeptShadesBot()), Computer(Race.Protoss, Difficulty.Medium)], realtime=False, ) diff --git a/examples/protoss/threebase_voidray.py b/examples/protoss/threebase_voidray.py index 972c2f28..2030a28f 100644 --- a/examples/protoss/threebase_voidray.py +++ b/examples/protoss/threebase_voidray.py @@ -9,8 +9,6 @@ class ThreebaseVoidrayBot(BotAI): - - # pylint: disable=R0912 async def on_step(self, iteration): target_base_count = 3 target_stargate_count = 3 @@ -54,8 +52,11 @@ async def on_step(self, iteration): # If we are low on supply, build pylon if ( - self.supply_left < 2 and self.already_pending(UnitTypeId.PYLON) == 0 - or self.supply_used > 15 and self.supply_left < 4 and self.already_pending(UnitTypeId.PYLON) < 2 + self.supply_left < 2 + and self.already_pending(UnitTypeId.PYLON) == 0 + or self.supply_used > 15 + and self.supply_left < 4 + and self.already_pending(UnitTypeId.PYLON) < 2 ): # Always check if you can afford something before you build it if self.can_afford(UnitTypeId.PYLON): @@ -109,8 +110,8 @@ async def on_step(self, iteration): pylon = self.structures(UnitTypeId.PYLON).ready.random if ( self.townhalls.ready.amount + self.already_pending(UnitTypeId.NEXUS) >= target_base_count - and self.structures(UnitTypeId.STARGATE).ready.amount + self.already_pending(UnitTypeId.STARGATE) < - target_stargate_count + and self.structures(UnitTypeId.STARGATE).ready.amount + self.already_pending(UnitTypeId.STARGATE) + < target_stargate_count ): if self.can_afford(UnitTypeId.STARGATE): await self.build(UnitTypeId.STARGATE, near=pylon) @@ -125,8 +126,7 @@ async def on_step(self, iteration): def main(): run_game( maps.get("(2)CatalystLE"), - [Bot(Race.Protoss, ThreebaseVoidrayBot()), - Computer(Race.Protoss, Difficulty.Easy)], + [Bot(Race.Protoss, ThreebaseVoidrayBot()), Computer(Race.Protoss, Difficulty.Easy)], realtime=False, ) diff --git a/examples/protoss/warpgate_push.py b/examples/protoss/warpgate_push.py index 0ae6e350..7058ea43 100644 --- a/examples/protoss/warpgate_push.py +++ b/examples/protoss/warpgate_push.py @@ -11,9 +11,7 @@ from sc2.player import Bot, Computer -# pylint: disable=W0231 class WarpGateBot(BotAI): - def __init__(self): # Initialize inherited class self.proxy_built = False @@ -31,7 +29,6 @@ async def warp_new_units(self, proxy): return warpgate.warp_in(UnitTypeId.STALKER, placement) - # pylint: disable=R0912 async def on_step(self, iteration): await self.distribute_workers() @@ -92,7 +89,8 @@ async def on_step(self, iteration): # Research warp gate if cybercore is completed if ( - self.structures(UnitTypeId.CYBERNETICSCORE).ready and self.can_afford(AbilityId.RESEARCH_WARPGATE) + self.structures(UnitTypeId.CYBERNETICSCORE).ready + and self.can_afford(AbilityId.RESEARCH_WARPGATE) and self.already_pending_upgrade(UpgradeId.WARPGATERESEARCH) == 0 ): ccore = self.structures(UnitTypeId.CYBERNETICSCORE).ready.first @@ -118,7 +116,8 @@ async def on_step(self, iteration): # Build proxy pylon if ( - self.structures(UnitTypeId.CYBERNETICSCORE).amount >= 1 and not self.proxy_built + self.structures(UnitTypeId.CYBERNETICSCORE).amount >= 1 + and not self.proxy_built and self.can_afford(UnitTypeId.PYLON) ): p = self.game_info.map_center.towards(self.enemy_start_locations[0], 20) diff --git a/examples/simulate_fight_scenario.py b/examples/simulate_fight_scenario.py index d32a4169..b3d66511 100644 --- a/examples/simulate_fight_scenario.py +++ b/examples/simulate_fight_scenario.py @@ -13,7 +13,6 @@ class FightBot(BotAI): - def __init__(self): super().__init__() self.enemy_location: Point2 = None @@ -48,16 +47,14 @@ async def reset_arena(self): 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.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.MARINE, 4, self.start_location.towards(self.enemy_location, 8), MY_PLAYER_ID], ] ) @@ -81,7 +78,7 @@ def main(): maps.get("Flat64"), # NOTE: you can have two bots fighting with each other here [Bot(Race.Terran, FightBot()), Computer(Race.Terran, Difficulty.Medium)], - realtime=True + realtime=True, ) diff --git a/examples/terran/cyclone_push.py b/examples/terran/cyclone_push.py index eaa8d9f2..cf04c91a 100644 --- a/examples/terran/cyclone_push.py +++ b/examples/terran/cyclone_push.py @@ -10,7 +10,6 @@ class CyclonePush(BotAI): - def select_target(self) -> Point2: # Pick a random enemy structure's position targets = self.enemy_structures @@ -23,13 +22,12 @@ def select_target(self) -> Point2: return targets.random.position # Pick enemy start location if it has no friendly units nearby - if min((unit.distance_to(self.enemy_start_locations[0]) for unit in self.units)) > 5: + if min(unit.distance_to(self.enemy_start_locations[0]) for unit in self.units) > 5: return self.enemy_start_locations[0] # Pick a random mineral field on the map return self.mineral_field.random.position - # pylint: disable=R0912 async def on_step(self, iteration): CCs: Units = self.townhalls(UnitTypeId.COMMANDCENTER) # If no command center exists, attack-move with all workers and cyclones @@ -58,7 +56,8 @@ async def on_step(self, iteration): # While we have less than 22 workers: build more # Check if we can afford them (by minerals and by supply) if ( - self.can_afford(UnitTypeId.SCV) and self.supply_workers + self.already_pending(UnitTypeId.SCV) < 22 + self.can_afford(UnitTypeId.SCV) + and self.supply_workers + self.already_pending(UnitTypeId.SCV) < 22 and cc.is_idle ): cc.train(UnitTypeId.SCV) diff --git a/examples/terran/mass_reaper.py b/examples/terran/mass_reaper.py index f8034199..01aba5dd 100644 --- a/examples/terran/mass_reaper.py +++ b/examples/terran/mass_reaper.py @@ -7,7 +7,6 @@ """ import random -from typing import Set from sc2 import maps from sc2.bot_ai import BotAI @@ -21,14 +20,11 @@ from sc2.units import Units -# pylint: disable=W0231 class MassReaperBot(BotAI): - def __init__(self): # Select distance calculation method 0, which is the pure python distance calculation without caching or indexing, using math.hypot(), for more info see bot_ai_internal.py _distances_override_functions() function self.distance_calculation_method = 3 - # pylint: disable=R0912,R0914 async def on_step(self, iteration): # Benchmark and print duration time of the on_step method based on "self.distance_calculation_method" value # logger.info(self.time_formatted, self.supply_used, self.step_time[1]) @@ -39,8 +35,11 @@ async def on_step(self, iteration): - self.already_pending(TYPE) counts how many units are queued """ if ( - self.supply_left < 5 and self.townhalls and self.supply_used >= 14 - and self.can_afford(UnitTypeId.SUPPLYDEPOT) and self.already_pending(UnitTypeId.SUPPLYDEPOT) < 1 + self.supply_left < 5 + and self.townhalls + and self.supply_used >= 14 + and self.can_afford(UnitTypeId.SUPPLYDEPOT) + and self.already_pending(UnitTypeId.SUPPLYDEPOT) < 1 ): workers: Units = self.workers.gathering # If workers were found @@ -68,7 +67,8 @@ async def on_step(self, iteration): # Expand if we can afford (400 minerals) and have less than 2 bases if ( - 1 <= self.townhalls.amount < 2 and self.already_pending(UnitTypeId.COMMANDCENTER) == 0 + 1 <= self.townhalls.amount < 2 + and self.already_pending(UnitTypeId.COMMANDCENTER) == 0 and self.can_afford(UnitTypeId.COMMANDCENTER) ): # get_next_expansion returns the position of the next possible expansion location where you can place a command center @@ -88,8 +88,8 @@ async def on_step(self, iteration): # self.structures.of_type( # [UnitTypeId.SUPPLYDEPOT, UnitTypeId.SUPPLYDEPOTLOWERED, UnitTypeId.SUPPLYDEPOTDROP] # ).ready - and self.structures(UnitTypeId.BARRACKS).ready.amount + self.already_pending(UnitTypeId.BARRACKS) < 4 and - self.can_afford(UnitTypeId.BARRACKS) + and self.structures(UnitTypeId.BARRACKS).ready.amount + self.already_pending(UnitTypeId.BARRACKS) < 4 + and self.can_afford(UnitTypeId.BARRACKS) ): workers: Units = self.workers.gathering if ( @@ -113,8 +113,9 @@ async def on_step(self, iteration): # Find all vespene geysers that are closer than range 10 to this townhall vgs: Units = self.vespene_geyser.closer_than(10, th) for vg in vgs: - if await self.can_place_single(UnitTypeId.REFINERY, - vg.position) and self.can_afford(UnitTypeId.REFINERY): + if await self.can_place_single(UnitTypeId.REFINERY, vg.position) and self.can_afford( + UnitTypeId.REFINERY + ): workers: Units = self.workers.gathering if workers: # same condition as above worker: Unit = workers.closest_to(vg) @@ -126,10 +127,13 @@ async def on_step(self, iteration): # 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) - # pylint: disable=R0916 if ( - self.can_afford(UnitTypeId.SCV) and self.supply_left > 0 and self.supply_workers < 22 and ( - self.structures(UnitTypeId.BARRACKS).ready.amount < 1 and self.townhalls(UnitTypeId.COMMANDCENTER).idle + self.can_afford(UnitTypeId.SCV) + and self.supply_left > 0 + and self.supply_workers < 22 + and ( + self.structures(UnitTypeId.BARRACKS).ready.amount < 1 + and self.townhalls(UnitTypeId.COMMANDCENTER).idle or self.townhalls(UnitTypeId.ORBITALCOMMAND).idle ) ): @@ -151,17 +155,17 @@ async def on_step(self, iteration): 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): - # 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 ) # 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) + retreat_points: set[Point2] = self.neighbors8(r.position, distance=2) | self.neighbors8( + r.position, distance=4 + ) # Filter points that are pathable - retreat_points: Set[Point2] = {x for x in retreat_points if self.in_pathing_grid(x)} + 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) retreat_point: Unit = closest_enemy.position.furthest(retreat_points) @@ -179,13 +183,14 @@ async def on_step(self, iteration): 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 - # pylint: disable=W0212 - reaper_grenade_range: float = ( - self.game_data.abilities[AbilityId.KD8CHARGE_KD8CHARGE.value]._proto.cast_range - ) + reaper_grenade_range: float = self.game_data.abilities[ + AbilityId.KD8CHARGE_KD8CHARGE.value + ]._proto.cast_range enemy_ground_units_in_grenade_range: Units = enemies_can_attack.filter( - 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 + 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 ) if enemy_ground_units_in_grenade_range and (r.is_attacking or r.is_moving): # If AbilityId.KD8CHARGE_KD8CHARGE in abilities, we check that to see if the reaper grenade is off cooldown @@ -208,10 +213,11 @@ async def on_step(self, iteration): ) # 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) + retreat_points: set[Point2] = self.neighbors8(r.position, distance=2) | self.neighbors8( + r.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)} + 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) retreat_point: Point2 = max( @@ -253,13 +259,13 @@ async def on_step(self, iteration): # Stolen and modified from position.py @staticmethod - def neighbors4(position, distance=1) -> Set[Point2]: + def neighbors4(position, distance=1) -> set[Point2]: p = position d = distance return {Point2((p.x - d, p.y)), Point2((p.x + d, p.y)), Point2((p.x, p.y - d)), Point2((p.x, p.y + d))} # Stolen and modified from position.py - def neighbors8(self, position, distance=1) -> Set[Point2]: + def neighbors8(self, position, distance=1) -> set[Point2]: p = position d = distance return self.neighbors4(position, distance) | { @@ -270,7 +276,6 @@ def neighbors8(self, position, distance=1) -> Set[Point2]: } # Distribute workers function rewritten, the default distribute_workers() function did not saturate gas quickly enough - # pylint: disable=R0912 async def my_distribute_workers(self, performance_heavy=True, only_saturate_gas=False): mineral_tags = [x.tag for x in self.mineral_field] gas_building_tags = [x.tag for x in self.gas_buildings] @@ -288,8 +293,10 @@ async def my_distribute_workers(self, performance_heavy=True, only_saturate_gas= deficit_gas_buildings[g.tag] = {"unit": g, "deficit": deficit} elif deficit < 0: surplus_workers = self.workers.closer_than(10, g).filter( - lambda w: w not in worker_pool_tags and len(w.orders) == 1 and w.orders[0].ability.id in - [AbilityId.HARVEST_GATHER] and w.orders[0].target in gas_building_tags + lambda w: w not in worker_pool_tags + and len(w.orders) == 1 + and w.orders[0].ability.id in [AbilityId.HARVEST_GATHER] + and w.orders[0].target in gas_building_tags ) for _ in range(-deficit): if surplus_workers.amount > 0: @@ -308,8 +315,10 @@ async def my_distribute_workers(self, performance_heavy=True, only_saturate_gas= deficit_townhalls[th.tag] = {"unit": th, "deficit": deficit} elif deficit < 0: surplus_workers = self.workers.closer_than(10, th).filter( - lambda w: w.tag not in worker_pool_tags and len(w.orders) == 1 and w.orders[0].ability.id in - [AbilityId.HARVEST_GATHER] and w.orders[0].target in mineral_tags + lambda w: w.tag not in worker_pool_tags + and len(w.orders) == 1 + and w.orders[0].ability.id in [AbilityId.HARVEST_GATHER] + and w.orders[0].target in mineral_tags ) # worker_pool.extend(surplus_workers) for _ in range(-deficit): @@ -337,7 +346,8 @@ async def my_distribute_workers(self, performance_heavy=True, only_saturate_gas= -gasInfo["deficit"] for gasTag, gasInfo in surplusgas_buildings.items() if gasInfo["deficit"] < 0 ) surplus_count += sum( - -townhall_info["deficit"] for townhall_tag, townhall_info in surplus_townhalls.items() + -townhall_info["deficit"] + for townhall_tag, townhall_info in surplus_townhalls.items() if townhall_info["deficit"] < 0 ) @@ -347,8 +357,10 @@ async def my_distribute_workers(self, performance_heavy=True, only_saturate_gas= if worker_pool.amount >= deficit_gas_count: break 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 and w.orders[0].ability.id in - [AbilityId.HARVEST_GATHER] and w.orders[0].target in mineral_tags + lambda w: w.tag not in worker_pool_tags + and len(w.orders) == 1 + and w.orders[0].ability.id in [AbilityId.HARVEST_GATHER] + and w.orders[0].target in mineral_tags ) while workers_near_gas.amount > 0 and worker_pool.amount < deficit_gas_count: w = workers_near_gas.pop() diff --git a/examples/terran/onebase_battlecruiser.py b/examples/terran/onebase_battlecruiser.py index cd7fb221..1173af5e 100644 --- a/examples/terran/onebase_battlecruiser.py +++ b/examples/terran/onebase_battlecruiser.py @@ -1,4 +1,4 @@ -from typing import List, Tuple +from __future__ import annotations from sc2 import maps from sc2.bot_ai import BotAI @@ -13,9 +13,8 @@ class BCRushBot(BotAI): - - def select_target(self) -> Tuple[Point2, bool]: - """ Select an enemy target the units should attack. """ + def select_target(self) -> tuple[Point2, bool]: + """Select an enemy target the units should attack.""" targets: Units = self.enemy_structures if targets: return targets.random.position, True @@ -24,12 +23,12 @@ def select_target(self) -> Tuple[Point2, bool]: if targets: 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: + 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 - # pylint: disable=R0912 async def on_step(self, iteration): ccs: Units = self.townhalls # If we no longer have townhalls, attack with all workers @@ -102,8 +101,9 @@ async def on_step(self, iteration): # Build starport once we can build starports, up to 2 elif ( factories.ready - and self.structures.of_type({UnitTypeId.STARPORT, UnitTypeId.STARPORTFLYING}).ready.amount + - self.already_pending(UnitTypeId.STARPORT) < 2 + and self.structures.of_type({UnitTypeId.STARPORT, UnitTypeId.STARPORTFLYING}).ready.amount + + self.already_pending(UnitTypeId.STARPORT) + < 2 ): if self.can_afford(UnitTypeId.STARPORT): await self.build( @@ -111,8 +111,8 @@ async def on_step(self, iteration): near=cc.position.towards(self.game_info.map_center, 15).random_on_distance(8), ) - def starport_points_to_build_addon(sp_position: Point2) -> List[Point2]: - """ Return all points that need to be checked when trying to build an addon. Returns 4 points. """ + def starport_points_to_build_addon(sp_position: Point2) -> list[Point2]: + """Return all points that need to be checked when trying to build an addon. Returns 4 points.""" addon_offset: Point2 = Point2((2.5, -0.5)) addon_position: Point2 = sp_position + addon_offset addon_points = [ @@ -126,15 +126,17 @@ def starport_points_to_build_addon(sp_position: Point2) -> List[Point2]: if not sp.has_add_on and self.can_afford(UnitTypeId.STARPORTTECHLAB): addon_points = starport_points_to_build_addon(sp.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 + 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) else: sp(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. """ + 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) @@ -147,10 +149,10 @@ def starport_land_positions(sp_position: Point2) -> List[Point2]: 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) + 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 + 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 diff --git a/examples/terran/proxy_rax.py b/examples/terran/proxy_rax.py index 5101cc36..0ce8d789 100644 --- a/examples/terran/proxy_rax.py +++ b/examples/terran/proxy_rax.py @@ -10,11 +10,9 @@ class ProxyRaxBot(BotAI): - async def on_start(self): self.client.game_step = 2 - # pylint: disable=R0912 async def on_step(self, iteration): # If we don't have a townhall anymore, send all units to attack ccs: Units = self.townhalls(UnitTypeId.COMMANDCENTER) @@ -45,8 +43,9 @@ async def on_step(self, iteration): await self.build(UnitTypeId.SUPPLYDEPOT, near=cc.position.towards(self.game_info.map_center, 5)) # Build proxy barracks - elif self.structures(UnitTypeId.BARRACKS - ).amount < 3 or (self.minerals > 400 and self.structures(UnitTypeId.BARRACKS).amount < 5): + elif self.structures(UnitTypeId.BARRACKS).amount < 3 or ( + self.minerals > 400 and self.structures(UnitTypeId.BARRACKS).amount < 5 + ): if self.can_afford(UnitTypeId.BARRACKS): p: Point2 = self.game_info.map_center.towards(self.enemy_start_locations[0], 25) await self.build(UnitTypeId.BARRACKS, near=p) diff --git a/examples/terran/ramp_wall.py b/examples/terran/ramp_wall.py index 47c675df..244bce99 100644 --- a/examples/terran/ramp_wall.py +++ b/examples/terran/ramp_wall.py @@ -1,5 +1,4 @@ import random -from typing import FrozenSet, Set import numpy as np from loguru import logger @@ -17,12 +16,9 @@ class RampWallBot(BotAI): - - # pylint: disable=W0231 def __init__(self): self.unit_command_uses_self_do = False - # pylint: disable=R0912 async def on_step(self, iteration): ccs: Units = self.townhalls(UnitTypeId.COMMANDCENTER) if not ccs: @@ -74,7 +70,7 @@ async def on_step(self, iteration): # Draw if two selected units are facing each other - green if this guy is facing the other, red if he is not self.draw_facing_units() - depot_placement_positions: FrozenSet[Point2] = self.main_base_ramp.corner_depots + depot_placement_positions: frozenset[Point2] = self.main_base_ramp.corner_depots # Uncomment the following if you want to build 3 supply depots in the wall instead of a barracks in the middle + 2 depots in the corner # depot_placement_positions = self.main_base_ramp.corner_depots | {self.main_base_ramp.depot_in_middle} @@ -86,9 +82,8 @@ async def on_step(self, iteration): # Filter locations close to finished supply depots if depots: - depot_placement_positions: Set[Point2] = { - d - for d in depot_placement_positions if depots.closest_distance_to(d) > 1 + depot_placement_positions: set[Point2] = { + d for d in depot_placement_positions if depots.closest_distance_to(d) > 1 } # Build depots @@ -248,7 +243,7 @@ def draw_example(self): self.client.debug_text_simple(text="Hello world2!") def draw_facing_units(self): - """ Draws green box on top of selected_unit2, if selected_unit2 is facing selected_unit1 """ + """Draws green box on top of selected_unit2, if selected_unit2 is facing selected_unit1""" selected_unit1: Unit selected_unit2: Unit red = Point3((255, 0, 0)) diff --git a/examples/too_slow_bot.py b/examples/too_slow_bot.py index b36abd99..28d32c0e 100644 --- a/examples/too_slow_bot.py +++ b/examples/too_slow_bot.py @@ -9,7 +9,6 @@ class SlowBot(ProxyRaxBot): - async def on_step(self, iteration): await asyncio.sleep(random.random()) await super().on_step(iteration) diff --git a/examples/watch_replay.py b/examples/watch_replay.py index 30cc5d33..67861468 100644 --- a/examples/watch_replay.py +++ b/examples/watch_replay.py @@ -1,4 +1,3 @@ -import os import platform from pathlib import Path @@ -32,14 +31,13 @@ async def on_step(self, iteration: int): if not replay_path.is_file(): logger.warning(f"You are on linux, please put the replay in directory {home_replay_folder}") raise FileNotFoundError - replay_path = str(replay_path) - elif os.path.isabs(replay_name): - replay_path = replay_name + elif Path(replay_name).is_absolute(): + replay_path = Path(replay_name) else: # Convert relative path to absolute path, assuming this replay is in this folder - folder_path = os.path.dirname(__file__) - replay_path = os.path.join(folder_path, replay_name) - assert os.path.isfile( - replay_path + folder_path = Path(__file__).parent + replay_path = folder_path / replay_name + assert ( + replay_path.is_file() ), "Run worker_rush.py in the same folder first to generate a replay. Then run watch_replay.py again." run_replay(my_observer_ai, replay_path=replay_path) diff --git a/examples/worker_rush.py b/examples/worker_rush.py index 537d4cf4..686c7256 100644 --- a/examples/worker_rush.py +++ b/examples/worker_rush.py @@ -6,7 +6,6 @@ class WorkerRushBot(BotAI): - async def on_step(self, iteration): if iteration == 0: for worker in self.workers: diff --git a/examples/worker_stack_bot.py b/examples/worker_stack_bot.py index eed7acab..0a0cbbb2 100644 --- a/examples/worker_stack_bot.py +++ b/examples/worker_stack_bot.py @@ -13,7 +13,7 @@ - Re-assign workers when gas mines out """ -from typing import Dict, Set +from __future__ import annotations from loguru import logger @@ -27,12 +27,10 @@ from sc2.units import Units -# pylint: disable=W0231 class WorkerStackBot(BotAI): - def __init__(self): - self.worker_to_mineral_patch_dict: Dict[int, int] = {} - self.mineral_patch_to_list_of_workers: Dict[int, Set[int]] = {} + self.worker_to_mineral_patch_dict: dict[int, int] = {} + self.mineral_patch_to_list_of_workers: dict[int, set[int]] = {} self.minerals_sorted_by_distance: Units = Units([], self) # Distance 0.01 to 0.1 seems fine self.townhall_distance_threshold = 0.01 @@ -44,10 +42,9 @@ async def on_start(self): await self.assign_workers() async def assign_workers(self): - self.minerals_sorted_by_distance = self.mineral_field.closer_than(10, - self.start_location).sorted_by_distance_to( - self.start_location - ) + self.minerals_sorted_by_distance = self.mineral_field.closer_than( + 10, self.start_location + ).sorted_by_distance_to(self.start_location) # Assign workers to mineral patch, start with the mineral patch closest to base for mineral in self.minerals_sorted_by_distance: @@ -69,16 +66,15 @@ async def assign_workers(self): async def on_step(self, iteration: int): if self.worker_to_mineral_patch_dict: # Quick-access cache mineral tag to mineral Unit - minerals: Dict[int, Unit] = {mineral.tag: mineral for mineral in self.mineral_field} + minerals: dict[int, Unit] = {mineral.tag: mineral for mineral in self.mineral_field} + worker: Unit for worker in self.workers: if not self.townhalls: logger.error("All townhalls died - can't return resources") break - - worker: Unit mineral_tag = self.worker_to_mineral_patch_dict[worker.tag] - mineral = minerals.get(mineral_tag, None) + mineral = minerals.get(mineral_tag) if mineral is None: logger.error(f"Mined out mineral with tag {mineral_tag} for worker {worker.tag}") continue @@ -93,6 +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: @@ -100,6 +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") @@ -107,8 +105,7 @@ async def on_step(self, iteration: int): def main(): run_game( maps.get("AcropolisLE"), - [Bot(Race.Protoss, WorkerStackBot()), - Computer(Race.Terran, Difficulty.Medium)], + [Bot(Race.Protoss, WorkerStackBot()), Computer(Race.Terran, Difficulty.Medium)], realtime=False, random_seed=0, ) diff --git a/examples/zerg/banes_banes_banes.py b/examples/zerg/banes_banes_banes.py index 8ba4b632..85a00c70 100644 --- a/examples/zerg/banes_banes_banes.py +++ b/examples/zerg/banes_banes_banes.py @@ -23,14 +23,11 @@ def select_target(self) -> Point2: return random.choice(self.enemy_structures).position return self.enemy_start_locations[0] - # pylint: disable=R0912 async def on_step(self, iteration): larvae: Units = self.larva lings: Units = self.units(UnitTypeId.ZERGLING) # Send all idle banes to enemy - if banes := [ - u for u in self.units if u.type_id == UnitTypeId.BANELING and u.is_idle - ]: + if banes := [u for u in self.units if u.type_id == UnitTypeId.BANELING and u.is_idle]: for unit in banes: unit.attack(self.select_target()) @@ -45,11 +42,7 @@ async def on_step(self, iteration): return # If bane nest is ready, train banes - if ( - lings - and self.can_afford(UnitTypeId.BANELING) - and self.structures(UnitTypeId.BANELINGNEST).ready - ): + if lings and self.can_afford(UnitTypeId.BANELING) and self.structures(UnitTypeId.BANELINGNEST).ready: # TODO: Get lings.random.train(UnitTypeId.BANELING) to work # Broken on recent patches # lings.random.train(UnitTypeId.BANELING) @@ -60,9 +53,7 @@ async def on_step(self, iteration): # If all our townhalls are dead, send all our units to attack if not self.townhalls: - for unit in self.units.of_type( - {UnitTypeId.DRONE, UnitTypeId.QUEEN, UnitTypeId.ZERGLING} - ): + for unit in self.units.of_type({UnitTypeId.DRONE, UnitTypeId.QUEEN, UnitTypeId.ZERGLING}): unit.attack(self.enemy_start_locations[0]) return @@ -77,11 +68,7 @@ async def on_step(self, iteration): queen(AbilityId.EFFECT_INJECTLARVA, hq) # Build spawning pool - if ( - self.structures(UnitTypeId.SPAWNINGPOOL).amount - + self.already_pending(UnitTypeId.SPAWNINGPOOL) - == 0 - ): + if self.structures(UnitTypeId.SPAWNINGPOOL).amount + self.already_pending(UnitTypeId.SPAWNINGPOOL) == 0: if self.can_afford(UnitTypeId.SPAWNINGPOOL): await self.build( UnitTypeId.SPAWNINGPOOL, @@ -95,14 +82,8 @@ async def on_step(self, iteration): # hq.build(UnitTypeId.LAIR) # If lair is ready and we have no hydra den on the way: build hydra den - if self.structures(UnitTypeId.SPAWNINGPOOL).ready and self.can_afford( - UnitTypeId.BANELINGNEST - ): - if ( - self.structures(UnitTypeId.BANELINGNEST).amount - + self.already_pending(UnitTypeId.BANELINGNEST) - == 0 - ): + if self.structures(UnitTypeId.SPAWNINGPOOL).ready and self.can_afford(UnitTypeId.BANELINGNEST): + if self.structures(UnitTypeId.BANELINGNEST).amount + self.already_pending(UnitTypeId.BANELINGNEST) == 0: await self.build( UnitTypeId.BANELINGNEST, near=hq.position.towards(self.game_info.map_center, 5), @@ -111,8 +92,7 @@ async def on_step(self, iteration): # If we dont have both extractors: build them if ( self.structures(UnitTypeId.SPAWNINGPOOL) - and self.gas_buildings.amount + self.already_pending(UnitTypeId.EXTRACTOR) - < 2 + and self.gas_buildings.amount + self.already_pending(UnitTypeId.EXTRACTOR) < 2 and self.can_afford(UnitTypeId.EXTRACTOR) ): # May crash if we dont have any drones diff --git a/examples/zerg/expand_everywhere.py b/examples/zerg/expand_everywhere.py index f8f5fe64..552ae0f9 100644 --- a/examples/zerg/expand_everywhere.py +++ b/examples/zerg/expand_everywhere.py @@ -1,6 +1,5 @@ import random from contextlib import suppress -from typing import Set from sc2 import maps from sc2.bot_ai import BotAI @@ -13,7 +12,6 @@ class ExpandEverywhere(BotAI): - async def on_start(self): self.client.game_step = 50 await self.client.debug_show_map() @@ -21,7 +19,9 @@ async def on_start(self): async def on_step(self, iteration): # Build overlords if about to be supply blocked if ( - self.supply_left < 2 and self.supply_cap < 200 and self.already_pending(UnitTypeId.OVERLORD) < 2 + self.supply_left < 2 + and self.supply_cap < 200 + and self.already_pending(UnitTypeId.OVERLORD) < 2 and self.can_afford(UnitTypeId.OVERLORD) ): self.train(UnitTypeId.OVERLORD) @@ -29,8 +29,8 @@ async def on_step(self, iteration): # While we have less than 16 drones, make more drones if ( self.can_afford(UnitTypeId.DRONE) - and self.supply_workers - self.worker_en_route_to_build(UnitTypeId.HATCHERY) < - (self.townhalls.amount + self.placeholders(UnitTypeId.HATCHERY).amount) * 16 + and self.supply_workers - self.worker_en_route_to_build(UnitTypeId.HATCHERY) + < (self.townhalls.amount + self.placeholders(UnitTypeId.HATCHERY).amount) * 16 ): self.train(UnitTypeId.DRONE) @@ -40,10 +40,10 @@ async def on_step(self, iteration): # Expand if we have 300 minerals, try to expand if there is one more expansion location available with suppress(AssertionError): if self.can_afford(UnitTypeId.HATCHERY): - planned_hatch_locations: Set[Point2] = {placeholder.position for placeholder in self.placeholders} - my_structure_locations: Set[Point2] = {structure.position for structure in self.structures} - enemy_structure_locations: Set[Point2] = {structure.position for structure in self.enemy_structures} - blocked_locations: Set[Point2] = ( + planned_hatch_locations: set[Point2] = {placeholder.position for placeholder in self.placeholders} + my_structure_locations: set[Point2] = {structure.position for structure in self.structures} + enemy_structure_locations: set[Point2] = {structure.position for structure in self.enemy_structures} + blocked_locations: set[Point2] = ( my_structure_locations | planned_hatch_locations | enemy_structure_locations ) shuffled_expansions = self.expansion_locations_list.copy() @@ -61,7 +61,7 @@ async def on_step(self, iteration): await self.client.debug_kill_unit(self.enemy_units) async def on_building_construction_complete(self, unit: Unit): - """ Set rally point of new hatcheries. """ + """Set rally point of new hatcheries.""" if unit.type_id == UnitTypeId.HATCHERY and self.mineral_field: mf = self.mineral_field.closest_to(unit) unit.smart(mf) @@ -70,8 +70,7 @@ async def on_building_construction_complete(self, unit: Unit): def main(): run_game( maps.get("AcropolisLE"), - [Bot(Race.Zerg, ExpandEverywhere()), - Computer(Race.Terran, Difficulty.Medium)], + [Bot(Race.Zerg, ExpandEverywhere()), Computer(Race.Terran, Difficulty.Medium)], realtime=False, save_replay_as="ZvT.SC2Replay", ) diff --git a/examples/zerg/hydralisk_push.py b/examples/zerg/hydralisk_push.py index 9ea30d13..6e6d17e2 100644 --- a/examples/zerg/hydralisk_push.py +++ b/examples/zerg/hydralisk_push.py @@ -14,13 +14,11 @@ class Hydralisk(BotAI): - def select_target(self) -> Point2: if self.enemy_structures: return random.choice(self.enemy_structures).position return self.enemy_start_locations[0] - # pylint: disable=R0912 async def on_step(self, iteration): larvae: Units = self.larva forces: Units = self.units.of_type({UnitTypeId.ZERGLING, UnitTypeId.HYDRALISK}) @@ -39,11 +37,13 @@ async def on_step(self, iteration): hydra_dens = self.structures(UnitTypeId.HYDRALISKDEN) if hydra_dens: for hydra_den in hydra_dens.ready.idle: - if self.already_pending_upgrade(UpgradeId.EVOLVEGROOVEDSPINES - ) == 0 and self.can_afford(UpgradeId.EVOLVEGROOVEDSPINES): + if self.already_pending_upgrade(UpgradeId.EVOLVEGROOVEDSPINES) == 0 and self.can_afford( + UpgradeId.EVOLVEGROOVEDSPINES + ): hydra_den.research(UpgradeId.EVOLVEGROOVEDSPINES) - elif self.already_pending_upgrade(UpgradeId.EVOLVEMUSCULARAUGMENTS - ) == 0 and self.can_afford(UpgradeId.EVOLVEMUSCULARAUGMENTS): + elif self.already_pending_upgrade(UpgradeId.EVOLVEMUSCULARAUGMENTS) == 0 and self.can_afford( + UpgradeId.EVOLVEMUSCULARAUGMENTS + ): hydra_den.research(UpgradeId.EVOLVEMUSCULARAUGMENTS) # If hydra den is ready, train hydra diff --git a/examples/zerg/onebase_broodlord.py b/examples/zerg/onebase_broodlord.py index 88561a3d..72d75bca 100644 --- a/examples/zerg/onebase_broodlord.py +++ b/examples/zerg/onebase_broodlord.py @@ -1,3 +1,4 @@ +# noqa: SIM102 import random from sc2 import maps @@ -13,13 +14,11 @@ class BroodlordBot(BotAI): - def select_target(self) -> Point2: if self.enemy_structures: return random.choice(self.enemy_structures).position return self.enemy_start_locations[0] - # pylint: disable=R0912 async def on_step(self, iteration): larvae: Units = self.larva forces: Units = self.units.of_type({UnitTypeId.ZERGLING, UnitTypeId.CORRUPTOR, UnitTypeId.BROODLORD}) diff --git a/examples/zerg/worker_split.py b/examples/zerg/worker_split.py index 3d78a5bc..3edec5bb 100644 --- a/examples/zerg/worker_split.py +++ b/examples/zerg/worker_split.py @@ -18,9 +18,8 @@ class WorkerSplitBot(BotAI): - async def on_before_start(self): - """ This function is run before the expansion locations and ramps are calculated. These calculations can take up to a second, depending on the CPU. """ + """This function is run before the expansion locations and ramps are calculated. These calculations can take up to a second, depending on the CPU.""" mf: Units = self.mineral_field for w in self.workers: w.gather(mf.closest_to(w)) @@ -29,7 +28,7 @@ async def on_before_start(self): await asyncio.sleep(3) async def on_start(self): - """ This function is run after the expansion locations and ramps are calculated. """ + """This function is run after the expansion locations and ramps are calculated.""" async def on_step(self, iteration): if iteration % 10 == 0: diff --git a/examples/zerg/zerg_rush.py b/examples/zerg/zerg_rush.py index 6fa7c47b..93139434 100644 --- a/examples/zerg/zerg_rush.py +++ b/examples/zerg/zerg_rush.py @@ -15,16 +15,13 @@ from sc2.units import Units -# pylint: disable=W0231 class ZergRushBot(BotAI): - def __init__(self): self.on_end_called = False async def on_start(self): self.client.game_step = 2 - # pylint: disable=R0912 async def on_step(self, iteration): if iteration == 0: await self.chat_send("(glhf)") @@ -63,8 +60,9 @@ async def on_step(self, iteration): drone.gather(mineral, queue=True) # If we have 100 vespene, this will try to research zergling speed once the spawning pool is at 100% completion - if self.already_pending_upgrade(UpgradeId.ZERGLINGMOVEMENTSPEED - ) == 0 and self.can_afford(UpgradeId.ZERGLINGMOVEMENTSPEED): + if self.already_pending_upgrade(UpgradeId.ZERGLINGMOVEMENTSPEED) == 0 and self.can_afford( + UpgradeId.ZERGLINGMOVEMENTSPEED + ): spawning_pools_ready: Units = self.structures(UnitTypeId.SPAWNINGPOOL).ready if spawning_pools_ready: self.research(UpgradeId.ZERGLINGMOVEMENTSPEED) @@ -75,7 +73,8 @@ async def on_step(self, iteration): # While we have less than 88 vespene mined: send drones into extractor one frame at a time if ( - self.gas_buildings.ready and self.vespene < 88 + self.gas_buildings.ready + and self.vespene < 88 and self.already_pending_upgrade(UpgradeId.ZERGLINGMOVEMENTSPEED) == 0 ): extractor: Unit = self.gas_buildings.first @@ -101,7 +100,8 @@ async def on_step(self, iteration): # If we have no extractor, build extractor if ( self.gas_buildings.amount + self.already_pending(UnitTypeId.EXTRACTOR) == 0 - and self.can_afford(UnitTypeId.EXTRACTOR) and self.workers + and self.can_afford(UnitTypeId.EXTRACTOR) + and self.workers ): drone: Unit = self.workers.random target: Unit = self.vespene_geyser.closest_to(drone) @@ -136,6 +136,7 @@ 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/generate_dicts_from_data_json.py b/generate_dicts_from_data_json.py index c64a27d3..b6d882cd 100644 --- a/generate_dicts_from_data_json.py +++ b/generate_dicts_from_data_json.py @@ -9,13 +9,14 @@ data.json origin: https://github.com/BurnySc2/sc2-techtree/tree/develop/data """ + +from __future__ import annotations + import json import lzma -import os import pickle from collections import OrderedDict from pathlib import Path -from typing import Dict, List, Optional, Set, Union from loguru import logger @@ -32,19 +33,17 @@ def get_map_file_path() -> Path: # Custom repr function so that the output is always the same and only changes when there were changes in the data.json tech tree file # The output just needs to be ordered (sorted by enum name), but it does not matter anymore if the bot then imports an unordered dict and set class OrderedDict2(OrderedDict): - def __repr__(self): if not self: return "{}" return ( - "{" + - ", ".join(f"{repr(key)}: {repr(value)}" - for key, value in sorted(self.items(), key=lambda u: u[0].name)) + "}" + "{" + + ", ".join(f"{repr(key)}: {repr(value)}" for key, value in sorted(self.items(), key=lambda u: u[0].name)) + + "}" ) class OrderedSet2(set): - def __repr__(self): if not self: return "set()" @@ -63,7 +62,7 @@ def dump_dict_to_file( f.write(repr(my_dict)) -def generate_init_file(dict_file_paths: List[Path], file_path: Path, file_header: str): +def generate_init_file(dict_file_paths: list[Path], file_path: Path, file_header: str): base_file_names = sorted(path.stem for path in dict_file_paths) with file_path.open("w") as f: @@ -81,14 +80,14 @@ def get_unit_train_build_abilities(data): _upgrade_data = data["Upgrade"] # From which abilities can a unit be trained - train_abilities: Dict[UnitTypeId, Set[AbilityId]] = OrderedDict2() + train_abilities: dict[UnitTypeId, set[AbilityId]] = OrderedDict2() # If the ability requires a placement position - ability_requires_placement: Set[AbilityId] = set() + ability_requires_placement: set[AbilityId] = set() # Map ability to unittypeid - ability_to_unittypeid_dict: Dict[AbilityId, UnitTypeId] = OrderedDict2() + ability_to_unittypeid_dict: dict[AbilityId, UnitTypeId] = OrderedDict2() # From which abilities can a unit be morphed - # unit_morph_abilities: Dict[UnitTypeId, Set[AbilityId]] = {} + # unit_morph_abilities: dict[UnitTypeId, set[AbilityId]] = {} entry: dict for entry in ability_data: @@ -115,7 +114,9 @@ def get_unit_train_build_abilities(data): # Collect larva morph abilities, and one way morphs (exclude burrow, hellbat morph, siege tank siege) # Also doesnt include building addons if not train_unit_type_id_value and ( - "LARVATRAIN_" in ability_id.name or ability_id in { + "LARVATRAIN_" in ability_id.name + or ability_id + in { AbilityId.MORPHTOBROODLORD_BROODLORD, AbilityId.MORPHZERGLINGTOBANELING_BANELING, AbilityId.MORPHTORAVAGER_RAVAGER, @@ -174,7 +175,7 @@ def get_unit_train_build_abilities(data): } } """ - unit_train_abilities: Dict[UnitTypeId, Dict[str, Union[AbilityId, bool, UnitTypeId]]] = OrderedDict2() + unit_train_abilities: dict[UnitTypeId, dict[str, AbilityId | bool | UnitTypeId]] = OrderedDict2() for entry in unit_data: unit_abilities = entry.get("abilities", []) unit_type = UnitTypeId(entry["id"]) @@ -188,7 +189,7 @@ def get_unit_train_build_abilities(data): continue requires_techlab: bool = False - required_building: Optional[UnitTypeId] = None + required_building: UnitTypeId | None = None requires_placement_position: bool = False requires_power: bool = False """ @@ -201,7 +202,7 @@ def get_unit_train_build_abilities(data): } ] """ - requirements: List[Dict[str, int]] = ability_info.get("requirements", []) + requirements: list[dict[str, int]] = ability_info.get("requirements", []) if requirements: # Assume train abilities only have one tech building requirement; thors requiring armory and techlab is seperatedly counted assert ( @@ -245,7 +246,7 @@ def get_upgrade_abilities(data): unit_data = data["Unit"] _upgrade_data = data["Upgrade"] - ability_to_upgrade_dict: Dict[AbilityId, UpgradeId] = OrderedDict2() + ability_to_upgrade_dict: dict[AbilityId, UpgradeId] = OrderedDict2() """ We want to be able to research an upgrade by doing await self.can_research(UpgradeId, return_idle_structures=True) -> returns list of idle structures that can research it @@ -258,7 +259,6 @@ def get_upgrade_abilities(data): if isinstance(entry.get("target", {}), str): continue ability_id: AbilityId = AbilityId(entry["id"]) - researched_ability_id: UnitTypeId upgrade_id_value: int = entry.get("target", {}).get("Research", {}).get("upgrade", 0) if upgrade_id_value: @@ -363,12 +363,12 @@ def get_unit_abilities(data: dict): unit_data = data["Unit"] _upgrade_data = data["Upgrade"] - all_unit_abilities: Dict[UnitTypeId, Set[AbilityId]] = OrderedDict2() + all_unit_abilities: dict[UnitTypeId, set[AbilityId]] = OrderedDict2() entry: dict for entry in unit_data: entry_unit_abilities = entry.get("abilities", []) unit_type = UnitTypeId(entry["id"]) - current_collected_unit_abilities: Set[AbilityId] = OrderedSet2() + current_collected_unit_abilities: set[AbilityId] = OrderedSet2() for ability_info in entry_unit_abilities: ability_id_value: int = ability_info.get("ability", 0) if ability_id_value: @@ -394,15 +394,15 @@ def generate_unit_alias_dict(data: dict): raw_game_data, raw_game_info, raw_observation = pickle.load(f) game_data = GameData(raw_game_data.data) - all_unit_aliases: Dict[UnitTypeId, UnitTypeId] = OrderedDict2() - all_tech_aliases: Dict[UnitTypeId, Set[UnitTypeId]] = OrderedDict2() + all_unit_aliases: dict[UnitTypeId, UnitTypeId] = OrderedDict2() + all_tech_aliases: dict[UnitTypeId, set[UnitTypeId]] = OrderedDict2() entry: dict for entry in unit_data: unit_type_value = entry["id"] unit_type = UnitTypeId(entry["id"]) - current_unit_tech_aliases: Set[UnitTypeId] = OrderedSet2() + current_unit_tech_aliases: set[UnitTypeId] = OrderedSet2() assert ( unit_type_value in game_data.units @@ -413,7 +413,7 @@ def generate_unit_alias_dict(data: dict): unit_alias_unit_type_id = UnitTypeId(unit_alias) all_unit_aliases[unit_type] = unit_alias_unit_type_id - tech_aliases: List[int] = game_data.units[unit_type_value]._proto.tech_alias + tech_aliases: list[int] = game_data.units[unit_type_value]._proto.tech_alias for tech_alias in tech_aliases: # Might be 0 if it has no alias @@ -431,7 +431,7 @@ def generate_redirect_abilities_dict(data: dict): _unit_data = data["Unit"] _upgrade_data = data["Upgrade"] - all_redirect_abilities: Dict[AbilityId, AbilityId] = OrderedDict2() + all_redirect_abilities: dict[AbilityId, AbilityId] = OrderedDict2() entry: dict for entry in ability_data: @@ -459,7 +459,7 @@ def main(): data = json.load(f) dicts_path = path / "sc2" / "dicts" - os.makedirs(dicts_path, exist_ok=True) + Path(dicts_path).mkdir(parents=True, exist_ok=True) # All unit train and build abilities unit_train_abilities = get_unit_train_build_abilities(data=data) @@ -501,7 +501,7 @@ def main(): # from sc2.ids.buff_id import BuffId # from sc2.ids.effect_id import EffectId -from typing import Dict, Set, Union +from typing import Union """ dict_file_paths = [ @@ -517,8 +517,7 @@ def main(): init_file_path = dicts_path / "__init__.py" init_header = f"""# DO NOT EDIT! # This file was automatically generated by "{file_name}" - - """ +""" generate_init_file(dict_file_paths=dict_file_paths, file_path=init_file_path, file_header=init_header) dump_dict_to_file( @@ -526,57 +525,56 @@ def main(): unit_creation_dict_path, dict_name="TRAIN_INFO", file_header=file_header, - dict_type_annotation=": Dict[UnitTypeId, Dict[UnitTypeId, Dict[str, Union[AbilityId, bool, UnitTypeId]]]]", + dict_type_annotation=": dict[UnitTypeId, dict[UnitTypeId, dict[str, Union[AbilityId, bool, UnitTypeId]]]]", ) dump_dict_to_file( unit_research_abilities, unit_research_abilities_dict_path, dict_name="RESEARCH_INFO", file_header=file_header, - dict_type_annotation= - ": Dict[UnitTypeId, Dict[UpgradeId, Dict[str, Union[AbilityId, bool, UnitTypeId, UpgradeId]]]]", + dict_type_annotation=": dict[UnitTypeId, dict[UpgradeId, dict[str, Union[AbilityId, bool, UnitTypeId, UpgradeId]]]]", ) dump_dict_to_file( unit_trained_from, unit_trained_from_dict_path, dict_name="UNIT_TRAINED_FROM", file_header=file_header, - dict_type_annotation=": Dict[UnitTypeId, Set[UnitTypeId]]", + dict_type_annotation=": dict[UnitTypeId, set[UnitTypeId]]", ) dump_dict_to_file( upgrade_researched_from, upgrade_researched_from_dict_path, dict_name="UPGRADE_RESEARCHED_FROM", file_header=file_header, - dict_type_annotation=": Dict[UpgradeId, UnitTypeId]", + dict_type_annotation=": dict[UpgradeId, UnitTypeId]", ) dump_dict_to_file( unit_abilities, unit_abilities_dict_path, dict_name="UNIT_ABILITIES", file_header=file_header, - dict_type_annotation=": Dict[UnitTypeId, Set[AbilityId]]", + dict_type_annotation=": dict[UnitTypeId, set[AbilityId]]", ) dump_dict_to_file( unit_unit_alias, unit_unit_alias_dict_path, dict_name="UNIT_UNIT_ALIAS", file_header=file_header, - dict_type_annotation=": Dict[UnitTypeId, UnitTypeId]", + dict_type_annotation=": dict[UnitTypeId, UnitTypeId]", ) dump_dict_to_file( unit_tech_alias, unit_tech_alias_dict_path, dict_name="UNIT_TECH_ALIAS", file_header=file_header, - dict_type_annotation=": Dict[UnitTypeId, Set[UnitTypeId]]", + dict_type_annotation=": dict[UnitTypeId, set[UnitTypeId]]", ) dump_dict_to_file( all_redirect_abilities, all_redirect_abilities_path, dict_name="GENERIC_REDIRECT_ABILITIES", file_header=file_header, - dict_type_annotation=": Dict[AbilityId, AbilityId]", + dict_type_annotation=": dict[AbilityId, AbilityId]", ) diff --git a/pyproject.toml b/pyproject.toml index afa60362..35521a2d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,8 +4,7 @@ version = "7.0.1" description = "A StarCraft II API Client for Python 3" authors = [{ name = "BurnySc2", email = "gamingburny@gmail.com" }] requires-python = ">=3.9, <3.13" -license = "MIT" -documentation = "https://burnysc2.github.io/python-sc2/docs/index.html" +license = { file = "LICENSE" } keywords = ["StarCraft", "StarCraft 2", "StarCraft II", "AI", "Bot"] classifiers = [ "Intended Audience :: Developers", @@ -51,11 +50,15 @@ dev = [ "pre-commit>=4.0.1", "pyglet>=2.0.20", "pylint>=3.3.2", + # Type checker + "pyre-check>=0.9.23", "pytest>=8.3.4", "pytest-asyncio>=0.25.0", "pytest-benchmark>=5.1.0", "pytest-cov>=6.0.0", "radon>=6.0.1", + # Linter + "ruff>=0.8.3", "sphinx>=7.4.7", "sphinx-autodoc-typehints>=2.3.0", "sphinx-rtd-theme>=3.0.2", @@ -63,74 +66,63 @@ dev = [ "yapf>=0.43.0", ] +[tool.setuptools.packages.find] +where = ["."] +include = ["sc2"] + [project.urls] Repository = "https://github.com/Burnysc2/python-sc2" Documentation = "https://burnysc2.github.io/python-sc2" -[tool.mypy] -python_version = "3.10" -ignore_missing_imports = true - -[tool.pycln] -all = true - -[tool.isort] -line_length = 120 -multi_line_output = 3 -include_trailing_comma = true -force_grid_wrap = 0 -use_parentheses = true -ensure_newline_before_comments = true - -[tool.pylint.design] -# Maximum number of locals for function / method body -max-locals = 25 -[tool.pylint.messages_control] -max-line-length = 120 -# C0301 Line too long -# C0114 module Docstring -# C0115 missing class docstring -# C0116 missing function docstring -# R0913 function with too many arguments -# C0413 import order -# C0411 import order of external libs -# W0511 TODO -# W0105 string statement with no effect -# R0801 duplicate code -# W0621 redefining name from outer score -# C0103 variable name does not conform snake case naming style -# R0903: Too few public methods of a class -# E1101: Class 'SqlMetaclass' has no '__annotations__' member (no-member) -# C0302: Too many lines in module (2027/1000) (too-many-lines) -# R0902: Too many instance attributes (62/7) (too-many-instance-attributes) -# R0915: Too many statements (61/50) (too-many-statements) -# W0640: Cell variable mining_place defined in loop (cell-var-from-loop) -# W1514: Using open without explicitly specifying an encoding (unspecified-encoding) -disable = [ - "C0301", - "C0114", - "C0115", - "C0116", - "R0913", - "C0413", - "C0411", - "W0511", - "W0105", - "R0801", - "W0621", - "C0103", - "R0903", - "E1101", - "C0302", - "R0902", - "R0915", - "W0640", - "W1514", -] - [tool.yapf] based_on_style = "pep8" column_limit = 120 split_arguments_when_comma_terminated = true dedent_closing_brackets = true allow_split_before_dict_value = false + +[tool.ruff] +target-version = 'py310' +line-length = 120 + +[tool.ruff.lint] +# Allow unused variables when underscore-prefixed. +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" +select = [ + "C4", # flake8-comprehensions + "E", # Error + "F", # pyflakes + "BLE", # flake8-blind-except + # "I", # isort + "N", # pep8-naming + "PGH", # pygrep-hooks + "PTH", # flake8-use-pathlib + "SIM", # flake8-simplify + "W", # Warning + "Q", # flake8-quotes + "YTT", # flake8-2020 + "UP", # pyupgrade + # "A", # flake8-builtins +] +# Allow Pydantic's `@validator` decorator to trigger class method treatment. +pep8-naming.classmethod-decorators = ["pydantic.validator", "classmethod"] +ignore = [ + "E501", # Line too long + "E402", # Module level import not at top of file + "F841", # Local variable `...` is assigned to but never used + "BLE001", # Do not catch blind exception: `Exception` + "N802", # Function name `...` should be lowercase + "N806", # Variable `...` in function should be lowercase. + "SIM102", # Use a single `if` statement instead of nested `if` statements + "UP007", # Use `X | Y` for type annotations + "UP038", # Use `X | Y` in `isinstance` call instead of `(X, Y)` +] + +[tool.ruff.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] +# Allow Pydantic's `@validator` decorator to trigger class method treatment. +classmethod-decorators = ["pydantic.validator", "classmethod"] diff --git a/sc2/action.py b/sc2/action.py index 9e84ac7d..54ff11b1 100644 --- a/sc2/action.py +++ b/sc2/action.py @@ -1,8 +1,9 @@ from __future__ import annotations from itertools import groupby -from typing import TYPE_CHECKING, Union +from typing import TYPE_CHECKING +# pyre-ignore[21] from s2clientprotocol import raw_pb2 as raw_pb from sc2.position import Point2 @@ -13,7 +14,6 @@ from sc2.unit_command import UnitCommand -# pylint: disable=R0912 def combine_actions(action_iter): """ Example input: @@ -26,7 +26,7 @@ def combine_actions(action_iter): """ for key, items in groupby(action_iter, key=lambda a: a.combining_tuple): ability: AbilityId - target: Union[None, Point2, Unit] + target: None | Point2 | Unit queue: bool # See constants.py for combineable abilities combineable: bool @@ -35,8 +35,7 @@ def combine_actions(action_iter): 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, 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): diff --git a/sc2/bot_ai.py b/sc2/bot_ai.py index e986b1d9..5e56d8f8 100644 --- a/sc2/bot_ai.py +++ b/sc2/bot_ai.py @@ -1,4 +1,4 @@ -# pylint: disable=W0212,R0916,R0904 +# pyre-ignore-all-errors[6, 16] from __future__ import annotations import math @@ -6,7 +6,7 @@ import warnings from collections import Counter from functools import cached_property -from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Union +from typing import TYPE_CHECKING from loguru import logger @@ -44,17 +44,17 @@ class BotAI(BotAIInternal): @property def time(self) -> float: - """ Returns time in seconds, assumes the game is played on 'faster' """ + """Returns time in seconds, assumes the game is played on 'faster'""" return self.state.game_loop / 22.4 # / (1/1.4) * (1/16) @property def time_formatted(self) -> str: - """ Returns time as string in min:sec format """ + """Returns time as string in min:sec format""" t = self.time return f"{int(t // 60):02}:{int(t % 60):02}" @property - def step_time(self) -> Tuple[float, float, float, float]: + def step_time(self) -> tuple[float, float, float, float]: """Returns a tuple of step duration in milliseconds. First value is the minimum step duration - the shortest the bot ever took Second value is the average step duration @@ -71,6 +71,7 @@ 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. @@ -121,7 +122,7 @@ def start_location(self) -> Point2: return self.game_info.player_start_location @property - def enemy_start_locations(self) -> List[Point2]: + def enemy_start_locations(self) -> list[Point2]: """Possible start locations for enemies.""" return self.game_info.start_locations @@ -149,35 +150,33 @@ def main_base_ramp(self) -> Ramp: return found_main_base_ramp @property_cache_once_per_frame - def expansion_locations_list(self) -> List[Point2]: - """ Returns a list of expansion positions, not sorted in any way. """ - assert ( - self._expansion_positions_list - ), "self._find_expansion_locations() has not been run yet, so accessing the list of expansion locations is pointless." + def expansion_locations_list(self) -> list[Point2]: + """Returns a list of expansion positions, not sorted in any way.""" + assert self._expansion_positions_list, "self._find_expansion_locations() has not been run yet, so accessing the list of expansion locations is pointless." return self._expansion_positions_list @property_cache_once_per_frame - def expansion_locations_dict(self) -> Dict[Point2, Units]: + def expansion_locations_dict(self) -> dict[Point2, Units]: """ Returns dict with the correct expansion position Point2 object as key, resources as Units (mineral fields and vespene geysers) as value. Caution: This function is slow. If you only need the expansion locations, use the property above. """ - assert ( - self._expansion_positions_list - ), "self._find_expansion_locations() has not been run yet, so accessing the list of expansion locations is pointless." - expansion_locations: Dict[Point2, Units] = {pos: Units([], self) for pos in self._expansion_positions_list} + assert self._expansion_positions_list, "self._find_expansion_locations() has not been run yet, so accessing the list of expansion locations is pointless." + expansion_locations: dict[Point2, Units] = {pos: Units([], self) for pos in self._expansion_positions_list} for resource in self.resources: # It may be that some resources are not mapped to an expansion location - exp_position: Point2 = self._resource_location_to_expansion_position_dict.get(resource.position, None) + exp_position: Point2 | None = self._resource_location_to_expansion_position_dict.get( + resource.position, None + ) if exp_position: assert exp_position in expansion_locations expansion_locations[exp_position].append(resource) return expansion_locations @property - def units_created(self) -> Counter[UnitTypeId, int]: + def units_created(self) -> Counter[UnitTypeId]: """Returns a Counter for all your units and buildings you have created so far. This may be used for statistics (at the end of the game) or for strategic decision making. @@ -197,8 +196,8 @@ async def on_unit_created(self, unit: Unit): return self._units_created async def get_available_abilities( - self, units: Union[List[Unit], Units], ignore_resource_requirements: bool = False - ) -> List[List[AbilityId]]: + self, units: list[Unit] | Units, ignore_resource_requirements: bool = False + ) -> list[list[AbilityId]]: """Returns available abilities of one or more units. Right now only checks cooldown, energy cost, and whether the ability has been researched. Examples:: @@ -213,14 +212,19 @@ async def get_available_abilities( :param ignore_resource_requirements:""" return await self.client.query_available_abilities(units, ignore_resource_requirements) - async def expand_now(self, building: UnitTypeId = None, max_distance: int = 10, location: Optional[Point2] = None): + async def expand_now( + self, + building: UnitTypeId | None = None, + max_distance: int = 10, + location: Point2 | None = None, + ) -> None: """Finds the next possible expansion via 'self.get_next_expansion()'. If the target expansion is blocked (e.g. an enemy unit), it will misplace the expansion. :param building: :param max_distance: :param location:""" - if not building: + if building is None: # self.race is never Race.Random start_townhall_type = { Race.Protoss: UnitTypeId.NEXUS, @@ -239,7 +243,7 @@ async def expand_now(self, building: UnitTypeId = None, max_distance: int = 10, return await self.build(building, near=location, max_distance=max_distance, random_alternative=False, placement_step=1) - async def get_next_expansion(self) -> Optional[Point2]: + async def get_next_expansion(self) -> Point2 | None: """Find next expansion location.""" closest = None @@ -264,8 +268,7 @@ def is_near_to_expansion(t): return closest - # pylint: disable=R0912 - async def distribute_workers(self, resource_ratio: float = 2): + async def distribute_workers(self, resource_ratio: float = 2) -> None: """ Distributes workers across all the bases taken. Keyword `resource_ratio` takes a float. If the current minerals to gas @@ -298,21 +301,20 @@ async def distribute_workers(self, resource_ratio: float = 2): # get all workers that target the gas extraction site # or are on their way back from it local_workers = self.workers.filter( - lambda unit: unit.order_target == mining_place.tag or - (unit.is_carrying_vespene and unit.order_target == bases.closest_to(mining_place).tag) + lambda unit: unit.order_target == mining_place.tag + or (unit.is_carrying_vespene and unit.order_target == bases.closest_to(mining_place).tag) ) else: # get tags of minerals around expansion local_minerals_tags = { - mineral.tag - for mineral in self.mineral_field if mineral.distance_to(mining_place) <= 8 + mineral.tag for mineral in self.mineral_field if mineral.distance_to(mining_place) <= 8 } # get all target tags a worker can have # tags of the minerals he could mine at that base # get workers that work at that gather site local_workers = self.workers.filter( - lambda unit: unit.order_target in local_minerals_tags or - (unit.is_carrying_minerals and unit.order_target == mining_place.tag) + lambda unit: unit.order_target in local_minerals_tags + or (unit.is_carrying_minerals and unit.order_target == mining_place.tag) ) # too many workers if difference > 0: @@ -325,9 +327,11 @@ async def distribute_workers(self, resource_ratio: float = 2): # prepare all minerals near a base if we have too many workers # and need to send them to the closest patch + all_minerals_near_base = [] if len(worker_pool) > len(deficit_mining_places): all_minerals_near_base = [ - mineral for mineral in self.mineral_field + mineral + for mineral in self.mineral_field if any(mineral.distance_to(base) <= 8 for base in self.townhalls.ready) ] # distribute every worker in the pool @@ -371,7 +375,7 @@ async def distribute_workers(self, resource_ratio: float = 2): pass @property_cache_once_per_frame - def owned_expansions(self) -> Dict[Point2, Unit]: + def owned_expansions(self) -> dict[Point2, Unit]: """Dict of expansions owned by the player with mapping {expansion_location: townhall_structure}.""" owned = {} for el in self.expansion_locations_list: @@ -442,7 +446,7 @@ def calculate_unit_value(self, unit_type: UnitTypeId) -> Cost: unit_data = self.game_data.units[unit_type.value] return Cost(unit_data._proto.mineral_cost, unit_data._proto.vespene_cost) - def calculate_cost(self, item_id: Union[UnitTypeId, UpgradeId, AbilityId]) -> Cost: + def calculate_cost(self, item_id: UnitTypeId | UpgradeId | AbilityId) -> Cost: """ Calculate the required build, train or morph cost of a unit. It is recommended to use the UnitTypeId instead of the ability to create the unit. The total cost to create a ravager is 100/100, but the actual morph cost from roach to ravager is only 25/75, so this function returns 25/75. @@ -494,7 +498,7 @@ def calculate_cost(self, item_id: Union[UnitTypeId, UpgradeId, AbilityId]) -> Co cost = self.game_data.calculate_ability_cost(item_id) return cost - def can_afford(self, item_id: Union[UnitTypeId, UpgradeId, AbilityId], check_supply_cost: bool = True) -> bool: + def can_afford(self, item_id: UnitTypeId | UpgradeId | AbilityId, check_supply_cost: bool = True) -> bool: """Tests if the player has enough resources to build a unit or structure. Example:: @@ -525,9 +529,9 @@ async def can_cast( self, unit: Unit, ability_id: AbilityId, - target: Optional[Union[Unit, Point2]] = None, + target: Unit | Point2 | None = None, only_check_energy_and_cooldown: bool = False, - cached_abilities_of_unit: List[AbilityId] = None, + cached_abilities_of_unit: list[AbilityId] | None = None, ) -> bool: """Tests if a unit has an ability available and enough energy to cast it. @@ -559,25 +563,29 @@ 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 ( - ability_target == 1 or ability_target == Target.PointOrNone.value and isinstance(target, Point2) + 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 return True # Check if able to use ability on a unit if ( - ability_target in {Target.Unit.value, Target.PointOrUnit.value} and isinstance(target, Unit) + ability_target in {Target.Unit.value, Target.PointOrUnit.value} + and isinstance(target, Unit) and unit.distance_to(target) <= unit.radius + target.radius + cast_range ): return True # Check if able to use ability on a position if ( - ability_target in {Target.Point.value, Target.PointOrUnit.value} and isinstance(target, Point2) + ability_target in {Target.Point.value, Target.PointOrUnit.value} + and isinstance(target, Point2) and unit.distance_to(target) <= unit.radius + cast_range ): return True return False - def select_build_worker(self, pos: Union[Unit, Point2], force: bool = False) -> Optional[Unit]: + def select_build_worker(self, pos: Unit | Point2, force: bool = False) -> Unit | None: """Select a worker to build a building with. Example:: @@ -596,7 +604,9 @@ def select_build_worker(self, pos: Union[Unit, Point2], force: bool = False) -> if workers: for worker in workers.sorted_by_distance_to(pos).prefer_idle: if ( - worker not in self.unit_tags_received_action and not worker.orders or len(worker.orders) == 1 + worker not in self.unit_tags_received_action + and not worker.orders + or len(worker.orders) == 1 and worker.orders[0].ability.id in {AbilityId.MOVE, AbilityId.HARVEST_GATHER} ): return worker @@ -604,15 +614,14 @@ def select_build_worker(self, pos: Union[Unit, Point2], force: bool = False) -> return workers.random if force else None return None - async def can_place_single(self, building: Union[AbilityId, UnitTypeId], position: Point2) -> bool: - """ Checks the placement for only one position. """ + 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] return (await self.client._query_building_placement_fast(building, [position]))[0] - async def can_place(self, building: Union[AbilityData, AbilityId, UnitTypeId], - positions: List[Point2]) -> List[bool]: + async def can_place(self, building: AbilityData | AbilityId | UnitTypeId, positions: list[Point2]) -> list[bool]: """Tests if a building can be placed in the given locations. Example:: @@ -652,13 +661,13 @@ async def can_place(self, building: Union[AbilityData, AbilityId, UnitTypeId], async def find_placement( self, - building: Union[UnitTypeId, AbilityId], + building: UnitTypeId | AbilityId, near: Point2, max_distance: int = 20, random_alternative: bool = True, placement_step: int = 2, addon_place: bool = False, - ) -> Optional[Point2]: + ) -> Point2 | None: """Finds a placement location for building. Example:: @@ -680,9 +689,9 @@ async def find_placement( if isinstance(building, UnitTypeId): building = self.game_data.units[building.value].creation_ability.id - if await self.can_place_single( - building, near - ) and (not addon_place or await self.can_place_single(UnitTypeId.SUPPLYDEPOT, near.offset((2.5, -0.5)))): + if await self.can_place_single(building, near) and ( + not addon_place or await self.can_place_single(UnitTypeId.SUPPLYDEPOT, near.offset((2.5, -0.5))) + ): return near if max_distance == 0: @@ -690,11 +699,12 @@ async def find_placement( for distance in range(placement_step, max_distance, placement_step): possible_positions = [ - Point2(p).offset(near).to2 for p in ( - [(dx, -distance) for dx in range(-distance, distance + 1, placement_step)] + - [(dx, distance) for dx in range(-distance, distance + 1, placement_step)] + - [(-distance, dy) for dy in range(-distance, distance + 1, placement_step)] + - [(distance, dy) for dy in range(-distance, distance + 1, placement_step)] + Point2(p).offset(near).to2 + for p in ( + [(dx, -distance) for dx in range(-distance, distance + 1, placement_step)] + + [(dx, distance) for dx in range(-distance, distance + 1, placement_step)] + + [(-distance, dy) for dy in range(-distance, distance + 1, placement_step)] + + [(distance, dy) for dy in range(-distance, distance + 1, placement_step)] ) ] res = await self.client._query_building_placement_fast(building, possible_positions) @@ -743,7 +753,7 @@ def already_pending_upgrade(self, upgrade_type: UpgradeId) -> float: return order.progress return 0 - def structure_type_build_progress(self, structure_type: Union[UnitTypeId, int]) -> float: + def structure_type_build_progress(self, structure_type: UnitTypeId | int) -> float: """ Returns the build progress of a structure type. @@ -779,9 +789,8 @@ def structure_type_build_progress(self, structure_type: Union[UnitTypeId, int]) else: structure_type_value = structure_type.value assert structure_type_value, f"structure_type can not be 0 or NOTAUNIT, but was: {structure_type_value}" - equiv_values: Set[int] = {structure_type_value} | { - s_type.value - for s_type in EQUIVALENTS_FOR_TECH_PROGRESS.get(structure_type, set()) + equiv_values: set[int] = {structure_type_value} | { + s_type.value for s_type in EQUIVALENTS_FOR_TECH_PROGRESS.get(structure_type, set()) } # 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: @@ -791,8 +800,8 @@ def structure_type_build_progress(self, structure_type: Union[UnitTypeId, int]) return 0 creation_ability: AbilityId = creation_ability_data.exact_id max_value = max( - [s.build_progress for s in self.structures if s._proto.unit_type in equiv_values] + - [self._abilities_count_and_build_progress[1].get(creation_ability, 0)], + [s.build_progress for s in self.structures if s._proto.unit_type in equiv_values] + + [self._abilities_count_and_build_progress[1].get(creation_ability, 0)], default=0, ) return max_value @@ -830,12 +839,12 @@ def tech_requirement_progress(self, structure_type: UnitTypeId) -> float: # unit_info_id_value = self.game_data.units[structure_type.value]._proto.tech_requirement if not unit_info_id_value: # Equivalent to "if unit_info_id_value == 0:" return 1 - progresses: List[float] = [self.structure_type_build_progress(unit_info_id_value)] + progresses: list[float] = [self.structure_type_build_progress(unit_info_id_value)] for equiv_structure in EQUIVALENTS_FOR_TECH_PROGRESS.get(unit_info_id, []): progresses.append(self.structure_type_build_progress(equiv_structure.value)) return max(progresses) - def already_pending(self, unit_type: Union[UpgradeId, UnitTypeId]) -> float: + def already_pending(self, unit_type: UpgradeId | UnitTypeId) -> float: """ Returns a number of buildings or units already in progress, or if a worker is en route to build it. This also includes queued orders for @@ -875,7 +884,7 @@ def worker_en_route_to_build(self, unit_type: UnitTypeId) -> float: def structures_without_construction_SCVs(self) -> Units: """Returns all structures that do not have an SCV constructing it. Warning: this function may move to become a Units filter.""" - worker_targets: Set[Union[int, Point2]] = set() + worker_targets: set[int | Point2] = set() for worker in self.workers: # Ignore repairing workers if not worker.is_constructing_scv: @@ -886,17 +895,19 @@ def structures_without_construction_SCVs(self) -> Units: return self.structures.filter( lambda structure: structure.build_progress < 1 # Redundant check? - and structure.type_id in TERRAN_STRUCTURES_REQUIRE_SCV and structure.position not in worker_targets and - structure.tag not in worker_targets and structure.tag in self._structures_previous_map and self. - _structures_previous_map[structure.tag].build_progress == structure.build_progress + and structure.type_id in TERRAN_STRUCTURES_REQUIRE_SCV + and structure.position not in worker_targets + and structure.tag not in worker_targets + and structure.tag in self._structures_previous_map + and self._structures_previous_map[structure.tag].build_progress == structure.build_progress ) async def build( self, building: UnitTypeId, - near: Union[Unit, Point2], + near: Unit | Point2, max_distance: int = 20, - build_worker: Optional[Unit] = None, + build_worker: Unit | None = None, random_alternative: bool = True, placement_step: int = 2, ) -> bool: @@ -938,8 +949,8 @@ def train( self, unit_type: UnitTypeId, amount: int = 1, - closest_to: Point2 = None, - train_only_idle_buildings: bool = True + closest_to: Point2 | None = None, + train_only_idle_buildings: bool = True, ) -> int: """Trains a specified number of units. Trains only one if amount is not specified. Warning: currently has issues with warp gate warp ins @@ -986,7 +997,7 @@ def train( trained_amount = 0 # All train structure types: queen can made from hatchery, lair, hive - train_structure_type: Set[UnitTypeId] = UNIT_TRAINED_FROM[unit_type] + train_structure_type: set[UnitTypeId] = UNIT_TRAINED_FROM[unit_type] train_structures = self.structures if self.race != Race.Zerg else self.structures | self.larva requires_techlab = any( TRAIN_INFO[structure_type][unit_type].get("requires_techlab", False) @@ -995,7 +1006,6 @@ def train( is_protoss = self.race == Race.Protoss is_terran = self.race == Race.Terran can_have_addons = any( - # pylint: disable=C0208 u in train_structure_type for u in {UnitTypeId.BARRACKS, UnitTypeId.FACTORY, UnitTypeId.STARPORT} ) # Sort structures closest to a point @@ -1004,8 +1014,8 @@ def train( elif can_have_addons: # This should sort the structures in ascending order: first structures with reactor, then naked, then with techlab train_structures = train_structures.sorted( - key=lambda structure: -1 * (structure.add_on_tag in self.reactor_tags) + 1 * - (structure.add_on_tag in self.techlab_tags) + key=lambda structure: -1 * (structure.add_on_tag in self.reactor_tags) + + 1 * (structure.add_on_tag in self.techlab_tags) ) structure: Unit @@ -1041,7 +1051,10 @@ def train( else: # Normal train a unit from larva or inside a structure successfully_trained = self.do( - structure.train(unit_type), subtract_cost=True, subtract_supply=True, ignore_warning=True + structure.train(unit_type), + subtract_cost=True, + subtract_supply=True, + ignore_warning=True, ) # Check if structure has reactor: queue same unit again if ( @@ -1106,8 +1119,9 @@ def research(self, upgrade_type: UpgradeId) -> bool: if not self.can_afford(upgrade_type): return False - research_structure_types: UnitTypeId = UPGRADE_RESEARCHED_FROM[upgrade_type] - required_tech_building: Optional[UnitTypeId] = RESEARCH_INFO[research_structure_types][upgrade_type].get( + research_structure_type: UnitTypeId = UPGRADE_RESEARCHED_FROM[upgrade_type] + # pyre-ignore[9] + required_tech_building: UnitTypeId | None = RESEARCH_INFO[research_structure_type][upgrade_type].get( "required_building", None ) @@ -1129,8 +1143,8 @@ def research(self, upgrade_type: UpgradeId) -> bool: } # Convert to a set, or equivalent structures are chosen # Overlord speed upgrade can be researched from hatchery, lair or hive - research_structure_types: Set[UnitTypeId] = equiv_structures.get( - research_structure_types, {research_structure_types} + research_structure_types: set[UnitTypeId] = equiv_structures.get( + research_structure_type, {research_structure_type} ) structure: Unit @@ -1147,12 +1161,14 @@ def research(self, upgrade_type: UpgradeId) -> bool: ): # Can_afford check was already done earlier in this function successful_action: bool = self.do( - structure.research(upgrade_type), subtract_cost=True, ignore_warning=True + structure.research(upgrade_type), + subtract_cost=True, + ignore_warning=True, ) return successful_action return False - async def chat_send(self, message: str, team_only: bool = False): + async def chat_send(self, message: str, team_only: bool = False) -> None: """Send a chat message to the SC2 Client. Example:: @@ -1164,18 +1180,21 @@ async def chat_send(self, message: str, team_only: bool = False): assert isinstance(message, str), f"{message} is not a string" await self.client.chat_send(message, team_only) - def in_map_bounds(self, pos: Union[Point2, tuple, list]) -> bool: + def in_map_bounds(self, pos: Point2 | tuple | list) -> bool: """Tests if a 2 dimensional point is within the map boundaries of the pixelmaps. :param pos:""" return ( - self.game_info.playable_area.x <= pos[0] < - self.game_info.playable_area.x + self.game_info.playable_area.width and self.game_info.playable_area.y <= - pos[1] < self.game_info.playable_area.y + self.game_info.playable_area.height + self.game_info.playable_area.x + <= pos[0] + < self.game_info.playable_area.x + self.game_info.playable_area.width + and self.game_info.playable_area.y + <= pos[1] + < self.game_info.playable_area.y + self.game_info.playable_area.height ) # For the functions below, make sure you are inside the boundaries of the map size. - def get_terrain_height(self, pos: Union[Point2, Unit]) -> int: + def get_terrain_height(self, pos: Point2 | Unit) -> int: """Returns terrain height at a position. Caution: terrain height is different from a unit's z-coordinate. @@ -1184,7 +1203,7 @@ def get_terrain_height(self, pos: Union[Point2, Unit]) -> int: pos = pos.position.rounded return self.game_info.terrain_height[pos] - def get_terrain_z_height(self, pos: Union[Point2, Unit]) -> float: + def get_terrain_z_height(self, pos: Point2 | Unit) -> float: """Returns terrain z-height at a position. :param pos:""" @@ -1192,7 +1211,7 @@ def get_terrain_z_height(self, pos: Union[Point2, Unit]) -> float: pos = pos.position.rounded return -16 + 32 * self.game_info.terrain_height[pos] / 255 - def in_placement_grid(self, pos: Union[Point2, Unit]) -> bool: + def in_placement_grid(self, pos: Point2 | Unit) -> bool: """Returns True if you can place something at a position. Remember, buildings usually use 2x2, 3x3 or 5x5 of these grid points. Caution: some x and y offset might be required, see ramp code in game_info.py @@ -1202,7 +1221,7 @@ def in_placement_grid(self, pos: Union[Point2, Unit]) -> bool: pos = pos.position.rounded return self.game_info.placement_grid[pos] == 1 - def in_pathing_grid(self, pos: Union[Point2, Unit]) -> bool: + def in_pathing_grid(self, pos: Point2 | Unit) -> bool: """Returns True if a ground unit can pass through a grid point. :param pos:""" @@ -1210,7 +1229,7 @@ def in_pathing_grid(self, pos: Union[Point2, Unit]) -> bool: pos = pos.position.rounded return self.game_info.pathing_grid[pos] == 1 - def is_visible(self, pos: Union[Point2, Unit]) -> bool: + def is_visible(self, pos: Point2 | Unit) -> bool: """Returns True if you have vision on a grid point. :param pos:""" @@ -1219,7 +1238,7 @@ def is_visible(self, pos: Union[Point2, Unit]) -> bool: pos = pos.position.rounded return self.state.visibility[pos] == 2 - def has_creep(self, pos: Union[Point2, Unit]) -> bool: + def has_creep(self, pos: Point2 | Unit) -> bool: """Returns True if there is creep on the grid point. :param pos:""" @@ -1227,7 +1246,7 @@ def has_creep(self, pos: Union[Point2, Unit]) -> bool: pos = pos.position.rounded return self.state.creep[pos] == 1 - async def on_unit_destroyed(self, unit_tag: int): + async def on_unit_destroyed(self, unit_tag: int) -> None: """ Override this in your bot class. Note that this function uses unit tags and not the unit objects @@ -1238,12 +1257,12 @@ async def on_unit_destroyed(self, unit_tag: int): :param unit_tag: """ - async def on_unit_created(self, unit: Unit): + async def on_unit_created(self, unit: Unit) -> None: """Override this in your bot class. This function is called when a unit is created. :param unit:""" - async def on_unit_type_changed(self, unit: Unit, previous_type: UnitTypeId): + async def on_unit_type_changed(self, unit: Unit, previous_type: UnitTypeId) -> None: """Override this in your bot class. This function is called when a unit type has changed. To get the current UnitTypeId of the unit, use 'unit.type_id' This may happen when a larva morphed to an egg, siege tank sieged, a zerg unit burrowed, a hatchery morphed to lair, @@ -1257,7 +1276,7 @@ async def on_unit_type_changed(self, unit: Unit, previous_type: UnitTypeId): :param previous_type: """ - async def on_building_construction_started(self, unit: Unit): + async def on_building_construction_started(self, unit: Unit) -> None: """ Override this in your bot class. This function is called when a building construction has started. @@ -1265,7 +1284,7 @@ async def on_building_construction_started(self, unit: Unit): :param unit: """ - async def on_building_construction_complete(self, unit: Unit): + async def on_building_construction_complete(self, unit: Unit) -> None: """ Override this in your bot class. This function is called when a building construction is completed. @@ -1273,14 +1292,14 @@ async def on_building_construction_complete(self, unit: Unit): :param unit: """ - async def on_upgrade_complete(self, upgrade: UpgradeId): + async def on_upgrade_complete(self, upgrade: UpgradeId) -> None: """ Override this in your bot class. This function is called with the upgrade id of an upgrade that was not finished last step and is now. :param upgrade: """ - async def on_unit_took_damage(self, unit: Unit, amount_damage_taken: float): + async def on_unit_took_damage(self, unit: Unit, amount_damage_taken: float) -> None: """ Override this in your bot class. This function is called when your own unit (unit or structure) took damage. It will not be called if the unit died this frame. @@ -1297,14 +1316,14 @@ async def on_unit_took_damage(self, unit: Unit, amount_damage_taken: float): :param amount_damage_taken: """ - async def on_enemy_unit_entered_vision(self, unit: Unit): + async def on_enemy_unit_entered_vision(self, unit: Unit) -> None: """ Override this in your bot class. This function is called when an enemy unit (unit or structure) entered vision (which was not visible last frame). :param unit: """ - async def on_enemy_unit_left_vision(self, unit_tag: int): + async def on_enemy_unit_left_vision(self, unit_tag: int) -> None: """ Override this in your bot class. This function is called when an enemy unit (unit or structure) left vision (which was visible last frame). Same as the self.on_unit_destroyed event, this function is called with the unit's tag because the unit is no longer visible anymore. @@ -1318,7 +1337,7 @@ async def on_enemy_unit_left_vision(self, unit_tag: int): :param unit_tag: """ - async def on_before_start(self): + async def on_before_start(self) -> None: """ Override this in your bot class. This function is called before "on_start" and before "prepare_first_step" that calculates expansion locations. @@ -1326,7 +1345,7 @@ async def on_before_start(self): This function is useful in realtime=True mode to split your workers or start producing the first worker. """ - async def on_start(self): + async def on_start(self) -> None: """ Override this in your bot class. At this point, game_data, game_info and the first iteration of game_state (self.state) are available. @@ -1342,7 +1361,8 @@ async def on_step(self, iteration: int): """ raise NotImplementedError - async def on_end(self, game_result: Result): + # 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 a2a40e1b..b8c9c1a6 100644 --- a/sc2/bot_ai_internal.py +++ b/sc2/bot_ai_internal.py @@ -1,4 +1,4 @@ -# pylint: disable=W0201,W0212,R0912 +# pyre-ignore-all-errors[6, 16, 29] from __future__ import annotations import itertools @@ -7,13 +7,14 @@ import warnings from abc import ABC from collections import Counter +from collections.abc import Generator, Iterable from contextlib import suppress -from typing import TYPE_CHECKING, Any -from typing import Counter as CounterType -from typing import Dict, Generator, Iterable, List, Set, Tuple, Union, final +from typing import TYPE_CHECKING, Any, final import numpy as np from loguru import logger + +# pyre-ignore[21] from s2clientprotocol import sc2api_pb2 as sc_pb from sc2.cache import property_cache_once_per_frame @@ -41,6 +42,7 @@ with warnings.catch_warnings(): warnings.simplefilter("ignore") + # pyre-ignore[21] from scipy.spatial.distance import cdist, pdist if TYPE_CHECKING: @@ -51,16 +53,19 @@ class BotAIInternal(ABC): """Base class for bots.""" + def __init__(self) -> None: + self._initialize_variables() + @final - def _initialize_variables(self): - """ Called from main.py internally """ - self.cache: Dict[str, Any] = {} + def _initialize_variables(self) -> 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 # The bot ID will stay the same each game so your bot can "adapt" to the opponent if not hasattr(self, "opponent_id"): # Prevent overwriting the opponent_id which is set here https://github.com/Hannessa/python-sc2-ladderbot/blob/master/__init__.py#L40 # otherwise set it to None - self.opponent_id: str = None + self.opponent_id: str | None = None # Select distance calculation method, see _distances_override_functions function if not hasattr(self, "distance_calculation_method"): self.distance_calculation_method: int = 2 @@ -87,8 +92,8 @@ def _initialize_variables(self): self.mineral_field: Units = Units([], self) self.vespene_geyser: Units = Units([], self) self.placeholders: Units = Units([], self) - self.techlab_tags: Set[int] = set() - self.reactor_tags: Set[int] = set() + self.techlab_tags: set[int] = set() + self.reactor_tags: set[int] = set() self.minerals: int = 50 self.vespene: int = 0 self.supply_army: float = 0 @@ -99,35 +104,36 @@ def _initialize_variables(self): self.idle_worker_count: int = 0 self.army_count: int = 0 self.warp_gate_count: int = 0 - self.actions: List[UnitCommand] = [] - self.blips: Set[Blip] = set() - self.race: Race = None - self.enemy_race: Race = None + self.actions: list[UnitCommand] = [] + self.blips: set[Blip] = set() + # pyre-ignore[11] + self.race: Race | None = None + self.enemy_race: Race | None = None self._generated_frame = -100 self._units_created: Counter = Counter() - self._unit_tags_seen_this_game: Set[int] = set() - self._units_previous_map: Dict[int, Unit] = {} - self._structures_previous_map: Dict[int, Unit] = {} - self._enemy_units_previous_map: Dict[int, Unit] = {} - self._enemy_structures_previous_map: Dict[int, Unit] = {} - self._all_units_previous_map: Dict[int, Unit] = {} - self._previous_upgrades: Set[UpgradeId] = set() - self._expansion_positions_list: List[Point2] = [] - self._resource_location_to_expansion_position_dict: Dict[Point2, Point2] = {} - self._time_before_step: float = None - self._time_after_step: float = None + self._unit_tags_seen_this_game: set[int] = set() + self._units_previous_map: dict[int, Unit] = {} + self._structures_previous_map: dict[int, Unit] = {} + self._enemy_units_previous_map: dict[int, Unit] = {} + self._enemy_structures_previous_map: dict[int, Unit] = {} + self._all_units_previous_map: dict[int, Unit] = {} + self._previous_upgrades: set[UpgradeId] = set() + self._expansion_positions_list: list[Point2] = [] + self._resource_location_to_expansion_position_dict: dict[Point2, Point2] = {} + self._time_before_step: float = 0 + self._time_after_step: float = 0 self._min_step_time: float = math.inf self._max_step_time: float = 0 self._last_step_step_time: float = 0 self._total_time_in_on_step: float = 0 self._total_steps_iterations: int = 0 # Internally used to keep track which units received an action in this frame, so that self.train() function does not give the same larva two orders - cleared every frame - self.unit_tags_received_action: Set[int] = set() + self.unit_tags_received_action: set[int] = set() @final @property def _game_info(self) -> GameInfo: - """ See game_info.py """ + """See game_info.py""" warnings.warn( "Using self._game_info is deprecated and may be removed soon. Please use self.game_info directly.", DeprecationWarning, @@ -138,7 +144,7 @@ def _game_info(self) -> GameInfo: @final @property def _game_data(self) -> GameData: - """ See game_data.py """ + """See game_data.py""" warnings.warn( "Using self._game_data is deprecated and may be removed soon. Please use self.game_data directly.", DeprecationWarning, @@ -149,7 +155,7 @@ def _game_data(self) -> GameData: @final @property def _client(self) -> Client: - """ See client.py """ + """See client.py""" warnings.warn( "Using self._client is deprecated and may be removed soon. Please use self.client directly.", DeprecationWarning, @@ -159,11 +165,9 @@ def _client(self) -> Client: @final @property_cache_once_per_frame - def expansion_locations(self) -> 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." + def expansion_locations(self) -> 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." warnings.warn( "You are using 'self.expansion_locations', please use 'self.expansion_locations_list' (fast) or 'self.expansion_locations_dict' (slow) instead.", DeprecationWarning, @@ -172,16 +176,17 @@ def expansion_locations(self) -> Dict[Point2, Units]: return self.expansion_locations_dict @final - def _find_expansion_locations(self): - """ Ran once at the start of the game to calculate expansion locations. """ + def _find_expansion_locations(self) -> None: + """Ran once at the start of the game to calculate expansion locations.""" # Idea: create a group for every resource, then merge these groups if # any resource in a group is closer than a threshold to any resource of another group # Distance we group resources by resource_spread_threshold: float = 8.5 # Create a group for every resource - resource_groups: List[List[Unit]] = [ - [resource] for resource in self.resources + resource_groups: list[list[Unit]] = [ + [resource] + for resource in self.resources if resource.name != "MineralField450" # dont use low mineral count patches ] # Loop the merging process as long as we change something @@ -210,7 +215,8 @@ def _find_expansion_locations(self): # Distance offsets we apply to center of each resource group to find expansion position offset_range = 7 offsets = [ - (x, y) for x, y in itertools.product(range(-offset_range, offset_range + 1), repeat=2) + (x, y) + for x, y in itertools.product(range(-offset_range, offset_range + 1), repeat=2) if 4 < math.hypot(x, y) <= 8 ] # Dict we want to return @@ -226,7 +232,8 @@ def _find_expansion_locations(self): possible_points = (Point2((offset[0] + center_x, offset[1] + center_y)) for offset in offsets) # Filter out points that are too near possible_points = ( - point for point in possible_points + point + for point in possible_points # Check if point can be built on if self.game_info.placement_grid[point.rounded] == 1 # Check if all resources have enough space to point @@ -247,7 +254,7 @@ def _find_expansion_locations(self): self._resource_location_to_expansion_position_dict[resource.position] = result @final - def _correct_zerg_supply(self): + def _correct_zerg_supply(self) -> None: """The client incorrectly rounds zerg supply down instead of up (see https://github.com/Blizzard/s2client-proto/issues/123), so self.supply_used and friends return the wrong value when there are an odd number of zerglings @@ -267,43 +274,42 @@ def _correct_zerg_supply(self): @final @property_cache_once_per_frame - def _abilities_count_and_build_progress(self) -> Tuple[CounterType[AbilityId], Dict[AbilityId, float]]: + def _abilities_count_and_build_progress(self) -> tuple[Counter[AbilityId], dict[AbilityId, float]]: """Cache for the already_pending function, includes protoss units warping in, all units in production and all structures, and all morphs""" - abilities_amount: CounterType[AbilityId] = Counter() - max_build_progress: Dict[AbilityId, float] = {} + abilities_amount: Counter[AbilityId] = Counter() + max_build_progress: dict[AbilityId, float] = {} unit: Unit for unit in self.units + self.structures: for order in unit.orders: abilities_amount[order.ability.exact_id] += 1 - if not unit.is_ready: - if self.race != Race.Terran or not unit.is_structure: - # If an SCV is constructing a building, already_pending would count this structure twice - # (once from the SCV order, and once from "not structure.is_ready") - 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 - else: - # Hotfix for rich geysirs - creation_ability = CREATION_ABILITY_FIX[unit.type_id] - abilities_amount[creation_ability] += 1 + if not unit.is_ready and (self.race != Race.Terran or not unit.is_structure): + # If an SCV is constructing a building, already_pending would count this structure twice + # (once from the SCV order, and once from "not structure.is_ready") + 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 else: - creation_ability: AbilityId = self.game_data.units[unit.type_id.value].creation_ability.exact_id + # Hotfix for rich geysirs + creation_ability = CREATION_ABILITY_FIX[unit.type_id] abilities_amount[creation_ability] += 1 - max_build_progress[creation_ability] = max( - max_build_progress.get(creation_ability, 0), unit.build_progress - ) + 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 + ) return abilities_amount, max_build_progress @final @property_cache_once_per_frame - def _worker_orders(self) -> CounterType[AbilityId]: - """ This function is used internally, do not use! It is to store all worker abilities. """ - abilities_amount: CounterType[AbilityId] = Counter() - structures_in_production: Set[Union[Point2, int]] = set() + def _worker_orders(self) -> Counter[AbilityId]: + """This function is used internally, do not use! It is to store all worker abilities.""" + abilities_amount: Counter[AbilityId] = Counter() + structures_in_production: set[Point2 | int] = set() for structure in self.structures: if structure.type_id in TERRAN_STRUCTURES_REQUIRE_SCV: structures_in_production.add(structure.position) @@ -411,7 +417,7 @@ async def synchronous_do(self, action: UnitCommand): return r @final - async def _do_actions(self, actions: List[UnitCommand], prevent_double: bool = True): + async def _do_actions(self, actions: list[UnitCommand], prevent_double: bool = True): """Used internally by main.py automatically, use self.do() instead! :param actions: @@ -451,7 +457,9 @@ def prevent_double_actions(action) -> bool: return True @final - def _prepare_start(self, client, player_id, game_info, game_data, realtime: bool = False, base_build: int = -1): + def _prepare_start( + self, client, player_id: int, game_info, game_data, realtime: bool = False, base_build: int = -1 + ) -> None: """ Ran until game start to set game and player data. @@ -476,7 +484,7 @@ def _prepare_start(self, client, player_id, game_info, game_data, realtime: bool self._distances_override_functions(self.distance_calculation_method) @final - def _prepare_first_step(self): + def _prepare_first_step(self) -> None: """First step extra preparations. Must not be called before _prepare_step.""" if self.townhalls: self.game_info.player_start_location = self.townhalls.first.position @@ -486,7 +494,7 @@ def _prepare_first_step(self): self._time_before_step: float = time.perf_counter() @final - def _prepare_step(self, state, proto_game_info): + def _prepare_step(self, state, proto_game_info) -> None: """ :param state: :param proto_game_info: @@ -496,14 +504,13 @@ def _prepare_step(self, state, proto_game_info): # update pathing grid, which unfortunately is in GameInfo instead of GameState self.game_info.pathing_grid = PixelMap(proto_game_info.game_info.start_raw.pathing_grid, in_bits=True) # Required for events, needs to be before self.units are initialized so the old units are stored - self._units_previous_map: Dict[int, Unit] = {unit.tag: unit for unit in self.units} - self._structures_previous_map: Dict[int, Unit] = {structure.tag: structure for structure in self.structures} - self._enemy_units_previous_map: Dict[int, Unit] = {unit.tag: unit for unit in self.enemy_units} - self._enemy_structures_previous_map: Dict[int, Unit] = { - structure.tag: structure - for structure in self.enemy_structures + self._units_previous_map: dict[int, Unit] = {unit.tag: unit for unit in self.units} + self._structures_previous_map: dict[int, Unit] = {structure.tag: structure for structure in self.structures} + self._enemy_units_previous_map: dict[int, Unit] = {unit.tag: unit for unit in self.enemy_units} + self._enemy_structures_previous_map: dict[int, Unit] = { + structure.tag: structure for structure in self.enemy_structures } - self._all_units_previous_map: Dict[int, Unit] = {unit.tag: unit for unit in self.all_units} + self._all_units_previous_map: dict[int, Unit] = {unit.tag: unit for unit in self.all_units} self._prepare_units() self.minerals: int = state.common.minerals @@ -528,9 +535,9 @@ def _prepare_step(self, state, proto_game_info): self.enemy_race = Race(self.all_enemy_units.first.race) @final - def _prepare_units(self): + def _prepare_units(self) -> 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.blips: set[Blip] = set() self.all_units: Units = Units([], self) self.units: Units = Units([], self) self.workers: Units = Units([], self) @@ -548,10 +555,10 @@ def _prepare_units(self): self.mineral_field: Units = Units([], self) self.vespene_geyser: Units = Units([], self) self.placeholders: Units = Units([], self) - self.techlab_tags: Set[int] = set() - self.reactor_tags: Set[int] = set() + self.techlab_tags: set[int] = set() + self.reactor_tags: set[int] = set() - worker_types: Set[UnitTypeId] = {UnitTypeId.DRONE, UnitTypeId.DRONEBURROWED, UnitTypeId.SCV, UnitTypeId.PROBE} + worker_types: set[UnitTypeId] = {UnitTypeId.DRONE, UnitTypeId.DRONEBURROWED, UnitTypeId.SCV, UnitTypeId.PROBE} index: int = 0 for unit in self.state.observation_raw.units: @@ -633,7 +640,7 @@ def _prepare_units(self): @final async def _after_step(self) -> int: - """ Executed by main.py after each on_step function. """ + """Executed by main.py after each on_step function.""" # Keep track of the bot on_step duration self._time_after_step: float = time.perf_counter() step_duration = self._time_after_step - self._time_before_step @@ -654,7 +661,7 @@ async def _after_step(self) -> int: return self.state.game_loop @final - async def _advance_steps(self, steps: int): + async def _advance_steps(self, 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() @@ -667,7 +674,7 @@ async def _advance_steps(self, steps: int): await self.issue_events() @final - async def issue_events(self): + async def issue_events(self) -> None: """This function will be automatically run from main.py and triggers the following functions: - on_unit_created - on_unit_destroyed @@ -682,7 +689,7 @@ async def issue_events(self): await self._issue_vision_events() @final - async def _issue_unit_added_events(self): + async def _issue_unit_added_events(self) -> 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) @@ -699,14 +706,14 @@ async def _issue_unit_added_events(self): await self.on_unit_type_changed(unit, previous_frame_unit.type_id) @final - async def _issue_upgrade_events(self): + async def _issue_upgrade_events(self) -> 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): + async def _issue_building_events(self) -> None: for structure in self.structures: if structure.tag not in self._structures_previous_map: if structure.build_progress < 1: @@ -723,8 +730,10 @@ async def _issue_building_events(self): or structure.shield < previous_frame_structure.shield ): damage_amount = ( - previous_frame_structure.health - structure.health + previous_frame_structure.shield - - structure.shield + previous_frame_structure.health + - structure.health + + previous_frame_structure.shield + - structure.shield ) await self.on_unit_took_damage(structure, damage_amount) # Check if a structure changed its type @@ -736,7 +745,7 @@ async def _issue_building_events(self): await self.on_building_construction_complete(structure) @final - async def _issue_vision_events(self): + async def _issue_vision_events(self) -> 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: @@ -746,15 +755,15 @@ async def _issue_vision_events(self): await self.on_enemy_unit_entered_vision(enemy_structure) # Call events for enemy unit left vision - enemy_units_left_vision: Set[int] = set(self._enemy_units_previous_map) - self.enemy_units.tags + enemy_units_left_vision: set[int] = set(self._enemy_units_previous_map) - self.enemy_units.tags for enemy_unit_tag in enemy_units_left_vision: await self.on_enemy_unit_left_vision(enemy_unit_tag) - enemy_structures_left_vision: Set[int] = set(self._enemy_structures_previous_map) - self.enemy_structures.tags + enemy_structures_left_vision: set[int] = set(self._enemy_structures_previous_map) - self.enemy_structures.tags for enemy_structure_tag in enemy_structures_left_vision: await self.on_enemy_unit_left_vision(enemy_structure_tag) @final - async def _issue_unit_dead_events(self): + async def _issue_unit_dead_events(self) -> None: for unit_tag in self.state.dead_units & set(self._all_units_previous_map): await self.on_unit_destroyed(unit_tag) @@ -768,7 +777,7 @@ def _units_count(self) -> int: @final @property def _pdist(self) -> np.ndarray: - """ As property, so it will be recalculated each time it is called, or return from cache if it is called multiple times in teh same game_loop. """ + """As property, so it will be recalculated each time it is called, or return from cache if it is called multiple times in teh same game_loop.""" if self._generated_frame != self.state.game_loop: return self.calculate_distances() return self._cached_pdist @@ -776,7 +785,7 @@ def _pdist(self) -> np.ndarray: @final @property def _cdist(self) -> np.ndarray: - """ As property, so it will be recalculated each time it is called, or return from cache if it is called multiple times in teh same game_loop. """ + """As property, so it will be recalculated each time it is called, or return from cache if it is called multiple times in teh same game_loop.""" if self._generated_frame != self.state.game_loop: return self.calculate_distances() return self._cached_cdist @@ -817,7 +826,7 @@ def _calculate_distances_method2(self) -> np.ndarray: @final def _calculate_distances_method3(self) -> np.ndarray: - """ Nearly same as above, but without asserts""" + """Nearly same as above, but without asserts""" self._generated_frame = self.state.game_loop flat_positions = (coord for unit in self.all_units for coord in unit.position_tuple) positions_array: np.ndarray = np.fromiter( @@ -843,8 +852,8 @@ def square_to_condensed(self, i, j) -> int: @final @staticmethod - def convert_tuple_to_numpy_array(pos: Tuple[float, float]) -> np.ndarray: - """ Converts a single position to a 2d numpy array with 1 row and 2 columns. """ + def convert_tuple_to_numpy_array(pos: tuple[float, float]) -> np.ndarray: + """Converts a single position to a 2d numpy array with 1 row and 2 columns.""" return np.fromiter(pos, dtype=float, count=2).reshape((1, 2)) # Fast and simple calculation functions @@ -852,16 +861,16 @@ def convert_tuple_to_numpy_array(pos: Tuple[float, float]) -> np.ndarray: @final @staticmethod def distance_math_hypot( - p1: Union[Tuple[float, float], Point2], - p2: Union[Tuple[float, float], Point2], + p1: tuple[float, float] | Point2, + p2: tuple[float, float] | Point2, ) -> float: return math.hypot(p1[0] - p2[0], p1[1] - p2[1]) @final @staticmethod def distance_math_hypot_squared( - p1: Union[Tuple[float, float], Point2], - p2: Union[Tuple[float, float], Point2], + p1: tuple[float, float] | Point2, + p2: tuple[float, float] | Point2, ) -> float: return pow(p1[0] - p2[0], 2) + pow(p1[1] - p2[1], 2) @@ -878,8 +887,8 @@ def _distance_squared_unit_to_unit_method1(self, unit1: Unit, unit2: Unit) -> fl return 0 # Calculate index, needs to be after pdist has been calculated and cached condensed_index = self.square_to_condensed(unit1.distance_calculation_index, unit2.distance_calculation_index) - assert condensed_index < len( - self._cached_pdist + assert ( + condensed_index < len(self._cached_pdist) ), f"Condensed index is larger than amount of calculated distances: {condensed_index} < {len(self._cached_pdist)}, units that caused the assert error: {unit1} and {unit2}" distance = self._pdist[condensed_index] return distance @@ -894,8 +903,8 @@ def _distance_squared_unit_to_unit_method2(self, unit1: Unit, unit2: Unit) -> fl @final def _distance_pos_to_pos( self, - pos1: Union[Tuple[float, float], Point2], - pos2: Union[Tuple[float, float], Point2], + pos1: tuple[float, float] | Point2, + pos2: tuple[float, float] | Point2, ) -> float: return self.distance_math_hypot(pos1, pos2) @@ -903,23 +912,23 @@ def _distance_pos_to_pos( def _distance_units_to_pos( self, units: Units, - pos: Union[Tuple[float, float], Point2], + pos: tuple[float, float] | Point2, ) -> Generator[float, None, None]: - """ This function does not scale well, if len(units) > 100 it gets fairly slow """ + """This function does not scale well, if len(units) > 100 it gets fairly slow""" return (self.distance_math_hypot(u.position_tuple, pos) for u in units) @final def _distance_unit_to_points( self, unit: Unit, - points: Iterable[Tuple[float, float]], + points: Iterable[tuple[float, float]], ) -> Generator[float, None, None]: - """ This function does not scale well, if len(points) > 100 it gets fairly slow """ + """This function does not scale well, if len(points) > 100 it gets fairly slow""" pos = unit.position_tuple return (self.distance_math_hypot(p, pos) for p in points) @final - def _distances_override_functions(self, method: int = 0): + def _distances_override_functions(self, method: int = 0) -> None: """Overrides the internal distance calculation functions at game start in bot_ai.py self._prepare_start() function method 0: Use python's math.hypot The following methods calculate the distances between all units once: diff --git a/sc2/cache.py b/sc2/cache.py index f807e112..d3e9090d 100644 --- a/sc2/cache.py +++ b/sc2/cache.py @@ -1,6 +1,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Callable, Hashable, TypeVar +from collections.abc import Callable, Hashable +from typing import TYPE_CHECKING, TypeVar if TYPE_CHECKING: from sc2.bot_ai import BotAI @@ -9,16 +10,15 @@ class CacheDict(dict): - def retrieve_and_set(self, key: Hashable, func: Callable[[], T]) -> T: - """ Either return the value at a certain key, - or set the return value of a function to that key, then return that value. """ + """Either return the value at a certain key, + or set the return value of a function to that key, then return that value.""" if key not in self: self[key] = func() return self[key] -class property_cache_once_per_frame(property): +class property_cache_once_per_frame(property): # noqa: N801 """This decorator caches the return value for one game loop, then clears it if it is accessed in a different game loop. Only works on properties of the bot object, because it requires @@ -27,20 +27,22 @@ class property_cache_once_per_frame(property): This decorator compared to the above runs a little faster, however you should only use this decorator if you are sure that you do not modify the mutable once it is calculated and cached. Copied and modified from https://tedboy.github.io/flask/_modules/werkzeug/utils.html#cached_property - # """ + #""" - def __init__(self, func: Callable[[BotAI], T], name=None): - # pylint: disable=W0231 + def __init__(self, func: Callable[[BotAI], T], name=None) -> None: self.__name__ = name or func.__name__ self.__frame__ = f"__frame__{self.__name__}" self.func = func - def __set__(self, obj: BotAI, value: T): + 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 e2dff958..19247008 100644 --- a/sc2/client.py +++ b/sc2/client.py @@ -1,8 +1,12 @@ +# pyre-ignore-all-errors[6, 9, 16, 29, 58] from __future__ import annotations -from typing import Dict, Iterable, List, Optional, Set, Tuple, Union +from collections.abc import Iterable +from pathlib import Path from loguru import logger + +# pyre-ignore[21] from s2clientprotocol import debug_pb2 as debug_pb from s2clientprotocol import query_pb2 as query_pb from s2clientprotocol import raw_pb2 as raw_pb @@ -16,27 +20,25 @@ from sc2.ids.ability_id import AbilityId from sc2.ids.unit_typeid import UnitTypeId from sc2.position import Point2, Point3 -from sc2.protocol import ConnectionAlreadyClosed, Protocol, ProtocolError +from sc2.protocol import ConnectionAlreadyClosedError, Protocol, ProtocolError from sc2.renderer import Renderer from sc2.unit import Unit from sc2.units import Units -# pylint: disable=R0904 class Client(Protocol): - - def __init__(self, ws, save_replay_path: str = None): + def __init__(self, ws, save_replay_path: str = None) -> None: """ :param ws: """ super().__init__(ws) # How many frames will be waited between iterations before the next one is called self.game_step: int = 4 - self.save_replay_path: Optional[str] = save_replay_path + self.save_replay_path: str | None = save_replay_path self._player_id = None self._game_result = None # 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_hash_tuple_last_iteration: tuple[int, int, int, int] = (0, 0, 0, 0) self._debug_draw_last_frame = False self._debug_texts = [] self._debug_lines = [] @@ -101,8 +103,8 @@ async def join_game(self, name=None, race=None, observed_player_id=None, portcon self._player_id = result.join_game.player_id return result.join_game.player_id - async def leave(self): - """ You can use 'await self.client.leave()' to surrender midst game. """ + async def leave(self) -> None: + """You can use 'await self.client.leave()' to surrender midst game.""" is_resign = self._game_result is None if is_resign: @@ -115,14 +117,14 @@ async def leave(self): await self.save_replay(self.save_replay_path) self.save_replay_path = None await self._execute(leave_game=sc_pb.RequestLeaveGame()) - except (ProtocolError, ConnectionAlreadyClosed): + except (ProtocolError, ConnectionAlreadyClosedError): if is_resign: raise - async def save_replay(self, path): + async def save_replay(self, path) -> None: logger.debug("Requesting replay from server") result = await self._execute(save_replay=sc_pb.RequestSaveReplay()) - with open(path, "wb") as f: + with Path(path).open("wb") as f: f.write(result.save_replay.data) logger.info(f"Saved replay to {path}") @@ -151,7 +153,7 @@ async def observation(self, game_loop: int = None): return result async def step(self, step_size: int = None): - """ EXPERIMENTAL: Change self._client.game_step during the step function to increase or decrease steps per second """ + """EXPERIMENTAL: Change self._client.game_step during the step function to increase or decrease steps per second""" step_size = step_size or self.game_step return await self._execute(step=sc_pb.RequestStep(count=step_size)) @@ -161,7 +163,14 @@ async def get_game_data(self) -> GameData: ) return GameData(result.data) - async def dump_data(self, ability_id=True, unit_type_id=True, upgrade_id=True, buff_id=True, effect_id=True): + async def dump_data( + self, + ability_id: bool = True, + unit_type_id: bool = True, + upgrade_id: bool = True, + buff_id: bool = True, + effect_id: bool = True, + ) -> None: """ Dump the game data files choose what data to dump in the keywords @@ -178,14 +187,14 @@ async def dump_data(self, ability_id=True, unit_type_id=True, upgrade_id=True, b effect_id=effect_id, ) ) - with open("data_dump.txt", "a") as file: + with Path("data_dump.txt").open("a") as file: file.write(str(result.data)) async def get_game_info(self) -> GameInfo: result = await self._execute(game_info=sc_pb.RequestGameInfo()) return GameInfo(result.game_info) - async def actions(self, actions, return_successes=False): + async def actions(self, actions, return_successes: bool = False): if not actions: return None if not isinstance(actions, list): @@ -202,8 +211,7 @@ async def actions(self, actions, return_successes=False): return [ActionResult(r) for r in res.action.result] return [ActionResult(r) for r in res.action.result if ActionResult(r) != ActionResult.Success] - async def query_pathing(self, start: Union[Unit, Point2, Point3], - end: Union[Point2, Point3]) -> Optional[Union[int, float]]: + async def query_pathing(self, start: Unit | Point2 | Point3, end: Point2 | Point3) -> int | float | None: """Caution: returns "None" when path not found Try to combine queries with the function below because the pathing query is generally slow. @@ -221,7 +229,7 @@ async def query_pathing(self, start: Union[Unit, Point2, Point3], return None return distance - async def query_pathings(self, zipped_list: List[List[Union[Unit, Point2, Point3]]]) -> List[float]: + async def query_pathings(self, zipped_list: list[list[Unit | Point2 | Point3]]) -> list[float]: """Usage: await self.query_pathings([[unit1, target2], [unit2, target2]]) -> returns [distance1, distance2] Caution: returns 0 when path not found @@ -244,8 +252,8 @@ async def query_pathings(self, zipped_list: List[List[Union[Unit, Point2, Point3 return [float(d.distance) for d in results.query.pathing] async def _query_building_placement_fast( - self, ability: AbilityId, positions: List[Union[Point2, Point3]], ignore_resources: bool = True - ) -> List[bool]: + self, ability: AbilityId, positions: list[Point2 | Point3], ignore_resources: bool = True + ) -> list[bool]: """ Returns a list of booleans. Return True for positions that are valid, False otherwise. @@ -268,9 +276,10 @@ async def _query_building_placement_fast( async def query_building_placement( self, ability: AbilityData, - positions: List[Union[Point2, Point3]], - ignore_resources: bool = True - ) -> List[ActionResult]: + positions: list[Point2 | Point3], + ignore_resources: bool = True, + # pyre-fixme[11] + ) -> list[ActionResult]: """This function might be deleted in favor of the function above (_query_building_placement_fast). :param ability: @@ -290,9 +299,9 @@ async def query_building_placement( return [ActionResult(p.result) for p in result.query.placements] async def query_available_abilities( - self, units: Union[List[Unit], Units], ignore_resource_requirements: bool = False - ) -> List[List[AbilityId]]: - """ Query abilities of multiple units """ + self, units: list[Unit] | Units, ignore_resource_requirements: bool = False + ) -> list[list[AbilityId]]: + """Query abilities of multiple units""" input_was_a_list = True if not isinstance(units, list): """ Deprecated, accepting a single unit may be removed in the future, query a list of units instead """ @@ -308,13 +317,14 @@ async def query_available_abilities( ) """ Fix for bots that only query a single unit, may be removed soon """ if not input_was_a_list: + # pyre-fixme[7] 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] async def query_available_abilities_with_tag( - self, units: Union[List[Unit], Units], ignore_resource_requirements: bool = False - ) -> Dict[int, Set[AbilityId]]: - """ Query abilities of multiple units """ + 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( @@ -324,8 +334,8 @@ async def query_available_abilities_with_tag( ) return {b.unit_tag: {AbilityId(a.ability_id) for a in b.abilities} for b in result.query.abilities} - async def chat_send(self, message: str, team_only: bool): - """ Writes a message to the chat """ + async def chat_send(self, message: str, team_only: bool) -> None: + """Writes a message to the chat""" ch = ChatChannel.Team if team_only else ChatChannel.Broadcast await self._execute( action=sc_pb.RequestAction( @@ -333,7 +343,7 @@ async def chat_send(self, message: str, team_only: bool): ) ) - async def toggle_autocast(self, units: Union[List[Unit], Units], ability: AbilityId): + async def toggle_autocast(self, units: list[Unit] | Units, ability: AbilityId) -> None: """Toggle autocast of all specified units :param units: @@ -348,15 +358,16 @@ async def toggle_autocast(self, units: Union[List[Unit], Units], ability: Abilit actions=[ 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)) + toggle_autocast=raw_pb.ActionRawToggleAutocast( + ability_id=ability.value, unit_tags=(u.tag for u in units) + ) ) ) ] ) ) - async def debug_create_unit(self, unit_spawn_commands: List[List[Union[UnitTypeId, int, Point2, Point3]]]): + async def debug_create_unit(self, unit_spawn_commands: list[list[UnitTypeId | int | Point2 | Point3]]) -> 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]]) @@ -380,12 +391,13 @@ async def debug_create_unit(self, unit_spawn_commands: List[List[Union[UnitTypeI pos=position.as_Point2D, quantity=amount_of_units, ) - ) for unit_type, amount_of_units, position, owner_id in unit_spawn_commands + ) + for unit_type, amount_of_units, position, owner_id in unit_spawn_commands ) ) ) - async def debug_kill_unit(self, unit_tags: Union[Unit, Units, List[int], Set[int]]): + async def debug_kill_unit(self, unit_tags: Unit | Units | list[int] | set[int]) -> None: """ :param unit_tags: """ @@ -399,7 +411,7 @@ async def debug_kill_unit(self, unit_tags: Union[Unit, Units, List[int], Set[int debug=sc_pb.RequestDebug(debug=[debug_pb.DebugCommand(kill_unit=debug_pb.DebugKillUnit(tag=unit_tags))]) ) - async def move_camera(self, position: Union[Unit, Units, Point2, Point3]): + async def move_camera(self, position: Unit | Units | Point2 | Point3) -> None: """Moves camera to the target position :param position:""" @@ -420,7 +432,7 @@ async def move_camera(self, position: Union[Unit, Units, Point2, Point3]): ) ) - async def obs_move_camera(self, position: Union[Unit, Units, Point2, Point3]): + async def obs_move_camera(self, position: Unit | Units | Point2 | Point3) -> None: """Moves observer camera to the target position. Only works when observing (e.g. watching the replay). :param position:""" @@ -437,7 +449,7 @@ async def obs_move_camera(self, position: Union[Unit, Units, Point2, Point3]): ) ) - async def move_camera_spatial(self, position: Union[Point2, Point3]): + async def move_camera_spatial(self, position: Point2 | Point3) -> None: """Moves camera to the target position using the spatial aciton interface :param position:""" @@ -449,17 +461,17 @@ async def move_camera_spatial(self, position: Union[Point2, Point3]): ) await self._execute(action=sc_pb.RequestAction(actions=[action])) - def debug_text_simple(self, text: str): - """ Draws a text in the top left corner of the screen (up to a max of 6 messages fit there). """ + def debug_text_simple(self, text: str) -> None: + """Draws a text in the top left corner of the screen (up to a max of 6 messages fit there).""" self._debug_texts.append(DrawItemScreenText(text=text, color=None, start_point=Point2((0, 0)), font_size=8)) def debug_text_screen( self, text: str, - pos: Union[Point2, Point3, tuple, list], - color: Union[tuple, list, Point3] = None, + pos: Point2 | Point3 | tuple | list, + color: tuple | list | Point3 = None, size: int = 8, - ): + ) -> None: """ Draws a text on the screen (monitor / game window) with coordinates 0 <= x, y <= 1. @@ -477,15 +489,15 @@ def debug_text_screen( def debug_text_2d( self, text: str, - pos: Union[Point2, Point3, tuple, list], - color: Union[tuple, list, Point3] = None, + pos: Point2 | Point3 | tuple | list, + color: tuple | list | Point3 = None, size: int = 8, ): return self.debug_text_screen(text, pos, color, size) def debug_text_world( - self, text: str, pos: Union[Unit, Point3], color: Union[tuple, list, Point3] = None, size: int = 8 - ): + self, text: str, pos: Unit | Point3, color: tuple | list | Point3 = None, size: int = 8 + ) -> None: """ Draws a text at Point3 position in the game world. To grab a unit's 3d position, use unit.position3d @@ -500,14 +512,10 @@ def debug_text_world( assert isinstance(pos, Point3) self._debug_texts.append(DrawItemWorldText(text=text, color=color, start_point=pos, font_size=size)) - def debug_text_3d( - self, text: str, pos: Union[Unit, Point3], color: Union[tuple, list, Point3] = None, size: int = 8 - ): + def debug_text_3d(self, text: str, pos: Unit | Point3, color: tuple | list | Point3 = None, size: int = 8): return self.debug_text_world(text, pos, color, size) - def debug_line_out( - self, p0: Union[Unit, Point3], p1: Union[Unit, Point3], color: Union[tuple, list, Point3] = None - ): + def debug_line_out(self, p0: Unit | Point3, p1: Unit | Point3, color: tuple | list | Point3 = None) -> None: """ Draws a line from p0 to p1. @@ -525,10 +533,10 @@ def debug_line_out( def debug_box_out( self, - p_min: Union[Unit, Point3], - p_max: Union[Unit, Point3], - color: Union[tuple, list, Point3] = None, - ): + p_min: Unit | Point3, + p_max: Unit | Point3, + color: tuple | list | Point3 = None, + ) -> None: """ Draws a box with p_min and p_max as corners of the box. @@ -546,10 +554,10 @@ def debug_box_out( def debug_box2_out( self, - pos: Union[Unit, Point3], + pos: Unit | Point3, half_vertex_length: float = 0.25, - color: Union[tuple, list, Point3] = None, - ): + color: tuple | list | Point3 = None, + ) -> None: """ Draws a box center at a position 'pos', with box side lengths (vertices) of two times 'half_vertex_length'. @@ -564,7 +572,7 @@ def debug_box2_out( p1 = pos + Point3((half_vertex_length, half_vertex_length, half_vertex_length)) self._debug_boxes.append(DrawItemBox(start_point=p0, end_point=p1, color=color)) - def debug_sphere_out(self, p: Union[Unit, Point3], r: float, color: Union[tuple, list, Point3] = None): + def debug_sphere_out(self, p: Unit | Point3, r: float, color: tuple | list | Point3 = None) -> None: """ Draws a sphere at point p with radius r. @@ -577,7 +585,7 @@ def debug_sphere_out(self, p: Union[Unit, Point3], r: float, color: Union[tuple, assert isinstance(p, Point3) self._debug_spheres.append(DrawItemSphere(start_point=p, radius=r, color=color)) - async def _send_debug(self): + async def _send_debug(self) -> None: """Sends the debug draw execution. This is run by main.py now automatically, if there is any items in the list. You do not need to run this manually any longer. Check examples/terran/ramp_wall.py for example drawing. Each draw request needs to be sent again in every single on_step iteration. """ @@ -597,14 +605,18 @@ async def _send_debug(self): debug=[ debug_pb.DebugCommand( draw=debug_pb.DebugDraw( - text=[text.to_proto() - for text in self._debug_texts] if self._debug_texts else None, - lines=[line.to_proto() - for line in self._debug_lines] if self._debug_lines else None, - boxes=[box.to_proto() - for box in self._debug_boxes] if self._debug_boxes else None, - spheres=[sphere.to_proto() - for sphere in self._debug_spheres] if self._debug_spheres else None, + text=[text.to_proto() for text in self._debug_texts] + if self._debug_texts + else None, + lines=[line.to_proto() for line in self._debug_lines] + if self._debug_lines + else None, + boxes=[box.to_proto() for box in self._debug_boxes] + if self._debug_boxes + else None, + spheres=[sphere.to_proto() for sphere in self._debug_spheres] + if self._debug_spheres + else None, ) ) ] @@ -629,10 +641,12 @@ async def _send_debug(self): ) self._debug_draw_last_frame = False - async def debug_leave(self): + async def debug_leave(self) -> None: await self._execute(debug=sc_pb.RequestDebug(debug=[debug_pb.DebugCommand(end_game=debug_pb.DebugEndGame())])) - async def debug_set_unit_value(self, unit_tags: Union[Iterable[int], Units, Unit], unit_value: int, value: float): + async def debug_set_unit_value( + self, unit_tags: Iterable[int] | Units | Unit, unit_value: int, value: float + ) -> None: """Sets a "unit value" (Energy, Life or Shields) of the given units to the given value. Can't set the life of a unit to 0, use "debug_kill_unit" for that. Also can't set the life above the unit's maximum. The following example sets the health of all your workers to 1: @@ -654,15 +668,17 @@ async def debug_set_unit_value(self, unit_tags: Union[Iterable[int], Units, Unit debug=sc_pb.RequestDebug( debug=( debug_pb.DebugCommand( - unit_value=debug_pb. - DebugSetUnitValue(unit_value=unit_value, value=float(value), unit_tag=unit_tag) - ) for unit_tag in unit_tags + unit_value=debug_pb.DebugSetUnitValue( + unit_value=unit_value, value=float(value), unit_tag=unit_tag + ) + ) + for unit_tag in unit_tags ) ) ) - async def debug_hang(self, delay_in_seconds: float): - """ Freezes the SC2 client. Not recommended to be used. """ + async def debug_hang(self, delay_in_seconds: float) -> None: + """Freezes the SC2 client. Not recommended to be used.""" delay_in_ms = int(round(delay_in_seconds * 1000)) await self._execute( debug=sc_pb.RequestDebug( @@ -670,60 +686,60 @@ async def debug_hang(self, delay_in_seconds: float): ) ) - async def debug_show_map(self): - """ Reveals the whole map for the bot. Using it a second time disables it again. """ + 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)])) - async def debug_control_enemy(self): - """ 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. """ + 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)])) - async def debug_food(self): - """ Should disable food usage (does not seem to work?). Using it a second time disables it again. """ + 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)])) - async def debug_free(self): - """ Units, structures and upgrades are free of mineral and gas cost. Using it a second time disables it again. """ + 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)])) - async def debug_all_resources(self): - """ Gives 5000 minerals and 5000 vespene to the bot. """ + 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)])) - async def debug_god(self): - """ Your units and structures no longer take any damage. Using it a second time disables it again. """ + 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)])) - async def debug_minerals(self): - """ Gives 5000 minerals to the bot. """ + 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)])) - async def debug_gas(self): - """ Gives 5000 vespene to the bot. This does not seem to be working. """ + 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)])) - async def debug_cooldown(self): - """ Disables cooldowns of unit abilities for the bot. Using it a second time disables it again. """ + 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)])) - async def debug_tech_tree(self): - """ Removes all tech requirements (e.g. can build a factory without having a barracks). Using it a second time disables it again. """ + 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)])) - async def debug_upgrade(self): - """ 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. """ + 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)])) - async def debug_fast_build(self): - """ Sets the build time of units and structures and upgrades to zero. Using it a second time disables it again. """ + 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)])) - async def quick_save(self): + async def quick_save(self) -> None: """Saves the current game state to an in-memory bookmark. See: https://github.com/Blizzard/s2client-proto/blob/eeaf5efaea2259d7b70247211dff98da0a2685a2/s2clientprotocol/sc2api.proto#L93""" await self._execute(quick_save=sc_pb.RequestQuickSave()) - async def quick_load(self): + async def quick_load(self) -> None: """Loads the game state from the previously stored in-memory bookmark. Caution: - The SC2 Client will crash if the game wasn't quicksaved @@ -733,10 +749,9 @@ async def quick_load(self): class DrawItem: - @staticmethod - def to_debug_color(color: Union[tuple, Point3]): - """ Helper function for color conversion """ + def to_debug_color(color: tuple | Point3): + """Helper function for color conversion""" if color is None: return debug_pb.Color(r=255, g=255, b=255) # Need to check if not of type Point3 because Point3 inherits from tuple @@ -746,6 +761,7 @@ def to_debug_color(color: Union[tuple, 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 @@ -755,8 +771,7 @@ def to_debug_color(color: Union[tuple, Point3]): class DrawItemScreenText(DrawItem): - - def __init__(self, start_point: Point2 = None, color: Point3 = None, text: str = "", font_size: int = 8): + def __init__(self, start_point: Point2 = None, color: Point3 = None, text: str = "", font_size: int = 8) -> None: self._start_point: Point2 = start_point self._color: Point3 = color self._text: str = text @@ -771,13 +786,12 @@ def to_proto(self): size=self._font_size, ) - def __hash__(self): + def __hash__(self) -> int: return hash((self._start_point, self._color, self._text, self._font_size)) class DrawItemWorldText(DrawItem): - - def __init__(self, start_point: Point3 = None, color: Point3 = None, text: str = "", font_size: int = 8): + def __init__(self, start_point: Point3 = None, color: Point3 = None, text: str = "", font_size: int = 8) -> None: self._start_point: Point3 = start_point self._color: Point3 = color self._text: str = text @@ -792,13 +806,12 @@ def to_proto(self): size=self._font_size, ) - def __hash__(self): + def __hash__(self) -> int: return hash((self._start_point, self._text, self._font_size, self._color)) class DrawItemLine(DrawItem): - - def __init__(self, start_point: Point3 = None, end_point: Point3 = None, color: Point3 = None): + def __init__(self, start_point: Point3 = None, end_point: Point3 = None, color: Point3 = None) -> None: self._start_point: Point3 = start_point self._end_point: Point3 = end_point self._color: Point3 = color @@ -809,13 +822,12 @@ def to_proto(self): color=self.to_debug_color(self._color), ) - def __hash__(self): + def __hash__(self) -> int: return hash((self._start_point, self._end_point, self._color)) class DrawItemBox(DrawItem): - - def __init__(self, start_point: Point3 = None, end_point: Point3 = None, color: Point3 = None): + def __init__(self, start_point: Point3 = None, end_point: Point3 = None, color: Point3 = None) -> None: self._start_point: Point3 = start_point self._end_point: Point3 = end_point self._color: Point3 = color @@ -827,13 +839,12 @@ def to_proto(self): color=self.to_debug_color(self._color), ) - def __hash__(self): + def __hash__(self) -> int: return hash((self._start_point, self._end_point, self._color)) class DrawItemSphere(DrawItem): - - def __init__(self, start_point: Point3 = None, radius: float = None, color: Point3 = None): + def __init__(self, start_point: Point3 = None, radius: float = None, color: Point3 = None) -> None: self._start_point: Point3 = start_point self._radius: float = radius self._color: Point3 = color @@ -843,5 +854,5 @@ def to_proto(self): p=self._start_point.as_Point, r=self._radius, color=self.to_debug_color(self._color) ) - def __hash__(self): + def __hash__(self) -> int: return hash((self._start_point, self._radius, self._color)) diff --git a/sc2/constants.py b/sc2/constants.py index d0c4067e..a4393f11 100644 --- a/sc2/constants.py +++ b/sc2/constants.py @@ -1,5 +1,8 @@ +# pyre-ignore-all-errors[16] +from __future__ import annotations + from collections import defaultdict -from typing import Any, Dict, Set +from typing import Any from sc2.data import Alliance, Attribute, CloakState, DisplayType, TargetType from sc2.ids.ability_id import AbilityId @@ -7,7 +10,7 @@ from sc2.ids.unit_typeid import UnitTypeId from sc2.ids.upgrade_id import UpgradeId -mineral_ids: Set[int] = { +mineral_ids: set[int] = { UnitTypeId.RICHMINERALFIELD.value, UnitTypeId.RICHMINERALFIELD750.value, UnitTypeId.MINERALFIELD.value, @@ -24,7 +27,7 @@ UnitTypeId.MINERALFIELDOPAQUE.value, UnitTypeId.MINERALFIELDOPAQUE900.value, } -geyser_ids: Set[int] = { +geyser_ids: set[int] = { UnitTypeId.VESPENEGEYSER.value, UnitTypeId.SPACEPLATFORMGEYSER.value, UnitTypeId.RICHVESPENEGEYSER.value, @@ -32,7 +35,7 @@ UnitTypeId.PURIFIERVESPENEGEYSER.value, UnitTypeId.SHAKURASVESPENEGEYSER.value, } -transforming: Dict[UnitTypeId, AbilityId] = { +transforming: dict[UnitTypeId, AbilityId] = { # Terran structures UnitTypeId.BARRACKS: AbilityId.LAND_BARRACKS, UnitTypeId.BARRACKSFLYING: AbilityId.LAND_BARRACKS, @@ -99,7 +102,7 @@ UnitTypeId.ZERGLINGBURROWED: AbilityId.BURROWDOWN_ZERGLING, } # For now only contains units that cost supply, used in bot_ai.do() -abilityid_to_unittypeid: Dict[AbilityId, UnitTypeId] = { +abilityid_to_unittypeid: dict[AbilityId, UnitTypeId] = { # Protoss AbilityId.NEXUSTRAIN_PROBE: UnitTypeId.PROBE, AbilityId.GATEWAYTRAIN_ZEALOT: UnitTypeId.ZEALOT, @@ -166,25 +169,25 @@ IS_PSIONIC: int = Attribute.Psionic.value UNIT_BATTLECRUISER: UnitTypeId = UnitTypeId.BATTLECRUISER UNIT_ORACLE: UnitTypeId = UnitTypeId.ORACLE -TARGET_GROUND: Set[int] = {TargetType.Ground.value, TargetType.Any.value} -TARGET_AIR: Set[int] = {TargetType.Air.value, TargetType.Any.value} -TARGET_BOTH = TARGET_GROUND | TARGET_AIR +TARGET_GROUND: set[int] = {TargetType.Ground.value, TargetType.Any.value} +TARGET_AIR: set[int] = {TargetType.Air.value, TargetType.Any.value} +TARGET_BOTH: set[int] = TARGET_GROUND | TARGET_AIR IS_SNAPSHOT = DisplayType.Snapshot.value IS_VISIBLE = DisplayType.Visible.value IS_PLACEHOLDER = DisplayType.Placeholder.value IS_MINE = Alliance.Self.value IS_ENEMY = Alliance.Enemy.value -IS_CLOAKED: Set[int] = {CloakState.Cloaked.value, CloakState.CloakedDetected.value, CloakState.CloakedAllied.value} +IS_CLOAKED: set[int] = {CloakState.Cloaked.value, CloakState.CloakedDetected.value, CloakState.CloakedAllied.value} IS_REVEALED: int = CloakState.CloakedDetected.value -CAN_BE_ATTACKED: Set[int] = {CloakState.NotCloaked.value, CloakState.CloakedDetected.value} -IS_CARRYING_MINERALS: Set[BuffId] = {BuffId.CARRYMINERALFIELDMINERALS, BuffId.CARRYHIGHYIELDMINERALFIELDMINERALS} -IS_CARRYING_VESPENE: Set[BuffId] = { +CAN_BE_ATTACKED: set[int] = {CloakState.NotCloaked.value, CloakState.CloakedDetected.value} +IS_CARRYING_MINERALS: set[BuffId] = {BuffId.CARRYMINERALFIELDMINERALS, BuffId.CARRYHIGHYIELDMINERALFIELDMINERALS} +IS_CARRYING_VESPENE: set[BuffId] = { BuffId.CARRYHARVESTABLEVESPENEGEYSERGAS, BuffId.CARRYHARVESTABLEVESPENEGEYSERGASPROTOSS, BuffId.CARRYHARVESTABLEVESPENEGEYSERGASZERG, } -IS_CARRYING_RESOURCES: Set[BuffId] = IS_CARRYING_MINERALS | IS_CARRYING_VESPENE -IS_ATTACKING: Set[AbilityId] = { +IS_CARRYING_RESOURCES: set[BuffId] = IS_CARRYING_MINERALS | IS_CARRYING_VESPENE +IS_ATTACKING: set[AbilityId] = { AbilityId.ATTACK, AbilityId.ATTACK_ATTACK, AbilityId.ATTACK_ATTACKTOWARDS, @@ -194,8 +197,8 @@ IS_PATROLLING: AbilityId = AbilityId.PATROL_PATROL IS_GATHERING: AbilityId = AbilityId.HARVEST_GATHER IS_RETURNING: AbilityId = AbilityId.HARVEST_RETURN -IS_COLLECTING: Set[AbilityId] = {IS_GATHERING, IS_RETURNING} -IS_CONSTRUCTING_SCV: Set[AbilityId] = { +IS_COLLECTING: set[AbilityId] = {IS_GATHERING, IS_RETURNING} +IS_CONSTRUCTING_SCV: set[AbilityId] = { AbilityId.TERRANBUILD_ARMORY, AbilityId.TERRANBUILD_BARRACKS, AbilityId.TERRANBUILD_BUNKER, @@ -210,8 +213,8 @@ AbilityId.TERRANBUILD_STARPORT, AbilityId.TERRANBUILD_SUPPLYDEPOT, } -IS_REPAIRING: Set[AbilityId] = {AbilityId.EFFECT_REPAIR, AbilityId.EFFECT_REPAIR_MULE, AbilityId.EFFECT_REPAIR_SCV} -IS_DETECTOR: Set[UnitTypeId] = { +IS_REPAIRING: set[AbilityId] = {AbilityId.EFFECT_REPAIR, AbilityId.EFFECT_REPAIR_MULE, AbilityId.EFFECT_REPAIR_SCV} +IS_DETECTOR: set[UnitTypeId] = { UnitTypeId.OBSERVER, UnitTypeId.OBSERVERSIEGEMODE, UnitTypeId.RAVEN, @@ -220,7 +223,7 @@ UnitTypeId.OVERSEERSIEGEMODE, UnitTypeId.SPORECRAWLER, } -SPEED_UPGRADE_DICT: Dict[UnitTypeId, UpgradeId] = { +SPEED_UPGRADE_DICT: dict[UnitTypeId, UpgradeId] = { # Terran UnitTypeId.MEDIVAC: UpgradeId.MEDIVACRAPIDDEPLOYMENT, UnitTypeId.BANSHEE: UpgradeId.BANSHEESPEED, @@ -237,7 +240,7 @@ UnitTypeId.ROACH: UpgradeId.GLIALRECONSTITUTION, UnitTypeId.LURKERMP: UpgradeId.DIGGINGCLAWS, } -SPEED_INCREASE_DICT: Dict[UnitTypeId, float] = { +SPEED_INCREASE_DICT: dict[UnitTypeId, float] = { # Terran UnitTypeId.MEDIVAC: 1.18, UnitTypeId.BANSHEE: 1.3636, @@ -259,7 +262,7 @@ assert temp1 == temp2, f"{temp1.symmetric_difference(temp2)}" del temp1 del temp2 -SPEED_INCREASE_ON_CREEP_DICT: Dict[UnitTypeId, float] = { +SPEED_INCREASE_ON_CREEP_DICT: dict[UnitTypeId, float] = { UnitTypeId.QUEEN: 2.67, UnitTypeId.ZERGLING: 1.3, UnitTypeId.BANELING: 1.3, @@ -275,11 +278,11 @@ UnitTypeId.SPINECRAWLER: 2.5, UnitTypeId.SPORECRAWLER: 2.5, } -OFF_CREEP_SPEED_UPGRADE_DICT: Dict[UnitTypeId, UpgradeId] = { +OFF_CREEP_SPEED_UPGRADE_DICT: dict[UnitTypeId, UpgradeId] = { UnitTypeId.HYDRALISK: UpgradeId.EVOLVEMUSCULARAUGMENTS, UnitTypeId.ULTRALISK: UpgradeId.ANABOLICSYNTHESIS, } -OFF_CREEP_SPEED_INCREASE_DICT: Dict[UnitTypeId, float] = { +OFF_CREEP_SPEED_INCREASE_DICT: dict[UnitTypeId, float] = { UnitTypeId.HYDRALISK: 1.25, UnitTypeId.ULTRALISK: 1.2, } @@ -289,7 +292,7 @@ del temp1 del temp2 # Movement speed gets altered by this factor if it is affected by this buff -SPEED_ALTERING_BUFFS: Dict[BuffId, float] = { +SPEED_ALTERING_BUFFS: dict[BuffId, float] = { # Stimpack increases speed by 1.5 BuffId.STIMPACK: 1.5, BuffId.STIMPACKMARAUDER: 1.5, @@ -307,7 +310,7 @@ UNIT_PHOTONCANNON: UnitTypeId = UnitTypeId.PHOTONCANNON UNIT_COLOSSUS: UnitTypeId = UnitTypeId.COLOSSUS # Used in unit_command.py and action.py to combine only certain abilities -COMBINEABLE_ABILITIES: Set[AbilityId] = { +COMBINEABLE_ABILITIES: set[AbilityId] = { AbilityId.MOVE, AbilityId.ATTACK, AbilityId.SCAN_MOVE, @@ -328,18 +331,18 @@ AbilityId.EFFECT_BLINK, AbilityId.MORPH_ARCHON, } -FakeEffectRadii: Dict[int, float] = { +FakeEffectRadii: dict[int, float] = { UnitTypeId.KD8CHARGE.value: 2, UnitTypeId.PARASITICBOMBDUMMY.value: 3, UnitTypeId.FORCEFIELD.value: 1.5, } -FakeEffectID: Dict[int, str] = { +FakeEffectID: dict[int, str] = { UnitTypeId.KD8CHARGE.value: "KD8CHARGE", UnitTypeId.PARASITICBOMBDUMMY.value: "PARASITICBOMB", UnitTypeId.FORCEFIELD.value: "FORCEFIELD", } -TERRAN_STRUCTURES_REQUIRE_SCV: Set[UnitTypeId] = { +TERRAN_STRUCTURES_REQUIRE_SCV: set[UnitTypeId] = { UnitTypeId.ARMORY, UnitTypeId.BARRACKS, UnitTypeId.BUNKER, @@ -363,7 +366,7 @@ def return_NOTAUNIT() -> UnitTypeId: # Hotfix for structures and units as the API does not seem to return the correct values, e.g. ghost and thor have None in the requirements -TERRAN_TECH_REQUIREMENT: Dict[UnitTypeId, UnitTypeId] = defaultdict( +TERRAN_TECH_REQUIREMENT: dict[UnitTypeId, UnitTypeId] = defaultdict( return_NOTAUNIT, { UnitTypeId.MISSILETURRET: UnitTypeId.ENGINEERINGBAY, @@ -383,7 +386,7 @@ def return_NOTAUNIT() -> UnitTypeId: UnitTypeId.BATTLECRUISER: UnitTypeId.FUSIONCORE, }, ) -PROTOSS_TECH_REQUIREMENT: Dict[UnitTypeId, UnitTypeId] = defaultdict( +PROTOSS_TECH_REQUIREMENT: dict[UnitTypeId, UnitTypeId] = defaultdict( return_NOTAUNIT, { UnitTypeId.PHOTONCANNON: UnitTypeId.FORGE, @@ -407,7 +410,7 @@ def return_NOTAUNIT() -> UnitTypeId: UnitTypeId.DISRUPTOR: UnitTypeId.ROBOTICSBAY, }, ) -ZERG_TECH_REQUIREMENT: Dict[UnitTypeId, UnitTypeId] = defaultdict( +ZERG_TECH_REQUIREMENT: dict[UnitTypeId, UnitTypeId] = defaultdict( return_NOTAUNIT, { UnitTypeId.ZERGLING: UnitTypeId.SPAWNINGPOOL, @@ -440,7 +443,7 @@ def return_NOTAUNIT() -> UnitTypeId: }, ) # Required in 'tech_requirement_progress' bot_ai.py function -EQUIVALENTS_FOR_TECH_PROGRESS: Dict[UnitTypeId, Set[UnitTypeId]] = { +EQUIVALENTS_FOR_TECH_PROGRESS: dict[UnitTypeId, set[UnitTypeId]] = { # Protoss UnitTypeId.GATEWAY: {UnitTypeId.WARPGATE}, UnitTypeId.WARPPRISM: {UnitTypeId.WARPPRISMPHASING}, @@ -482,7 +485,7 @@ def return_NOTAUNIT() -> UnitTypeId: UnitTypeId.ULTRALISK: {UnitTypeId.ULTRALISKBURROWED}, # TODO What about morphing untis? E.g. roach to ravager, overlord to drop-overlord or overseer } -ALL_GAS: Set[UnitTypeId] = { +ALL_GAS: set[UnitTypeId] = { UnitTypeId.ASSIMILATOR, UnitTypeId.ASSIMILATORRICH, UnitTypeId.REFINERY, @@ -490,189 +493,55 @@ def return_NOTAUNIT() -> UnitTypeId: UnitTypeId.EXTRACTOR, UnitTypeId.EXTRACTORRICH, } -DAMAGE_BONUS_PER_UPGRADE: Dict[UnitTypeId, Dict[TargetType, Any]] = { +# pyre-ignore[11] +DAMAGE_BONUS_PER_UPGRADE: dict[UnitTypeId, dict[TargetType, Any]] = { # # Protoss # - UnitTypeId.PROBE: { - TargetType.Ground.value: { - None: 0 - } - }, + UnitTypeId.PROBE: {TargetType.Ground.value: {None: 0}}, # Gateway Units - UnitTypeId.ADEPT: { - TargetType.Ground.value: { - IS_LIGHT: 1 - } - }, - UnitTypeId.STALKER: { - TargetType.Any.value: { - IS_ARMORED: 1 - } - }, - UnitTypeId.DARKTEMPLAR: { - TargetType.Ground.value: { - None: 5 - } - }, - UnitTypeId.ARCHON: { - TargetType.Any.value: { - None: 3, - IS_BIOLOGICAL: 1 - } - }, + UnitTypeId.ADEPT: {TargetType.Ground.value: {IS_LIGHT: 1}}, + UnitTypeId.STALKER: {TargetType.Any.value: {IS_ARMORED: 1}}, + UnitTypeId.DARKTEMPLAR: {TargetType.Ground.value: {None: 5}}, + UnitTypeId.ARCHON: {TargetType.Any.value: {None: 3, IS_BIOLOGICAL: 1}}, # Robo Units - UnitTypeId.IMMORTAL: { - TargetType.Ground.value: { - None: 2, - IS_ARMORED: 3 - } - }, - UnitTypeId.COLOSSUS: { - TargetType.Ground.value: { - IS_LIGHT: 1 - } - }, + UnitTypeId.IMMORTAL: {TargetType.Ground.value: {None: 2, IS_ARMORED: 3}}, + UnitTypeId.COLOSSUS: {TargetType.Ground.value: {IS_LIGHT: 1}}, # Stargate Units - UnitTypeId.ORACLE: { - TargetType.Ground.value: { - None: 0 - } - }, - UnitTypeId.TEMPEST: { - TargetType.Ground.value: { - None: 4 - }, - TargetType.Air.value: { - None: 3, - IS_MASSIVE: 2 - } - }, + UnitTypeId.ORACLE: {TargetType.Ground.value: {None: 0}}, + UnitTypeId.TEMPEST: {TargetType.Ground.value: {None: 4}, TargetType.Air.value: {None: 3, IS_MASSIVE: 2}}, # # Terran # - UnitTypeId.SCV: { - TargetType.Ground.value: { - None: 0 - } - }, + UnitTypeId.SCV: {TargetType.Ground.value: {None: 0}}, # Barracks Units - UnitTypeId.MARAUDER: { - TargetType.Ground.value: { - IS_ARMORED: 1 - } - }, - UnitTypeId.GHOST: { - TargetType.Any.value: { - IS_LIGHT: 1 - } - }, + UnitTypeId.MARAUDER: {TargetType.Ground.value: {IS_ARMORED: 1}}, + UnitTypeId.GHOST: {TargetType.Any.value: {IS_LIGHT: 1}}, # Factory Units - UnitTypeId.HELLION: { - TargetType.Ground.value: { - IS_LIGHT: 1 - } - }, - UnitTypeId.HELLIONTANK: { - TargetType.Ground.value: { - None: 2, - IS_LIGHT: 1 - } - }, - UnitTypeId.CYCLONE: { - TargetType.Any.value: { - None: 2 - } - }, - UnitTypeId.SIEGETANK: { - TargetType.Ground.value: { - None: 2, - IS_ARMORED: 1 - } - }, - UnitTypeId.SIEGETANKSIEGED: { - TargetType.Ground.value: { - None: 4, - IS_ARMORED: 1 - } - }, - UnitTypeId.THOR: { - TargetType.Ground.value: { - None: 3 - }, - TargetType.Air.value: { - IS_LIGHT: 1 - } - }, - UnitTypeId.THORAP: { - TargetType.Ground.value: { - None: 3 - }, - TargetType.Air.value: { - None: 3, - IS_MASSIVE: 1 - } - }, + UnitTypeId.HELLION: {TargetType.Ground.value: {IS_LIGHT: 1}}, + UnitTypeId.HELLIONTANK: {TargetType.Ground.value: {None: 2, IS_LIGHT: 1}}, + UnitTypeId.CYCLONE: {TargetType.Any.value: {None: 2}}, + UnitTypeId.SIEGETANK: {TargetType.Ground.value: {None: 2, IS_ARMORED: 1}}, + UnitTypeId.SIEGETANKSIEGED: {TargetType.Ground.value: {None: 4, IS_ARMORED: 1}}, + UnitTypeId.THOR: {TargetType.Ground.value: {None: 3}, TargetType.Air.value: {IS_LIGHT: 1}}, + UnitTypeId.THORAP: {TargetType.Ground.value: {None: 3}, TargetType.Air.value: {None: 3, IS_MASSIVE: 1}}, # Starport Units - UnitTypeId.VIKINGASSAULT: { - TargetType.Ground.value: { - IS_MECHANICAL: 1 - } - }, - UnitTypeId.LIBERATORAG: { - TargetType.Ground.value: { - None: 5 - } - }, + UnitTypeId.VIKINGASSAULT: {TargetType.Ground.value: {IS_MECHANICAL: 1}}, + UnitTypeId.LIBERATORAG: {TargetType.Ground.value: {None: 5}}, # # Zerg # - UnitTypeId.DRONE: { - TargetType.Ground.value: { - None: 0 - } - }, + UnitTypeId.DRONE: {TargetType.Ground.value: {None: 0}}, # Hatch Tech Units (Queen, Ling, Bane, Roach, Ravager) - UnitTypeId.BANELING: { - TargetType.Ground.value: { - None: 2, - IS_LIGHT: 2, - IS_STRUCTURE: 3 - } - }, - UnitTypeId.ROACH: { - TargetType.Ground.value: { - None: 2 - } - }, - UnitTypeId.RAVAGER: { - TargetType.Ground.value: { - None: 2 - } - }, + UnitTypeId.BANELING: {TargetType.Ground.value: {None: 2, IS_LIGHT: 2, IS_STRUCTURE: 3}}, + UnitTypeId.ROACH: {TargetType.Ground.value: {None: 2}}, + UnitTypeId.RAVAGER: {TargetType.Ground.value: {None: 2}}, # Lair Tech Units (Hydra, Lurker, Ultra) - UnitTypeId.LURKERMPBURROWED: { - TargetType.Ground.value: { - None: 2, - IS_ARMORED: 1 - } - }, - UnitTypeId.ULTRALISK: { - TargetType.Ground.value: { - None: 3 - } - }, + UnitTypeId.LURKERMPBURROWED: {TargetType.Ground.value: {None: 2, IS_ARMORED: 1}}, + UnitTypeId.ULTRALISK: {TargetType.Ground.value: {None: 3}}, # Spire Units (Muta, Corruptor, BL) - UnitTypeId.CORRUPTOR: { - TargetType.Air.value: { - IS_MASSIVE: 1 - } - }, - UnitTypeId.BROODLORD: { - TargetType.Ground.value: { - None: 2 - } - }, + UnitTypeId.CORRUPTOR: {TargetType.Air.value: {IS_MASSIVE: 1}}, + UnitTypeId.BROODLORD: {TargetType.Ground.value: {None: 2}}, } TARGET_HELPER = { 1: "no target", @@ -681,7 +550,7 @@ def return_NOTAUNIT() -> UnitTypeId: 4: "Point2 or Unit", 5: "Point2 or no target", } -CREATION_ABILITY_FIX: Dict[UnitTypeId, AbilityId] = { +CREATION_ABILITY_FIX: dict[UnitTypeId, AbilityId] = { UnitTypeId.ARCHON: AbilityId.ARCHON_WARP_TARGET, UnitTypeId.ASSIMILATORRICH: AbilityId.PROTOSSBUILD_ASSIMILATOR, UnitTypeId.BANELINGCOCOON: AbilityId.MORPHZERGLINGTOBANELING_BANELING, diff --git a/sc2/controller.py b/sc2/controller.py index a3d53aef..2e480330 100644 --- a/sc2/controller.py +++ b/sc2/controller.py @@ -2,6 +2,8 @@ from pathlib import Path from loguru import logger + +# pyre-ignore[21] from s2clientprotocol import sc2api_pb2 as sc_pb from sc2.player import Computer @@ -9,14 +11,12 @@ class Controller(Protocol): - - def __init__(self, ws, process): + def __init__(self, ws, process) -> None: super().__init__(ws) self._process = process @property - def running(self): - # pylint: disable=W0212 + def running(self) -> bool: return self._process._process is not None async def create_game(self, game_map, players, realtime: bool, random_seed=None, disable_fog=None): @@ -46,13 +46,13 @@ async def request_available_maps(self): return result async def request_save_map(self, download_path: str): - """ Not working on linux. """ + """Not working on linux.""" req = sc_pb.RequestSaveMap(map_path=download_path) result = await self._execute(save_map=req) return result async def request_replay_info(self, replay_path: str): - """ Not working on linux. """ + """Not working on linux.""" req = sc_pb.RequestReplayInfo(replay_path=replay_path, download_data=False) result = await self._execute(replay_info=req) return result diff --git a/sc2/data.py b/sc2/data.py index 7c386052..b0c9425f 100644 --- a/sc2/data.py +++ b/sc2/data.py @@ -1,13 +1,17 @@ -""" For the list of enums, see here +# pyre-ignore-all-errors[16, 19] +"""For the list of enums, see here https://github.com/Blizzard/s2client-api/blob/d9ba0a33d6ce9d233c2a4ee988360c188fbe9dbf/include/sc2api/sc2_gametypes.h https://github.com/Blizzard/s2client-api/blob/d9ba0a33d6ce9d233c2a4ee988360c188fbe9dbf/include/sc2api/sc2_action.h https://github.com/Blizzard/s2client-api/blob/d9ba0a33d6ce9d233c2a4ee988360c188fbe9dbf/include/sc2api/sc2_unit.h https://github.com/Blizzard/s2client-api/blob/d9ba0a33d6ce9d233c2a4ee988360c188fbe9dbf/include/sc2api/sc2_data.h """ + +from __future__ import annotations + import enum -from typing import Dict, Set +# pyre-ignore[21] from s2clientprotocol import common_pb2 as common_pb from s2clientprotocol import data_pb2 as data_pb from s2clientprotocol import error_pb2 as error_pb @@ -39,13 +43,14 @@ ActionResult = enum.Enum("ActionResult", error_pb.ActionResult.items()) -race_worker: Dict[Race, UnitTypeId] = { +# pyre-ignore[11] +race_worker: dict[Race, UnitTypeId] = { Race.Protoss: UnitTypeId.PROBE, Race.Terran: UnitTypeId.SCV, Race.Zerg: UnitTypeId.DRONE, } -race_townhalls: Dict[Race, Set[UnitTypeId]] = { +race_townhalls: dict[Race, set[UnitTypeId]] = { Race.Protoss: {UnitTypeId.NEXUS}, Race.Terran: { UnitTypeId.COMMANDCENTER, @@ -71,7 +76,7 @@ }, } -warpgate_abilities: Dict[AbilityId, AbilityId] = { +warpgate_abilities: dict[AbilityId, AbilityId] = { AbilityId.GATEWAYTRAIN_ZEALOT: AbilityId.WARPGATETRAIN_ZEALOT, AbilityId.GATEWAYTRAIN_STALKER: AbilityId.WARPGATETRAIN_STALKER, AbilityId.GATEWAYTRAIN_HIGHTEMPLAR: AbilityId.WARPGATETRAIN_HIGHTEMPLAR, @@ -80,7 +85,7 @@ AbilityId.TRAIN_ADEPT: AbilityId.TRAINWARP_ADEPT, } -race_gas: Dict[Race, UnitTypeId] = { +race_gas: dict[Race, UnitTypeId] = { Race.Protoss: UnitTypeId.ASSIMILATOR, Race.Terran: UnitTypeId.REFINERY, Race.Zerg: UnitTypeId.EXTRACTOR, diff --git a/sc2/dicts/__init__.py b/sc2/dicts/__init__.py index 8e39b102..b4c46780 100644 --- a/sc2/dicts/__init__.py +++ b/sc2/dicts/__init__.py @@ -2,6 +2,12 @@ # This file was automatically generated by "generate_dicts_from_data_json.py" __all__ = [ - 'generic_redirect_abilities', 'unit_abilities', 'unit_research_abilities', 'unit_tech_alias', - 'unit_train_build_abilities', 'unit_trained_from', 'unit_unit_alias', 'upgrade_researched_from' + "generic_redirect_abilities", + "unit_abilities", + "unit_research_abilities", + "unit_tech_alias", + "unit_train_build_abilities", + "unit_trained_from", + "unit_unit_alias", + "upgrade_researched_from", ] diff --git a/sc2/dicts/generic_redirect_abilities.py b/sc2/dicts/generic_redirect_abilities.py index ddbe7def..f2d7e618 100644 --- a/sc2/dicts/generic_redirect_abilities.py +++ b/sc2/dicts/generic_redirect_abilities.py @@ -1,14 +1,12 @@ # THIS FILE WAS AUTOMATICALLY GENERATED BY "generate_dicts_from_data_json.py" DO NOT CHANGE MANUALLY! # ANY CHANGE WILL BE OVERWRITTEN -from typing import Dict - from sc2.ids.ability_id import AbilityId - # from sc2.ids.buff_id import BuffId # from sc2.ids.effect_id import EffectId -GENERIC_REDIRECT_ABILITIES: Dict[AbilityId, AbilityId] = { + +GENERIC_REDIRECT_ABILITIES: dict[AbilityId, AbilityId] = { AbilityId.ARMORYRESEARCH_TERRANSHIPWEAPONSLEVEL1: AbilityId.RESEARCH_TERRANSHIPWEAPONS, AbilityId.ARMORYRESEARCH_TERRANSHIPWEAPONSLEVEL2: AbilityId.RESEARCH_TERRANSHIPWEAPONS, AbilityId.ARMORYRESEARCH_TERRANSHIPWEAPONSLEVEL3: AbilityId.RESEARCH_TERRANSHIPWEAPONS, @@ -129,41 +127,6 @@ AbilityId.DEFILERMPBURROW_BURROWDOWN: AbilityId.BURROWDOWN, AbilityId.DEFILERMPBURROW_CANCEL: AbilityId.CANCEL, AbilityId.DEFILERMPUNBURROW_BURROWUP: AbilityId.BURROWUP, - AbilityId.DUMMYABIL156_MEDIVACSPEEDBOOST: AbilityId.CANCEL, - AbilityId.DUMMYABIL159_MEDIVACSPEEDBOOST: AbilityId.CANCEL, - AbilityId.DUMMYABIL229_DUMMYABIL229: AbilityId.BURROWDOWN, - AbilityId.DUMMYABIL230_DUMMYABIL230: AbilityId.BURROWUP, - AbilityId.DUMMYABIL296_DUMMYABIL296: AbilityId.CANCEL, - AbilityId.DUMMYABIL297_DUMMYABIL297: AbilityId.CANCEL, - AbilityId.DUMMYABIL30_MEDIVACSPEEDBOOST: AbilityId.BURROWDOWN, - AbilityId.DUMMYABIL31_MEDIVACSPEEDBOOST: AbilityId.BURROWUP, - AbilityId.DUMMYABIL32_MEDIVACSPEEDBOOST: AbilityId.BURROWDOWN, - AbilityId.DUMMYABIL33_MEDIVACSPEEDBOOST: AbilityId.BURROWUP, - AbilityId.DUMMYABIL34_MEDIVACSPEEDBOOST: AbilityId.BURROWDOWN, - AbilityId.DUMMYABIL35_MEDIVACSPEEDBOOST: AbilityId.BURROWUP, - AbilityId.DUMMYABIL36_MEDIVACSPEEDBOOST: AbilityId.BURROWDOWN, - AbilityId.DUMMYABIL37_MEDIVACSPEEDBOOST: AbilityId.BURROWUP, - AbilityId.DUMMYABIL38_MEDIVACSPEEDBOOST: AbilityId.BURROWDOWN, - AbilityId.DUMMYABIL39_MEDIVACSPEEDBOOST: AbilityId.BURROWUP, - AbilityId.DUMMYABIL40_MEDIVACSPEEDBOOST: AbilityId.BURROWDOWN, - AbilityId.DUMMYABIL41_MEDIVACSPEEDBOOST: AbilityId.BURROWUP, - AbilityId.DUMMYABIL42_MEDIVACSPEEDBOOST: AbilityId.BURROWDOWN, - AbilityId.DUMMYABIL43_MEDIVACSPEEDBOOST: AbilityId.BURROWDOWN, - AbilityId.DUMMYABIL44_MEDIVACSPEEDBOOST: AbilityId.BURROWUP, - AbilityId.DUMMYABIL45_MEDIVACSPEEDBOOST: AbilityId.BURROWUP, - AbilityId.DUMMYABIL46_MEDIVACSPEEDBOOST: AbilityId.BURROWDOWN, - AbilityId.DUMMYABIL47_MEDIVACSPEEDBOOST: AbilityId.BURROWUP, - AbilityId.DUMMYABIL48_MEDIVACSPEEDBOOST: AbilityId.BURROWDOWN, - AbilityId.DUMMYABIL49_MEDIVACSPEEDBOOST: AbilityId.BURROWUP, - AbilityId.DUMMYABIL52_MEDIVACSPEEDBOOST: AbilityId.BURROWDOWN, - AbilityId.DUMMYABIL53_MEDIVACSPEEDBOOST: AbilityId.BURROWUP, - AbilityId.DUMMYABIL61_MEDIVACSPEEDBOOST: AbilityId.BURROWDOWN, - AbilityId.DUMMYABIL80_MEDIVACSPEEDBOOST: AbilityId.BURROWDOWN, - AbilityId.DUMMYABIL81_MEDIVACSPEEDBOOST: AbilityId.BURROWUP, - AbilityId.DUMMYABIL94_MEDIVACSPEEDBOOST: AbilityId.BURROWDOWN, - AbilityId.DUMMYABIL95_MEDIVACSPEEDBOOST: AbilityId.BURROWUP, - AbilityId.DUMMYABIL96_MEDIVACSPEEDBOOST: AbilityId.BURROWDOWN, - AbilityId.DUMMYABIL97_MEDIVACSPEEDBOOST: AbilityId.BURROWUP, AbilityId.EFFECT_BLINK_STALKER: AbilityId.EFFECT_BLINK, AbilityId.EFFECT_MASSRECALL_MOTHERSHIPCORE: AbilityId.EFFECT_MASSRECALL, AbilityId.EFFECT_MASSRECALL_NEXUS: AbilityId.EFFECT_MASSRECALL, @@ -228,7 +191,9 @@ AbilityId.MORPHBACKTOGATEWAY_CANCEL: AbilityId.CANCEL, AbilityId.MORPHTOBANELING_CANCEL: AbilityId.CANCEL, AbilityId.MORPHTOCOLLAPSIBLEPURIFIERTOWERDEBRIS_CANCEL: AbilityId.CANCEL, + AbilityId.MORPHTOCOLLAPSIBLEROCKTOWERDEBRISRAMPLEFTGREEN_CANCEL: AbilityId.CANCEL, AbilityId.MORPHTOCOLLAPSIBLEROCKTOWERDEBRISRAMPLEFT_CANCEL: AbilityId.CANCEL, + AbilityId.MORPHTOCOLLAPSIBLEROCKTOWERDEBRISRAMPRIGHTGREEN_CANCEL: AbilityId.CANCEL, AbilityId.MORPHTOCOLLAPSIBLEROCKTOWERDEBRISRAMPRIGHT_CANCEL: AbilityId.CANCEL, AbilityId.MORPHTOCOLLAPSIBLEROCKTOWERDEBRIS_CANCEL: AbilityId.CANCEL, AbilityId.MORPHTOCOLLAPSIBLETERRANTOWERDEBRISRAMPLEFT_CANCEL: AbilityId.CANCEL, @@ -239,6 +204,7 @@ AbilityId.MORPHTOSWARMHOSTBURROWEDMP_CANCEL: AbilityId.CANCEL, AbilityId.MOVE_BATTLECRUISER: AbilityId.MOVE, AbilityId.MOVE_MOVE: AbilityId.MOVE, + AbilityId.NULL_NULL: AbilityId.CANCEL, AbilityId.PATROL_BATTLECRUISER: AbilityId.PATROL, AbilityId.PATROL_PATROL: AbilityId.PATROL, AbilityId.PHASINGMODE_CANCEL: AbilityId.CANCEL, @@ -300,5 +266,5 @@ AbilityId.UPGRADETOWARPGATE_CANCEL: AbilityId.CANCEL, AbilityId.WARPABLE_CANCEL: AbilityId.CANCEL, AbilityId.WIDOWMINEBURROW_CANCEL: AbilityId.CANCEL, - AbilityId.ZERGBUILD_CANCEL: AbilityId.HALT + AbilityId.ZERGBUILD_CANCEL: AbilityId.HALT, } diff --git a/sc2/dicts/unit_abilities.py b/sc2/dicts/unit_abilities.py index 6255e3f2..32f401b8 100644 --- a/sc2/dicts/unit_abilities.py +++ b/sc2/dicts/unit_abilities.py @@ -1,128 +1,241 @@ # THIS FILE WAS AUTOMATICALLY GENERATED BY "generate_dicts_from_data_json.py" DO NOT CHANGE MANUALLY! # ANY CHANGE WILL BE OVERWRITTEN -from typing import Dict, Set - -from sc2.ids.ability_id import AbilityId from sc2.ids.unit_typeid import UnitTypeId - +from sc2.ids.ability_id import AbilityId # from sc2.ids.buff_id import BuffId # from sc2.ids.effect_id import EffectId -UNIT_ABILITIES: Dict[UnitTypeId, Set[AbilityId]] = { + +UNIT_ABILITIES: dict[UnitTypeId, set[AbilityId]] = { UnitTypeId.ADEPT: { - AbilityId.ADEPTPHASESHIFT_ADEPTPHASESHIFT, AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, - AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ADEPTPHASESHIFT_ADEPTPHASESHIFT, + AbilityId.ATTACK_ATTACK, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.ADEPTPHASESHIFT: { - AbilityId.ATTACK_ATTACK, AbilityId.CANCEL_ADEPTSHADEPHASESHIFT, AbilityId.HOLDPOSITION_HOLD, - AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SCAN_MOVE, AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.CANCEL_ADEPTSHADEPHASESHIFT, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SCAN_MOVE, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.ARBITERMP: { - AbilityId.ARBITERMPRECALL_ARBITERMPRECALL, AbilityId.ARBITERMPSTASISFIELD_ARBITERMPSTASISFIELD, - AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, - AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ARBITERMPRECALL_ARBITERMPRECALL, + AbilityId.ARBITERMPSTASISFIELD_ARBITERMPSTASISFIELD, + AbilityId.ATTACK_ATTACK, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.ARCHON: { - AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, - AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.ARMORY: { - AbilityId.ARMORYRESEARCH_TERRANSHIPWEAPONSLEVEL1, AbilityId.ARMORYRESEARCH_TERRANSHIPWEAPONSLEVEL2, - AbilityId.ARMORYRESEARCH_TERRANSHIPWEAPONSLEVEL3, AbilityId.ARMORYRESEARCH_TERRANVEHICLEANDSHIPPLATINGLEVEL1, + AbilityId.ARMORYRESEARCH_TERRANSHIPWEAPONSLEVEL1, + AbilityId.ARMORYRESEARCH_TERRANSHIPWEAPONSLEVEL2, + AbilityId.ARMORYRESEARCH_TERRANSHIPWEAPONSLEVEL3, + AbilityId.ARMORYRESEARCH_TERRANVEHICLEANDSHIPPLATINGLEVEL1, AbilityId.ARMORYRESEARCH_TERRANVEHICLEANDSHIPPLATINGLEVEL2, - AbilityId.ARMORYRESEARCH_TERRANVEHICLEANDSHIPPLATINGLEVEL3, AbilityId.ARMORYRESEARCH_TERRANVEHICLEWEAPONSLEVEL1, - AbilityId.ARMORYRESEARCH_TERRANVEHICLEWEAPONSLEVEL2, AbilityId.ARMORYRESEARCH_TERRANVEHICLEWEAPONSLEVEL3 + AbilityId.ARMORYRESEARCH_TERRANVEHICLEANDSHIPPLATINGLEVEL3, + AbilityId.ARMORYRESEARCH_TERRANVEHICLEWEAPONSLEVEL1, + AbilityId.ARMORYRESEARCH_TERRANVEHICLEWEAPONSLEVEL2, + AbilityId.ARMORYRESEARCH_TERRANVEHICLEWEAPONSLEVEL3, }, UnitTypeId.AUTOTURRET: {AbilityId.ATTACK_ATTACK, AbilityId.SMART, AbilityId.STOP_STOP}, UnitTypeId.BANELING: { - AbilityId.ATTACK_ATTACK, AbilityId.BEHAVIOR_BUILDINGATTACKON, AbilityId.BURROWDOWN_BANELING, - AbilityId.EXPLODE_EXPLODE, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, - AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.BEHAVIOR_BUILDINGATTACKON, + AbilityId.BURROWDOWN_BANELING, + AbilityId.EXPLODE_EXPLODE, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.BANELINGBURROWED: {AbilityId.BURROWUP_BANELING, AbilityId.EXPLODE_EXPLODE}, UnitTypeId.BANELINGCOCOON: {AbilityId.RALLY_BUILDING, AbilityId.SMART}, UnitTypeId.BANELINGNEST: {AbilityId.RESEARCH_CENTRIFUGALHOOKS}, UnitTypeId.BANSHEE: { - AbilityId.ATTACK_ATTACK, AbilityId.BEHAVIOR_CLOAKON_BANSHEE, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, - AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.BEHAVIOR_CLOAKON_BANSHEE, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.BARRACKS: { - AbilityId.BARRACKSTRAIN_GHOST, AbilityId.BARRACKSTRAIN_MARAUDER, AbilityId.BARRACKSTRAIN_MARINE, - AbilityId.BARRACKSTRAIN_REAPER, AbilityId.BUILD_REACTOR_BARRACKS, AbilityId.BUILD_TECHLAB_BARRACKS, - AbilityId.LIFT_BARRACKS, AbilityId.RALLY_BUILDING, AbilityId.SMART + AbilityId.BARRACKSTRAIN_GHOST, + AbilityId.BARRACKSTRAIN_MARAUDER, + AbilityId.BARRACKSTRAIN_MARINE, + AbilityId.BARRACKSTRAIN_REAPER, + AbilityId.BUILD_REACTOR_BARRACKS, + AbilityId.BUILD_TECHLAB_BARRACKS, + AbilityId.LIFT_BARRACKS, + AbilityId.RALLY_BUILDING, + AbilityId.SMART, }, UnitTypeId.BARRACKSFLYING: { - AbilityId.BUILD_REACTOR_BARRACKS, AbilityId.BUILD_TECHLAB_BARRACKS, AbilityId.HOLDPOSITION_HOLD, - AbilityId.LAND_BARRACKS, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.BUILD_REACTOR_BARRACKS, + AbilityId.BUILD_TECHLAB_BARRACKS, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.LAND_BARRACKS, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.BARRACKSTECHLAB: { - AbilityId.BARRACKSTECHLABRESEARCH_STIMPACK, AbilityId.RESEARCH_COMBATSHIELD, AbilityId.RESEARCH_CONCUSSIVESHELLS + AbilityId.BARRACKSTECHLABRESEARCH_STIMPACK, + AbilityId.RESEARCH_COMBATSHIELD, + AbilityId.RESEARCH_CONCUSSIVESHELLS, }, UnitTypeId.BATTLECRUISER: { - AbilityId.ATTACK_BATTLECRUISER, AbilityId.EFFECT_TACTICALJUMP, AbilityId.HOLDPOSITION_BATTLECRUISER, - AbilityId.MOVE_BATTLECRUISER, AbilityId.PATROL_BATTLECRUISER, AbilityId.SMART, AbilityId.STOP_BATTLECRUISER, - AbilityId.YAMATO_YAMATOGUN + AbilityId.ATTACK_BATTLECRUISER, + AbilityId.EFFECT_TACTICALJUMP, + AbilityId.HOLDPOSITION_BATTLECRUISER, + AbilityId.MOVE_BATTLECRUISER, + AbilityId.PATROL_BATTLECRUISER, + AbilityId.SMART, + AbilityId.STOP_BATTLECRUISER, + AbilityId.YAMATO_YAMATOGUN, }, UnitTypeId.BROODLING: { - AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, - AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.BROODLORD: { - AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, - AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.BUNKER: {AbilityId.EFFECT_SALVAGE, AbilityId.LOAD_BUNKER, AbilityId.RALLY_BUILDING, AbilityId.SMART}, UnitTypeId.BYPASSARMORDRONE: {AbilityId.ATTACK_ATTACK, AbilityId.MOVE_MOVE, AbilityId.SMART, AbilityId.STOP_STOP}, UnitTypeId.CARRIER: { - AbilityId.ATTACK_ATTACK, AbilityId.BUILD_INTERCEPTORS, AbilityId.CANCEL_HANGARQUEUE5, - AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.BUILD_INTERCEPTORS, + AbilityId.CANCEL_HANGARQUEUE5, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.CHANGELING: { - AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SCAN_MOVE, AbilityId.SMART, - AbilityId.STOP_STOP + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SCAN_MOVE, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.CHANGELINGMARINE: { - AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SCAN_MOVE, AbilityId.SMART, - AbilityId.STOP_STOP + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SCAN_MOVE, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.CHANGELINGMARINESHIELD: { - AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SCAN_MOVE, AbilityId.SMART, - AbilityId.STOP_STOP + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SCAN_MOVE, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.CHANGELINGZEALOT: { - AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SCAN_MOVE, AbilityId.SMART, - AbilityId.STOP_STOP + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SCAN_MOVE, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.CHANGELINGZERGLING: { - AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SCAN_MOVE, AbilityId.SMART, - AbilityId.STOP_STOP + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SCAN_MOVE, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.CHANGELINGZERGLINGWINGS: { - AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SCAN_MOVE, AbilityId.SMART, - AbilityId.STOP_STOP + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SCAN_MOVE, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.COLOSSUS: { - AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, - AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.COMMANDCENTER: { - AbilityId.COMMANDCENTERTRAIN_SCV, AbilityId.LIFT_COMMANDCENTER, AbilityId.LOADALL_COMMANDCENTER, - AbilityId.RALLY_COMMANDCENTER, AbilityId.SMART, AbilityId.UPGRADETOORBITAL_ORBITALCOMMAND, - AbilityId.UPGRADETOPLANETARYFORTRESS_PLANETARYFORTRESS + AbilityId.COMMANDCENTERTRAIN_SCV, + AbilityId.LIFT_COMMANDCENTER, + AbilityId.LOADALL_COMMANDCENTER, + AbilityId.RALLY_COMMANDCENTER, + AbilityId.SMART, + AbilityId.UPGRADETOORBITAL_ORBITALCOMMAND, + AbilityId.UPGRADETOPLANETARYFORTRESS_PLANETARYFORTRESS, }, UnitTypeId.COMMANDCENTERFLYING: { - AbilityId.HOLDPOSITION_HOLD, AbilityId.LAND_COMMANDCENTER, AbilityId.LOADALL_COMMANDCENTER, AbilityId.MOVE_MOVE, - AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.HOLDPOSITION_HOLD, + AbilityId.LAND_COMMANDCENTER, + AbilityId.LOADALL_COMMANDCENTER, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.CORRUPTOR: { - AbilityId.ATTACK_ATTACK, AbilityId.CAUSTICSPRAY_CAUSTICSPRAY, AbilityId.HOLDPOSITION_HOLD, - AbilityId.MORPHTOBROODLORD_BROODLORD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SMART, - AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.CAUSTICSPRAY_CAUSTICSPRAY, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MORPHTOBROODLORD_BROODLORD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.CORSAIRMP: { - AbilityId.ATTACK_ATTACK, AbilityId.CORSAIRMPDISRUPTIONWEB_CORSAIRMPDISRUPTIONWEB, AbilityId.HOLDPOSITION_HOLD, - AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.CORSAIRMPDISRUPTIONWEB_CORSAIRMPDISRUPTIONWEB, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.CREEPTUMOR: {AbilityId.BUILD_CREEPTUMOR_TUMOR, AbilityId.SMART}, UnitTypeId.CREEPTUMORBURROWED: {AbilityId.BUILD_CREEPTUMOR, AbilityId.BUILD_CREEPTUMOR_TUMOR, AbilityId.SMART}, @@ -132,44 +245,91 @@ AbilityId.CYBERNETICSCORERESEARCH_PROTOSSAIRARMORLEVEL3, AbilityId.CYBERNETICSCORERESEARCH_PROTOSSAIRWEAPONSLEVEL1, AbilityId.CYBERNETICSCORERESEARCH_PROTOSSAIRWEAPONSLEVEL2, - AbilityId.CYBERNETICSCORERESEARCH_PROTOSSAIRWEAPONSLEVEL3, AbilityId.RESEARCH_WARPGATE + AbilityId.CYBERNETICSCORERESEARCH_PROTOSSAIRWEAPONSLEVEL3, + AbilityId.RESEARCH_WARPGATE, }, UnitTypeId.CYCLONE: { - AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.LOCKON_LOCKON, AbilityId.MOVE_MOVE, - AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.LOCKON_LOCKON, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.DARKSHRINE: {AbilityId.RESEARCH_SHADOWSTRIKE}, UnitTypeId.DARKTEMPLAR: { - AbilityId.ATTACK_ATTACK, AbilityId.EFFECT_SHADOWSTRIDE, AbilityId.HOLDPOSITION_HOLD, AbilityId.MORPH_ARCHON, - AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.EFFECT_SHADOWSTRIDE, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MORPH_ARCHON, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.DEFILERMP: { - AbilityId.DEFILERMPBURROW_BURROWDOWN, AbilityId.DEFILERMPCONSUME_DEFILERMPCONSUME, - AbilityId.DEFILERMPDARKSWARM_DEFILERMPDARKSWARM, AbilityId.DEFILERMPPLAGUE_DEFILERMPPLAGUE, - AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SCAN_MOVE, AbilityId.SMART, - AbilityId.STOP_STOP + AbilityId.DEFILERMPBURROW_BURROWDOWN, + AbilityId.DEFILERMPCONSUME_DEFILERMPCONSUME, + AbilityId.DEFILERMPDARKSWARM_DEFILERMPDARKSWARM, + AbilityId.DEFILERMPPLAGUE_DEFILERMPPLAGUE, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SCAN_MOVE, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.DEFILERMPBURROWED: {AbilityId.DEFILERMPUNBURROW_BURROWUP}, UnitTypeId.DEVOURERMP: { - AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, - AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.DISRUPTOR: { - AbilityId.EFFECT_PURIFICATIONNOVA, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, - AbilityId.SCAN_MOVE, AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.EFFECT_PURIFICATIONNOVA, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SCAN_MOVE, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.DISRUPTORPHASED: { - AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SCAN_MOVE, AbilityId.SMART, - AbilityId.STOP_STOP + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SCAN_MOVE, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.DRONE: { - AbilityId.ATTACK_ATTACK, AbilityId.BUILD_LURKERDEN, AbilityId.BURROWDOWN_DRONE, AbilityId.EFFECT_SPRAY_ZERG, - AbilityId.HARVEST_GATHER_DRONE, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, - AbilityId.SMART, AbilityId.STOP_STOP, AbilityId.ZERGBUILD_BANELINGNEST, AbilityId.ZERGBUILD_EVOLUTIONCHAMBER, - AbilityId.ZERGBUILD_EXTRACTOR, AbilityId.ZERGBUILD_HATCHERY, AbilityId.ZERGBUILD_HYDRALISKDEN, - AbilityId.ZERGBUILD_INFESTATIONPIT, AbilityId.ZERGBUILD_NYDUSNETWORK, AbilityId.ZERGBUILD_ROACHWARREN, - AbilityId.ZERGBUILD_SPAWNINGPOOL, AbilityId.ZERGBUILD_SPINECRAWLER, AbilityId.ZERGBUILD_SPIRE, - AbilityId.ZERGBUILD_SPORECRAWLER, AbilityId.ZERGBUILD_ULTRALISKCAVERN + AbilityId.ATTACK_ATTACK, + AbilityId.BUILD_LURKERDEN, + AbilityId.BURROWDOWN_DRONE, + AbilityId.EFFECT_SPRAY_ZERG, + AbilityId.HARVEST_GATHER_DRONE, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, + AbilityId.ZERGBUILD_BANELINGNEST, + AbilityId.ZERGBUILD_EVOLUTIONCHAMBER, + AbilityId.ZERGBUILD_EXTRACTOR, + AbilityId.ZERGBUILD_HATCHERY, + AbilityId.ZERGBUILD_HYDRALISKDEN, + AbilityId.ZERGBUILD_INFESTATIONPIT, + AbilityId.ZERGBUILD_NYDUSNETWORK, + AbilityId.ZERGBUILD_ROACHWARREN, + AbilityId.ZERGBUILD_SPAWNINGPOOL, + AbilityId.ZERGBUILD_SPINECRAWLER, + AbilityId.ZERGBUILD_SPIRE, + AbilityId.ZERGBUILD_SPORECRAWLER, + AbilityId.ZERGBUILD_ULTRALISKCAVERN, }, UnitTypeId.DRONEBURROWED: {AbilityId.BURROWUP_DRONE}, UnitTypeId.EGG: {AbilityId.RALLY_BUILDING, AbilityId.SMART}, @@ -180,482 +340,985 @@ AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYARMORLEVEL3, AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYWEAPONSLEVEL1, AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYWEAPONSLEVEL2, - AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYWEAPONSLEVEL3, AbilityId.RESEARCH_HISECAUTOTRACKING, - AbilityId.RESEARCH_TERRANSTRUCTUREARMORUPGRADE + AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYWEAPONSLEVEL3, + AbilityId.RESEARCH_HISECAUTOTRACKING, + AbilityId.RESEARCH_TERRANSTRUCTUREARMORUPGRADE, }, UnitTypeId.EVOLUTIONCHAMBER: { - AbilityId.RESEARCH_ZERGGROUNDARMORLEVEL1, AbilityId.RESEARCH_ZERGGROUNDARMORLEVEL2, - AbilityId.RESEARCH_ZERGGROUNDARMORLEVEL3, AbilityId.RESEARCH_ZERGMELEEWEAPONSLEVEL1, - AbilityId.RESEARCH_ZERGMELEEWEAPONSLEVEL2, AbilityId.RESEARCH_ZERGMELEEWEAPONSLEVEL3, - AbilityId.RESEARCH_ZERGMISSILEWEAPONSLEVEL1, AbilityId.RESEARCH_ZERGMISSILEWEAPONSLEVEL2, - AbilityId.RESEARCH_ZERGMISSILEWEAPONSLEVEL3 + AbilityId.RESEARCH_ZERGGROUNDARMORLEVEL1, + AbilityId.RESEARCH_ZERGGROUNDARMORLEVEL2, + AbilityId.RESEARCH_ZERGGROUNDARMORLEVEL3, + AbilityId.RESEARCH_ZERGMELEEWEAPONSLEVEL1, + AbilityId.RESEARCH_ZERGMELEEWEAPONSLEVEL2, + AbilityId.RESEARCH_ZERGMELEEWEAPONSLEVEL3, + AbilityId.RESEARCH_ZERGMISSILEWEAPONSLEVEL1, + AbilityId.RESEARCH_ZERGMISSILEWEAPONSLEVEL2, + AbilityId.RESEARCH_ZERGMISSILEWEAPONSLEVEL3, }, UnitTypeId.FACTORY: { - AbilityId.BUILD_REACTOR_FACTORY, AbilityId.BUILD_TECHLAB_FACTORY, AbilityId.FACTORYTRAIN_HELLION, - AbilityId.FACTORYTRAIN_SIEGETANK, AbilityId.FACTORYTRAIN_THOR, AbilityId.FACTORYTRAIN_WIDOWMINE, - AbilityId.LIFT_FACTORY, AbilityId.RALLY_BUILDING, AbilityId.SMART, AbilityId.TRAIN_CYCLONE, - AbilityId.TRAIN_HELLBAT + AbilityId.BUILD_REACTOR_FACTORY, + AbilityId.BUILD_TECHLAB_FACTORY, + AbilityId.FACTORYTRAIN_HELLION, + AbilityId.FACTORYTRAIN_SIEGETANK, + AbilityId.FACTORYTRAIN_THOR, + AbilityId.FACTORYTRAIN_WIDOWMINE, + AbilityId.LIFT_FACTORY, + AbilityId.RALLY_BUILDING, + AbilityId.SMART, + AbilityId.TRAIN_CYCLONE, + AbilityId.TRAIN_HELLBAT, }, UnitTypeId.FACTORYFLYING: { - AbilityId.BUILD_REACTOR_FACTORY, AbilityId.BUILD_TECHLAB_FACTORY, AbilityId.HOLDPOSITION_HOLD, - AbilityId.LAND_FACTORY, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.BUILD_REACTOR_FACTORY, + AbilityId.BUILD_TECHLAB_FACTORY, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.LAND_FACTORY, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.FACTORYTECHLAB: { - AbilityId.FACTORYTECHLABRESEARCH_CYCLONERESEARCHHURRICANETHRUSTERS, AbilityId.RESEARCH_DRILLINGCLAWS, - AbilityId.RESEARCH_INFERNALPREIGNITER, AbilityId.RESEARCH_SMARTSERVOS + AbilityId.FACTORYTECHLABRESEARCH_CYCLONERESEARCHHURRICANETHRUSTERS, + AbilityId.RESEARCH_DRILLINGCLAWS, + AbilityId.RESEARCH_INFERNALPREIGNITER, + AbilityId.RESEARCH_SMARTSERVOS, }, UnitTypeId.FLEETBEACON: { AbilityId.FLEETBEACONRESEARCH_RESEARCHVOIDRAYSPEEDUPGRADE, - AbilityId.FLEETBEACONRESEARCH_TEMPESTRESEARCHGROUNDATTACKUPGRADE, AbilityId.RESEARCH_PHOENIXANIONPULSECRYSTALS + AbilityId.FLEETBEACONRESEARCH_TEMPESTRESEARCHGROUNDATTACKUPGRADE, + AbilityId.RESEARCH_PHOENIXANIONPULSECRYSTALS, }, UnitTypeId.FORGE: { - AbilityId.FORGERESEARCH_PROTOSSGROUNDARMORLEVEL1, AbilityId.FORGERESEARCH_PROTOSSGROUNDARMORLEVEL2, - AbilityId.FORGERESEARCH_PROTOSSGROUNDARMORLEVEL3, AbilityId.FORGERESEARCH_PROTOSSGROUNDWEAPONSLEVEL1, - AbilityId.FORGERESEARCH_PROTOSSGROUNDWEAPONSLEVEL2, AbilityId.FORGERESEARCH_PROTOSSGROUNDWEAPONSLEVEL3, - AbilityId.FORGERESEARCH_PROTOSSSHIELDSLEVEL1, AbilityId.FORGERESEARCH_PROTOSSSHIELDSLEVEL2, - AbilityId.FORGERESEARCH_PROTOSSSHIELDSLEVEL3 + AbilityId.FORGERESEARCH_PROTOSSGROUNDARMORLEVEL1, + AbilityId.FORGERESEARCH_PROTOSSGROUNDARMORLEVEL2, + AbilityId.FORGERESEARCH_PROTOSSGROUNDARMORLEVEL3, + AbilityId.FORGERESEARCH_PROTOSSGROUNDWEAPONSLEVEL1, + AbilityId.FORGERESEARCH_PROTOSSGROUNDWEAPONSLEVEL2, + AbilityId.FORGERESEARCH_PROTOSSGROUNDWEAPONSLEVEL3, + AbilityId.FORGERESEARCH_PROTOSSSHIELDSLEVEL1, + AbilityId.FORGERESEARCH_PROTOSSSHIELDSLEVEL2, + AbilityId.FORGERESEARCH_PROTOSSSHIELDSLEVEL3, }, UnitTypeId.FUSIONCORE: { - AbilityId.FUSIONCORERESEARCH_RESEARCHBALLISTICRANGE, AbilityId.FUSIONCORERESEARCH_RESEARCHMEDIVACENERGYUPGRADE, - AbilityId.RESEARCH_BATTLECRUISERWEAPONREFIT + AbilityId.FUSIONCORERESEARCH_RESEARCHBALLISTICRANGE, + AbilityId.FUSIONCORERESEARCH_RESEARCHMEDIVACENERGYUPGRADE, + AbilityId.RESEARCH_BATTLECRUISERWEAPONREFIT, }, UnitTypeId.GATEWAY: { - AbilityId.GATEWAYTRAIN_DARKTEMPLAR, AbilityId.GATEWAYTRAIN_HIGHTEMPLAR, AbilityId.GATEWAYTRAIN_SENTRY, - AbilityId.GATEWAYTRAIN_STALKER, AbilityId.GATEWAYTRAIN_ZEALOT, AbilityId.MORPH_WARPGATE, - AbilityId.RALLY_BUILDING, AbilityId.SMART, AbilityId.TRAIN_ADEPT + AbilityId.GATEWAYTRAIN_DARKTEMPLAR, + AbilityId.GATEWAYTRAIN_HIGHTEMPLAR, + AbilityId.GATEWAYTRAIN_SENTRY, + AbilityId.GATEWAYTRAIN_STALKER, + AbilityId.GATEWAYTRAIN_ZEALOT, + AbilityId.MORPH_WARPGATE, + AbilityId.RALLY_BUILDING, + AbilityId.SMART, + AbilityId.TRAIN_ADEPT, }, UnitTypeId.GHOST: { - AbilityId.ATTACK_ATTACK, AbilityId.BEHAVIOR_CLOAKON_GHOST, AbilityId.BEHAVIOR_HOLDFIREON_GHOST, - AbilityId.EFFECT_GHOSTSNIPE, AbilityId.EMP_EMP, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, - AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.BEHAVIOR_CLOAKON_GHOST, + AbilityId.BEHAVIOR_HOLDFIREON_GHOST, + AbilityId.EFFECT_GHOSTSNIPE, + AbilityId.EMP_EMP, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.GHOSTACADEMY: {AbilityId.BUILD_NUKE, AbilityId.RESEARCH_PERSONALCLOAKING}, UnitTypeId.GHOSTNOVA: { - AbilityId.ATTACK_ATTACK, AbilityId.BEHAVIOR_CLOAKON_GHOST, AbilityId.BEHAVIOR_HOLDFIREON_GHOST, - AbilityId.EFFECT_GHOSTSNIPE, AbilityId.EMP_EMP, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, - AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.BEHAVIOR_CLOAKON_GHOST, + AbilityId.BEHAVIOR_HOLDFIREON_GHOST, + AbilityId.EFFECT_GHOSTSNIPE, + AbilityId.EMP_EMP, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.GREATERSPIRE: { - AbilityId.RESEARCH_ZERGFLYERARMORLEVEL1, AbilityId.RESEARCH_ZERGFLYERARMORLEVEL2, - AbilityId.RESEARCH_ZERGFLYERARMORLEVEL3, AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL1, - AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL2, AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL3 + AbilityId.RESEARCH_ZERGFLYERARMORLEVEL1, + AbilityId.RESEARCH_ZERGFLYERARMORLEVEL2, + AbilityId.RESEARCH_ZERGFLYERARMORLEVEL3, + AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL1, + AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL2, + AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL3, }, UnitTypeId.GUARDIANMP: { - AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, - AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.HATCHERY: { - AbilityId.RALLY_HATCHERY_UNITS, AbilityId.RALLY_HATCHERY_WORKERS, AbilityId.RESEARCH_BURROW, - AbilityId.RESEARCH_PNEUMATIZEDCARAPACE, AbilityId.SMART, AbilityId.TRAINQUEEN_QUEEN, - AbilityId.UPGRADETOLAIR_LAIR + AbilityId.RALLY_HATCHERY_UNITS, + AbilityId.RALLY_HATCHERY_WORKERS, + AbilityId.RESEARCH_BURROW, + AbilityId.RESEARCH_PNEUMATIZEDCARAPACE, + AbilityId.SMART, + AbilityId.TRAINQUEEN_QUEEN, + AbilityId.UPGRADETOLAIR_LAIR, }, UnitTypeId.HELLION: { - AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MORPH_HELLBAT, AbilityId.MOVE_MOVE, - AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MORPH_HELLBAT, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.HELLIONTANK: { - AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MORPH_HELLION, AbilityId.MOVE_MOVE, - AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MORPH_HELLION, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.HERC: { - AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, - AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.HERCPLACEMENT: { - AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, - AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.HIGHTEMPLAR: { - AbilityId.ATTACK_ATTACK, AbilityId.FEEDBACK_FEEDBACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MORPH_ARCHON, - AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.PSISTORM_PSISTORM, AbilityId.SCAN_MOVE, AbilityId.SMART, - AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.FEEDBACK_FEEDBACK, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MORPH_ARCHON, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.PSISTORM_PSISTORM, + AbilityId.SCAN_MOVE, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.HIVE: { - AbilityId.RALLY_HATCHERY_UNITS, AbilityId.RALLY_HATCHERY_WORKERS, AbilityId.RESEARCH_BURROW, - AbilityId.RESEARCH_PNEUMATIZEDCARAPACE, AbilityId.SMART, AbilityId.TRAINQUEEN_QUEEN + AbilityId.RALLY_HATCHERY_UNITS, + AbilityId.RALLY_HATCHERY_WORKERS, + AbilityId.RESEARCH_BURROW, + AbilityId.RESEARCH_PNEUMATIZEDCARAPACE, + AbilityId.SMART, + AbilityId.TRAINQUEEN_QUEEN, }, UnitTypeId.HYDRALISK: { - AbilityId.ATTACK_ATTACK, AbilityId.BURROWDOWN_HYDRALISK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MORPH_LURKER, - AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.BURROWDOWN_HYDRALISK, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MORPH_LURKER, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.HYDRALISKBURROWED: {AbilityId.BURROWUP_HYDRALISK}, UnitTypeId.HYDRALISKDEN: {AbilityId.RESEARCH_GROOVEDSPINES, AbilityId.RESEARCH_MUSCULARAUGMENTS}, UnitTypeId.IMMORTAL: { - AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, - AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.INFESTATIONPIT: {AbilityId.RESEARCH_NEURALPARASITE}, UnitTypeId.INFESTEDTERRANSEGG: { - AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SMART + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, }, UnitTypeId.INFESTOR: { - AbilityId.AMORPHOUSARMORCLOUD_AMORPHOUSARMORCLOUD, AbilityId.BURROWDOWN_INFESTOR, - AbilityId.BURROWDOWN_INFESTORTERRAN, AbilityId.FUNGALGROWTH_FUNGALGROWTH, AbilityId.HOLDPOSITION_HOLD, - AbilityId.MOVE_MOVE, AbilityId.NEURALPARASITE_NEURALPARASITE, AbilityId.PATROL_PATROL, AbilityId.SCAN_MOVE, - AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.AMORPHOUSARMORCLOUD_AMORPHOUSARMORCLOUD, + AbilityId.BURROWDOWN_INFESTOR, + AbilityId.BURROWDOWN_INFESTORTERRAN, + AbilityId.FUNGALGROWTH_FUNGALGROWTH, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.NEURALPARASITE_NEURALPARASITE, + AbilityId.PATROL_PATROL, + AbilityId.SCAN_MOVE, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.INFESTORBURROWED: { - AbilityId.BURROWUP_INFESTOR, AbilityId.BURROWUP_INFESTORTERRAN, AbilityId.HOLDPOSITION_HOLD, - AbilityId.MOVE_MOVE, AbilityId.NEURALPARASITE_NEURALPARASITE, AbilityId.PATROL_PATROL, AbilityId.SCAN_MOVE, - AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.BURROWUP_INFESTOR, + AbilityId.BURROWUP_INFESTORTERRAN, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.NEURALPARASITE_NEURALPARASITE, + AbilityId.PATROL_PATROL, + AbilityId.SCAN_MOVE, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.INFESTORTERRAN: { - AbilityId.ATTACK_ATTACK, AbilityId.BURROWDOWN_INFESTORTERRAN, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, - AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.BURROWDOWN_INFESTORTERRAN, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.INFESTORTERRANBURROWED: {AbilityId.BURROWUP_INFESTORTERRAN}, UnitTypeId.INTERCEPTOR: { - AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, - AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.LAIR: { - AbilityId.RALLY_HATCHERY_UNITS, AbilityId.RALLY_HATCHERY_WORKERS, AbilityId.RESEARCH_BURROW, - AbilityId.RESEARCH_PNEUMATIZEDCARAPACE, AbilityId.SMART, AbilityId.TRAINQUEEN_QUEEN, - AbilityId.UPGRADETOHIVE_HIVE + AbilityId.RALLY_HATCHERY_UNITS, + AbilityId.RALLY_HATCHERY_WORKERS, + AbilityId.RESEARCH_BURROW, + AbilityId.RESEARCH_PNEUMATIZEDCARAPACE, + AbilityId.SMART, + AbilityId.TRAINQUEEN_QUEEN, + AbilityId.UPGRADETOHIVE_HIVE, }, UnitTypeId.LARVA: { - AbilityId.LARVATRAIN_CORRUPTOR, AbilityId.LARVATRAIN_DRONE, AbilityId.LARVATRAIN_HYDRALISK, - AbilityId.LARVATRAIN_INFESTOR, AbilityId.LARVATRAIN_MUTALISK, AbilityId.LARVATRAIN_OVERLORD, - AbilityId.LARVATRAIN_ROACH, AbilityId.LARVATRAIN_ULTRALISK, AbilityId.LARVATRAIN_VIPER, - AbilityId.LARVATRAIN_ZERGLING, AbilityId.TRAIN_SWARMHOST + AbilityId.LARVATRAIN_CORRUPTOR, + AbilityId.LARVATRAIN_DRONE, + AbilityId.LARVATRAIN_HYDRALISK, + AbilityId.LARVATRAIN_INFESTOR, + AbilityId.LARVATRAIN_MUTALISK, + AbilityId.LARVATRAIN_OVERLORD, + AbilityId.LARVATRAIN_ROACH, + AbilityId.LARVATRAIN_ULTRALISK, + AbilityId.LARVATRAIN_VIPER, + AbilityId.LARVATRAIN_ZERGLING, + AbilityId.TRAIN_SWARMHOST, }, UnitTypeId.LIBERATOR: { - AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MORPH_LIBERATORAGMODE, AbilityId.MOVE_MOVE, - AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MORPH_LIBERATORAGMODE, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.LIBERATORAG: { - AbilityId.ATTACK_ATTACK, AbilityId.MORPH_LIBERATORAAMODE, AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.MORPH_LIBERATORAAMODE, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.LOCUSTMP: { - AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, - AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.LOCUSTMPFLYING: { - AbilityId.ATTACK_ATTACK, AbilityId.EFFECT_LOCUSTSWOOP, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, - AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.EFFECT_LOCUSTSWOOP, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.LURKERDENMP: {AbilityId.LURKERDENRESEARCH_RESEARCHLURKERRANGE, AbilityId.RESEARCH_ADAPTIVETALONS}, UnitTypeId.LURKERMP: { - AbilityId.ATTACK_ATTACK, AbilityId.BURROWDOWN_LURKER, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, - AbilityId.PATROL_PATROL, AbilityId.SCAN_MOVE, AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.BURROWDOWN_LURKER, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SCAN_MOVE, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.LURKERMPBURROWED: { - AbilityId.ATTACK_ATTACK, AbilityId.BEHAVIOR_HOLDFIREON_LURKER, AbilityId.BURROWUP_LURKER, AbilityId.SMART, - AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.BEHAVIOR_HOLDFIREON_LURKER, + AbilityId.BURROWUP_LURKER, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.LURKERMPEGG: {AbilityId.RALLY_BUILDING, AbilityId.SMART}, UnitTypeId.MARAUDER: { - AbilityId.ATTACK_ATTACK, AbilityId.EFFECT_STIM_MARAUDER, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, - AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.EFFECT_STIM_MARAUDER, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.MARINE: { - AbilityId.ATTACK_ATTACK, AbilityId.EFFECT_STIM_MARINE, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, - AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.EFFECT_STIM_MARINE, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.MEDIVAC: { - AbilityId.EFFECT_MEDIVACIGNITEAFTERBURNERS, AbilityId.HOLDPOSITION_HOLD, AbilityId.LOAD_MEDIVAC, - AbilityId.MEDIVACHEAL_HEAL, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SCAN_MOVE, AbilityId.SMART, - AbilityId.STOP_STOP + AbilityId.EFFECT_MEDIVACIGNITEAFTERBURNERS, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.LOAD_MEDIVAC, + AbilityId.MEDIVACHEAL_HEAL, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SCAN_MOVE, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.MISSILETURRET: {AbilityId.ATTACK_ATTACK, AbilityId.SMART, AbilityId.STOP_STOP}, UnitTypeId.MOTHERSHIP: { - AbilityId.ATTACK_ATTACK, AbilityId.EFFECT_MASSRECALL_STRATEGICRECALL, AbilityId.EFFECT_TIMEWARP, - AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.EFFECT_MASSRECALL_STRATEGICRECALL, + AbilityId.EFFECT_TIMEWARP, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.MOTHERSHIPCORE: { - AbilityId.ATTACK_ATTACK, AbilityId.EFFECT_MASSRECALL_MOTHERSHIPCORE, AbilityId.EFFECT_PHOTONOVERCHARGE, - AbilityId.EFFECT_TIMEWARP, AbilityId.HOLDPOSITION_HOLD, AbilityId.MORPH_MOTHERSHIP, AbilityId.MOVE_MOVE, - AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.EFFECT_MASSRECALL_MOTHERSHIPCORE, + AbilityId.EFFECT_PHOTONOVERCHARGE, + AbilityId.EFFECT_TIMEWARP, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MORPH_MOTHERSHIP, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.MULE: { - AbilityId.EFFECT_REPAIR_MULE, AbilityId.HARVEST_GATHER_MULE, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, - AbilityId.PATROL_PATROL, AbilityId.SCAN_MOVE, AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.EFFECT_REPAIR_MULE, + AbilityId.HARVEST_GATHER_MULE, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SCAN_MOVE, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.MUTALISK: { - AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, - AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.NEXUS: { - AbilityId.BATTERYOVERCHARGE_BATTERYOVERCHARGE, AbilityId.EFFECT_CHRONOBOOSTENERGYCOST, - AbilityId.EFFECT_MASSRECALL_NEXUS, AbilityId.NEXUSTRAINMOTHERSHIP_MOTHERSHIP, AbilityId.NEXUSTRAIN_PROBE, - AbilityId.RALLY_NEXUS, AbilityId.SMART + AbilityId.BATTERYOVERCHARGE_BATTERYOVERCHARGE, + AbilityId.EFFECT_CHRONOBOOSTENERGYCOST, + AbilityId.EFFECT_MASSRECALL_NEXUS, + AbilityId.NEXUSTRAINMOTHERSHIP_MOTHERSHIP, + AbilityId.NEXUSTRAIN_PROBE, + AbilityId.RALLY_NEXUS, + AbilityId.SMART, }, UnitTypeId.NYDUSCANAL: {AbilityId.LOAD_NYDUSWORM, AbilityId.RALLY_BUILDING, AbilityId.SMART, AbilityId.STOP_STOP}, UnitTypeId.NYDUSCANALATTACKER: {AbilityId.ATTACK_ATTACK, AbilityId.SMART, AbilityId.STOP_STOP}, UnitTypeId.NYDUSCANALCREEPER: { - AbilityId.ATTACK_ATTACK, AbilityId.DIGESTERCREEPSPRAY_DIGESTERCREEPSPRAY, AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.DIGESTERCREEPSPRAY_DIGESTERCREEPSPRAY, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.NYDUSNETWORK: { - AbilityId.BUILD_NYDUSWORM, AbilityId.LOAD_NYDUSNETWORK, AbilityId.RALLY_BUILDING, AbilityId.SMART, - AbilityId.STOP_STOP + AbilityId.BUILD_NYDUSWORM, + AbilityId.LOAD_NYDUSNETWORK, + AbilityId.RALLY_BUILDING, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.OBSERVER: { - AbilityId.HOLDPOSITION_HOLD, AbilityId.MORPH_SURVEILLANCEMODE, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, - AbilityId.SCAN_MOVE, AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MORPH_SURVEILLANCEMODE, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SCAN_MOVE, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.OBSERVERSIEGEMODE: {AbilityId.MORPH_OBSERVERMODE, AbilityId.STOP_STOP}, UnitTypeId.ORACLE: { - AbilityId.ATTACK_ATTACK, AbilityId.BEHAVIOR_PULSARBEAMON, AbilityId.BUILD_STASISTRAP, - AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.ORACLEREVELATION_ORACLEREVELATION, - AbilityId.PATROL_PATROL, AbilityId.SCAN_MOVE, AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.BEHAVIOR_PULSARBEAMON, + AbilityId.BUILD_STASISTRAP, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.ORACLEREVELATION_ORACLEREVELATION, + AbilityId.PATROL_PATROL, + AbilityId.SCAN_MOVE, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.ORBITALCOMMAND: { - AbilityId.CALLDOWNMULE_CALLDOWNMULE, AbilityId.COMMANDCENTERTRAIN_SCV, AbilityId.LIFT_ORBITALCOMMAND, - AbilityId.RALLY_COMMANDCENTER, AbilityId.SCANNERSWEEP_SCAN, AbilityId.SMART, AbilityId.SUPPLYDROP_SUPPLYDROP + AbilityId.CALLDOWNMULE_CALLDOWNMULE, + AbilityId.COMMANDCENTERTRAIN_SCV, + AbilityId.LIFT_ORBITALCOMMAND, + AbilityId.RALLY_COMMANDCENTER, + AbilityId.SCANNERSWEEP_SCAN, + AbilityId.SMART, + AbilityId.SUPPLYDROP_SUPPLYDROP, }, UnitTypeId.ORBITALCOMMANDFLYING: { - AbilityId.HOLDPOSITION_HOLD, AbilityId.LAND_ORBITALCOMMAND, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, - AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.HOLDPOSITION_HOLD, + AbilityId.LAND_ORBITALCOMMAND, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.OVERLORD: { - AbilityId.BEHAVIOR_GENERATECREEPON, AbilityId.HOLDPOSITION_HOLD, AbilityId.MORPH_OVERLORDTRANSPORT, - AbilityId.MORPH_OVERSEER, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SCAN_MOVE, AbilityId.SMART, - AbilityId.STOP_STOP + AbilityId.BEHAVIOR_GENERATECREEPON, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MORPH_OVERLORDTRANSPORT, + AbilityId.MORPH_OVERSEER, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SCAN_MOVE, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.OVERLORDTRANSPORT: { - AbilityId.BEHAVIOR_GENERATECREEPON, AbilityId.HOLDPOSITION_HOLD, AbilityId.LOAD_OVERLORD, - AbilityId.MORPH_OVERSEER, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SCAN_MOVE, AbilityId.SMART, - AbilityId.STOP_STOP + AbilityId.BEHAVIOR_GENERATECREEPON, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.LOAD_OVERLORD, + AbilityId.MORPH_OVERSEER, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SCAN_MOVE, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.OVERSEER: { - AbilityId.CONTAMINATE_CONTAMINATE, AbilityId.HOLDPOSITION_HOLD, AbilityId.MORPH_OVERSIGHTMODE, - AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SCAN_MOVE, AbilityId.SMART, - AbilityId.SPAWNCHANGELING_SPAWNCHANGELING, AbilityId.STOP_STOP + AbilityId.CONTAMINATE_CONTAMINATE, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MORPH_OVERSIGHTMODE, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SCAN_MOVE, + AbilityId.SMART, + AbilityId.SPAWNCHANGELING_SPAWNCHANGELING, + AbilityId.STOP_STOP, }, UnitTypeId.OVERSEERSIEGEMODE: { - AbilityId.CONTAMINATE_CONTAMINATE, AbilityId.MORPH_OVERSEERMODE, AbilityId.SMART, - AbilityId.SPAWNCHANGELING_SPAWNCHANGELING, AbilityId.STOP_STOP + AbilityId.CONTAMINATE_CONTAMINATE, + AbilityId.MORPH_OVERSEERMODE, + AbilityId.SMART, + AbilityId.SPAWNCHANGELING_SPAWNCHANGELING, + AbilityId.STOP_STOP, }, UnitTypeId.PHOENIX: { - AbilityId.ATTACK_ATTACK, AbilityId.GRAVITONBEAM_GRAVITONBEAM, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, - AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.GRAVITONBEAM_GRAVITONBEAM, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.PHOTONCANNON: {AbilityId.ATTACK_ATTACK, AbilityId.SMART, AbilityId.STOP_STOP}, UnitTypeId.PLANETARYFORTRESS: { - AbilityId.ATTACK_ATTACK, AbilityId.COMMANDCENTERTRAIN_SCV, AbilityId.LOADALL_COMMANDCENTER, - AbilityId.RALLY_COMMANDCENTER, AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.COMMANDCENTERTRAIN_SCV, + AbilityId.LOADALL_COMMANDCENTER, + AbilityId.RALLY_COMMANDCENTER, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.PROBE: { - AbilityId.ATTACK_ATTACK, AbilityId.BUILD_SHIELDBATTERY, AbilityId.EFFECT_SPRAY_PROTOSS, - AbilityId.HARVEST_GATHER_PROBE, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, - AbilityId.PROTOSSBUILD_ASSIMILATOR, AbilityId.PROTOSSBUILD_CYBERNETICSCORE, AbilityId.PROTOSSBUILD_DARKSHRINE, - AbilityId.PROTOSSBUILD_FLEETBEACON, AbilityId.PROTOSSBUILD_FORGE, AbilityId.PROTOSSBUILD_GATEWAY, - AbilityId.PROTOSSBUILD_NEXUS, AbilityId.PROTOSSBUILD_PHOTONCANNON, AbilityId.PROTOSSBUILD_PYLON, - AbilityId.PROTOSSBUILD_ROBOTICSBAY, AbilityId.PROTOSSBUILD_ROBOTICSFACILITY, AbilityId.PROTOSSBUILD_STARGATE, - AbilityId.PROTOSSBUILD_TEMPLARARCHIVE, AbilityId.PROTOSSBUILD_TWILIGHTCOUNCIL, AbilityId.SMART, - AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.BUILD_SHIELDBATTERY, + AbilityId.EFFECT_SPRAY_PROTOSS, + AbilityId.HARVEST_GATHER_PROBE, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.PROTOSSBUILD_ASSIMILATOR, + AbilityId.PROTOSSBUILD_CYBERNETICSCORE, + AbilityId.PROTOSSBUILD_DARKSHRINE, + AbilityId.PROTOSSBUILD_FLEETBEACON, + AbilityId.PROTOSSBUILD_FORGE, + AbilityId.PROTOSSBUILD_GATEWAY, + AbilityId.PROTOSSBUILD_NEXUS, + AbilityId.PROTOSSBUILD_PHOTONCANNON, + AbilityId.PROTOSSBUILD_PYLON, + AbilityId.PROTOSSBUILD_ROBOTICSBAY, + AbilityId.PROTOSSBUILD_ROBOTICSFACILITY, + AbilityId.PROTOSSBUILD_STARGATE, + AbilityId.PROTOSSBUILD_TEMPLARARCHIVE, + AbilityId.PROTOSSBUILD_TWILIGHTCOUNCIL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.QUEEN: { - AbilityId.ATTACK_ATTACK, AbilityId.BUILD_CREEPTUMOR, AbilityId.BUILD_CREEPTUMOR_QUEEN, - AbilityId.BURROWDOWN_QUEEN, AbilityId.EFFECT_INJECTLARVA, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, - AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP, AbilityId.TRANSFUSION_TRANSFUSION + AbilityId.ATTACK_ATTACK, + AbilityId.BUILD_CREEPTUMOR, + AbilityId.BUILD_CREEPTUMOR_QUEEN, + AbilityId.BURROWDOWN_QUEEN, + AbilityId.EFFECT_INJECTLARVA, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, + AbilityId.TRANSFUSION_TRANSFUSION, }, UnitTypeId.QUEENBURROWED: {AbilityId.BURROWUP_QUEEN}, UnitTypeId.QUEENMP: { - AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, - AbilityId.QUEENMPENSNARE_QUEENMPENSNARE, AbilityId.QUEENMPINFESTCOMMANDCENTER_QUEENMPINFESTCOMMANDCENTER, - AbilityId.QUEENMPSPAWNBROODLINGS_QUEENMPSPAWNBROODLINGS, AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.QUEENMPENSNARE_QUEENMPENSNARE, + AbilityId.QUEENMPINFESTCOMMANDCENTER_QUEENMPINFESTCOMMANDCENTER, + AbilityId.QUEENMPSPAWNBROODLINGS_QUEENMPSPAWNBROODLINGS, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.RAVAGER: { - AbilityId.ATTACK_ATTACK, AbilityId.BURROWDOWN_RAVAGER, AbilityId.EFFECT_CORROSIVEBILE, - AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.BURROWDOWN_RAVAGER, + AbilityId.EFFECT_CORROSIVEBILE, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.RAVAGERBURROWED: {AbilityId.BURROWUP_RAVAGER}, UnitTypeId.RAVAGERCOCOON: {AbilityId.RALLY_BUILDING, AbilityId.SMART}, UnitTypeId.RAVEN: { - AbilityId.BUILDAUTOTURRET_AUTOTURRET, AbilityId.EFFECT_ANTIARMORMISSILE, AbilityId.EFFECT_INTERFERENCEMATRIX, - AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SCAN_MOVE, AbilityId.SMART, - AbilityId.STOP_STOP + AbilityId.BUILDAUTOTURRET_AUTOTURRET, + AbilityId.EFFECT_ANTIARMORMISSILE, + AbilityId.EFFECT_INTERFERENCEMATRIX, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SCAN_MOVE, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.RAVENREPAIRDRONE: {AbilityId.EFFECT_REPAIR_REPAIRDRONE, AbilityId.SMART, AbilityId.STOP_STOP}, UnitTypeId.REAPER: { - AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.KD8CHARGE_KD8CHARGE, AbilityId.MOVE_MOVE, - AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.KD8CHARGE_KD8CHARGE, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.REPLICANT: { - AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, - AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.ROACH: { - AbilityId.ATTACK_ATTACK, AbilityId.BURROWDOWN_ROACH, AbilityId.HOLDPOSITION_HOLD, - AbilityId.MORPHTORAVAGER_RAVAGER, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SMART, - AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.BURROWDOWN_ROACH, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MORPHTORAVAGER_RAVAGER, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.ROACHBURROWED: { - AbilityId.BURROWUP_ROACH, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, - AbilityId.SCAN_MOVE, AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.BURROWUP_ROACH, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SCAN_MOVE, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.ROACHWARREN: {AbilityId.RESEARCH_GLIALREGENERATION, AbilityId.RESEARCH_TUNNELINGCLAWS}, UnitTypeId.ROBOTICSBAY: { - AbilityId.RESEARCH_EXTENDEDTHERMALLANCE, AbilityId.RESEARCH_GRAVITICBOOSTER, AbilityId.RESEARCH_GRAVITICDRIVE + AbilityId.RESEARCH_EXTENDEDTHERMALLANCE, + AbilityId.RESEARCH_GRAVITICBOOSTER, + AbilityId.RESEARCH_GRAVITICDRIVE, }, UnitTypeId.ROBOTICSFACILITY: { - AbilityId.RALLY_BUILDING, AbilityId.ROBOTICSFACILITYTRAIN_COLOSSUS, AbilityId.ROBOTICSFACILITYTRAIN_IMMORTAL, - AbilityId.ROBOTICSFACILITYTRAIN_OBSERVER, AbilityId.ROBOTICSFACILITYTRAIN_WARPPRISM, AbilityId.SMART, - AbilityId.TRAIN_DISRUPTOR + AbilityId.RALLY_BUILDING, + AbilityId.ROBOTICSFACILITYTRAIN_COLOSSUS, + AbilityId.ROBOTICSFACILITYTRAIN_IMMORTAL, + AbilityId.ROBOTICSFACILITYTRAIN_OBSERVER, + AbilityId.ROBOTICSFACILITYTRAIN_WARPPRISM, + AbilityId.SMART, + AbilityId.TRAIN_DISRUPTOR, }, UnitTypeId.SCOURGEMP: { - AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, - AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.SCOUTMP: { - AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, - AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.SCV: { - AbilityId.ATTACK_ATTACK, AbilityId.EFFECT_REPAIR_SCV, AbilityId.EFFECT_SPRAY_TERRAN, - AbilityId.HARVEST_GATHER_SCV, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, - AbilityId.SMART, AbilityId.STOP_STOP, AbilityId.TERRANBUILD_ARMORY, AbilityId.TERRANBUILD_BARRACKS, - AbilityId.TERRANBUILD_BUNKER, AbilityId.TERRANBUILD_COMMANDCENTER, AbilityId.TERRANBUILD_ENGINEERINGBAY, - AbilityId.TERRANBUILD_FACTORY, AbilityId.TERRANBUILD_FUSIONCORE, AbilityId.TERRANBUILD_GHOSTACADEMY, - AbilityId.TERRANBUILD_MISSILETURRET, AbilityId.TERRANBUILD_REFINERY, AbilityId.TERRANBUILD_SENSORTOWER, - AbilityId.TERRANBUILD_STARPORT, AbilityId.TERRANBUILD_SUPPLYDEPOT + AbilityId.ATTACK_ATTACK, + AbilityId.EFFECT_REPAIR_SCV, + AbilityId.EFFECT_SPRAY_TERRAN, + AbilityId.HARVEST_GATHER_SCV, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, + AbilityId.TERRANBUILD_ARMORY, + AbilityId.TERRANBUILD_BARRACKS, + AbilityId.TERRANBUILD_BUNKER, + AbilityId.TERRANBUILD_COMMANDCENTER, + AbilityId.TERRANBUILD_ENGINEERINGBAY, + AbilityId.TERRANBUILD_FACTORY, + AbilityId.TERRANBUILD_FUSIONCORE, + AbilityId.TERRANBUILD_GHOSTACADEMY, + AbilityId.TERRANBUILD_MISSILETURRET, + AbilityId.TERRANBUILD_REFINERY, + AbilityId.TERRANBUILD_SENSORTOWER, + AbilityId.TERRANBUILD_STARPORT, + AbilityId.TERRANBUILD_SUPPLYDEPOT, }, UnitTypeId.SENTRY: { - AbilityId.ATTACK_ATTACK, AbilityId.FORCEFIELD_FORCEFIELD, AbilityId.GUARDIANSHIELD_GUARDIANSHIELD, - AbilityId.HALLUCINATION_ADEPT, AbilityId.HALLUCINATION_ARCHON, AbilityId.HALLUCINATION_COLOSSUS, - AbilityId.HALLUCINATION_DISRUPTOR, AbilityId.HALLUCINATION_HIGHTEMPLAR, AbilityId.HALLUCINATION_IMMORTAL, - AbilityId.HALLUCINATION_ORACLE, AbilityId.HALLUCINATION_PHOENIX, AbilityId.HALLUCINATION_PROBE, - AbilityId.HALLUCINATION_STALKER, AbilityId.HALLUCINATION_VOIDRAY, AbilityId.HALLUCINATION_WARPPRISM, - AbilityId.HALLUCINATION_ZEALOT, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, - AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.FORCEFIELD_FORCEFIELD, + AbilityId.GUARDIANSHIELD_GUARDIANSHIELD, + AbilityId.HALLUCINATION_ADEPT, + AbilityId.HALLUCINATION_ARCHON, + AbilityId.HALLUCINATION_COLOSSUS, + AbilityId.HALLUCINATION_DISRUPTOR, + AbilityId.HALLUCINATION_HIGHTEMPLAR, + AbilityId.HALLUCINATION_IMMORTAL, + AbilityId.HALLUCINATION_ORACLE, + AbilityId.HALLUCINATION_PHOENIX, + AbilityId.HALLUCINATION_PROBE, + AbilityId.HALLUCINATION_STALKER, + AbilityId.HALLUCINATION_VOIDRAY, + AbilityId.HALLUCINATION_WARPPRISM, + AbilityId.HALLUCINATION_ZEALOT, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.SHIELDBATTERY: {AbilityId.SHIELDBATTERYRECHARGEEX5_SHIELDBATTERYRECHARGE, AbilityId.SMART}, UnitTypeId.SIEGETANK: { - AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, - AbilityId.SIEGEMODE_SIEGEMODE, AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SIEGEMODE_SIEGEMODE, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.SIEGETANKSIEGED: { - AbilityId.ATTACK_ATTACK, AbilityId.SMART, AbilityId.STOP_STOP, AbilityId.UNSIEGE_UNSIEGE + AbilityId.ATTACK_ATTACK, + AbilityId.SMART, + AbilityId.STOP_STOP, + AbilityId.UNSIEGE_UNSIEGE, }, UnitTypeId.SPAWNINGPOOL: {AbilityId.RESEARCH_ZERGLINGADRENALGLANDS, AbilityId.RESEARCH_ZERGLINGMETABOLICBOOST}, UnitTypeId.SPINECRAWLER: { - AbilityId.ATTACK_ATTACK, AbilityId.SMART, AbilityId.SPINECRAWLERUPROOT_SPINECRAWLERUPROOT, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.SMART, + AbilityId.SPINECRAWLERUPROOT_SPINECRAWLERUPROOT, + AbilityId.STOP_STOP, }, UnitTypeId.SPINECRAWLERUPROOTED: { - AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SCAN_MOVE, AbilityId.SMART, - AbilityId.SPINECRAWLERROOT_SPINECRAWLERROOT, AbilityId.STOP_STOP + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SCAN_MOVE, + AbilityId.SMART, + AbilityId.SPINECRAWLERROOT_SPINECRAWLERROOT, + AbilityId.STOP_STOP, }, UnitTypeId.SPIRE: { - AbilityId.RESEARCH_ZERGFLYERARMORLEVEL1, AbilityId.RESEARCH_ZERGFLYERARMORLEVEL2, - AbilityId.RESEARCH_ZERGFLYERARMORLEVEL3, AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL1, - AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL2, AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL3, - AbilityId.UPGRADETOGREATERSPIRE_GREATERSPIRE + AbilityId.RESEARCH_ZERGFLYERARMORLEVEL1, + AbilityId.RESEARCH_ZERGFLYERARMORLEVEL2, + AbilityId.RESEARCH_ZERGFLYERARMORLEVEL3, + AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL1, + AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL2, + AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL3, + AbilityId.UPGRADETOGREATERSPIRE_GREATERSPIRE, }, UnitTypeId.SPORECRAWLER: { - AbilityId.ATTACK_ATTACK, AbilityId.SMART, AbilityId.SPORECRAWLERUPROOT_SPORECRAWLERUPROOT, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.SMART, + AbilityId.SPORECRAWLERUPROOT_SPORECRAWLERUPROOT, + AbilityId.STOP_STOP, }, UnitTypeId.SPORECRAWLERUPROOTED: { - AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SCAN_MOVE, AbilityId.SMART, - AbilityId.SPORECRAWLERROOT_SPORECRAWLERROOT, AbilityId.STOP_STOP + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SCAN_MOVE, + AbilityId.SMART, + AbilityId.SPORECRAWLERROOT_SPORECRAWLERROOT, + AbilityId.STOP_STOP, }, UnitTypeId.STALKER: { - AbilityId.ATTACK_ATTACK, AbilityId.EFFECT_BLINK_STALKER, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, - AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.EFFECT_BLINK_STALKER, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.STARGATE: { - AbilityId.RALLY_BUILDING, AbilityId.SMART, AbilityId.STARGATETRAIN_CARRIER, AbilityId.STARGATETRAIN_ORACLE, - AbilityId.STARGATETRAIN_PHOENIX, AbilityId.STARGATETRAIN_TEMPEST, AbilityId.STARGATETRAIN_VOIDRAY + AbilityId.RALLY_BUILDING, + AbilityId.SMART, + AbilityId.STARGATETRAIN_CARRIER, + AbilityId.STARGATETRAIN_ORACLE, + AbilityId.STARGATETRAIN_PHOENIX, + AbilityId.STARGATETRAIN_TEMPEST, + AbilityId.STARGATETRAIN_VOIDRAY, }, UnitTypeId.STARPORT: { - AbilityId.BUILD_REACTOR_STARPORT, AbilityId.BUILD_TECHLAB_STARPORT, AbilityId.LIFT_STARPORT, - AbilityId.RALLY_BUILDING, AbilityId.SMART, AbilityId.STARPORTTRAIN_BANSHEE, - AbilityId.STARPORTTRAIN_BATTLECRUISER, AbilityId.STARPORTTRAIN_LIBERATOR, AbilityId.STARPORTTRAIN_MEDIVAC, - AbilityId.STARPORTTRAIN_RAVEN, AbilityId.STARPORTTRAIN_VIKINGFIGHTER + AbilityId.BUILD_REACTOR_STARPORT, + AbilityId.BUILD_TECHLAB_STARPORT, + AbilityId.LIFT_STARPORT, + AbilityId.RALLY_BUILDING, + AbilityId.SMART, + AbilityId.STARPORTTRAIN_BANSHEE, + AbilityId.STARPORTTRAIN_BATTLECRUISER, + AbilityId.STARPORTTRAIN_LIBERATOR, + AbilityId.STARPORTTRAIN_MEDIVAC, + AbilityId.STARPORTTRAIN_RAVEN, + AbilityId.STARPORTTRAIN_VIKINGFIGHTER, }, UnitTypeId.STARPORTFLYING: { - AbilityId.BUILD_REACTOR_STARPORT, AbilityId.BUILD_TECHLAB_STARPORT, AbilityId.HOLDPOSITION_HOLD, - AbilityId.LAND_STARPORT, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.BUILD_REACTOR_STARPORT, + AbilityId.BUILD_TECHLAB_STARPORT, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.LAND_STARPORT, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.STARPORTTECHLAB: { - AbilityId.RESEARCH_BANSHEECLOAKINGFIELD, AbilityId.RESEARCH_BANSHEEHYPERFLIGHTROTORS, - AbilityId.STARPORTTECHLABRESEARCH_RESEARCHRAVENINTERFERENCEMATRIX + AbilityId.RESEARCH_BANSHEECLOAKINGFIELD, + AbilityId.RESEARCH_BANSHEEHYPERFLIGHTROTORS, + AbilityId.STARPORTTECHLABRESEARCH_RESEARCHRAVENINTERFERENCEMATRIX, }, UnitTypeId.SUPPLYDEPOT: {AbilityId.MORPH_SUPPLYDEPOT_LOWER}, UnitTypeId.SUPPLYDEPOTLOWERED: {AbilityId.MORPH_SUPPLYDEPOT_RAISE}, UnitTypeId.SWARMHOSTBURROWEDMP: {AbilityId.EFFECT_SPAWNLOCUSTS, AbilityId.SMART}, UnitTypeId.SWARMHOSTMP: { - AbilityId.BURROWDOWN_SWARMHOST, AbilityId.EFFECT_SPAWNLOCUSTS, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, - AbilityId.PATROL_PATROL, AbilityId.SCAN_MOVE, AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.BURROWDOWN_SWARMHOST, + AbilityId.EFFECT_SPAWNLOCUSTS, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SCAN_MOVE, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.TECHLAB: { - AbilityId.BARRACKSTECHLABRESEARCH_STIMPACK, AbilityId.RESEARCH_BANSHEECLOAKINGFIELD, - AbilityId.RESEARCH_COMBATSHIELD, AbilityId.RESEARCH_CONCUSSIVESHELLS, AbilityId.RESEARCH_DRILLINGCLAWS, - AbilityId.RESEARCH_INFERNALPREIGNITER, AbilityId.RESEARCH_RAVENCORVIDREACTOR + AbilityId.BARRACKSTECHLABRESEARCH_STIMPACK, + AbilityId.RESEARCH_BANSHEECLOAKINGFIELD, + AbilityId.RESEARCH_COMBATSHIELD, + AbilityId.RESEARCH_CONCUSSIVESHELLS, + AbilityId.RESEARCH_DRILLINGCLAWS, + AbilityId.RESEARCH_INFERNALPREIGNITER, + AbilityId.RESEARCH_RAVENCORVIDREACTOR, }, UnitTypeId.TEMPEST: { - AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, - AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.TEMPLARARCHIVE: {AbilityId.RESEARCH_PSISTORM}, UnitTypeId.THOR: { - AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MORPH_THORHIGHIMPACTMODE, AbilityId.MOVE_MOVE, - AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MORPH_THORHIGHIMPACTMODE, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.THORAP: { - AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MORPH_THOREXPLOSIVEMODE, AbilityId.MOVE_MOVE, - AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MORPH_THOREXPLOSIVEMODE, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.TWILIGHTCOUNCIL: { - AbilityId.RESEARCH_ADEPTRESONATINGGLAIVES, AbilityId.RESEARCH_BLINK, AbilityId.RESEARCH_CHARGE + AbilityId.RESEARCH_ADEPTRESONATINGGLAIVES, + AbilityId.RESEARCH_BLINK, + AbilityId.RESEARCH_CHARGE, }, UnitTypeId.ULTRALISK: { - AbilityId.ATTACK_ATTACK, AbilityId.BURROWDOWN_ULTRALISK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, - AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.BURROWDOWN_ULTRALISK, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.ULTRALISKBURROWED: {AbilityId.BURROWUP_ULTRALISK}, UnitTypeId.ULTRALISKCAVERN: {AbilityId.RESEARCH_ANABOLICSYNTHESIS, AbilityId.RESEARCH_CHITINOUSPLATING}, UnitTypeId.VIKINGASSAULT: { - AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MORPH_VIKINGFIGHTERMODE, AbilityId.MOVE_MOVE, - AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MORPH_VIKINGFIGHTERMODE, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.VIKINGFIGHTER: { - AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MORPH_VIKINGASSAULTMODE, AbilityId.MOVE_MOVE, - AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MORPH_VIKINGASSAULTMODE, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.VIPER: { - AbilityId.BLINDINGCLOUD_BLINDINGCLOUD, AbilityId.EFFECT_ABDUCT, AbilityId.HOLDPOSITION_HOLD, - AbilityId.MOVE_MOVE, AbilityId.PARASITICBOMB_PARASITICBOMB, AbilityId.PATROL_PATROL, AbilityId.SCAN_MOVE, - AbilityId.SMART, AbilityId.STOP_STOP, AbilityId.VIPERCONSUMESTRUCTURE_VIPERCONSUME + AbilityId.BLINDINGCLOUD_BLINDINGCLOUD, + AbilityId.EFFECT_ABDUCT, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PARASITICBOMB_PARASITICBOMB, + AbilityId.PATROL_PATROL, + AbilityId.SCAN_MOVE, + AbilityId.SMART, + AbilityId.STOP_STOP, + AbilityId.VIPERCONSUMESTRUCTURE_VIPERCONSUME, }, UnitTypeId.VOIDMPIMMORTALREVIVECORPSE: { - AbilityId.RALLY_BUILDING, AbilityId.SMART, AbilityId.VOIDMPIMMORTALREVIVEREBUILD_IMMORTAL + AbilityId.RALLY_BUILDING, + AbilityId.SMART, + AbilityId.VOIDMPIMMORTALREVIVEREBUILD_IMMORTAL, }, UnitTypeId.VOIDRAY: { - AbilityId.ATTACK_ATTACK, AbilityId.EFFECT_VOIDRAYPRISMATICALIGNMENT, AbilityId.HOLDPOSITION_HOLD, - AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.EFFECT_VOIDRAYPRISMATICALIGNMENT, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.WARHOUND: { - AbilityId.ATTACK_ATTACK, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, - AbilityId.SMART, AbilityId.STOP_STOP, AbilityId.TORNADOMISSILE_TORNADOMISSILE + AbilityId.ATTACK_ATTACK, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, + AbilityId.TORNADOMISSILE_TORNADOMISSILE, }, UnitTypeId.WARPGATE: { - AbilityId.MORPH_GATEWAY, AbilityId.SMART, AbilityId.TRAINWARP_ADEPT, AbilityId.WARPGATETRAIN_DARKTEMPLAR, - AbilityId.WARPGATETRAIN_HIGHTEMPLAR, AbilityId.WARPGATETRAIN_SENTRY, AbilityId.WARPGATETRAIN_STALKER, - AbilityId.WARPGATETRAIN_ZEALOT + AbilityId.MORPH_GATEWAY, + AbilityId.SMART, + AbilityId.TRAINWARP_ADEPT, + AbilityId.WARPGATETRAIN_DARKTEMPLAR, + AbilityId.WARPGATETRAIN_HIGHTEMPLAR, + AbilityId.WARPGATETRAIN_SENTRY, + AbilityId.WARPGATETRAIN_STALKER, + AbilityId.WARPGATETRAIN_ZEALOT, }, UnitTypeId.WARPPRISM: { - AbilityId.HOLDPOSITION_HOLD, AbilityId.LOAD_WARPPRISM, AbilityId.MORPH_WARPPRISMPHASINGMODE, - AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SCAN_MOVE, AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.HOLDPOSITION_HOLD, + AbilityId.LOAD_WARPPRISM, + AbilityId.MORPH_WARPPRISMPHASINGMODE, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SCAN_MOVE, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.WARPPRISMPHASING: { - AbilityId.LOAD_WARPPRISM, AbilityId.MORPH_WARPPRISMTRANSPORTMODE, AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.LOAD_WARPPRISM, + AbilityId.MORPH_WARPPRISMTRANSPORTMODE, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.WIDOWMINE: { - AbilityId.BURROWDOWN_WIDOWMINE, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, - AbilityId.SCAN_MOVE, AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.BURROWDOWN_WIDOWMINE, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SCAN_MOVE, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.WIDOWMINEBURROWED: { - AbilityId.BURROWUP_WIDOWMINE, AbilityId.SMART, AbilityId.WIDOWMINEATTACK_WIDOWMINEATTACK + AbilityId.BURROWUP_WIDOWMINE, + AbilityId.SMART, + AbilityId.WIDOWMINEATTACK_WIDOWMINEATTACK, }, UnitTypeId.ZEALOT: { - AbilityId.ATTACK_ATTACK, AbilityId.EFFECT_CHARGE, AbilityId.HOLDPOSITION_HOLD, AbilityId.MOVE_MOVE, - AbilityId.PATROL_PATROL, AbilityId.SMART, AbilityId.STOP_STOP + AbilityId.ATTACK_ATTACK, + AbilityId.EFFECT_CHARGE, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, }, UnitTypeId.ZERGLING: { - AbilityId.ATTACK_ATTACK, AbilityId.BURROWDOWN_ZERGLING, AbilityId.HOLDPOSITION_HOLD, - AbilityId.MORPHTOBANELING_BANELING, AbilityId.MOVE_MOVE, AbilityId.PATROL_PATROL, AbilityId.SMART, - AbilityId.STOP_STOP - }, - UnitTypeId.ZERGLINGBURROWED: {AbilityId.BURROWUP_ZERGLING} + AbilityId.ATTACK_ATTACK, + AbilityId.BURROWDOWN_ZERGLING, + AbilityId.HOLDPOSITION_HOLD, + AbilityId.MORPHTOBANELING_BANELING, + AbilityId.MOVE_MOVE, + AbilityId.PATROL_PATROL, + AbilityId.SMART, + AbilityId.STOP_STOP, + }, + UnitTypeId.ZERGLINGBURROWED: {AbilityId.BURROWUP_ZERGLING}, } diff --git a/sc2/dicts/unit_research_abilities.py b/sc2/dicts/unit_research_abilities.py index 5fad7b91..e9e905be 100644 --- a/sc2/dicts/unit_research_abilities.py +++ b/sc2/dicts/unit_research_abilities.py @@ -1,456 +1,340 @@ # THIS FILE WAS AUTOMATICALLY GENERATED BY "generate_dicts_from_data_json.py" DO NOT CHANGE MANUALLY! # ANY CHANGE WILL BE OVERWRITTEN -from typing import Dict, Union - -from sc2.ids.ability_id import AbilityId from sc2.ids.unit_typeid import UnitTypeId +from sc2.ids.ability_id import AbilityId from sc2.ids.upgrade_id import UpgradeId - # from sc2.ids.buff_id import BuffId # from sc2.ids.effect_id import EffectId -RESEARCH_INFO: Dict[UnitTypeId, Dict[UpgradeId, Dict[str, Union[AbilityId, bool, UnitTypeId, UpgradeId]]]] = { +from typing import Union + +RESEARCH_INFO: dict[UnitTypeId, dict[UpgradeId, dict[str, Union[AbilityId, bool, UnitTypeId, UpgradeId]]]] = { UnitTypeId.ARMORY: { - UpgradeId.TERRANSHIPWEAPONSLEVEL1: { - 'ability': AbilityId.ARMORYRESEARCH_TERRANSHIPWEAPONSLEVEL1 - }, + UpgradeId.TERRANSHIPWEAPONSLEVEL1: {"ability": AbilityId.ARMORYRESEARCH_TERRANSHIPWEAPONSLEVEL1}, UpgradeId.TERRANSHIPWEAPONSLEVEL2: { - 'ability': AbilityId.ARMORYRESEARCH_TERRANSHIPWEAPONSLEVEL2, - 'required_upgrade': UpgradeId.TERRANSHIPWEAPONSLEVEL1 + "ability": AbilityId.ARMORYRESEARCH_TERRANSHIPWEAPONSLEVEL2, + "required_upgrade": UpgradeId.TERRANSHIPWEAPONSLEVEL1, }, UpgradeId.TERRANSHIPWEAPONSLEVEL3: { - 'ability': AbilityId.ARMORYRESEARCH_TERRANSHIPWEAPONSLEVEL3, - 'required_upgrade': UpgradeId.TERRANSHIPWEAPONSLEVEL2 + "ability": AbilityId.ARMORYRESEARCH_TERRANSHIPWEAPONSLEVEL3, + "required_upgrade": UpgradeId.TERRANSHIPWEAPONSLEVEL2, }, UpgradeId.TERRANVEHICLEANDSHIPARMORSLEVEL1: { - 'ability': AbilityId.ARMORYRESEARCH_TERRANVEHICLEANDSHIPPLATINGLEVEL1 + "ability": AbilityId.ARMORYRESEARCH_TERRANVEHICLEANDSHIPPLATINGLEVEL1 }, UpgradeId.TERRANVEHICLEANDSHIPARMORSLEVEL2: { - 'ability': AbilityId.ARMORYRESEARCH_TERRANVEHICLEANDSHIPPLATINGLEVEL2, - 'required_upgrade': UpgradeId.TERRANVEHICLEANDSHIPARMORSLEVEL1 + "ability": AbilityId.ARMORYRESEARCH_TERRANVEHICLEANDSHIPPLATINGLEVEL2, + "required_upgrade": UpgradeId.TERRANVEHICLEANDSHIPARMORSLEVEL1, }, UpgradeId.TERRANVEHICLEANDSHIPARMORSLEVEL3: { - 'ability': AbilityId.ARMORYRESEARCH_TERRANVEHICLEANDSHIPPLATINGLEVEL3, - 'required_upgrade': UpgradeId.TERRANVEHICLEANDSHIPARMORSLEVEL2 - }, - UpgradeId.TERRANVEHICLEWEAPONSLEVEL1: { - 'ability': AbilityId.ARMORYRESEARCH_TERRANVEHICLEWEAPONSLEVEL1 + "ability": AbilityId.ARMORYRESEARCH_TERRANVEHICLEANDSHIPPLATINGLEVEL3, + "required_upgrade": UpgradeId.TERRANVEHICLEANDSHIPARMORSLEVEL2, }, + UpgradeId.TERRANVEHICLEWEAPONSLEVEL1: {"ability": AbilityId.ARMORYRESEARCH_TERRANVEHICLEWEAPONSLEVEL1}, UpgradeId.TERRANVEHICLEWEAPONSLEVEL2: { - 'ability': AbilityId.ARMORYRESEARCH_TERRANVEHICLEWEAPONSLEVEL2, - 'required_upgrade': UpgradeId.TERRANVEHICLEWEAPONSLEVEL1 + "ability": AbilityId.ARMORYRESEARCH_TERRANVEHICLEWEAPONSLEVEL2, + "required_upgrade": UpgradeId.TERRANVEHICLEWEAPONSLEVEL1, }, UpgradeId.TERRANVEHICLEWEAPONSLEVEL3: { - 'ability': AbilityId.ARMORYRESEARCH_TERRANVEHICLEWEAPONSLEVEL3, - 'required_upgrade': UpgradeId.TERRANVEHICLEWEAPONSLEVEL2 - } + "ability": AbilityId.ARMORYRESEARCH_TERRANVEHICLEWEAPONSLEVEL3, + "required_upgrade": UpgradeId.TERRANVEHICLEWEAPONSLEVEL2, + }, }, UnitTypeId.BANELINGNEST: { UpgradeId.CENTRIFICALHOOKS: { - 'ability': AbilityId.RESEARCH_CENTRIFUGALHOOKS, - 'required_building': UnitTypeId.LAIR + "ability": AbilityId.RESEARCH_CENTRIFUGALHOOKS, + "required_building": UnitTypeId.LAIR, } }, UnitTypeId.BARRACKSTECHLAB: { - UpgradeId.PUNISHERGRENADES: { - 'ability': AbilityId.RESEARCH_CONCUSSIVESHELLS - }, - UpgradeId.SHIELDWALL: { - 'ability': AbilityId.RESEARCH_COMBATSHIELD - }, - UpgradeId.STIMPACK: { - 'ability': AbilityId.BARRACKSTECHLABRESEARCH_STIMPACK - } + UpgradeId.PUNISHERGRENADES: {"ability": AbilityId.RESEARCH_CONCUSSIVESHELLS}, + UpgradeId.SHIELDWALL: {"ability": AbilityId.RESEARCH_COMBATSHIELD}, + UpgradeId.STIMPACK: {"ability": AbilityId.BARRACKSTECHLABRESEARCH_STIMPACK}, }, UnitTypeId.CYBERNETICSCORE: { UpgradeId.PROTOSSAIRARMORSLEVEL1: { - 'ability': AbilityId.CYBERNETICSCORERESEARCH_PROTOSSAIRARMORLEVEL1, - 'requires_power': True + "ability": AbilityId.CYBERNETICSCORERESEARCH_PROTOSSAIRARMORLEVEL1, + "requires_power": True, }, UpgradeId.PROTOSSAIRARMORSLEVEL2: { - 'ability': AbilityId.CYBERNETICSCORERESEARCH_PROTOSSAIRARMORLEVEL2, - 'required_building': UnitTypeId.FLEETBEACON, - 'required_upgrade': UpgradeId.PROTOSSAIRARMORSLEVEL1, - 'requires_power': True + "ability": AbilityId.CYBERNETICSCORERESEARCH_PROTOSSAIRARMORLEVEL2, + "required_building": UnitTypeId.FLEETBEACON, + "required_upgrade": UpgradeId.PROTOSSAIRARMORSLEVEL1, + "requires_power": True, }, UpgradeId.PROTOSSAIRARMORSLEVEL3: { - 'ability': AbilityId.CYBERNETICSCORERESEARCH_PROTOSSAIRARMORLEVEL3, - 'required_building': UnitTypeId.FLEETBEACON, - 'required_upgrade': UpgradeId.PROTOSSAIRARMORSLEVEL2, - 'requires_power': True + "ability": AbilityId.CYBERNETICSCORERESEARCH_PROTOSSAIRARMORLEVEL3, + "required_building": UnitTypeId.FLEETBEACON, + "required_upgrade": UpgradeId.PROTOSSAIRARMORSLEVEL2, + "requires_power": True, }, UpgradeId.PROTOSSAIRWEAPONSLEVEL1: { - 'ability': AbilityId.CYBERNETICSCORERESEARCH_PROTOSSAIRWEAPONSLEVEL1, - 'requires_power': True + "ability": AbilityId.CYBERNETICSCORERESEARCH_PROTOSSAIRWEAPONSLEVEL1, + "requires_power": True, }, UpgradeId.PROTOSSAIRWEAPONSLEVEL2: { - 'ability': AbilityId.CYBERNETICSCORERESEARCH_PROTOSSAIRWEAPONSLEVEL2, - 'required_building': UnitTypeId.FLEETBEACON, - 'required_upgrade': UpgradeId.PROTOSSAIRWEAPONSLEVEL1, - 'requires_power': True + "ability": AbilityId.CYBERNETICSCORERESEARCH_PROTOSSAIRWEAPONSLEVEL2, + "required_building": UnitTypeId.FLEETBEACON, + "required_upgrade": UpgradeId.PROTOSSAIRWEAPONSLEVEL1, + "requires_power": True, }, UpgradeId.PROTOSSAIRWEAPONSLEVEL3: { - 'ability': AbilityId.CYBERNETICSCORERESEARCH_PROTOSSAIRWEAPONSLEVEL3, - 'required_building': UnitTypeId.FLEETBEACON, - 'required_upgrade': UpgradeId.PROTOSSAIRWEAPONSLEVEL2, - 'requires_power': True - }, - UpgradeId.WARPGATERESEARCH: { - 'ability': AbilityId.RESEARCH_WARPGATE, - 'requires_power': True - } + "ability": AbilityId.CYBERNETICSCORERESEARCH_PROTOSSAIRWEAPONSLEVEL3, + "required_building": UnitTypeId.FLEETBEACON, + "required_upgrade": UpgradeId.PROTOSSAIRWEAPONSLEVEL2, + "requires_power": True, + }, + UpgradeId.WARPGATERESEARCH: {"ability": AbilityId.RESEARCH_WARPGATE, "requires_power": True}, }, UnitTypeId.DARKSHRINE: { - UpgradeId.DARKTEMPLARBLINKUPGRADE: { - 'ability': AbilityId.RESEARCH_SHADOWSTRIKE, - 'requires_power': True - } + UpgradeId.DARKTEMPLARBLINKUPGRADE: {"ability": AbilityId.RESEARCH_SHADOWSTRIKE, "requires_power": True} }, UnitTypeId.ENGINEERINGBAY: { - UpgradeId.HISECAUTOTRACKING: { - 'ability': AbilityId.RESEARCH_HISECAUTOTRACKING - }, - UpgradeId.TERRANBUILDINGARMOR: { - 'ability': AbilityId.RESEARCH_TERRANSTRUCTUREARMORUPGRADE - }, - UpgradeId.TERRANINFANTRYARMORSLEVEL1: { - 'ability': AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYARMORLEVEL1 - }, + UpgradeId.HISECAUTOTRACKING: {"ability": AbilityId.RESEARCH_HISECAUTOTRACKING}, + UpgradeId.TERRANBUILDINGARMOR: {"ability": AbilityId.RESEARCH_TERRANSTRUCTUREARMORUPGRADE}, + UpgradeId.TERRANINFANTRYARMORSLEVEL1: {"ability": AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYARMORLEVEL1}, UpgradeId.TERRANINFANTRYARMORSLEVEL2: { - 'ability': AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYARMORLEVEL2, - 'required_building': UnitTypeId.ARMORY, - 'required_upgrade': UpgradeId.TERRANINFANTRYARMORSLEVEL1 + "ability": AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYARMORLEVEL2, + "required_building": UnitTypeId.ARMORY, + "required_upgrade": UpgradeId.TERRANINFANTRYARMORSLEVEL1, }, UpgradeId.TERRANINFANTRYARMORSLEVEL3: { - 'ability': AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYARMORLEVEL3, - 'required_building': UnitTypeId.ARMORY, - 'required_upgrade': UpgradeId.TERRANINFANTRYARMORSLEVEL2 + "ability": AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYARMORLEVEL3, + "required_building": UnitTypeId.ARMORY, + "required_upgrade": UpgradeId.TERRANINFANTRYARMORSLEVEL2, }, UpgradeId.TERRANINFANTRYWEAPONSLEVEL1: { - 'ability': AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYWEAPONSLEVEL1 + "ability": AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYWEAPONSLEVEL1 }, UpgradeId.TERRANINFANTRYWEAPONSLEVEL2: { - 'ability': AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYWEAPONSLEVEL2, - 'required_building': UnitTypeId.ARMORY, - 'required_upgrade': UpgradeId.TERRANINFANTRYWEAPONSLEVEL1 + "ability": AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYWEAPONSLEVEL2, + "required_building": UnitTypeId.ARMORY, + "required_upgrade": UpgradeId.TERRANINFANTRYWEAPONSLEVEL1, }, UpgradeId.TERRANINFANTRYWEAPONSLEVEL3: { - 'ability': AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYWEAPONSLEVEL3, - 'required_building': UnitTypeId.ARMORY, - 'required_upgrade': UpgradeId.TERRANINFANTRYWEAPONSLEVEL2 - } + "ability": AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYWEAPONSLEVEL3, + "required_building": UnitTypeId.ARMORY, + "required_upgrade": UpgradeId.TERRANINFANTRYWEAPONSLEVEL2, + }, }, UnitTypeId.EVOLUTIONCHAMBER: { - UpgradeId.ZERGGROUNDARMORSLEVEL1: { - 'ability': AbilityId.RESEARCH_ZERGGROUNDARMORLEVEL1 - }, + UpgradeId.ZERGGROUNDARMORSLEVEL1: {"ability": AbilityId.RESEARCH_ZERGGROUNDARMORLEVEL1}, UpgradeId.ZERGGROUNDARMORSLEVEL2: { - 'ability': AbilityId.RESEARCH_ZERGGROUNDARMORLEVEL2, - 'required_building': UnitTypeId.LAIR, - 'required_upgrade': UpgradeId.ZERGGROUNDARMORSLEVEL1 + "ability": AbilityId.RESEARCH_ZERGGROUNDARMORLEVEL2, + "required_building": UnitTypeId.LAIR, + "required_upgrade": UpgradeId.ZERGGROUNDARMORSLEVEL1, }, UpgradeId.ZERGGROUNDARMORSLEVEL3: { - 'ability': AbilityId.RESEARCH_ZERGGROUNDARMORLEVEL3, - 'required_building': UnitTypeId.HIVE, - 'required_upgrade': UpgradeId.ZERGGROUNDARMORSLEVEL2 - }, - UpgradeId.ZERGMELEEWEAPONSLEVEL1: { - 'ability': AbilityId.RESEARCH_ZERGMELEEWEAPONSLEVEL1 + "ability": AbilityId.RESEARCH_ZERGGROUNDARMORLEVEL3, + "required_building": UnitTypeId.HIVE, + "required_upgrade": UpgradeId.ZERGGROUNDARMORSLEVEL2, }, + UpgradeId.ZERGMELEEWEAPONSLEVEL1: {"ability": AbilityId.RESEARCH_ZERGMELEEWEAPONSLEVEL1}, UpgradeId.ZERGMELEEWEAPONSLEVEL2: { - 'ability': AbilityId.RESEARCH_ZERGMELEEWEAPONSLEVEL2, - 'required_building': UnitTypeId.LAIR, - 'required_upgrade': UpgradeId.ZERGMELEEWEAPONSLEVEL1 + "ability": AbilityId.RESEARCH_ZERGMELEEWEAPONSLEVEL2, + "required_building": UnitTypeId.LAIR, + "required_upgrade": UpgradeId.ZERGMELEEWEAPONSLEVEL1, }, UpgradeId.ZERGMELEEWEAPONSLEVEL3: { - 'ability': AbilityId.RESEARCH_ZERGMELEEWEAPONSLEVEL3, - 'required_building': UnitTypeId.HIVE, - 'required_upgrade': UpgradeId.ZERGMELEEWEAPONSLEVEL2 - }, - UpgradeId.ZERGMISSILEWEAPONSLEVEL1: { - 'ability': AbilityId.RESEARCH_ZERGMISSILEWEAPONSLEVEL1 + "ability": AbilityId.RESEARCH_ZERGMELEEWEAPONSLEVEL3, + "required_building": UnitTypeId.HIVE, + "required_upgrade": UpgradeId.ZERGMELEEWEAPONSLEVEL2, }, + UpgradeId.ZERGMISSILEWEAPONSLEVEL1: {"ability": AbilityId.RESEARCH_ZERGMISSILEWEAPONSLEVEL1}, UpgradeId.ZERGMISSILEWEAPONSLEVEL2: { - 'ability': AbilityId.RESEARCH_ZERGMISSILEWEAPONSLEVEL2, - 'required_building': UnitTypeId.LAIR, - 'required_upgrade': UpgradeId.ZERGMISSILEWEAPONSLEVEL1 + "ability": AbilityId.RESEARCH_ZERGMISSILEWEAPONSLEVEL2, + "required_building": UnitTypeId.LAIR, + "required_upgrade": UpgradeId.ZERGMISSILEWEAPONSLEVEL1, }, UpgradeId.ZERGMISSILEWEAPONSLEVEL3: { - 'ability': AbilityId.RESEARCH_ZERGMISSILEWEAPONSLEVEL3, - 'required_building': UnitTypeId.HIVE, - 'required_upgrade': UpgradeId.ZERGMISSILEWEAPONSLEVEL2 - } + "ability": AbilityId.RESEARCH_ZERGMISSILEWEAPONSLEVEL3, + "required_building": UnitTypeId.HIVE, + "required_upgrade": UpgradeId.ZERGMISSILEWEAPONSLEVEL2, + }, }, UnitTypeId.FACTORYTECHLAB: { - UpgradeId.DRILLCLAWS: { - 'ability': AbilityId.RESEARCH_DRILLINGCLAWS, - 'required_building': UnitTypeId.ARMORY - }, - UpgradeId.HIGHCAPACITYBARRELS: { - 'ability': AbilityId.RESEARCH_INFERNALPREIGNITER - }, - UpgradeId.HURRICANETHRUSTERS: { - 'ability': AbilityId.FACTORYTECHLABRESEARCH_CYCLONERESEARCHHURRICANETHRUSTERS - }, - UpgradeId.SMARTSERVOS: { - 'ability': AbilityId.RESEARCH_SMARTSERVOS, - 'required_building': UnitTypeId.ARMORY - } + UpgradeId.DRILLCLAWS: {"ability": AbilityId.RESEARCH_DRILLINGCLAWS, "required_building": UnitTypeId.ARMORY}, + UpgradeId.HIGHCAPACITYBARRELS: {"ability": AbilityId.RESEARCH_INFERNALPREIGNITER}, + UpgradeId.HURRICANETHRUSTERS: {"ability": AbilityId.FACTORYTECHLABRESEARCH_CYCLONERESEARCHHURRICANETHRUSTERS}, + UpgradeId.SMARTSERVOS: {"ability": AbilityId.RESEARCH_SMARTSERVOS, "required_building": UnitTypeId.ARMORY}, }, UnitTypeId.FLEETBEACON: { UpgradeId.PHOENIXRANGEUPGRADE: { - 'ability': AbilityId.RESEARCH_PHOENIXANIONPULSECRYSTALS, - 'requires_power': True + "ability": AbilityId.RESEARCH_PHOENIXANIONPULSECRYSTALS, + "requires_power": True, }, UpgradeId.TEMPESTGROUNDATTACKUPGRADE: { - 'ability': AbilityId.FLEETBEACONRESEARCH_TEMPESTRESEARCHGROUNDATTACKUPGRADE, - 'requires_power': True + "ability": AbilityId.FLEETBEACONRESEARCH_TEMPESTRESEARCHGROUNDATTACKUPGRADE, + "requires_power": True, }, UpgradeId.VOIDRAYSPEEDUPGRADE: { - 'ability': AbilityId.FLEETBEACONRESEARCH_RESEARCHVOIDRAYSPEEDUPGRADE, - 'requires_power': True - } + "ability": AbilityId.FLEETBEACONRESEARCH_RESEARCHVOIDRAYSPEEDUPGRADE, + "requires_power": True, + }, }, UnitTypeId.FORGE: { UpgradeId.PROTOSSGROUNDARMORSLEVEL1: { - 'ability': AbilityId.FORGERESEARCH_PROTOSSGROUNDARMORLEVEL1, - 'requires_power': True + "ability": AbilityId.FORGERESEARCH_PROTOSSGROUNDARMORLEVEL1, + "requires_power": True, }, UpgradeId.PROTOSSGROUNDARMORSLEVEL2: { - 'ability': AbilityId.FORGERESEARCH_PROTOSSGROUNDARMORLEVEL2, - 'required_building': UnitTypeId.TWILIGHTCOUNCIL, - 'required_upgrade': UpgradeId.PROTOSSGROUNDARMORSLEVEL1, - 'requires_power': True + "ability": AbilityId.FORGERESEARCH_PROTOSSGROUNDARMORLEVEL2, + "required_building": UnitTypeId.TWILIGHTCOUNCIL, + "required_upgrade": UpgradeId.PROTOSSGROUNDARMORSLEVEL1, + "requires_power": True, }, UpgradeId.PROTOSSGROUNDARMORSLEVEL3: { - 'ability': AbilityId.FORGERESEARCH_PROTOSSGROUNDARMORLEVEL3, - 'required_building': UnitTypeId.TWILIGHTCOUNCIL, - 'required_upgrade': UpgradeId.PROTOSSGROUNDARMORSLEVEL2, - 'requires_power': True + "ability": AbilityId.FORGERESEARCH_PROTOSSGROUNDARMORLEVEL3, + "required_building": UnitTypeId.TWILIGHTCOUNCIL, + "required_upgrade": UpgradeId.PROTOSSGROUNDARMORSLEVEL2, + "requires_power": True, }, UpgradeId.PROTOSSGROUNDWEAPONSLEVEL1: { - 'ability': AbilityId.FORGERESEARCH_PROTOSSGROUNDWEAPONSLEVEL1, - 'requires_power': True + "ability": AbilityId.FORGERESEARCH_PROTOSSGROUNDWEAPONSLEVEL1, + "requires_power": True, }, UpgradeId.PROTOSSGROUNDWEAPONSLEVEL2: { - 'ability': AbilityId.FORGERESEARCH_PROTOSSGROUNDWEAPONSLEVEL2, - 'required_building': UnitTypeId.TWILIGHTCOUNCIL, - 'required_upgrade': UpgradeId.PROTOSSGROUNDWEAPONSLEVEL1, - 'requires_power': True + "ability": AbilityId.FORGERESEARCH_PROTOSSGROUNDWEAPONSLEVEL2, + "required_building": UnitTypeId.TWILIGHTCOUNCIL, + "required_upgrade": UpgradeId.PROTOSSGROUNDWEAPONSLEVEL1, + "requires_power": True, }, UpgradeId.PROTOSSGROUNDWEAPONSLEVEL3: { - 'ability': AbilityId.FORGERESEARCH_PROTOSSGROUNDWEAPONSLEVEL3, - 'required_building': UnitTypeId.TWILIGHTCOUNCIL, - 'required_upgrade': UpgradeId.PROTOSSGROUNDWEAPONSLEVEL2, - 'requires_power': True + "ability": AbilityId.FORGERESEARCH_PROTOSSGROUNDWEAPONSLEVEL3, + "required_building": UnitTypeId.TWILIGHTCOUNCIL, + "required_upgrade": UpgradeId.PROTOSSGROUNDWEAPONSLEVEL2, + "requires_power": True, }, UpgradeId.PROTOSSSHIELDSLEVEL1: { - 'ability': AbilityId.FORGERESEARCH_PROTOSSSHIELDSLEVEL1, - 'requires_power': True + "ability": AbilityId.FORGERESEARCH_PROTOSSSHIELDSLEVEL1, + "requires_power": True, }, UpgradeId.PROTOSSSHIELDSLEVEL2: { - 'ability': AbilityId.FORGERESEARCH_PROTOSSSHIELDSLEVEL2, - 'required_building': UnitTypeId.TWILIGHTCOUNCIL, - 'required_upgrade': UpgradeId.PROTOSSSHIELDSLEVEL1, - 'requires_power': True + "ability": AbilityId.FORGERESEARCH_PROTOSSSHIELDSLEVEL2, + "required_building": UnitTypeId.TWILIGHTCOUNCIL, + "required_upgrade": UpgradeId.PROTOSSSHIELDSLEVEL1, + "requires_power": True, }, UpgradeId.PROTOSSSHIELDSLEVEL3: { - 'ability': AbilityId.FORGERESEARCH_PROTOSSSHIELDSLEVEL3, - 'required_building': UnitTypeId.TWILIGHTCOUNCIL, - 'required_upgrade': UpgradeId.PROTOSSSHIELDSLEVEL2, - 'requires_power': True - } - }, - UnitTypeId.FUSIONCORE: { - UpgradeId.BATTLECRUISERENABLESPECIALIZATIONS: { - 'ability': AbilityId.RESEARCH_BATTLECRUISERWEAPONREFIT - }, - UpgradeId.LIBERATORAGRANGEUPGRADE: { - 'ability': AbilityId.FUSIONCORERESEARCH_RESEARCHBALLISTICRANGE + "ability": AbilityId.FORGERESEARCH_PROTOSSSHIELDSLEVEL3, + "required_building": UnitTypeId.TWILIGHTCOUNCIL, + "required_upgrade": UpgradeId.PROTOSSSHIELDSLEVEL2, + "requires_power": True, }, - UpgradeId.MEDIVACCADUCEUSREACTOR: { - 'ability': AbilityId.FUSIONCORERESEARCH_RESEARCHMEDIVACENERGYUPGRADE - } }, - UnitTypeId.GHOSTACADEMY: { - UpgradeId.PERSONALCLOAKING: { - 'ability': AbilityId.RESEARCH_PERSONALCLOAKING - } + UnitTypeId.FUSIONCORE: { + UpgradeId.BATTLECRUISERENABLESPECIALIZATIONS: {"ability": AbilityId.RESEARCH_BATTLECRUISERWEAPONREFIT}, + UpgradeId.LIBERATORAGRANGEUPGRADE: {"ability": AbilityId.FUSIONCORERESEARCH_RESEARCHBALLISTICRANGE}, + UpgradeId.MEDIVACCADUCEUSREACTOR: {"ability": AbilityId.FUSIONCORERESEARCH_RESEARCHMEDIVACENERGYUPGRADE}, }, + UnitTypeId.GHOSTACADEMY: {UpgradeId.PERSONALCLOAKING: {"ability": AbilityId.RESEARCH_PERSONALCLOAKING}}, UnitTypeId.GREATERSPIRE: { - UpgradeId.ZERGFLYERARMORSLEVEL1: { - 'ability': AbilityId.RESEARCH_ZERGFLYERARMORLEVEL1 - }, + UpgradeId.ZERGFLYERARMORSLEVEL1: {"ability": AbilityId.RESEARCH_ZERGFLYERARMORLEVEL1}, UpgradeId.ZERGFLYERARMORSLEVEL2: { - 'ability': AbilityId.RESEARCH_ZERGFLYERARMORLEVEL2, - 'required_building': UnitTypeId.LAIR, - 'required_upgrade': UpgradeId.ZERGFLYERARMORSLEVEL1 + "ability": AbilityId.RESEARCH_ZERGFLYERARMORLEVEL2, + "required_building": UnitTypeId.LAIR, + "required_upgrade": UpgradeId.ZERGFLYERARMORSLEVEL1, }, UpgradeId.ZERGFLYERARMORSLEVEL3: { - 'ability': AbilityId.RESEARCH_ZERGFLYERARMORLEVEL3, - 'required_building': UnitTypeId.HIVE, - 'required_upgrade': UpgradeId.ZERGFLYERARMORSLEVEL2 - }, - UpgradeId.ZERGFLYERWEAPONSLEVEL1: { - 'ability': AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL1 + "ability": AbilityId.RESEARCH_ZERGFLYERARMORLEVEL3, + "required_building": UnitTypeId.HIVE, + "required_upgrade": UpgradeId.ZERGFLYERARMORSLEVEL2, }, + UpgradeId.ZERGFLYERWEAPONSLEVEL1: {"ability": AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL1}, UpgradeId.ZERGFLYERWEAPONSLEVEL2: { - 'ability': AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL2, - 'required_building': UnitTypeId.LAIR, - 'required_upgrade': UpgradeId.ZERGFLYERWEAPONSLEVEL1 + "ability": AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL2, + "required_building": UnitTypeId.LAIR, + "required_upgrade": UpgradeId.ZERGFLYERWEAPONSLEVEL1, }, UpgradeId.ZERGFLYERWEAPONSLEVEL3: { - 'ability': AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL3, - 'required_building': UnitTypeId.HIVE, - 'required_upgrade': UpgradeId.ZERGFLYERWEAPONSLEVEL2 - } + "ability": AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL3, + "required_building": UnitTypeId.HIVE, + "required_upgrade": UpgradeId.ZERGFLYERWEAPONSLEVEL2, + }, }, UnitTypeId.HATCHERY: { - UpgradeId.BURROW: { - 'ability': AbilityId.RESEARCH_BURROW - }, - UpgradeId.OVERLORDSPEED: { - 'ability': AbilityId.RESEARCH_PNEUMATIZEDCARAPACE - } + UpgradeId.BURROW: {"ability": AbilityId.RESEARCH_BURROW}, + UpgradeId.OVERLORDSPEED: {"ability": AbilityId.RESEARCH_PNEUMATIZEDCARAPACE}, }, UnitTypeId.HIVE: { - UpgradeId.BURROW: { - 'ability': AbilityId.RESEARCH_BURROW - }, - UpgradeId.OVERLORDSPEED: { - 'ability': AbilityId.RESEARCH_PNEUMATIZEDCARAPACE - } + UpgradeId.BURROW: {"ability": AbilityId.RESEARCH_BURROW}, + UpgradeId.OVERLORDSPEED: {"ability": AbilityId.RESEARCH_PNEUMATIZEDCARAPACE}, }, UnitTypeId.HYDRALISKDEN: { - UpgradeId.EVOLVEGROOVEDSPINES: { - 'ability': AbilityId.RESEARCH_GROOVEDSPINES - }, - UpgradeId.EVOLVEMUSCULARAUGMENTS: { - 'ability': AbilityId.RESEARCH_MUSCULARAUGMENTS - } - }, - UnitTypeId.INFESTATIONPIT: { - UpgradeId.NEURALPARASITE: { - 'ability': AbilityId.RESEARCH_NEURALPARASITE - } + UpgradeId.EVOLVEGROOVEDSPINES: {"ability": AbilityId.RESEARCH_GROOVEDSPINES}, + UpgradeId.EVOLVEMUSCULARAUGMENTS: {"ability": AbilityId.RESEARCH_MUSCULARAUGMENTS}, }, + UnitTypeId.INFESTATIONPIT: {UpgradeId.NEURALPARASITE: {"ability": AbilityId.RESEARCH_NEURALPARASITE}}, UnitTypeId.LAIR: { - UpgradeId.BURROW: { - 'ability': AbilityId.RESEARCH_BURROW - }, - UpgradeId.OVERLORDSPEED: { - 'ability': AbilityId.RESEARCH_PNEUMATIZEDCARAPACE - } + UpgradeId.BURROW: {"ability": AbilityId.RESEARCH_BURROW}, + UpgradeId.OVERLORDSPEED: {"ability": AbilityId.RESEARCH_PNEUMATIZEDCARAPACE}, }, UnitTypeId.LURKERDENMP: { - UpgradeId.DIGGINGCLAWS: { - 'ability': AbilityId.RESEARCH_ADAPTIVETALONS, - 'required_building': UnitTypeId.HIVE - }, + UpgradeId.DIGGINGCLAWS: {"ability": AbilityId.RESEARCH_ADAPTIVETALONS, "required_building": UnitTypeId.HIVE}, UpgradeId.LURKERRANGE: { - 'ability': AbilityId.LURKERDENRESEARCH_RESEARCHLURKERRANGE, - 'required_building': UnitTypeId.HIVE - } + "ability": AbilityId.LURKERDENRESEARCH_RESEARCHLURKERRANGE, + "required_building": UnitTypeId.HIVE, + }, }, UnitTypeId.ROACHWARREN: { UpgradeId.GLIALRECONSTITUTION: { - 'ability': AbilityId.RESEARCH_GLIALREGENERATION, - 'required_building': UnitTypeId.LAIR + "ability": AbilityId.RESEARCH_GLIALREGENERATION, + "required_building": UnitTypeId.LAIR, }, - UpgradeId.TUNNELINGCLAWS: { - 'ability': AbilityId.RESEARCH_TUNNELINGCLAWS, - 'required_building': UnitTypeId.LAIR - } + UpgradeId.TUNNELINGCLAWS: {"ability": AbilityId.RESEARCH_TUNNELINGCLAWS, "required_building": UnitTypeId.LAIR}, }, UnitTypeId.ROBOTICSBAY: { - UpgradeId.EXTENDEDTHERMALLANCE: { - 'ability': AbilityId.RESEARCH_EXTENDEDTHERMALLANCE, - 'requires_power': True - }, - UpgradeId.GRAVITICDRIVE: { - 'ability': AbilityId.RESEARCH_GRAVITICDRIVE, - 'requires_power': True - }, - UpgradeId.OBSERVERGRAVITICBOOSTER: { - 'ability': AbilityId.RESEARCH_GRAVITICBOOSTER, - 'requires_power': True - } + UpgradeId.EXTENDEDTHERMALLANCE: {"ability": AbilityId.RESEARCH_EXTENDEDTHERMALLANCE, "requires_power": True}, + UpgradeId.GRAVITICDRIVE: {"ability": AbilityId.RESEARCH_GRAVITICDRIVE, "requires_power": True}, + UpgradeId.OBSERVERGRAVITICBOOSTER: {"ability": AbilityId.RESEARCH_GRAVITICBOOSTER, "requires_power": True}, }, UnitTypeId.SPAWNINGPOOL: { UpgradeId.ZERGLINGATTACKSPEED: { - 'ability': AbilityId.RESEARCH_ZERGLINGADRENALGLANDS, - 'required_building': UnitTypeId.HIVE + "ability": AbilityId.RESEARCH_ZERGLINGADRENALGLANDS, + "required_building": UnitTypeId.HIVE, }, - UpgradeId.ZERGLINGMOVEMENTSPEED: { - 'ability': AbilityId.RESEARCH_ZERGLINGMETABOLICBOOST - } + UpgradeId.ZERGLINGMOVEMENTSPEED: {"ability": AbilityId.RESEARCH_ZERGLINGMETABOLICBOOST}, }, UnitTypeId.SPIRE: { - UpgradeId.ZERGFLYERARMORSLEVEL1: { - 'ability': AbilityId.RESEARCH_ZERGFLYERARMORLEVEL1 - }, + UpgradeId.ZERGFLYERARMORSLEVEL1: {"ability": AbilityId.RESEARCH_ZERGFLYERARMORLEVEL1}, UpgradeId.ZERGFLYERARMORSLEVEL2: { - 'ability': AbilityId.RESEARCH_ZERGFLYERARMORLEVEL2, - 'required_building': UnitTypeId.LAIR, - 'required_upgrade': UpgradeId.ZERGFLYERARMORSLEVEL1 + "ability": AbilityId.RESEARCH_ZERGFLYERARMORLEVEL2, + "required_building": UnitTypeId.LAIR, + "required_upgrade": UpgradeId.ZERGFLYERARMORSLEVEL1, }, UpgradeId.ZERGFLYERARMORSLEVEL3: { - 'ability': AbilityId.RESEARCH_ZERGFLYERARMORLEVEL3, - 'required_building': UnitTypeId.HIVE, - 'required_upgrade': UpgradeId.ZERGFLYERARMORSLEVEL2 - }, - UpgradeId.ZERGFLYERWEAPONSLEVEL1: { - 'ability': AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL1 + "ability": AbilityId.RESEARCH_ZERGFLYERARMORLEVEL3, + "required_building": UnitTypeId.HIVE, + "required_upgrade": UpgradeId.ZERGFLYERARMORSLEVEL2, }, + UpgradeId.ZERGFLYERWEAPONSLEVEL1: {"ability": AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL1}, UpgradeId.ZERGFLYERWEAPONSLEVEL2: { - 'ability': AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL2, - 'required_building': UnitTypeId.LAIR, - 'required_upgrade': UpgradeId.ZERGFLYERWEAPONSLEVEL1 + "ability": AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL2, + "required_building": UnitTypeId.LAIR, + "required_upgrade": UpgradeId.ZERGFLYERWEAPONSLEVEL1, }, UpgradeId.ZERGFLYERWEAPONSLEVEL3: { - 'ability': AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL3, - 'required_building': UnitTypeId.HIVE, - 'required_upgrade': UpgradeId.ZERGFLYERWEAPONSLEVEL2 - } + "ability": AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL3, + "required_building": UnitTypeId.HIVE, + "required_upgrade": UpgradeId.ZERGFLYERWEAPONSLEVEL2, + }, }, UnitTypeId.STARPORTTECHLAB: { - UpgradeId.BANSHEECLOAK: { - 'ability': AbilityId.RESEARCH_BANSHEECLOAKINGFIELD - }, - UpgradeId.BANSHEESPEED: { - 'ability': AbilityId.RESEARCH_BANSHEEHYPERFLIGHTROTORS - }, - UpgradeId.INTERFERENCEMATRIX: { - 'ability': AbilityId.STARPORTTECHLABRESEARCH_RESEARCHRAVENINTERFERENCEMATRIX - } + UpgradeId.BANSHEECLOAK: {"ability": AbilityId.RESEARCH_BANSHEECLOAKINGFIELD}, + UpgradeId.BANSHEESPEED: {"ability": AbilityId.RESEARCH_BANSHEEHYPERFLIGHTROTORS}, + UpgradeId.INTERFERENCEMATRIX: {"ability": AbilityId.STARPORTTECHLABRESEARCH_RESEARCHRAVENINTERFERENCEMATRIX}, }, UnitTypeId.TEMPLARARCHIVE: { - UpgradeId.PSISTORMTECH: { - 'ability': AbilityId.RESEARCH_PSISTORM, - 'requires_power': True - } + UpgradeId.PSISTORMTECH: {"ability": AbilityId.RESEARCH_PSISTORM, "requires_power": True} }, UnitTypeId.TWILIGHTCOUNCIL: { - UpgradeId.ADEPTPIERCINGATTACK: { - 'ability': AbilityId.RESEARCH_ADEPTRESONATINGGLAIVES, - 'requires_power': True - }, - UpgradeId.BLINKTECH: { - 'ability': AbilityId.RESEARCH_BLINK, - 'requires_power': True - }, - UpgradeId.CHARGE: { - 'ability': AbilityId.RESEARCH_CHARGE, - 'requires_power': True - } + UpgradeId.ADEPTPIERCINGATTACK: {"ability": AbilityId.RESEARCH_ADEPTRESONATINGGLAIVES, "requires_power": True}, + UpgradeId.BLINKTECH: {"ability": AbilityId.RESEARCH_BLINK, "requires_power": True}, + UpgradeId.CHARGE: {"ability": AbilityId.RESEARCH_CHARGE, "requires_power": True}, }, UnitTypeId.ULTRALISKCAVERN: { - UpgradeId.ANABOLICSYNTHESIS: { - 'ability': AbilityId.RESEARCH_ANABOLICSYNTHESIS - }, - UpgradeId.CHITINOUSPLATING: { - 'ability': AbilityId.RESEARCH_CHITINOUSPLATING - } - } + UpgradeId.ANABOLICSYNTHESIS: {"ability": AbilityId.RESEARCH_ANABOLICSYNTHESIS}, + UpgradeId.CHITINOUSPLATING: {"ability": AbilityId.RESEARCH_CHITINOUSPLATING}, + }, } diff --git a/sc2/dicts/unit_tech_alias.py b/sc2/dicts/unit_tech_alias.py index d1997695..1eb330d5 100644 --- a/sc2/dicts/unit_tech_alias.py +++ b/sc2/dicts/unit_tech_alias.py @@ -1,14 +1,12 @@ # THIS FILE WAS AUTOMATICALLY GENERATED BY "generate_dicts_from_data_json.py" DO NOT CHANGE MANUALLY! # ANY CHANGE WILL BE OVERWRITTEN -from typing import Dict, Set - from sc2.ids.unit_typeid import UnitTypeId - # from sc2.ids.buff_id import BuffId # from sc2.ids.effect_id import EffectId -UNIT_TECH_ALIAS: Dict[UnitTypeId, Set[UnitTypeId]] = { + +UNIT_TECH_ALIAS: dict[UnitTypeId, set[UnitTypeId]] = { UnitTypeId.BARRACKSFLYING: {UnitTypeId.BARRACKS}, UnitTypeId.BARRACKSREACTOR: {UnitTypeId.REACTOR}, UnitTypeId.BARRACKSTECHLAB: {UnitTypeId.TECHLAB}, @@ -40,5 +38,5 @@ UnitTypeId.VIKINGFIGHTER: {UnitTypeId.VIKING}, UnitTypeId.WARPGATE: {UnitTypeId.GATEWAY}, UnitTypeId.WARPPRISMPHASING: {UnitTypeId.WARPPRISM}, - UnitTypeId.WIDOWMINEBURROWED: {UnitTypeId.WIDOWMINE} + UnitTypeId.WIDOWMINEBURROWED: {UnitTypeId.WIDOWMINE}, } diff --git a/sc2/dicts/unit_train_build_abilities.py b/sc2/dicts/unit_train_build_abilities.py index 97fd1201..8c9ab434 100644 --- a/sc2/dicts/unit_train_build_abilities.py +++ b/sc2/dicts/unit_train_build_abilities.py @@ -1,606 +1,427 @@ # THIS FILE WAS AUTOMATICALLY GENERATED BY "generate_dicts_from_data_json.py" DO NOT CHANGE MANUALLY! # ANY CHANGE WILL BE OVERWRITTEN -from typing import Dict, Union - -from sc2.ids.ability_id import AbilityId from sc2.ids.unit_typeid import UnitTypeId - +from sc2.ids.ability_id import AbilityId # from sc2.ids.buff_id import BuffId # from sc2.ids.effect_id import EffectId -TRAIN_INFO: Dict[UnitTypeId, Dict[UnitTypeId, Dict[str, Union[AbilityId, bool, UnitTypeId]]]] = { +from typing import Union + +TRAIN_INFO: dict[UnitTypeId, dict[UnitTypeId, dict[str, Union[AbilityId, bool, UnitTypeId]]]] = { UnitTypeId.BARRACKS: { UnitTypeId.GHOST: { - 'ability': AbilityId.BARRACKSTRAIN_GHOST, - 'requires_techlab': True, - 'required_building': UnitTypeId.GHOSTACADEMY - }, - UnitTypeId.MARAUDER: { - 'ability': AbilityId.BARRACKSTRAIN_MARAUDER, - 'requires_techlab': True + "ability": AbilityId.BARRACKSTRAIN_GHOST, + "requires_techlab": True, + "required_building": UnitTypeId.GHOSTACADEMY, }, - UnitTypeId.MARINE: { - 'ability': AbilityId.BARRACKSTRAIN_MARINE - }, - UnitTypeId.REAPER: { - 'ability': AbilityId.BARRACKSTRAIN_REAPER - } + UnitTypeId.MARAUDER: {"ability": AbilityId.BARRACKSTRAIN_MARAUDER, "requires_techlab": True}, + UnitTypeId.MARINE: {"ability": AbilityId.BARRACKSTRAIN_MARINE}, + UnitTypeId.REAPER: {"ability": AbilityId.BARRACKSTRAIN_REAPER}, }, UnitTypeId.COMMANDCENTER: { UnitTypeId.ORBITALCOMMAND: { - 'ability': AbilityId.UPGRADETOORBITAL_ORBITALCOMMAND, - 'required_building': UnitTypeId.BARRACKS + "ability": AbilityId.UPGRADETOORBITAL_ORBITALCOMMAND, + "required_building": UnitTypeId.BARRACKS, }, UnitTypeId.PLANETARYFORTRESS: { - 'ability': AbilityId.UPGRADETOPLANETARYFORTRESS_PLANETARYFORTRESS, - 'required_building': UnitTypeId.ENGINEERINGBAY + "ability": AbilityId.UPGRADETOPLANETARYFORTRESS_PLANETARYFORTRESS, + "required_building": UnitTypeId.ENGINEERINGBAY, }, - UnitTypeId.SCV: { - 'ability': AbilityId.COMMANDCENTERTRAIN_SCV - } + UnitTypeId.SCV: {"ability": AbilityId.COMMANDCENTERTRAIN_SCV}, }, UnitTypeId.CORRUPTOR: { UnitTypeId.BROODLORD: { - 'ability': AbilityId.MORPHTOBROODLORD_BROODLORD, - 'required_building': UnitTypeId.GREATERSPIRE + "ability": AbilityId.MORPHTOBROODLORD_BROODLORD, + "required_building": UnitTypeId.GREATERSPIRE, } }, UnitTypeId.CREEPTUMOR: { - UnitTypeId.CREEPTUMOR: { - 'ability': AbilityId.BUILD_CREEPTUMOR_TUMOR, - 'requires_placement_position': True - } + UnitTypeId.CREEPTUMOR: {"ability": AbilityId.BUILD_CREEPTUMOR_TUMOR, "requires_placement_position": True} }, UnitTypeId.CREEPTUMORBURROWED: { - UnitTypeId.CREEPTUMOR: { - 'ability': AbilityId.BUILD_CREEPTUMOR, - 'requires_placement_position': True - } + UnitTypeId.CREEPTUMOR: {"ability": AbilityId.BUILD_CREEPTUMOR, "requires_placement_position": True} }, UnitTypeId.DRONE: { UnitTypeId.BANELINGNEST: { - 'ability': AbilityId.ZERGBUILD_BANELINGNEST, - 'required_building': UnitTypeId.SPAWNINGPOOL, - 'requires_placement_position': True + "ability": AbilityId.ZERGBUILD_BANELINGNEST, + "required_building": UnitTypeId.SPAWNINGPOOL, + "requires_placement_position": True, }, UnitTypeId.EVOLUTIONCHAMBER: { - 'ability': AbilityId.ZERGBUILD_EVOLUTIONCHAMBER, - 'required_building': UnitTypeId.HATCHERY, - 'requires_placement_position': True - }, - UnitTypeId.EXTRACTOR: { - 'ability': AbilityId.ZERGBUILD_EXTRACTOR - }, - UnitTypeId.HATCHERY: { - 'ability': AbilityId.ZERGBUILD_HATCHERY, - 'requires_placement_position': True + "ability": AbilityId.ZERGBUILD_EVOLUTIONCHAMBER, + "required_building": UnitTypeId.HATCHERY, + "requires_placement_position": True, }, + UnitTypeId.EXTRACTOR: {"ability": AbilityId.ZERGBUILD_EXTRACTOR}, + UnitTypeId.HATCHERY: {"ability": AbilityId.ZERGBUILD_HATCHERY, "requires_placement_position": True}, UnitTypeId.HYDRALISKDEN: { - 'ability': AbilityId.ZERGBUILD_HYDRALISKDEN, - 'required_building': UnitTypeId.LAIR, - 'requires_placement_position': True + "ability": AbilityId.ZERGBUILD_HYDRALISKDEN, + "required_building": UnitTypeId.LAIR, + "requires_placement_position": True, }, UnitTypeId.INFESTATIONPIT: { - 'ability': AbilityId.ZERGBUILD_INFESTATIONPIT, - 'required_building': UnitTypeId.LAIR, - 'requires_placement_position': True + "ability": AbilityId.ZERGBUILD_INFESTATIONPIT, + "required_building": UnitTypeId.LAIR, + "requires_placement_position": True, }, UnitTypeId.LURKERDENMP: { - 'ability': AbilityId.BUILD_LURKERDEN, - 'required_building': UnitTypeId.HYDRALISKDEN, - 'requires_placement_position': True + "ability": AbilityId.BUILD_LURKERDEN, + "required_building": UnitTypeId.HYDRALISKDEN, + "requires_placement_position": True, }, UnitTypeId.NYDUSNETWORK: { - 'ability': AbilityId.ZERGBUILD_NYDUSNETWORK, - 'required_building': UnitTypeId.LAIR, - 'requires_placement_position': True + "ability": AbilityId.ZERGBUILD_NYDUSNETWORK, + "required_building": UnitTypeId.LAIR, + "requires_placement_position": True, }, UnitTypeId.ROACHWARREN: { - 'ability': AbilityId.ZERGBUILD_ROACHWARREN, - 'required_building': UnitTypeId.SPAWNINGPOOL, - 'requires_placement_position': True + "ability": AbilityId.ZERGBUILD_ROACHWARREN, + "required_building": UnitTypeId.SPAWNINGPOOL, + "requires_placement_position": True, }, UnitTypeId.SPAWNINGPOOL: { - 'ability': AbilityId.ZERGBUILD_SPAWNINGPOOL, - 'required_building': UnitTypeId.HATCHERY, - 'requires_placement_position': True + "ability": AbilityId.ZERGBUILD_SPAWNINGPOOL, + "required_building": UnitTypeId.HATCHERY, + "requires_placement_position": True, }, UnitTypeId.SPINECRAWLER: { - 'ability': AbilityId.ZERGBUILD_SPINECRAWLER, - 'required_building': UnitTypeId.SPAWNINGPOOL, - 'requires_placement_position': True + "ability": AbilityId.ZERGBUILD_SPINECRAWLER, + "required_building": UnitTypeId.SPAWNINGPOOL, + "requires_placement_position": True, }, UnitTypeId.SPIRE: { - 'ability': AbilityId.ZERGBUILD_SPIRE, - 'required_building': UnitTypeId.LAIR, - 'requires_placement_position': True + "ability": AbilityId.ZERGBUILD_SPIRE, + "required_building": UnitTypeId.LAIR, + "requires_placement_position": True, }, UnitTypeId.SPORECRAWLER: { - 'ability': AbilityId.ZERGBUILD_SPORECRAWLER, - 'required_building': UnitTypeId.SPAWNINGPOOL, - 'requires_placement_position': True + "ability": AbilityId.ZERGBUILD_SPORECRAWLER, + "required_building": UnitTypeId.SPAWNINGPOOL, + "requires_placement_position": True, }, UnitTypeId.ULTRALISKCAVERN: { - 'ability': AbilityId.ZERGBUILD_ULTRALISKCAVERN, - 'required_building': UnitTypeId.HIVE, - 'requires_placement_position': True - } + "ability": AbilityId.ZERGBUILD_ULTRALISKCAVERN, + "required_building": UnitTypeId.HIVE, + "requires_placement_position": True, + }, }, UnitTypeId.FACTORY: { - UnitTypeId.CYCLONE: { - 'ability': AbilityId.TRAIN_CYCLONE - }, - UnitTypeId.HELLION: { - 'ability': AbilityId.FACTORYTRAIN_HELLION - }, - UnitTypeId.HELLIONTANK: { - 'ability': AbilityId.TRAIN_HELLBAT, - 'required_building': UnitTypeId.ARMORY - }, - UnitTypeId.SIEGETANK: { - 'ability': AbilityId.FACTORYTRAIN_SIEGETANK, - 'requires_techlab': True - }, + UnitTypeId.CYCLONE: {"ability": AbilityId.TRAIN_CYCLONE}, + UnitTypeId.HELLION: {"ability": AbilityId.FACTORYTRAIN_HELLION}, + UnitTypeId.HELLIONTANK: {"ability": AbilityId.TRAIN_HELLBAT, "required_building": UnitTypeId.ARMORY}, + UnitTypeId.SIEGETANK: {"ability": AbilityId.FACTORYTRAIN_SIEGETANK, "requires_techlab": True}, UnitTypeId.THOR: { - 'ability': AbilityId.FACTORYTRAIN_THOR, - 'requires_techlab': True, - 'required_building': UnitTypeId.ARMORY + "ability": AbilityId.FACTORYTRAIN_THOR, + "requires_techlab": True, + "required_building": UnitTypeId.ARMORY, }, - UnitTypeId.WIDOWMINE: { - 'ability': AbilityId.FACTORYTRAIN_WIDOWMINE - } + UnitTypeId.WIDOWMINE: {"ability": AbilityId.FACTORYTRAIN_WIDOWMINE}, }, UnitTypeId.GATEWAY: { UnitTypeId.ADEPT: { - 'ability': AbilityId.TRAIN_ADEPT, - 'required_building': UnitTypeId.CYBERNETICSCORE, - 'requires_power': True + "ability": AbilityId.TRAIN_ADEPT, + "required_building": UnitTypeId.CYBERNETICSCORE, + "requires_power": True, }, UnitTypeId.DARKTEMPLAR: { - 'ability': AbilityId.GATEWAYTRAIN_DARKTEMPLAR, - 'required_building': UnitTypeId.DARKSHRINE, - 'requires_power': True + "ability": AbilityId.GATEWAYTRAIN_DARKTEMPLAR, + "required_building": UnitTypeId.DARKSHRINE, + "requires_power": True, }, UnitTypeId.HIGHTEMPLAR: { - 'ability': AbilityId.GATEWAYTRAIN_HIGHTEMPLAR, - 'required_building': UnitTypeId.TEMPLARARCHIVE, - 'requires_power': True + "ability": AbilityId.GATEWAYTRAIN_HIGHTEMPLAR, + "required_building": UnitTypeId.TEMPLARARCHIVE, + "requires_power": True, }, UnitTypeId.SENTRY: { - 'ability': AbilityId.GATEWAYTRAIN_SENTRY, - 'required_building': UnitTypeId.CYBERNETICSCORE, - 'requires_power': True + "ability": AbilityId.GATEWAYTRAIN_SENTRY, + "required_building": UnitTypeId.CYBERNETICSCORE, + "requires_power": True, }, UnitTypeId.STALKER: { - 'ability': AbilityId.GATEWAYTRAIN_STALKER, - 'required_building': UnitTypeId.CYBERNETICSCORE, - 'requires_power': True + "ability": AbilityId.GATEWAYTRAIN_STALKER, + "required_building": UnitTypeId.CYBERNETICSCORE, + "requires_power": True, }, - UnitTypeId.ZEALOT: { - 'ability': AbilityId.GATEWAYTRAIN_ZEALOT, - 'requires_power': True - } + UnitTypeId.ZEALOT: {"ability": AbilityId.GATEWAYTRAIN_ZEALOT, "requires_power": True}, }, UnitTypeId.HATCHERY: { - UnitTypeId.LAIR: { - 'ability': AbilityId.UPGRADETOLAIR_LAIR, - 'required_building': UnitTypeId.SPAWNINGPOOL - }, - UnitTypeId.QUEEN: { - 'ability': AbilityId.TRAINQUEEN_QUEEN, - 'required_building': UnitTypeId.SPAWNINGPOOL - } + UnitTypeId.LAIR: {"ability": AbilityId.UPGRADETOLAIR_LAIR, "required_building": UnitTypeId.SPAWNINGPOOL}, + UnitTypeId.QUEEN: {"ability": AbilityId.TRAINQUEEN_QUEEN, "required_building": UnitTypeId.SPAWNINGPOOL}, }, UnitTypeId.HIVE: { - UnitTypeId.QUEEN: { - 'ability': AbilityId.TRAINQUEEN_QUEEN, - 'required_building': UnitTypeId.SPAWNINGPOOL - } + UnitTypeId.QUEEN: {"ability": AbilityId.TRAINQUEEN_QUEEN, "required_building": UnitTypeId.SPAWNINGPOOL} }, UnitTypeId.HYDRALISK: { - UnitTypeId.LURKERMP: { - 'ability': AbilityId.MORPH_LURKER, - 'required_building': UnitTypeId.LURKERDENMP - } + UnitTypeId.LURKERMP: {"ability": AbilityId.MORPH_LURKER, "required_building": UnitTypeId.LURKERDENMP} }, UnitTypeId.LAIR: { - UnitTypeId.HIVE: { - 'ability': AbilityId.UPGRADETOHIVE_HIVE, - 'required_building': UnitTypeId.INFESTATIONPIT - }, - UnitTypeId.QUEEN: { - 'ability': AbilityId.TRAINQUEEN_QUEEN, - 'required_building': UnitTypeId.SPAWNINGPOOL - } + UnitTypeId.HIVE: {"ability": AbilityId.UPGRADETOHIVE_HIVE, "required_building": UnitTypeId.INFESTATIONPIT}, + UnitTypeId.QUEEN: {"ability": AbilityId.TRAINQUEEN_QUEEN, "required_building": UnitTypeId.SPAWNINGPOOL}, }, UnitTypeId.LARVA: { - UnitTypeId.CORRUPTOR: { - 'ability': AbilityId.LARVATRAIN_CORRUPTOR, - 'required_building': UnitTypeId.SPIRE - }, - UnitTypeId.DRONE: { - 'ability': AbilityId.LARVATRAIN_DRONE - }, - UnitTypeId.HYDRALISK: { - 'ability': AbilityId.LARVATRAIN_HYDRALISK, - 'required_building': UnitTypeId.HYDRALISKDEN - }, - UnitTypeId.INFESTOR: { - 'ability': AbilityId.LARVATRAIN_INFESTOR, - 'required_building': UnitTypeId.INFESTATIONPIT - }, - UnitTypeId.MUTALISK: { - 'ability': AbilityId.LARVATRAIN_MUTALISK, - 'required_building': UnitTypeId.SPIRE - }, - UnitTypeId.OVERLORD: { - 'ability': AbilityId.LARVATRAIN_OVERLORD - }, - UnitTypeId.ROACH: { - 'ability': AbilityId.LARVATRAIN_ROACH, - 'required_building': UnitTypeId.ROACHWARREN - }, - UnitTypeId.SWARMHOSTMP: { - 'ability': AbilityId.TRAIN_SWARMHOST, - 'required_building': UnitTypeId.INFESTATIONPIT - }, + UnitTypeId.CORRUPTOR: {"ability": AbilityId.LARVATRAIN_CORRUPTOR, "required_building": UnitTypeId.SPIRE}, + UnitTypeId.DRONE: {"ability": AbilityId.LARVATRAIN_DRONE}, + UnitTypeId.HYDRALISK: {"ability": AbilityId.LARVATRAIN_HYDRALISK, "required_building": UnitTypeId.HYDRALISKDEN}, + UnitTypeId.INFESTOR: {"ability": AbilityId.LARVATRAIN_INFESTOR, "required_building": UnitTypeId.INFESTATIONPIT}, + UnitTypeId.MUTALISK: {"ability": AbilityId.LARVATRAIN_MUTALISK, "required_building": UnitTypeId.SPIRE}, + UnitTypeId.OVERLORD: {"ability": AbilityId.LARVATRAIN_OVERLORD}, + UnitTypeId.ROACH: {"ability": AbilityId.LARVATRAIN_ROACH, "required_building": UnitTypeId.ROACHWARREN}, + UnitTypeId.SWARMHOSTMP: {"ability": AbilityId.TRAIN_SWARMHOST, "required_building": UnitTypeId.INFESTATIONPIT}, UnitTypeId.ULTRALISK: { - 'ability': AbilityId.LARVATRAIN_ULTRALISK, - 'required_building': UnitTypeId.ULTRALISKCAVERN + "ability": AbilityId.LARVATRAIN_ULTRALISK, + "required_building": UnitTypeId.ULTRALISKCAVERN, }, - UnitTypeId.VIPER: { - 'ability': AbilityId.LARVATRAIN_VIPER, - 'required_building': UnitTypeId.HIVE - }, - UnitTypeId.ZERGLING: { - 'ability': AbilityId.LARVATRAIN_ZERGLING, - 'required_building': UnitTypeId.SPAWNINGPOOL - } + UnitTypeId.VIPER: {"ability": AbilityId.LARVATRAIN_VIPER, "required_building": UnitTypeId.HIVE}, + UnitTypeId.ZERGLING: {"ability": AbilityId.LARVATRAIN_ZERGLING, "required_building": UnitTypeId.SPAWNINGPOOL}, }, UnitTypeId.NEXUS: { UnitTypeId.MOTHERSHIP: { - 'ability': AbilityId.NEXUSTRAINMOTHERSHIP_MOTHERSHIP, - 'required_building': UnitTypeId.FLEETBEACON + "ability": AbilityId.NEXUSTRAINMOTHERSHIP_MOTHERSHIP, + "required_building": UnitTypeId.FLEETBEACON, }, - UnitTypeId.PROBE: { - 'ability': AbilityId.NEXUSTRAIN_PROBE - } + UnitTypeId.PROBE: {"ability": AbilityId.NEXUSTRAIN_PROBE}, }, UnitTypeId.NYDUSNETWORK: { - UnitTypeId.NYDUSCANAL: { - 'ability': AbilityId.BUILD_NYDUSWORM, - 'requires_placement_position': True - } + UnitTypeId.NYDUSCANAL: {"ability": AbilityId.BUILD_NYDUSWORM, "requires_placement_position": True} }, UnitTypeId.ORACLE: { - UnitTypeId.ORACLESTASISTRAP: { - 'ability': AbilityId.BUILD_STASISTRAP, - 'requires_placement_position': True - } - }, - UnitTypeId.ORBITALCOMMAND: { - UnitTypeId.SCV: { - 'ability': AbilityId.COMMANDCENTERTRAIN_SCV - } + UnitTypeId.ORACLESTASISTRAP: {"ability": AbilityId.BUILD_STASISTRAP, "requires_placement_position": True} }, + UnitTypeId.ORBITALCOMMAND: {UnitTypeId.SCV: {"ability": AbilityId.COMMANDCENTERTRAIN_SCV}}, UnitTypeId.OVERLORD: { UnitTypeId.OVERLORDTRANSPORT: { - 'ability': AbilityId.MORPH_OVERLORDTRANSPORT, - 'required_building': UnitTypeId.LAIR + "ability": AbilityId.MORPH_OVERLORDTRANSPORT, + "required_building": UnitTypeId.LAIR, }, - UnitTypeId.OVERSEER: { - 'ability': AbilityId.MORPH_OVERSEER, - 'required_building': UnitTypeId.LAIR - } + UnitTypeId.OVERSEER: {"ability": AbilityId.MORPH_OVERSEER, "required_building": UnitTypeId.LAIR}, }, UnitTypeId.OVERLORDTRANSPORT: { - UnitTypeId.OVERSEER: { - 'ability': AbilityId.MORPH_OVERSEER, - 'required_building': UnitTypeId.LAIR - } - }, - UnitTypeId.OVERSEER: { - UnitTypeId.CHANGELING: { - 'ability': AbilityId.SPAWNCHANGELING_SPAWNCHANGELING - } - }, - UnitTypeId.OVERSEERSIEGEMODE: { - UnitTypeId.CHANGELING: { - 'ability': AbilityId.SPAWNCHANGELING_SPAWNCHANGELING - } - }, - UnitTypeId.PLANETARYFORTRESS: { - UnitTypeId.SCV: { - 'ability': AbilityId.COMMANDCENTERTRAIN_SCV - } + UnitTypeId.OVERSEER: {"ability": AbilityId.MORPH_OVERSEER, "required_building": UnitTypeId.LAIR} }, + UnitTypeId.OVERSEER: {UnitTypeId.CHANGELING: {"ability": AbilityId.SPAWNCHANGELING_SPAWNCHANGELING}}, + UnitTypeId.OVERSEERSIEGEMODE: {UnitTypeId.CHANGELING: {"ability": AbilityId.SPAWNCHANGELING_SPAWNCHANGELING}}, + UnitTypeId.PLANETARYFORTRESS: {UnitTypeId.SCV: {"ability": AbilityId.COMMANDCENTERTRAIN_SCV}}, UnitTypeId.PROBE: { - UnitTypeId.ASSIMILATOR: { - 'ability': AbilityId.PROTOSSBUILD_ASSIMILATOR - }, + UnitTypeId.ASSIMILATOR: {"ability": AbilityId.PROTOSSBUILD_ASSIMILATOR}, UnitTypeId.CYBERNETICSCORE: { - 'ability': AbilityId.PROTOSSBUILD_CYBERNETICSCORE, - 'required_building': UnitTypeId.GATEWAY, - 'requires_placement_position': True + "ability": AbilityId.PROTOSSBUILD_CYBERNETICSCORE, + "required_building": UnitTypeId.GATEWAY, + "requires_placement_position": True, }, UnitTypeId.DARKSHRINE: { - 'ability': AbilityId.PROTOSSBUILD_DARKSHRINE, - 'required_building': UnitTypeId.TWILIGHTCOUNCIL, - 'requires_placement_position': True + "ability": AbilityId.PROTOSSBUILD_DARKSHRINE, + "required_building": UnitTypeId.TWILIGHTCOUNCIL, + "requires_placement_position": True, }, UnitTypeId.FLEETBEACON: { - 'ability': AbilityId.PROTOSSBUILD_FLEETBEACON, - 'required_building': UnitTypeId.STARGATE, - 'requires_placement_position': True + "ability": AbilityId.PROTOSSBUILD_FLEETBEACON, + "required_building": UnitTypeId.STARGATE, + "requires_placement_position": True, }, UnitTypeId.FORGE: { - 'ability': AbilityId.PROTOSSBUILD_FORGE, - 'required_building': UnitTypeId.PYLON, - 'requires_placement_position': True + "ability": AbilityId.PROTOSSBUILD_FORGE, + "required_building": UnitTypeId.PYLON, + "requires_placement_position": True, }, UnitTypeId.GATEWAY: { - 'ability': AbilityId.PROTOSSBUILD_GATEWAY, - 'required_building': UnitTypeId.PYLON, - 'requires_placement_position': True - }, - UnitTypeId.NEXUS: { - 'ability': AbilityId.PROTOSSBUILD_NEXUS, - 'requires_placement_position': True + "ability": AbilityId.PROTOSSBUILD_GATEWAY, + "required_building": UnitTypeId.PYLON, + "requires_placement_position": True, }, + UnitTypeId.NEXUS: {"ability": AbilityId.PROTOSSBUILD_NEXUS, "requires_placement_position": True}, UnitTypeId.PHOTONCANNON: { - 'ability': AbilityId.PROTOSSBUILD_PHOTONCANNON, - 'required_building': UnitTypeId.FORGE, - 'requires_placement_position': True - }, - UnitTypeId.PYLON: { - 'ability': AbilityId.PROTOSSBUILD_PYLON, - 'requires_placement_position': True + "ability": AbilityId.PROTOSSBUILD_PHOTONCANNON, + "required_building": UnitTypeId.FORGE, + "requires_placement_position": True, }, + UnitTypeId.PYLON: {"ability": AbilityId.PROTOSSBUILD_PYLON, "requires_placement_position": True}, UnitTypeId.ROBOTICSBAY: { - 'ability': AbilityId.PROTOSSBUILD_ROBOTICSBAY, - 'required_building': UnitTypeId.ROBOTICSFACILITY, - 'requires_placement_position': True + "ability": AbilityId.PROTOSSBUILD_ROBOTICSBAY, + "required_building": UnitTypeId.ROBOTICSFACILITY, + "requires_placement_position": True, }, UnitTypeId.ROBOTICSFACILITY: { - 'ability': AbilityId.PROTOSSBUILD_ROBOTICSFACILITY, - 'required_building': UnitTypeId.CYBERNETICSCORE, - 'requires_placement_position': True + "ability": AbilityId.PROTOSSBUILD_ROBOTICSFACILITY, + "required_building": UnitTypeId.CYBERNETICSCORE, + "requires_placement_position": True, }, UnitTypeId.SHIELDBATTERY: { - 'ability': AbilityId.BUILD_SHIELDBATTERY, - 'required_building': UnitTypeId.CYBERNETICSCORE, - 'requires_placement_position': True + "ability": AbilityId.BUILD_SHIELDBATTERY, + "required_building": UnitTypeId.CYBERNETICSCORE, + "requires_placement_position": True, }, UnitTypeId.STARGATE: { - 'ability': AbilityId.PROTOSSBUILD_STARGATE, - 'required_building': UnitTypeId.CYBERNETICSCORE, - 'requires_placement_position': True + "ability": AbilityId.PROTOSSBUILD_STARGATE, + "required_building": UnitTypeId.CYBERNETICSCORE, + "requires_placement_position": True, }, UnitTypeId.TEMPLARARCHIVE: { - 'ability': AbilityId.PROTOSSBUILD_TEMPLARARCHIVE, - 'required_building': UnitTypeId.TWILIGHTCOUNCIL, - 'requires_placement_position': True + "ability": AbilityId.PROTOSSBUILD_TEMPLARARCHIVE, + "required_building": UnitTypeId.TWILIGHTCOUNCIL, + "requires_placement_position": True, }, UnitTypeId.TWILIGHTCOUNCIL: { - 'ability': AbilityId.PROTOSSBUILD_TWILIGHTCOUNCIL, - 'required_building': UnitTypeId.CYBERNETICSCORE, - 'requires_placement_position': True - } - }, - UnitTypeId.QUEEN: { - UnitTypeId.CREEPTUMOR: { - 'ability': AbilityId.BUILD_CREEPTUMOR, - 'requires_placement_position': True + "ability": AbilityId.PROTOSSBUILD_TWILIGHTCOUNCIL, + "required_building": UnitTypeId.CYBERNETICSCORE, + "requires_placement_position": True, }, - UnitTypeId.CREEPTUMORQUEEN: { - 'ability': AbilityId.BUILD_CREEPTUMOR_QUEEN, - 'requires_placement_position': True - } }, - UnitTypeId.RAVEN: { - UnitTypeId.AUTOTURRET: { - 'ability': AbilityId.BUILDAUTOTURRET_AUTOTURRET - } + UnitTypeId.QUEEN: { + UnitTypeId.CREEPTUMOR: {"ability": AbilityId.BUILD_CREEPTUMOR, "requires_placement_position": True}, + UnitTypeId.CREEPTUMORQUEEN: {"ability": AbilityId.BUILD_CREEPTUMOR_QUEEN, "requires_placement_position": True}, }, + UnitTypeId.RAVEN: {UnitTypeId.AUTOTURRET: {"ability": AbilityId.BUILDAUTOTURRET_AUTOTURRET}}, UnitTypeId.ROACH: { - UnitTypeId.RAVAGER: { - 'ability': AbilityId.MORPHTORAVAGER_RAVAGER, - 'required_building': UnitTypeId.HATCHERY - } + UnitTypeId.RAVAGER: {"ability": AbilityId.MORPHTORAVAGER_RAVAGER, "required_building": UnitTypeId.HATCHERY} }, UnitTypeId.ROBOTICSFACILITY: { UnitTypeId.COLOSSUS: { - 'ability': AbilityId.ROBOTICSFACILITYTRAIN_COLOSSUS, - 'required_building': UnitTypeId.ROBOTICSBAY, - 'requires_power': True + "ability": AbilityId.ROBOTICSFACILITYTRAIN_COLOSSUS, + "required_building": UnitTypeId.ROBOTICSBAY, + "requires_power": True, }, UnitTypeId.DISRUPTOR: { - 'ability': AbilityId.TRAIN_DISRUPTOR, - 'required_building': UnitTypeId.ROBOTICSBAY, - 'requires_power': True - }, - UnitTypeId.IMMORTAL: { - 'ability': AbilityId.ROBOTICSFACILITYTRAIN_IMMORTAL, - 'requires_power': True + "ability": AbilityId.TRAIN_DISRUPTOR, + "required_building": UnitTypeId.ROBOTICSBAY, + "requires_power": True, }, - UnitTypeId.OBSERVER: { - 'ability': AbilityId.ROBOTICSFACILITYTRAIN_OBSERVER, - 'requires_power': True - }, - UnitTypeId.WARPPRISM: { - 'ability': AbilityId.ROBOTICSFACILITYTRAIN_WARPPRISM, - 'requires_power': True - } + UnitTypeId.IMMORTAL: {"ability": AbilityId.ROBOTICSFACILITYTRAIN_IMMORTAL, "requires_power": True}, + UnitTypeId.OBSERVER: {"ability": AbilityId.ROBOTICSFACILITYTRAIN_OBSERVER, "requires_power": True}, + UnitTypeId.WARPPRISM: {"ability": AbilityId.ROBOTICSFACILITYTRAIN_WARPPRISM, "requires_power": True}, }, UnitTypeId.SCV: { UnitTypeId.ARMORY: { - 'ability': AbilityId.TERRANBUILD_ARMORY, - 'required_building': UnitTypeId.FACTORY, - 'requires_placement_position': True + "ability": AbilityId.TERRANBUILD_ARMORY, + "required_building": UnitTypeId.FACTORY, + "requires_placement_position": True, }, UnitTypeId.BARRACKS: { - 'ability': AbilityId.TERRANBUILD_BARRACKS, - 'required_building': UnitTypeId.SUPPLYDEPOT, - 'requires_placement_position': True + "ability": AbilityId.TERRANBUILD_BARRACKS, + "required_building": UnitTypeId.SUPPLYDEPOT, + "requires_placement_position": True, }, UnitTypeId.BUNKER: { - 'ability': AbilityId.TERRANBUILD_BUNKER, - 'required_building': UnitTypeId.BARRACKS, - 'requires_placement_position': True - }, - UnitTypeId.COMMANDCENTER: { - 'ability': AbilityId.TERRANBUILD_COMMANDCENTER, - 'requires_placement_position': True + "ability": AbilityId.TERRANBUILD_BUNKER, + "required_building": UnitTypeId.BARRACKS, + "requires_placement_position": True, }, + UnitTypeId.COMMANDCENTER: {"ability": AbilityId.TERRANBUILD_COMMANDCENTER, "requires_placement_position": True}, UnitTypeId.ENGINEERINGBAY: { - 'ability': AbilityId.TERRANBUILD_ENGINEERINGBAY, - 'required_building': UnitTypeId.COMMANDCENTER, - 'requires_placement_position': True + "ability": AbilityId.TERRANBUILD_ENGINEERINGBAY, + "required_building": UnitTypeId.COMMANDCENTER, + "requires_placement_position": True, }, UnitTypeId.FACTORY: { - 'ability': AbilityId.TERRANBUILD_FACTORY, - 'required_building': UnitTypeId.BARRACKS, - 'requires_placement_position': True + "ability": AbilityId.TERRANBUILD_FACTORY, + "required_building": UnitTypeId.BARRACKS, + "requires_placement_position": True, }, UnitTypeId.FUSIONCORE: { - 'ability': AbilityId.TERRANBUILD_FUSIONCORE, - 'required_building': UnitTypeId.STARPORT, - 'requires_placement_position': True + "ability": AbilityId.TERRANBUILD_FUSIONCORE, + "required_building": UnitTypeId.STARPORT, + "requires_placement_position": True, }, UnitTypeId.GHOSTACADEMY: { - 'ability': AbilityId.TERRANBUILD_GHOSTACADEMY, - 'required_building': UnitTypeId.BARRACKS, - 'requires_placement_position': True + "ability": AbilityId.TERRANBUILD_GHOSTACADEMY, + "required_building": UnitTypeId.BARRACKS, + "requires_placement_position": True, }, UnitTypeId.MISSILETURRET: { - 'ability': AbilityId.TERRANBUILD_MISSILETURRET, - 'required_building': UnitTypeId.ENGINEERINGBAY, - 'requires_placement_position': True - }, - UnitTypeId.REFINERY: { - 'ability': AbilityId.TERRANBUILD_REFINERY + "ability": AbilityId.TERRANBUILD_MISSILETURRET, + "required_building": UnitTypeId.ENGINEERINGBAY, + "requires_placement_position": True, }, + UnitTypeId.REFINERY: {"ability": AbilityId.TERRANBUILD_REFINERY}, UnitTypeId.SENSORTOWER: { - 'ability': AbilityId.TERRANBUILD_SENSORTOWER, - 'required_building': UnitTypeId.ENGINEERINGBAY, - 'requires_placement_position': True + "ability": AbilityId.TERRANBUILD_SENSORTOWER, + "required_building": UnitTypeId.ENGINEERINGBAY, + "requires_placement_position": True, }, UnitTypeId.STARPORT: { - 'ability': AbilityId.TERRANBUILD_STARPORT, - 'required_building': UnitTypeId.FACTORY, - 'requires_placement_position': True + "ability": AbilityId.TERRANBUILD_STARPORT, + "required_building": UnitTypeId.FACTORY, + "requires_placement_position": True, }, - UnitTypeId.SUPPLYDEPOT: { - 'ability': AbilityId.TERRANBUILD_SUPPLYDEPOT, - 'requires_placement_position': True - } + UnitTypeId.SUPPLYDEPOT: {"ability": AbilityId.TERRANBUILD_SUPPLYDEPOT, "requires_placement_position": True}, }, UnitTypeId.SPIRE: { UnitTypeId.GREATERSPIRE: { - 'ability': AbilityId.UPGRADETOGREATERSPIRE_GREATERSPIRE, - 'required_building': UnitTypeId.HIVE + "ability": AbilityId.UPGRADETOGREATERSPIRE_GREATERSPIRE, + "required_building": UnitTypeId.HIVE, } }, UnitTypeId.STARGATE: { UnitTypeId.CARRIER: { - 'ability': AbilityId.STARGATETRAIN_CARRIER, - 'required_building': UnitTypeId.FLEETBEACON, - 'requires_power': True - }, - UnitTypeId.ORACLE: { - 'ability': AbilityId.STARGATETRAIN_ORACLE, - 'requires_power': True - }, - UnitTypeId.PHOENIX: { - 'ability': AbilityId.STARGATETRAIN_PHOENIX, - 'requires_power': True + "ability": AbilityId.STARGATETRAIN_CARRIER, + "required_building": UnitTypeId.FLEETBEACON, + "requires_power": True, }, + UnitTypeId.ORACLE: {"ability": AbilityId.STARGATETRAIN_ORACLE, "requires_power": True}, + UnitTypeId.PHOENIX: {"ability": AbilityId.STARGATETRAIN_PHOENIX, "requires_power": True}, UnitTypeId.TEMPEST: { - 'ability': AbilityId.STARGATETRAIN_TEMPEST, - 'required_building': UnitTypeId.FLEETBEACON, - 'requires_power': True + "ability": AbilityId.STARGATETRAIN_TEMPEST, + "required_building": UnitTypeId.FLEETBEACON, + "requires_power": True, }, - UnitTypeId.VOIDRAY: { - 'ability': AbilityId.STARGATETRAIN_VOIDRAY, - 'requires_power': True - } + UnitTypeId.VOIDRAY: {"ability": AbilityId.STARGATETRAIN_VOIDRAY, "requires_power": True}, }, UnitTypeId.STARPORT: { - UnitTypeId.BANSHEE: { - 'ability': AbilityId.STARPORTTRAIN_BANSHEE, - 'requires_techlab': True - }, + UnitTypeId.BANSHEE: {"ability": AbilityId.STARPORTTRAIN_BANSHEE, "requires_techlab": True}, UnitTypeId.BATTLECRUISER: { - 'ability': AbilityId.STARPORTTRAIN_BATTLECRUISER, - 'requires_techlab': True, - 'required_building': UnitTypeId.FUSIONCORE - }, - UnitTypeId.LIBERATOR: { - 'ability': AbilityId.STARPORTTRAIN_LIBERATOR + "ability": AbilityId.STARPORTTRAIN_BATTLECRUISER, + "requires_techlab": True, + "required_building": UnitTypeId.FUSIONCORE, }, - UnitTypeId.MEDIVAC: { - 'ability': AbilityId.STARPORTTRAIN_MEDIVAC - }, - UnitTypeId.RAVEN: { - 'ability': AbilityId.STARPORTTRAIN_RAVEN, - 'requires_techlab': True - }, - UnitTypeId.VIKINGFIGHTER: { - 'ability': AbilityId.STARPORTTRAIN_VIKINGFIGHTER - } - }, - UnitTypeId.SWARMHOSTBURROWEDMP: { - UnitTypeId.LOCUSTMPFLYING: { - 'ability': AbilityId.EFFECT_SPAWNLOCUSTS - } - }, - UnitTypeId.SWARMHOSTMP: { - UnitTypeId.LOCUSTMPFLYING: { - 'ability': AbilityId.EFFECT_SPAWNLOCUSTS - } + UnitTypeId.LIBERATOR: {"ability": AbilityId.STARPORTTRAIN_LIBERATOR}, + UnitTypeId.MEDIVAC: {"ability": AbilityId.STARPORTTRAIN_MEDIVAC}, + UnitTypeId.RAVEN: {"ability": AbilityId.STARPORTTRAIN_RAVEN, "requires_techlab": True}, + UnitTypeId.VIKINGFIGHTER: {"ability": AbilityId.STARPORTTRAIN_VIKINGFIGHTER}, }, + UnitTypeId.SWARMHOSTBURROWEDMP: {UnitTypeId.LOCUSTMPFLYING: {"ability": AbilityId.EFFECT_SPAWNLOCUSTS}}, + UnitTypeId.SWARMHOSTMP: {UnitTypeId.LOCUSTMPFLYING: {"ability": AbilityId.EFFECT_SPAWNLOCUSTS}}, UnitTypeId.WARPGATE: { UnitTypeId.ADEPT: { - 'ability': AbilityId.TRAINWARP_ADEPT, - 'required_building': UnitTypeId.CYBERNETICSCORE, - 'requires_placement_position': True, - 'requires_power': True + "ability": AbilityId.TRAINWARP_ADEPT, + "required_building": UnitTypeId.CYBERNETICSCORE, + "requires_placement_position": True, + "requires_power": True, }, UnitTypeId.DARKTEMPLAR: { - 'ability': AbilityId.WARPGATETRAIN_DARKTEMPLAR, - 'required_building': UnitTypeId.DARKSHRINE, - 'requires_placement_position': True, - 'requires_power': True + "ability": AbilityId.WARPGATETRAIN_DARKTEMPLAR, + "required_building": UnitTypeId.DARKSHRINE, + "requires_placement_position": True, + "requires_power": True, }, UnitTypeId.HIGHTEMPLAR: { - 'ability': AbilityId.WARPGATETRAIN_HIGHTEMPLAR, - 'required_building': UnitTypeId.TEMPLARARCHIVE, - 'requires_placement_position': True, - 'requires_power': True + "ability": AbilityId.WARPGATETRAIN_HIGHTEMPLAR, + "required_building": UnitTypeId.TEMPLARARCHIVE, + "requires_placement_position": True, + "requires_power": True, }, UnitTypeId.SENTRY: { - 'ability': AbilityId.WARPGATETRAIN_SENTRY, - 'required_building': UnitTypeId.CYBERNETICSCORE, - 'requires_placement_position': True, - 'requires_power': True + "ability": AbilityId.WARPGATETRAIN_SENTRY, + "required_building": UnitTypeId.CYBERNETICSCORE, + "requires_placement_position": True, + "requires_power": True, }, UnitTypeId.STALKER: { - 'ability': AbilityId.WARPGATETRAIN_STALKER, - 'required_building': UnitTypeId.CYBERNETICSCORE, - 'requires_placement_position': True, - 'requires_power': True + "ability": AbilityId.WARPGATETRAIN_STALKER, + "required_building": UnitTypeId.CYBERNETICSCORE, + "requires_placement_position": True, + "requires_power": True, }, UnitTypeId.ZEALOT: { - 'ability': AbilityId.WARPGATETRAIN_ZEALOT, - 'requires_placement_position': True, - 'requires_power': True - } + "ability": AbilityId.WARPGATETRAIN_ZEALOT, + "requires_placement_position": True, + "requires_power": True, + }, }, UnitTypeId.ZERGLING: { UnitTypeId.BANELING: { - 'ability': AbilityId.MORPHTOBANELING_BANELING, - 'required_building': UnitTypeId.BANELINGNEST + "ability": AbilityId.MORPHTOBANELING_BANELING, + "required_building": UnitTypeId.BANELINGNEST, } - } + }, } diff --git a/sc2/dicts/unit_trained_from.py b/sc2/dicts/unit_trained_from.py index 6c734e0a..43febe47 100644 --- a/sc2/dicts/unit_trained_from.py +++ b/sc2/dicts/unit_trained_from.py @@ -1,14 +1,12 @@ # THIS FILE WAS AUTOMATICALLY GENERATED BY "generate_dicts_from_data_json.py" DO NOT CHANGE MANUALLY! # ANY CHANGE WILL BE OVERWRITTEN -from typing import Dict, Set - from sc2.ids.unit_typeid import UnitTypeId - # from sc2.ids.buff_id import BuffId # from sc2.ids.effect_id import EffectId -UNIT_TRAINED_FROM: Dict[UnitTypeId, Set[UnitTypeId]] = { + +UNIT_TRAINED_FROM: dict[UnitTypeId, set[UnitTypeId]] = { UnitTypeId.ADEPT: {UnitTypeId.GATEWAY, UnitTypeId.WARPGATE}, UnitTypeId.ARMORY: {UnitTypeId.SCV}, UnitTypeId.ASSIMILATOR: {UnitTypeId.PROBE}, @@ -115,5 +113,5 @@ UnitTypeId.WARPPRISM: {UnitTypeId.ROBOTICSFACILITY}, UnitTypeId.WIDOWMINE: {UnitTypeId.FACTORY}, UnitTypeId.ZEALOT: {UnitTypeId.GATEWAY, UnitTypeId.WARPGATE}, - UnitTypeId.ZERGLING: {UnitTypeId.LARVA} + UnitTypeId.ZERGLING: {UnitTypeId.LARVA}, } diff --git a/sc2/dicts/unit_unit_alias.py b/sc2/dicts/unit_unit_alias.py index a0d03b6c..f74cc825 100644 --- a/sc2/dicts/unit_unit_alias.py +++ b/sc2/dicts/unit_unit_alias.py @@ -1,14 +1,12 @@ # THIS FILE WAS AUTOMATICALLY GENERATED BY "generate_dicts_from_data_json.py" DO NOT CHANGE MANUALLY! # ANY CHANGE WILL BE OVERWRITTEN -from typing import Dict - from sc2.ids.unit_typeid import UnitTypeId - # from sc2.ids.buff_id import BuffId # from sc2.ids.effect_id import EffectId -UNIT_UNIT_ALIAS: Dict[UnitTypeId, UnitTypeId] = { + +UNIT_UNIT_ALIAS: dict[UnitTypeId, UnitTypeId] = { UnitTypeId.ADEPTPHASESHIFT: UnitTypeId.ADEPT, UnitTypeId.BANELINGBURROWED: UnitTypeId.BANELING, UnitTypeId.BARRACKSFLYING: UnitTypeId.BARRACKS, @@ -48,5 +46,5 @@ UnitTypeId.VIKINGASSAULT: UnitTypeId.VIKINGFIGHTER, UnitTypeId.WARPPRISMPHASING: UnitTypeId.WARPPRISM, UnitTypeId.WIDOWMINEBURROWED: UnitTypeId.WIDOWMINE, - UnitTypeId.ZERGLINGBURROWED: UnitTypeId.ZERGLING + UnitTypeId.ZERGLINGBURROWED: UnitTypeId.ZERGLING, } diff --git a/sc2/dicts/upgrade_researched_from.py b/sc2/dicts/upgrade_researched_from.py index 280d41c9..84cef804 100644 --- a/sc2/dicts/upgrade_researched_from.py +++ b/sc2/dicts/upgrade_researched_from.py @@ -1,15 +1,13 @@ # THIS FILE WAS AUTOMATICALLY GENERATED BY "generate_dicts_from_data_json.py" DO NOT CHANGE MANUALLY! # ANY CHANGE WILL BE OVERWRITTEN -from typing import Dict - from sc2.ids.unit_typeid import UnitTypeId from sc2.ids.upgrade_id import UpgradeId - # from sc2.ids.buff_id import BuffId # from sc2.ids.effect_id import EffectId -UPGRADE_RESEARCHED_FROM: Dict[UpgradeId, UnitTypeId] = { + +UPGRADE_RESEARCHED_FROM: dict[UpgradeId, UnitTypeId] = { UpgradeId.ADEPTPIERCINGATTACK: UnitTypeId.TWILIGHTCOUNCIL, UpgradeId.ANABOLICSYNTHESIS: UnitTypeId.ULTRALISKCAVERN, UpgradeId.BANSHEECLOAK: UnitTypeId.STARPORTTECHLAB, @@ -96,5 +94,5 @@ UpgradeId.ZERGMELEEWEAPONSLEVEL3: UnitTypeId.EVOLUTIONCHAMBER, UpgradeId.ZERGMISSILEWEAPONSLEVEL1: UnitTypeId.EVOLUTIONCHAMBER, UpgradeId.ZERGMISSILEWEAPONSLEVEL2: UnitTypeId.EVOLUTIONCHAMBER, - UpgradeId.ZERGMISSILEWEAPONSLEVEL3: UnitTypeId.EVOLUTIONCHAMBER + UpgradeId.ZERGMISSILEWEAPONSLEVEL3: UnitTypeId.EVOLUTIONCHAMBER, } diff --git a/sc2/expiring_dict.py b/sc2/expiring_dict.py index 92d3656f..ebbcb23c 100644 --- a/sc2/expiring_dict.py +++ b/sc2/expiring_dict.py @@ -1,8 +1,10 @@ +# pyre-ignore-all-errors[14, 15, 58] from __future__ import annotations from collections import OrderedDict +from collections.abc import Iterable from threading import RLock -from typing import TYPE_CHECKING, Any, Iterable, Union +from typing import TYPE_CHECKING, Any if TYPE_CHECKING: from sc2.bot_ai import BotAI @@ -29,21 +31,22 @@ async def on_step(iteration: int): print("test is not anymore in dict") """ - def __init__(self, bot: BotAI, max_age_frames: int = 1): + def __init__(self, bot: BotAI, max_age_frames: int = 1) -> None: assert max_age_frames >= -1 assert bot OrderedDict.__init__(self) self.bot: BotAI = bot - self.max_age: Union[int, float] = max_age_frames + self.max_age: int | float = max_age_frames self.lock: RLock = RLock() @property def frame(self) -> int: + # pyre-ignore[16] return self.bot.state.game_loop def __contains__(self, key) -> bool: - """ Return True if dict has key, else False, e.g. 'key in dict' """ + """Return True if dict has key, else False, e.g. 'key in dict'""" with self.lock: if OrderedDict.__contains__(self, key): # Each item is a list of [value, frame time] @@ -53,8 +56,8 @@ def __contains__(self, key) -> bool: del self[key] return False - def __getitem__(self, key, with_age=False) -> Any: - """ Return the item of the dict using d[key] """ + def __getitem__(self, key, with_age: bool = False) -> Any: + """Return the item of the dict using d[key]""" with self.lock: # Each item is a list of [value, frame time] item = OrderedDict.__getitem__(self, key) @@ -65,13 +68,13 @@ def __getitem__(self, key, with_age=False) -> Any: OrderedDict.__delitem__(self, key) raise KeyError(key) - def __setitem__(self, key, value): - """ Set d[key] = value """ + def __setitem__(self, key, value) -> None: + """Set d[key] = value""" with self.lock: OrderedDict.__setitem__(self, key, (value, self.frame)) - def __repr__(self): - """ Printable version of the dict instead of getting memory adress """ + def __repr__(self) -> str: + """Printable version of the dict instead of getting memory adress""" print_list = [] with self.lock: for key, value in OrderedDict.items(self): @@ -84,12 +87,12 @@ def __str__(self): return self.__repr__() def __iter__(self): - """ Override 'for key in dict:' """ + """Override 'for key in dict:'""" with self.lock: return self.keys() # TODO find a way to improve len - def __len__(self): + def __len__(self) -> int: """Override len method as key value pairs aren't instantly being deleted, but only on __get__(item). This function is slow because it has to check if each element is not expired yet.""" with self.lock: @@ -98,8 +101,8 @@ def __len__(self): count += 1 return count - def pop(self, key, default=None, with_age=False): - """ Return the item and remove it """ + def pop(self, key, default=None, with_age: bool = False): + """Return the item and remove it""" with self.lock: if OrderedDict.__contains__(self, key): item = OrderedDict.__getitem__(self, key) @@ -115,8 +118,8 @@ def pop(self, key, default=None, with_age=False): return default, self.frame return default - def get(self, key, default=None, with_age=False): - """ Return the value for key if key is in dict, else default """ + def get(self, key, default=None, with_age: bool = False): + """Return the value for key if key is in dict, else default""" with self.lock: if OrderedDict.__contains__(self, key): item = OrderedDict.__getitem__(self, key) @@ -131,27 +134,27 @@ def get(self, key, default=None, with_age=False): return None return None - def update(self, other_dict: dict): + def update(self, other_dict: dict) -> None: with self.lock: for key, value in other_dict.items(): self[key] = value def items(self) -> Iterable: - """ Return iterator of zipped list [keys, values] """ + """Return iterator of zipped list [keys, values]""" with self.lock: for key, value in OrderedDict.items(self): if self.frame - value[1] < self.max_age: yield key, value[0] def keys(self) -> Iterable: - """ Return iterator of keys """ + """Return iterator of keys""" with self.lock: for key, value in OrderedDict.items(self): if self.frame - value[1] < self.max_age: yield key def values(self) -> Iterable: - """ Return iterator of values """ + """Return iterator of values""" with self.lock: for value in OrderedDict.values(self): if self.frame - value[1] < self.max_age: diff --git a/sc2/game_data.py b/sc2/game_data.py index b4968b6a..3bc4fc78 100644 --- a/sc2/game_data.py +++ b/sc2/game_data.py @@ -1,11 +1,10 @@ -# pylint: disable=W0212 +# pyre-ignore-all-errors[29] from __future__ import annotations from bisect import bisect_left from contextlib import suppress from dataclasses import dataclass from functools import lru_cache -from typing import Dict, List, Optional, Union from sc2.data import Attribute, Race from sc2.ids.ability_id import AbilityId @@ -21,22 +20,20 @@ class GameData: - - def __init__(self, data): + def __init__(self, data) -> None: """ :param data: """ - ids = set(a.value for a in AbilityId if a.value != 0) - self.abilities: Dict[int, AbilityData] = { - a.ability_id: AbilityData(self, a) - for a in data.abilities if a.ability_id in ids + ids = {a.value for a in AbilityId if a.value != 0} + self.abilities: dict[int, AbilityData] = { + a.ability_id: AbilityData(self, a) for a in data.abilities if a.ability_id in ids } - self.units: Dict[int, UnitTypeData] = {u.unit_id: UnitTypeData(self, u) for u in data.units if u.available} - self.upgrades: Dict[int, UpgradeData] = {u.upgrade_id: UpgradeData(self, u) for u in data.upgrades} + self.units: dict[int, UnitTypeData] = {u.unit_id: UnitTypeData(self, u) for u in data.units if u.available} + self.upgrades: dict[int, UpgradeData] = {u.upgrade_id: UpgradeData(self, u) for u in data.upgrades} # Cached UnitTypeIds so that conversion does not take long. This needs to be moved elsewhere if a new GameData object is created multiple times per game @lru_cache(maxsize=256) - def calculate_ability_cost(self, ability: Union[AbilityData, AbilityId, UnitCommand]) -> Cost: + def calculate_ability_cost(self, ability: AbilityData | AbilityId | UnitCommand) -> Cost: if isinstance(ability, AbilityId): ability = self.abilities[ability.value] elif isinstance(ability, UnitCommand): @@ -51,6 +48,7 @@ def calculate_ability_cost(self, ability: Union[AbilityData, AbilityId, UnitComm if not AbilityData.id_exists(unit.creation_ability.id.value): continue + # pyre-ignore[16] if unit.creation_ability.is_free_morph: continue @@ -76,8 +74,7 @@ def calculate_ability_cost(self, ability: Union[AbilityData, AbilityId, UnitComm class AbilityData: - - ability_ids: List[int] = [ability_id.value for ability_id in AbilityId][1:] # sorted list + ability_ids: list[int] = [ability_id.value for ability_id in AbilityId][1:] # sorted list @classmethod def id_exists(cls, ability_id): @@ -87,7 +84,7 @@ def id_exists(cls, ability_id): i = bisect_left(cls.ability_ids, ability_id) # quick binary search return i != len(cls.ability_ids) and cls.ability_ids[i] == ability_id - def __init__(self, game_data, proto): + def __init__(self, game_data, proto) -> None: self._game_data = game_data self._proto = proto @@ -99,29 +96,29 @@ def __repr__(self) -> str: @property def id(self) -> AbilityId: - """ Returns the generic remap ID. See sc2/dicts/generic_redirect_abilities.py """ + """Returns the generic remap ID. See sc2/dicts/generic_redirect_abilities.py""" if self._proto.remaps_to_ability_id: return AbilityId(self._proto.remaps_to_ability_id) return AbilityId(self._proto.ability_id) @property def exact_id(self) -> AbilityId: - """ Returns the exact ID of the ability """ + """Returns the exact ID of the ability""" return AbilityId(self._proto.ability_id) @property def link_name(self) -> str: - """ For Stimpack this returns 'BarracksTechLabResearch' """ + """For Stimpack this returns 'BarracksTechLabResearch'""" return self._proto.link_name @property def button_name(self) -> str: - """ For Stimpack this returns 'Stimpack' """ + """For Stimpack this returns 'Stimpack'""" return self._proto.button_name @property def friendly_name(self) -> str: - """ For Stimpack this returns 'Research Stimpack' """ + """For Stimpack this returns 'Research Stimpack'""" return self._proto.friendly_name @property @@ -134,8 +131,7 @@ def cost(self) -> Cost: class UnitTypeData: - - def __init__(self, game_data: GameData, proto): + def __init__(self, game_data: GameData, proto) -> None: """ :param game_data: :param proto: @@ -161,7 +157,7 @@ def name(self) -> str: return self._proto.name @property - def creation_ability(self) -> Optional[AbilityData]: + def creation_ability(self) -> AbilityData | None: if self._proto.ability_id == 0: return None if self._proto.ability_id not in self._game_data.abilities: @@ -169,17 +165,19 @@ def creation_ability(self) -> Optional[AbilityData]: return self._game_data.abilities[self._proto.ability_id] @property - def footprint_radius(self) -> Optional[float]: - """ See unit.py footprint_radius """ + def footprint_radius(self) -> float | None: + """See unit.py footprint_radius""" if self.creation_ability is None: return None return self.creation_ability._proto.footprint_radius @property - def attributes(self) -> List[Attribute]: + # pyre-ignore[11] + def attributes(self) -> list[Attribute]: return self._proto.attributes def has_attribute(self, attr) -> bool: + # pyre-ignore[6] assert isinstance(attr, Attribute) return attr in self.attributes @@ -193,12 +191,12 @@ def has_vespene(self) -> bool: @property def cargo_size(self) -> int: - """ How much cargo this unit uses up in cargo_space """ + """How much cargo this unit uses up in cargo_space""" return self._proto.cargo_size @property - def tech_requirement(self) -> Optional[UnitTypeId]: - """ Tech-building requirement of buildings - may work for units but unreliably """ + def tech_requirement(self) -> UnitTypeId | None: + """Tech-building requirement of buildings - may work for units but unreliably""" if self._proto.tech_requirement == 0: return None if self._proto.tech_requirement not in self._game_data.units: @@ -206,7 +204,7 @@ def tech_requirement(self) -> Optional[UnitTypeId]: return UnitTypeId(self._proto.tech_requirement) @property - def tech_alias(self) -> Optional[List[UnitTypeId]]: + def tech_alias(self) -> list[UnitTypeId] | None: """Building tech equality, e.g. OrbitalCommand is the same as CommandCenter Building tech equality, e.g. Hive is the same as Lair and Hatchery For Hive, this returns [UnitTypeId.Hatchery, UnitTypeId.Lair] @@ -217,8 +215,8 @@ def tech_alias(self) -> Optional[List[UnitTypeId]]: return return_list if return_list else None @property - def unit_alias(self) -> Optional[UnitTypeId]: - """ Building type equality, e.g. FlyingOrbitalCommand is the same as OrbitalCommand """ + def unit_alias(self) -> UnitTypeId | None: + """Building type equality, e.g. FlyingOrbitalCommand is the same as OrbitalCommand""" if self._proto.unit_alias == 0: return None if self._proto.unit_alias not in self._game_data.units: @@ -227,6 +225,7 @@ def unit_alias(self) -> Optional[UnitTypeId]: return UnitTypeId(self._proto.unit_alias) @property + # pyre-ignore[11] def race(self) -> Race: return Race(self._proto.race) @@ -236,14 +235,15 @@ def cost(self) -> Cost: @property def cost_zerg_corrected(self) -> Cost: - """ This returns 25 for extractor and 200 for spawning pool instead of 75 and 250 respectively """ + """This returns 25 for extractor and 200 for spawning pool instead of 75 and 250 respectively""" + # pyre-ignore[16] if self.race == Race.Zerg and Attribute.Structure.value in self.attributes: return Cost(self._proto.mineral_cost - 50, self._proto.vespene_cost, self._proto.build_time) return self.cost @property - def morph_cost(self) -> Optional[Cost]: - """ This returns 150 minerals for OrbitalCommand instead of 550 """ + def morph_cost(self) -> Cost | None: + """This returns 150 minerals for OrbitalCommand instead of 550""" # Morphing units supply_cost = self._proto.food_required if supply_cost > 0 and self.id in UNIT_TRAINED_FROM and len(UNIT_TRAINED_FROM[self.id]) == 1: @@ -268,7 +268,9 @@ def morph_cost(self) -> Optional[Cost]: 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 for tech_alias in self.tech_alias + self._game_data.units[tech_alias.value].cost.vespene + # pyre-ignore[16] + for tech_alias in self.tech_alias ) return Cost( self._proto.mineral_cost - tech_alias_cost_minerals, @@ -278,8 +280,7 @@ def morph_cost(self) -> Optional[Cost]: class UpgradeData: - - def __init__(self, game_data: GameData, proto): + def __init__(self, game_data: GameData, proto) -> None: """ :param game_data: :param proto: @@ -287,7 +288,7 @@ def __init__(self, game_data: GameData, proto): self._game_data = game_data self._proto = proto - def __repr__(self): + def __repr__(self) -> str: return f"UpgradeData({self.name} - research ability: {self.research_ability}, {self.cost})" @property @@ -295,7 +296,7 @@ def name(self) -> str: return self._proto.name @property - def research_ability(self) -> Optional[AbilityData]: + def research_ability(self) -> AbilityData | None: if self._proto.ability_id == 0: return None if self._proto.ability_id not in self._game_data.abilities: @@ -313,9 +314,10 @@ class Cost: The cost of an action, a structure, a unit or a research upgrade. The time is given in frames (22.4 frames per game second). """ + minerals: int vespene: int - time: Optional[float] = None + time: float | None = None def __repr__(self) -> str: return f"Cost({self.minerals}, {self.vespene})" @@ -329,7 +331,7 @@ def __ne__(self, other: Cost) -> bool: def __bool__(self) -> bool: return self.minerals != 0 or self.vespene != 0 - def __add__(self, other) -> Cost: + def __add__(self, other: Cost) -> Cost: if not other: return self if not self: diff --git a/sc2/game_info.py b/sc2/game_info.py index f4189dc8..a67e45ea 100644 --- a/sc2/game_info.py +++ b/sc2/game_info.py @@ -1,10 +1,11 @@ +# pyre-ignore-all-errors[6, 11, 16, 58] from __future__ import annotations import heapq from collections import deque +from collections.abc import Iterable from dataclasses import dataclass from functools import cached_property -from typing import Deque, Dict, FrozenSet, Iterable, List, Optional, Set, Tuple import numpy as np @@ -15,7 +16,7 @@ @dataclass class Ramp: - points: FrozenSet[Point2] + points: frozenset[Point2] game_info: GameInfo @property @@ -40,8 +41,8 @@ def height_at(self, p: Point2) -> int: return self._height_map[p] @cached_property - def upper(self) -> FrozenSet[Point2]: - """ Returns the upper points of a ramp. """ + def upper(self) -> frozenset[Point2]: + """Returns the upper points of a ramp.""" current_max = -10000 result = set() for p in self.points: @@ -54,8 +55,8 @@ def upper(self) -> FrozenSet[Point2]: return frozenset(result) @cached_property - def upper2_for_ramp_wall(self) -> FrozenSet[Point2]: - """ Returns the 2 upper ramp points of the main base ramp required for the supply depot and barracks placement properties used in this file. """ + def upper2_for_ramp_wall(self) -> frozenset[Point2]: + """Returns the 2 upper ramp points of the main base ramp required for the supply depot and barracks placement properties used in this file.""" # From bottom center, find 2 points that are furthest away (within the same ramp) return frozenset(heapq.nlargest(2, self.upper, key=lambda x: x.distance_to_point2(self.bottom_center))) @@ -66,7 +67,7 @@ def top_center(self) -> Point2: return pos @cached_property - def lower(self) -> FrozenSet[Point2]: + def lower(self) -> frozenset[Point2]: current_min = 10000 result = set() for p in self.points: @@ -85,8 +86,8 @@ def bottom_center(self) -> Point2: return pos @cached_property - def barracks_in_middle(self) -> Optional[Point2]: - """ Barracks position in the middle of the 2 depots """ + def barracks_in_middle(self) -> Point2 | None: + """Barracks position in the middle of the 2 depots""" if len(self.upper) not in {2, 5}: return None if len(self.upper2_for_ramp_wall) == 2: @@ -97,12 +98,12 @@ def barracks_in_middle(self) -> Optional[Point2]: intersects = p1.circle_intersection(p2, 5**0.5) any_lower_point = next(iter(self.lower)) return max(intersects, key=lambda p: p.distance_to_point2(any_lower_point)) - # pylint: disable=broad-exception-raised + raise Exception("Not implemented. Trying to access a ramp that has a wrong amount of upper points.") @cached_property - def depot_in_middle(self) -> Optional[Point2]: - """ Depot in the middle of the 3 depots """ + def depot_in_middle(self) -> Point2 | None: + """Depot in the middle of the 3 depots""" if len(self.upper) not in {2, 5}: return None if len(self.upper2_for_ramp_wall) == 2: @@ -117,12 +118,12 @@ def depot_in_middle(self) -> Optional[Point2]: return None any_lower_point = next(iter(self.lower)) return max(intersects, key=lambda p: p.distance_to_point2(any_lower_point)) - # pylint: disable=broad-exception-raised + raise Exception("Not implemented. Trying to access a ramp that has a wrong amount of upper points.") @cached_property - def corner_depots(self) -> FrozenSet[Point2]: - """ Finds the 2 depot positions on the outside """ + def corner_depots(self) -> frozenset[Point2]: + """Finds the 2 depot positions on the outside""" if not self.upper2_for_ramp_wall: return frozenset() if len(self.upper2_for_ramp_wall) == 2: @@ -136,47 +137,47 @@ def corner_depots(self) -> FrozenSet[Point2]: # Offset from middle depot to corner depots is (2, 1) intersects = center.circle_intersection(depot_position, 5**0.5) return intersects - # pylint: disable=broad-exception-raised + raise Exception("Not implemented. Trying to access a ramp that has a wrong amount of upper points.") @cached_property def barracks_can_fit_addon(self) -> bool: - """ Test if a barracks can fit an addon at natural ramp """ + """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: return self.barracks_in_middle.x + 1 > max(self.corner_depots, key=lambda depot: depot.x).x - # pylint: disable=broad-exception-raised + raise Exception("Not implemented. Trying to access a ramp that has a wrong amount of upper points.") @cached_property - def barracks_correct_placement(self) -> Optional[Point2]: - """ Corrected placement so that an addon can fit """ + def barracks_correct_placement(self) -> Point2 | None: + """Corrected placement so that an addon can fit""" if self.barracks_in_middle is None: return None if len(self.upper2_for_ramp_wall) == 2: if self.barracks_can_fit_addon: return self.barracks_in_middle return self.barracks_in_middle.offset((-2, 0)) - # pylint: disable=broad-exception-raised + raise Exception("Not implemented. Trying to access a ramp that has a wrong amount of upper points.") @cached_property - def protoss_wall_pylon(self) -> Optional[Point2]: + def protoss_wall_pylon(self) -> Point2 | None: """ Pylon position that powers the two wall buildings and the warpin position. """ if len(self.upper) not in {2, 5}: return None if len(self.upper2_for_ramp_wall) != 2: - # pylint: disable=broad-exception-raised 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 direction = self.barracks_in_middle.negative_offset(middle) + # pyre-ignore[7] return middle + 6 * direction @cached_property - def protoss_wall_buildings(self) -> FrozenSet[Point2]: + def protoss_wall_buildings(self) -> frozenset[Point2]: """ List of two positions for 3x3 buildings that form a wall with a spot for a one unit block. These buildings can be powered by a pylon on the protoss_wall_pylon position. @@ -194,11 +195,11 @@ def protoss_wall_buildings(self) -> FrozenSet[Point2]: wall1: Point2 = sorted_depots[1].offset(direction) wall2 = middle + direction + (middle - wall1) / 1.5 return frozenset([wall1, wall2]) - # pylint: disable=broad-exception-raised + raise Exception("Not implemented. Trying to access a ramp that has a wrong amount of upper points.") @cached_property - def protoss_wall_warpin(self) -> Optional[Point2]: + def protoss_wall_warpin(self) -> Point2 | None: """ Position for a unit to block the wall created by protoss_wall_buildings. Powered by protoss_wall_pylon. @@ -206,7 +207,6 @@ def protoss_wall_warpin(self) -> Optional[Point2]: if len(self.upper) not in {2, 5}: return None if len(self.upper2_for_ramp_wall) != 2: - # pylint: disable=broad-exception-raised 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 @@ -217,12 +217,12 @@ def protoss_wall_warpin(self) -> Optional[Point2]: class GameInfo: - - def __init__(self, proto): + def __init__(self, proto) -> None: self._proto = proto - self.players: List[Player] = [Player.from_proto(p) for p in self._proto.player_info] + 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 @@ -233,33 +233,38 @@ def __init__(self, proto): 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 - self.map_ramps: List[Ramp] = None # Filled later by BotAI._prepare_first_step - self.vision_blockers: FrozenSet[Point2] = None # Filled later by BotAI._prepare_first_step - self.player_races: Dict[int, Race] = { - p.player_id: p.race_actual or p.race_requested - for p in self._proto.player_info + # pyre-ignore[8] + self.map_ramps: list[Ramp] = None # Filled later by BotAI._prepare_first_step + # pyre-ignore[8] + self.vision_blockers: frozenset[Point2] = None # Filled later by BotAI._prepare_first_step + self.player_races: dict[int, Race] = { + p.player_id: p.race_actual or p.race_requested for p in self._proto.player_info } - self.start_locations: List[Point2] = [ + self.start_locations: list[Point2] = [ Point2.from_proto(sl).round(decimals=1) for sl in self._proto.start_raw.start_locations ] + # pyre-ignore[8] 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]]: + def _find_ramps_and_vision_blockers(self) -> tuple[list[Ramp], frozenset[Point2]]: """Calculate points that are pathable but not placeable. Then divide them into ramp points if not all points around the points are equal height and into vision blockers if they are.""" def equal_height_around(tile): # mask to slice array 1 around tile - sliced = self.terrain_height.data_numpy[tile[1] - 1:tile[1] + 2, tile[0] - 1:tile[0] + 2] + sliced = self.terrain_height.data_numpy[tile[1] - 1 : tile[1] + 2, tile[0] - 1 : tile[0] + 2] return len(np.unique(sliced)) == 1 map_area = self.playable_area # all points in the playable area that are pathable but not placable points = [ - Point2((a, b)) for (b, a), value in np.ndenumerate(self.pathing_grid.data_numpy) - if value == 1 and map_area.x <= a < map_area.x + map_area.width and map_area.y <= b < map_area.y + - map_area.height and self.placement_grid[(a, b)] == 0 + Point2((a, b)) + for (b, a), value in np.ndenumerate(self.pathing_grid.data_numpy) + if value == 1 + and map_area.x <= a < map_area.x + map_area.width + and map_area.y <= b < map_area.y + map_area.height + and self.placement_grid[(a, b)] == 0 ] # divide points into ramp points and vision blockers ramp_points = [point for point in points if not equal_height_around(point)] @@ -267,7 +272,7 @@ def equal_height_around(tile): ramps = [Ramp(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: frozenset[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. @@ -278,20 +283,20 @@ def _find_groups(self, points: FrozenSet[Point2], minimum_points_per_group: int map_width = self.pathing_grid.width map_height = self.pathing_grid.height current_color: int = NOT_COLORED_YET - picture: List[List[int]] = [[-2 for _ in range(map_width)] for _ in range(map_height)] + picture: list[list[int]] = [[-2 for _ in range(map_width)] for _ in range(map_height)] def paint(pt: Point2) -> None: 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] + 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] - remaining: Set[Point2] = set(points) + remaining: set[Point2] = set(points) for point in remaining: paint(point) current_color = 1 - queue: Deque[Point2] = deque() + queue: deque[Point2] = deque() while remaining: - current_group: Set[Point2] = set() + current_group: set[Point2] = set() if not queue: start = remaining.pop() paint(start) diff --git a/sc2/game_state.py b/sc2/game_state.py index b2dbda44..b17fd12e 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 typing import List, Optional, Set, Union from loguru import logger @@ -25,8 +25,7 @@ class Blip: - - def __init__(self, proto): + def __init__(self, proto) -> None: """ :param proto: """ @@ -83,17 +82,16 @@ class Common: "larva_count", ] - def __init__(self, proto): + def __init__(self, proto) -> None: self._proto = proto - def __getattr__(self, attr): + def __getattr__(self, attr) -> int: assert attr in self.ATTRIBUTES, f"'{attr}' is not a valid attribute" return int(getattr(self._proto, attr)) class EffectData: - - def __init__(self, proto, fake=False): + def __init__(self, proto, fake: bool = False) -> None: """ :param proto: :param fake: @@ -102,14 +100,14 @@ def __init__(self, proto, fake=False): self.fake = fake @property - def id(self) -> Union[EffectId, str]: + def id(self) -> EffectId | str: if self.fake: # Returns the string from constants.py, e.g. "KD8CHARGE" return FakeEffectID[self._proto.unit_type] return EffectId(self._proto.effect_id) @property - def positions(self) -> Set[Point2]: + def positions(self) -> set[Point2]: if self.fake: return {Point2.from_proto(self._proto.pos)} return {Point2.from_proto(p) for p in self._proto.pos} @@ -120,12 +118,12 @@ def alliance(self) -> Alliance: @property def is_mine(self) -> bool: - """ Checks if the effect is caused by me. """ + """Checks if the effect is caused by me.""" return self._proto.alliance == IS_MINE @property def is_enemy(self) -> bool: - """ Checks if the effect is hostile. """ + """Checks if the effect is hostile.""" return self._proto.alliance == IS_ENEMY @property @@ -150,7 +148,6 @@ class ChatMessage: @dataclass class AbilityLookupTemplateClass: - @property def exact_id(self) -> AbilityId: return AbilityId(self.ability_id) @@ -167,17 +164,17 @@ def generic_id(self) -> AbilityId: class ActionRawUnitCommand(AbilityLookupTemplateClass): game_loop: int ability_id: int - unit_tags: List[int] + unit_tags: list[int] queue_command: bool - target_world_space_pos: Optional[Point2] - target_unit_tag: Optional[int] = None + target_world_space_pos: Point2 | None + target_unit_tag: int | None = None @dataclass class ActionRawToggleAutocast(AbilityLookupTemplateClass): game_loop: int ability_id: int - unit_tags: List[int] + unit_tags: list[int] @dataclass @@ -194,8 +191,7 @@ class ActionError(AbilityLookupTemplateClass): class GameState: - - def __init__(self, response_observation, previous_observation=None): + def __init__(self, response_observation, previous_observation=None) -> None: """ :param response_observation: :param previous_observation: @@ -218,7 +214,7 @@ def __init__(self, response_observation, previous_observation=None): # https://github.com/Blizzard/s2client-proto/blob/33f0ecf615aa06ca845ffe4739ef3133f37265a9/s2clientprotocol/score.proto#L31 self.score: ScoreDetails = ScoreDetails(self.observation.score) self.abilities = self.observation.abilities # abilities of selected units - self.upgrades: Set[UpgradeId] = {UpgradeId(upgrade) for upgrade in self.observation_raw.player.upgrade_ids} + self.upgrades: set[UpgradeId] = {UpgradeId(upgrade) for upgrade in self.observation_raw.player.upgrade_ids} # self.visibility[point]: 0=Hidden, 1=Fogged, 2=Visible self.visibility: PixelMap = PixelMap(self.observation_raw.map_state.visibility) @@ -226,7 +222,7 @@ def __init__(self, response_observation, previous_observation=None): self.creep: PixelMap = PixelMap(self.observation_raw.map_state.creep, in_bits=True) # Effects like ravager bile shot, lurker attack, everything in effect_id.py - self.effects: Set[EffectData] = {EffectData(effect) for effect in self.observation_raw.effects} + self.effects: set[EffectData] = {EffectData(effect) for effect in self.observation_raw.effects} """ Usage: for effect in self.state.effects: if effect.id == EffectId.RAVAGERCORROSIVEBILECP: @@ -235,15 +231,15 @@ def __init__(self, response_observation, previous_observation=None): """ @cached_property - def dead_units(self) -> Set[int]: - """ A set of unit tags that died this frame """ + def dead_units(self) -> set[int]: + """A set of unit tags that died this frame""" _dead_units = set(self.observation_raw.event.dead_units) if self.previous_observation: return _dead_units | set(self.previous_observation.observation.raw_data.event.dead_units) return _dead_units @cached_property - def chat(self) -> List[ChatMessage]: + def chat(self) -> list[ChatMessage]: """List of chat messages sent this frame (by either player).""" previous_frame_chat = self.previous_observation.chat if self.previous_observation else [] return [ @@ -252,7 +248,7 @@ def chat(self) -> List[ChatMessage]: ] @cached_property - def alerts(self) -> List[int]: + def alerts(self) -> list[int]: """ Game alerts, see https://github.com/Blizzard/s2client-proto/blob/01ab351e21c786648e4c6693d4aad023a176d45c/s2clientprotocol/sc2api.proto#L683-L706 """ @@ -261,7 +257,7 @@ def alerts(self) -> List[int]: return self.observation.alerts @cached_property - def actions(self) -> List[Union[ActionRawUnitCommand, ActionRawToggleAutocast, ActionRawCameraMove]]: + def actions(self) -> list[ActionRawUnitCommand | ActionRawToggleAutocast | ActionRawCameraMove]: """ List of successful actions since last frame. See https://github.com/Blizzard/s2client-proto/blob/01ab351e21c786648e4c6693d4aad023a176d45c/s2clientprotocol/sc2api.proto#L630-L637 @@ -315,23 +311,25 @@ def actions(self) -> List[Union[ActionRawUnitCommand, ActionRawToggleAutocast, A return actions @cached_property - def actions_unit_commands(self) -> List[ActionRawUnitCommand]: + 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 - def actions_toggle_autocast(self) -> List[ActionRawToggleAutocast]: + 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 - def action_errors(self) -> List[ActionError]: + def action_errors(self) -> list[ActionError]: """ List of erroneous actions since last frame. See https://github.com/Blizzard/s2client-proto/blob/01ab351e21c786648e4c6693d4aad023a176d45c/s2clientprotocol/sc2api.proto#L648-L652 diff --git a/sc2/generate_ids.py b/sc2/generate_ids.py index 23d9e1f4..ccc77dd0 100644 --- a/sc2/generate_ids.py +++ b/sc2/generate_ids.py @@ -1,14 +1,15 @@ -# pylint: disable=W0212 +from __future__ import annotations + import importlib import json import platform import sys from pathlib import Path +from typing import Any from loguru import logger -from sc2.game_data import AbilityData, GameData, UnitTypeData, UpgradeData -from sc2.ids.ability_id import AbilityId +from sc2.game_data import GameData try: from sc2.ids.id_version import ID_VERSION_STRING @@ -17,13 +18,18 @@ class IdGenerator: - - def __init__(self, game_data: GameData = None, game_version: str = None, verbose: bool = False): - self.game_data: GameData = game_data + def __init__( + self, game_data: GameData | None = None, game_version: str | None = None, verbose: bool = False + ) -> None: + self.game_data = game_data self.game_version = game_version self.verbose = verbose - self.HEADER = f'from __future__ import annotations\n# DO NOT EDIT!\n# This file was automatically generated by "{Path(__file__).name}"\n' + self.HEADER = f"""# pyre-ignore-all-errors[14] +from __future__ import annotations +# DO NOT EDIT! +# This file was automatically generated by "{Path(__file__).name}" +""" self.PF = platform.system() @@ -51,13 +57,13 @@ def __init__(self, game_data: GameData = None, game_version: str = None, verbose } @staticmethod - def make_key(key): + def make_key(key: str) -> str: if key[0].isdigit(): key = "_" + key # In patch 5.0, the key has "@" character in it which is not possible with python enums return key.upper().replace(" ", "_").replace("@", "") - def parse_data(self, data): + def parse_data(self, data) -> dict[str, Any]: # for d in data: # Units, Abilities, Upgrades, Buffs, Effects units = self.parse_simple("Units", data) @@ -130,7 +136,7 @@ def parse_simple(self, d, data): return units - def generate_python_code(self, enums): + def generate_python_code(self, enums) -> None: assert {"Units", "Abilities", "Upgrades", "Buffs", "Effects"} <= enums.keys() sc2dir = Path(__file__).parent @@ -181,29 +187,23 @@ def _missing_(cls, value: int) -> {class_name}: if self.game_version is not None: version_path = Path(__file__).parent / "ids" / "id_version.py" - with open(version_path, "w") as f: + with Path(version_path).open("w") as f: f.write(f'ID_VERSION_STRING = "{self.game_version}"\n') - def update_ids_from_stableid_json(self): - if self.game_version is None or ID_VERSION_STRING is None or ID_VERSION_STRING != self.game_version: + 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: logger.info( f"Game version is different (Old: {self.game_version}, new: {ID_VERSION_STRING}. Updating ids to match game version" ) stable_id_path = Path(self.DATA_JSON[self.PF]) - assert stable_id_path.is_file(), f"stable_id.json was not found at path \"{stable_id_path}\"" + assert stable_id_path.is_file(), f'stable_id.json was not found at path "{stable_id_path}"' with stable_id_path.open(encoding="utf-8") as data_file: data = json.loads(data_file.read()) self.generate_python_code(self.parse_data(data)) - # Update game_data if this is a live game - if self.game_data is not None: - self.reimport_ids() - self.update_game_data() - @staticmethod - def reimport_ids(): - + def reimport_ids() -> None: # Reload the newly written "id" files # TODO This only re-imports modules, but if they haven't been imported, it will yield an error importlib.reload(sys.modules["sc2.ids.ability_id"]) @@ -220,23 +220,6 @@ def reimport_ids(): importlib.reload(sys.modules["sc2.constants"]) - def update_game_data(self): - """Re-generate the dicts from self.game_data. - This should be done after the ids have been reimported.""" - ids = set(a.value for a in AbilityId if a.value != 0) - self.game_data.abilities = { - a.ability_id: AbilityData(self.game_data, a) - for a in self.game_data._proto.abilities if a.ability_id in ids - } - # self.game_data.abilities = { - # a.ability_id: AbilityData(self.game_data, a) for a in self.game_data._proto.abilities - # } - self.game_data.units = { - u.unit_id: UnitTypeData(self.game_data, u) - for u in self.game_data._proto.units if u.available - } - self.game_data.upgrades = {u.upgrade_id: UpgradeData(self.game_data, u) for u in self.game_data._proto.upgrades} - if __name__ == "__main__": updater = IdGenerator() diff --git a/sc2/ids/__init__.py b/sc2/ids/__init__.py index a69ff863..e13b2795 100644 --- a/sc2/ids/__init__.py +++ b/sc2/ids/__init__.py @@ -1,5 +1,5 @@ +# pyre-ignore-all-errors[14] from __future__ import annotations - # DO NOT EDIT! # This file was automatically generated by "generate_ids.py" diff --git a/sc2/ids/ability_id.py b/sc2/ids/ability_id.py index a078c135..f01fa358 100644 --- a/sc2/ids/ability_id.py +++ b/sc2/ids/ability_id.py @@ -1,10 +1,10 @@ +# pyre-ignore-all-errors[14] from __future__ import annotations - -import enum - # DO NOT EDIT! # This file was automatically generated by "generate_ids.py" +import enum + class AbilityId(enum.Enum): NULL_NULL = 0 @@ -1246,7 +1246,6 @@ class AbilityId(enum.Enum): NEXUSSHIELDRECHARGEONPYLON_NEXUSSHIELDRECHARGEONPYLON = 3761 INFESTORENSNARE_INFESTORENSNARE = 3763 EFFECT_RESTORE = 3765 - SHIELDBATTERYRECHARGECHANNELED_STOP = 3766 NEXUSSHIELDOVERCHARGE_NEXUSSHIELDOVERCHARGE = 3767 NEXUSSHIELDOVERCHARGEOFF_NEXUSSHIELDOVERCHARGEOFF = 3769 ATTACK_BATTLECRUISER = 3771 @@ -1269,6 +1268,7 @@ class AbilityId(enum.Enum): MOVE = 3794 PATROL = 3795 UNLOADUNIT = 3796 + LOADOUTSPRAY_LOADOUTSPRAY1 = 3797 LOADOUTSPRAY_LOADOUTSPRAY2 = 3798 LOADOUTSPRAY_LOADOUTSPRAY3 = 3799 LOADOUTSPRAY_LOADOUTSPRAY4 = 3800 @@ -1282,316 +1282,15 @@ class AbilityId(enum.Enum): LOADOUTSPRAY_LOADOUTSPRAY12 = 3808 LOADOUTSPRAY_LOADOUTSPRAY13 = 3809 LOADOUTSPRAY_LOADOUTSPRAY14 = 3810 - DUMMYABIL0_MEDIVACSPEEDBOOST = 3811 - DUMMYABIL1_MEDIVACSPEEDBOOST = 3812 - DUMMYABIL2_MEDIVACSPEEDBOOST = 3813 - DUMMYABIL3_MEDIVACSPEEDBOOST = 3814 - DUMMYABIL4_MEDIVACSPEEDBOOST = 3815 - DUMMYABIL5_MEDIVACSPEEDBOOST = 3816 - DUMMYABIL6_MEDIVACSPEEDBOOST = 3817 - DUMMYABIL7_MEDIVACSPEEDBOOST = 3818 - DUMMYABIL8_MEDIVACSPEEDBOOST = 3819 - DUMMYABIL9_MEDIVACSPEEDBOOST = 3820 - DUMMYABIL10_MEDIVACSPEEDBOOST = 3821 - DUMMYABIL11_MEDIVACSPEEDBOOST = 3822 - DUMMYABIL12_MEDIVACSPEEDBOOST = 3823 - DUMMYABIL13_MEDIVACSPEEDBOOST = 3824 - DUMMYABIL14_MEDIVACSPEEDBOOST = 3825 - DUMMYABIL15_MEDIVACSPEEDBOOST = 3826 - DUMMYABIL16_MEDIVACSPEEDBOOST = 3827 - DUMMYABIL17_MEDIVACSPEEDBOOST = 3828 - DUMMYABIL18_MEDIVACSPEEDBOOST = 3829 - DUMMYABIL19_MEDIVACSPEEDBOOST = 3830 - DUMMYABIL20_MEDIVACSPEEDBOOST = 3831 - DUMMYABIL21_MEDIVACSPEEDBOOST = 3832 - DUMMYABIL22_MEDIVACSPEEDBOOST = 3833 - DUMMYABIL23_MEDIVACSPEEDBOOST = 3834 - DUMMYABIL24_MEDIVACSPEEDBOOST = 3835 - DUMMYABIL25_MEDIVACSPEEDBOOST = 3836 - DUMMYABIL26_MEDIVACSPEEDBOOST = 3837 - DUMMYABIL27_MEDIVACSPEEDBOOST = 3838 - DUMMYABIL28_MEDIVACSPEEDBOOST = 3839 - DUMMYABIL29_MEDIVACSPEEDBOOST = 3840 - DUMMYABIL30_MEDIVACSPEEDBOOST = 3841 - DUMMYABIL31_MEDIVACSPEEDBOOST = 3842 - DUMMYABIL32_MEDIVACSPEEDBOOST = 3843 - DUMMYABIL33_MEDIVACSPEEDBOOST = 3844 - DUMMYABIL34_MEDIVACSPEEDBOOST = 3845 - DUMMYABIL35_MEDIVACSPEEDBOOST = 3846 - DUMMYABIL36_MEDIVACSPEEDBOOST = 3847 - DUMMYABIL37_MEDIVACSPEEDBOOST = 3848 - DUMMYABIL38_MEDIVACSPEEDBOOST = 3849 - DUMMYABIL39_MEDIVACSPEEDBOOST = 3850 - DUMMYABIL40_MEDIVACSPEEDBOOST = 3851 - DUMMYABIL41_MEDIVACSPEEDBOOST = 3852 - DUMMYABIL42_MEDIVACSPEEDBOOST = 3853 - DUMMYABIL43_MEDIVACSPEEDBOOST = 3854 - DUMMYABIL44_MEDIVACSPEEDBOOST = 3855 - DUMMYABIL45_MEDIVACSPEEDBOOST = 3856 - DUMMYABIL46_MEDIVACSPEEDBOOST = 3857 - DUMMYABIL47_MEDIVACSPEEDBOOST = 3858 - DUMMYABIL48_MEDIVACSPEEDBOOST = 3859 - DUMMYABIL49_MEDIVACSPEEDBOOST = 3860 - DUMMYABIL50_MEDIVACSPEEDBOOST = 3861 - DUMMYABIL51_MEDIVACSPEEDBOOST = 3862 - DUMMYABIL52_MEDIVACSPEEDBOOST = 3863 - DUMMYABIL53_MEDIVACSPEEDBOOST = 3864 - DUMMYABIL54_MEDIVACSPEEDBOOST = 3865 - DUMMYABIL55_MEDIVACSPEEDBOOST = 3866 - DUMMYABIL56_MEDIVACSPEEDBOOST = 3867 - DUMMYABIL57_MEDIVACSPEEDBOOST = 3868 - DUMMYABIL58_MEDIVACSPEEDBOOST = 3869 - DUMMYABIL59_MEDIVACSPEEDBOOST = 3870 - DUMMYABIL60_MEDIVACSPEEDBOOST = 3871 - DUMMYABIL61_MEDIVACSPEEDBOOST = 3872 - DUMMYABIL62_MEDIVACSPEEDBOOST = 3873 - DUMMYABIL63_MEDIVACSPEEDBOOST = 3874 - DUMMYABIL64_MEDIVACSPEEDBOOST = 3875 - DUMMYABIL65_MEDIVACSPEEDBOOST = 3876 - DUMMYABIL66_MEDIVACSPEEDBOOST = 3877 - DUMMYABIL67_MEDIVACSPEEDBOOST = 3878 - DUMMYABIL68_MEDIVACSPEEDBOOST = 3879 - DUMMYABIL69_MEDIVACSPEEDBOOST = 3880 - DUMMYABIL70_MEDIVACSPEEDBOOST = 3881 - DUMMYABIL71_MEDIVACSPEEDBOOST = 3882 - DUMMYABIL72_MEDIVACSPEEDBOOST = 3883 - DUMMYABIL73_MEDIVACSPEEDBOOST = 3884 - DUMMYABIL74_MEDIVACSPEEDBOOST = 3885 - DUMMYABIL75_MEDIVACSPEEDBOOST = 3886 - DUMMYABIL76_MEDIVACSPEEDBOOST = 3887 - DUMMYABIL77_MEDIVACSPEEDBOOST = 3888 - DUMMYABIL78_MEDIVACSPEEDBOOST = 3889 - DUMMYABIL79_MEDIVACSPEEDBOOST = 3890 - DUMMYABIL80_MEDIVACSPEEDBOOST = 3891 - DUMMYABIL81_MEDIVACSPEEDBOOST = 3892 - DUMMYABIL82_MEDIVACSPEEDBOOST = 3893 - DUMMYABIL83_MEDIVACSPEEDBOOST = 3894 - DUMMYABIL84_MEDIVACSPEEDBOOST = 3895 - DUMMYABIL85_MEDIVACSPEEDBOOST = 3896 - DUMMYABIL86_MEDIVACSPEEDBOOST = 3897 - DUMMYABIL87_MEDIVACSPEEDBOOST = 3898 - DUMMYABIL88_MEDIVACSPEEDBOOST = 3899 - DUMMYABIL89_MEDIVACSPEEDBOOST = 3900 - DUMMYABIL90_MEDIVACSPEEDBOOST = 3901 - DUMMYABIL91_MEDIVACSPEEDBOOST = 3902 - DUMMYABIL92_MEDIVACSPEEDBOOST = 3903 - DUMMYABIL93_MEDIVACSPEEDBOOST = 3904 - DUMMYABIL94_MEDIVACSPEEDBOOST = 3905 - DUMMYABIL95_MEDIVACSPEEDBOOST = 3906 - DUMMYABIL96_MEDIVACSPEEDBOOST = 3907 - DUMMYABIL97_MEDIVACSPEEDBOOST = 3908 - DUMMYABIL98_MEDIVACSPEEDBOOST = 3909 - DUMMYABIL99_MEDIVACSPEEDBOOST = 3910 - DUMMYABIL100_MEDIVACSPEEDBOOST = 3911 - DUMMYABIL101_MEDIVACSPEEDBOOST = 3912 - DUMMYABIL102_MEDIVACSPEEDBOOST = 3913 - DUMMYABIL103_MEDIVACSPEEDBOOST = 3914 - DUMMYABIL104_MEDIVACSPEEDBOOST = 3915 - DUMMYABIL105_MEDIVACSPEEDBOOST = 3916 - DUMMYABIL106_MEDIVACSPEEDBOOST = 3917 - DUMMYABIL107_MEDIVACSPEEDBOOST = 3918 - DUMMYABIL108_MEDIVACSPEEDBOOST = 3919 - DUMMYABIL109_MEDIVACSPEEDBOOST = 3920 - DUMMYABIL110_MEDIVACSPEEDBOOST = 3921 - DUMMYABIL111_MEDIVACSPEEDBOOST = 3922 - DUMMYABIL112_MEDIVACSPEEDBOOST = 3923 - DUMMYABIL113_MEDIVACSPEEDBOOST = 3924 - DUMMYABIL114_MEDIVACSPEEDBOOST = 3925 - DUMMYABIL115_MEDIVACSPEEDBOOST = 3926 - DUMMYABIL116_MEDIVACSPEEDBOOST = 3927 - DUMMYABIL117_MEDIVACSPEEDBOOST = 3928 - DUMMYABIL118_MEDIVACSPEEDBOOST = 3929 - DUMMYABIL119_MEDIVACSPEEDBOOST = 3930 - DUMMYABIL120_MEDIVACSPEEDBOOST = 3931 - DUMMYABIL121_MEDIVACSPEEDBOOST = 3932 - DUMMYABIL122_MEDIVACSPEEDBOOST = 3933 - DUMMYABIL123_MEDIVACSPEEDBOOST = 3934 - DUMMYABIL124_MEDIVACSPEEDBOOST = 3935 - DUMMYABIL125_MEDIVACSPEEDBOOST = 3936 - DUMMYABIL126_MEDIVACSPEEDBOOST = 3937 - DUMMYABIL127_MEDIVACSPEEDBOOST = 3938 - DUMMYABIL128_MEDIVACSPEEDBOOST = 3939 - DUMMYABIL129_MEDIVACSPEEDBOOST = 3940 - DUMMYABIL130_MEDIVACSPEEDBOOST = 3941 - DUMMYABIL131_MEDIVACSPEEDBOOST = 3942 - DUMMYABIL132_MEDIVACSPEEDBOOST = 3943 - DUMMYABIL133_MEDIVACSPEEDBOOST = 3944 - DUMMYABIL134_MEDIVACSPEEDBOOST = 3945 - DUMMYABIL135_MEDIVACSPEEDBOOST = 3946 - DUMMYABIL136_MEDIVACSPEEDBOOST = 3947 - DUMMYABIL137_MEDIVACSPEEDBOOST = 3948 - DUMMYABIL138_MEDIVACSPEEDBOOST = 3949 - DUMMYABIL139_MEDIVACSPEEDBOOST = 3950 - DUMMYABIL140_MEDIVACSPEEDBOOST = 3951 - DUMMYABIL141_MEDIVACSPEEDBOOST = 3952 - DUMMYABIL142_MEDIVACSPEEDBOOST = 3953 - DUMMYABIL143_MEDIVACSPEEDBOOST = 3954 - DUMMYABIL144_MEDIVACSPEEDBOOST = 3955 - DUMMYABIL145_MEDIVACSPEEDBOOST = 3956 - DUMMYABIL146_MEDIVACSPEEDBOOST = 3957 - DUMMYABIL147_MEDIVACSPEEDBOOST = 3958 - DUMMYABIL148_MEDIVACSPEEDBOOST = 3959 - DUMMYABIL149_MEDIVACSPEEDBOOST = 3960 - DUMMYABIL150_MEDIVACSPEEDBOOST = 3961 - DUMMYABIL151_MEDIVACSPEEDBOOST = 3962 - DUMMYABIL152_MEDIVACSPEEDBOOST = 3963 - DUMMYABIL153_MEDIVACSPEEDBOOST = 3964 - DUMMYABIL154_MEDIVACSPEEDBOOST = 3965 - DUMMYABIL155_MEDIVACSPEEDBOOST = 3966 - DUMMYABIL156_MEDIVACSPEEDBOOST = 3967 - DUMMYABIL157_MEDIVACSPEEDBOOST = 3968 - DUMMYABIL158_MEDIVACSPEEDBOOST = 3969 - DUMMYABIL159_MEDIVACSPEEDBOOST = 3970 - DUMMYABIL160_MEDIVACSPEEDBOOST = 3971 - DUMMYABIL161_MEDIVACSPEEDBOOST = 3972 - DUMMYABIL162_MEDIVACSPEEDBOOST = 3973 - DUMMYABIL163_MEDIVACSPEEDBOOST = 3974 - DUMMYABIL164_MEDIVACSPEEDBOOST = 3975 - DUMMYABIL165_MEDIVACSPEEDBOOST = 3976 - DUMMYABIL166_MEDIVACSPEEDBOOST = 3977 - DUMMYABIL167_MEDIVACSPEEDBOOST = 3978 - DUMMYABIL168_MEDIVACSPEEDBOOST = 3979 - DUMMYABIL169_MEDIVACSPEEDBOOST = 3980 - DUMMYABIL170_MEDIVACSPEEDBOOST = 3981 - DUMMYABIL171_MEDIVACSPEEDBOOST = 3982 - DUMMYABIL172_MEDIVACSPEEDBOOST = 3983 - DUMMYABIL173_MEDIVACSPEEDBOOST = 3984 - DUMMYABIL174_MEDIVACSPEEDBOOST = 3985 - DUMMYABIL175_MEDIVACSPEEDBOOST = 3986 - DUMMYABIL176_MEDIVACSPEEDBOOST = 3987 - DUMMYABIL177_MEDIVACSPEEDBOOST = 3988 - DUMMYABIL178_MEDIVACSPEEDBOOST = 3989 - DUMMYABIL179_MEDIVACSPEEDBOOST = 3990 - DUMMYABIL180_MEDIVACSPEEDBOOST = 3991 - DUMMYABIL181_MEDIVACSPEEDBOOST = 3992 - DUMMYABIL182_MEDIVACSPEEDBOOST = 3993 - DUMMYABIL183_MEDIVACSPEEDBOOST = 3994 - DUMMYABIL184_MEDIVACSPEEDBOOST = 3995 - DUMMYABIL185_MEDIVACSPEEDBOOST = 3996 - DUMMYABIL186_MEDIVACSPEEDBOOST = 3997 - DUMMYABIL187_MEDIVACSPEEDBOOST = 3998 - DUMMYABIL188_MEDIVACSPEEDBOOST = 3999 - DUMMYABIL189_MEDIVACSPEEDBOOST = 4000 - DUMMYABIL190_MEDIVACSPEEDBOOST = 4001 - DUMMYABIL191_MEDIVACSPEEDBOOST = 4002 - DUMMYABIL192_MEDIVACSPEEDBOOST = 4003 - DUMMYABIL193_MEDIVACSPEEDBOOST = 4004 - DUMMYABIL194_MEDIVACSPEEDBOOST = 4005 - DUMMYABIL195_MEDIVACSPEEDBOOST = 4006 - DUMMYABIL196_MEDIVACSPEEDBOOST = 4007 - DUMMYABIL197_MEDIVACSPEEDBOOST = 4008 - DUMMYABIL198_MEDIVACSPEEDBOOST = 4009 - DUMMYABIL199_MEDIVACSPEEDBOOST = 4010 - DUMMYABIL200_MEDIVACSPEEDBOOST = 4011 - DUMMYABIL201_MEDIVACSPEEDBOOST = 4012 - DUMMYABIL202_MEDIVACSPEEDBOOST = 4013 - DUMMYABIL203_MEDIVACSPEEDBOOST = 4014 - DUMMYABIL204_MEDIVACSPEEDBOOST = 4015 - DUMMYABIL205_MEDIVACSPEEDBOOST = 4016 - DUMMYABIL206_MEDIVACSPEEDBOOST = 4017 - DUMMYABIL207_MEDIVACSPEEDBOOST = 4018 - DUMMYABIL208_MEDIVACSPEEDBOOST = 4019 - DUMMYABIL209_MEDIVACSPEEDBOOST = 4020 - DUMMYABIL210_MEDIVACSPEEDBOOST = 4021 - DUMMYABIL211_DUMMYABIL211 = 4022 - DUMMYABIL212_DUMMYABIL212 = 4023 - DUMMYABIL213_DUMMYABIL213 = 4024 - DUMMYABIL214_DUMMYABIL214 = 4025 - DUMMYABIL215_DUMMYABIL215 = 4026 - DUMMYABIL216_DUMMYABIL216 = 4027 - DUMMYABIL217_DUMMYABIL217 = 4028 - DUMMYABIL218_DUMMYABIL218 = 4029 - DUMMYABIL219_DUMMYABIL219 = 4030 - DUMMYABIL220_DUMMYABIL220 = 4031 - DUMMYABIL221_DUMMYABIL221 = 4032 - DUMMYABIL222_DUMMYABIL222 = 4033 - DUMMYABIL223_DUMMYABIL223 = 4034 - DUMMYABIL224_DUMMYABIL224 = 4035 - DUMMYABIL225_DUMMYABIL225 = 4036 - DUMMYABIL226_DUMMYABIL226 = 4037 - DUMMYABIL227_DUMMYABIL227 = 4038 - DUMMYABIL228_DUMMYABIL228 = 4039 - DUMMYABIL229_DUMMYABIL229 = 4040 - DUMMYABIL230_DUMMYABIL230 = 4041 - DUMMYABIL231_DUMMYABIL231 = 4042 - DUMMYABIL232_DUMMYABIL232 = 4043 - DUMMYABIL233_DUMMYABIL233 = 4044 - DUMMYABIL234_DUMMYABIL234 = 4045 - DUMMYABIL235_DUMMYABIL235 = 4046 - DUMMYABIL236_DUMMYABIL236 = 4047 - DUMMYABIL237_DUMMYABIL237 = 4048 - DUMMYABIL238_DUMMYABIL238 = 4049 - DUMMYABIL239_DUMMYABIL239 = 4050 - DUMMYABIL240_DUMMYABIL240 = 4051 - DUMMYABIL241_DUMMYABIL241 = 4052 - DUMMYABIL242_DUMMYABIL242 = 4053 - DUMMYABIL243_DUMMYABIL243 = 4054 - DUMMYABIL244_DUMMYABIL244 = 4055 - DUMMYABIL245_DUMMYABIL245 = 4056 - DUMMYABIL246_DUMMYABIL246 = 4057 - DUMMYABIL247_DUMMYABIL247 = 4058 - DUMMYABIL248_DUMMYABIL248 = 4059 - DUMMYABIL249_DUMMYABIL249 = 4060 - DUMMYABIL250_DUMMYABIL250 = 4061 - DUMMYABIL251_DUMMYABIL251 = 4062 - DUMMYABIL252_DUMMYABIL252 = 4063 - DUMMYABIL253_DUMMYABIL253 = 4064 - DUMMYABIL254_DUMMYABIL254 = 4065 - DUMMYABIL255_DUMMYABIL255 = 4066 - DUMMYABIL256_DUMMYABIL256 = 4067 - DUMMYABIL257_DUMMYABIL257 = 4068 - DUMMYABIL258_DUMMYABIL258 = 4069 - DUMMYABIL259_DUMMYABIL259 = 4070 - DUMMYABIL260_DUMMYABIL260 = 4071 - DUMMYABIL261_DUMMYABIL261 = 4072 - DUMMYABIL262_DUMMYABIL262 = 4073 - DUMMYABIL263_DUMMYABIL263 = 4074 - DUMMYABIL264_DUMMYABIL264 = 4075 - DUMMYABIL265_DUMMYABIL265 = 4076 - DUMMYABIL266_DUMMYABIL266 = 4077 - DUMMYABIL267_DUMMYABIL267 = 4078 - DUMMYABIL268_DUMMYABIL268 = 4079 - DUMMYABIL269_DUMMYABIL269 = 4080 - DUMMYABIL270_DUMMYABIL270 = 4081 - DUMMYABIL271_DUMMYABIL271 = 4082 - DUMMYABIL272_DUMMYABIL272 = 4083 - DUMMYABIL273_DUMMYABIL273 = 4084 - DUMMYABIL274_DUMMYABIL274 = 4085 - DUMMYABIL275_DUMMYABIL275 = 4086 - DUMMYABIL276_DUMMYABIL276 = 4087 - DUMMYABIL277_DUMMYABIL277 = 4088 - DUMMYABIL278_DUMMYABIL278 = 4089 - DUMMYABIL279_DUMMYABIL279 = 4090 - DUMMYABIL280_DUMMYABIL280 = 4091 - DUMMYABIL281_DUMMYABIL281 = 4092 - DUMMYABIL282_DUMMYABIL282 = 4093 - DUMMYABIL283_DUMMYABIL283 = 4094 - DUMMYABIL284_DUMMYABIL284 = 4095 - DUMMYABIL285_DUMMYABIL285 = 4096 - DUMMYABIL286_DUMMYABIL286 = 4097 - DUMMYABIL287_DUMMYABIL287 = 4098 - DUMMYABIL288_DUMMYABIL288 = 4099 - DUMMYABIL289_DUMMYABIL289 = 4100 - DUMMYABIL290_DUMMYABIL290 = 4101 - DUMMYABIL291_DUMMYABIL291 = 4102 - DUMMYABIL292_DUMMYABIL292 = 4103 - DUMMYABIL293_DUMMYABIL293 = 4104 - DUMMYABIL294_DUMMYABIL294 = 4105 - DUMMYABIL295_DUMMYABIL295 = 4106 + MORPHTOCOLLAPSIBLEROCKTOWERDEBRISRAMPLEFTGREEN_CANCEL = 3967 + MORPHTOCOLLAPSIBLEROCKTOWERDEBRISRAMPRIGHTGREEN_CANCEL = 3970 BATTERYOVERCHARGE_BATTERYOVERCHARGE = 4107 - DUMMYABIL296_DUMMYABIL296 = 4108 AMORPHOUSARMORCLOUD_AMORPHOUSARMORCLOUD = 4109 - DUMMYABIL297_DUMMYABIL297 = 4110 SHIELDBATTERYRECHARGEEX5_SHIELDBATTERYRECHARGE = 4111 SHIELDBATTERYRECHARGEEX5_STOP = 4112 - DUMMYABIL298_DUMMYABIL298 = 4113 - DUMMYABIL299_DUMMYABIL299 = 4114 MORPHTOBANELING_BANELING = 4119 MORPHTOBANELING_CANCEL = 4120 MOTHERSHIPCLOAK_ORACLECLOAKFIELD = 4122 - LOADOUTSPRAY_LOADOUTSPRAY1 = 4124 - MORPHTOCOLLAPSIBLEROCKTOWERDEBRISRAMPLEFTGREEN_CANCEL = 4294 - MORPHTOCOLLAPSIBLEROCKTOWERDEBRISRAMPRIGHTGREEN_CANCEL = 4297 def __repr__(self) -> str: return f"AbilityId.{self.name}" diff --git a/sc2/ids/buff_id.py b/sc2/ids/buff_id.py index 1d0f19fc..f3b6d912 100644 --- a/sc2/ids/buff_id.py +++ b/sc2/ids/buff_id.py @@ -1,10 +1,10 @@ +# pyre-ignore-all-errors[14] from __future__ import annotations - -import enum - # DO NOT EDIT! # This file was automatically generated by "generate_ids.py" +import enum + class BuffId(enum.Enum): NULL = 0 @@ -299,7 +299,7 @@ class BuffId(enum.Enum): ACCELERATIONZONETEMPORALFIELD = 289 ACCELERATIONZONEFLYINGTEMPORALFIELD = 290 INHIBITORZONEFLYINGTEMPORALFIELD = 291 - DUMMYBUFF000 = 292 + LOADOUTSPRAYTRACKER = 292 INHIBITORZONETEMPORALFIELD = 293 CLOAKFIELD = 294 RESONATINGGLAIVESPHASESHIFT = 295 @@ -309,12 +309,6 @@ class BuffId(enum.Enum): TAKENDAMAGE = 299 RAVENSCRAMBLERMISSILECARRIER = 300 BATTERYOVERCHARGE = 301 - LOADOUTSPRAYTRACKER = 302 - DUMMYBUFF002 = 303 - DUMMYBUFF001 = 304 - DUMMYBUFF003 = 305 - DUMMYBUFF004 = 306 - DUMMYBUFF005 = 307 def __repr__(self) -> str: return f"BuffId.{self.name}" diff --git a/sc2/ids/effect_id.py b/sc2/ids/effect_id.py index f6c9a803..3f04e5e3 100644 --- a/sc2/ids/effect_id.py +++ b/sc2/ids/effect_id.py @@ -1,10 +1,10 @@ +# pyre-ignore-all-errors[14] from __future__ import annotations - -import enum - # DO NOT EDIT! # This file was automatically generated by "generate_ids.py" +import enum + class EffectId(enum.Enum): NULL = 0 diff --git a/sc2/ids/unit_typeid.py b/sc2/ids/unit_typeid.py index 45e362c5..a3f502b0 100644 --- a/sc2/ids/unit_typeid.py +++ b/sc2/ids/unit_typeid.py @@ -1,10 +1,10 @@ +# pyre-ignore-all-errors[14] from __future__ import annotations - -import enum - # DO NOT EDIT! # This file was automatically generated by "generate_ids.py" +import enum + class UnitTypeId(enum.Enum): NOTAUNIT = 0 @@ -2006,64 +2006,12 @@ class UnitTypeId(enum.Enum): MINERALFIELD450 = 1996 MINERALFIELDOPAQUE = 1997 MINERALFIELDOPAQUE900 = 1998 - MECHAZERGLINGACGLUESCREENDUMMY_2 = 1999 - MECHABANELINGACGLUESCREENDUMMY_2 = 2000 - MECHAHYDRALISKACGLUESCREENDUMMY_2 = 2001 - MECHAINFESTORACGLUESCREENDUMMY_2 = 2002 - MECHACORRUPTORACGLUESCREENDUMMY_2 = 2003 - MECHAULTRALISKACGLUESCREENDUMMY_2 = 2004 - MECHAOVERSEERACGLUESCREENDUMMY_2 = 2005 - MECHALURKERACGLUESCREENDUMMY_2 = 2006 - MECHABATTLECARRIERLORDACGLUESCREENDUMMY_2 = 2007 - MECHASPINECRAWLERACGLUESCREENDUMMY_2 = 2008 - MECHASPORECRAWLERACGLUESCREENDUMMY_2 = 2009 - TROOPERMENGSKACGLUESCREENDUMMY_2 = 2010 - MEDIVACMENGSKACGLUESCREENDUMMY_2 = 2011 - BLIMPMENGSKACGLUESCREENDUMMY_2 = 2012 - MARAUDERMENGSKACGLUESCREENDUMMY_2 = 2013 - GHOSTMENGSKACGLUESCREENDUMMY_2 = 2014 - SIEGETANKMENGSKACGLUESCREENDUMMY_2 = 2015 - THORMENGSKACGLUESCREENDUMMY_2 = 2016 - VIKINGMENGSKACGLUESCREENDUMMY_2 = 2017 - BATTLECRUISERMENGSKACGLUESCREENDUMMY_2 = 2018 - BUNKERDEPOTMENGSKACGLUESCREENDUMMY_2 = 2019 - MISSILETURRETMENGSKACGLUESCREENDUMMY_2 = 2020 - ARTILLERYMENGSKACGLUESCREENDUMMY_2 = 2021 - LOADOUTSPRAY1_2 = 2022 - LOADOUTSPRAY2_2 = 2023 - LOADOUTSPRAY3_2 = 2024 - LOADOUTSPRAY4_2 = 2025 - LOADOUTSPRAY5_2 = 2026 - LOADOUTSPRAY6_2 = 2027 - LOADOUTSPRAY7_2 = 2028 - LOADOUTSPRAY8_2 = 2029 - LOADOUTSPRAY9_2 = 2030 - LOADOUTSPRAY10_2 = 2031 - LOADOUTSPRAY11_2 = 2032 - LOADOUTSPRAY12_2 = 2033 - LOADOUTSPRAY13_2 = 2034 - LOADOUTSPRAY14_2 = 2035 - COLLAPSIBLEROCKTOWERDEBRISRAMPLEFTGREEN = 2036 - COLLAPSIBLEROCKTOWERDEBRISRAMPRIGHTGREEN = 2037 - COLLAPSIBLEROCKTOWERPUSHUNITRAMPLEFTGREEN = 2038 - COLLAPSIBLEROCKTOWERPUSHUNITRAMPRIGHTGREEN = 2039 - COLLAPSIBLEROCKTOWERRAMPLEFTGREEN = 2040 - COLLAPSIBLEROCKTOWERRAMPRIGHTGREEN = 2041 - DUMMYUNIT000 = 2042 - DUMMYUNIT001 = 2043 - DUMMYUNIT002 = 2044 - DUMMYUNIT003 = 2045 - DUMMYUNIT004 = 2046 - DUMMYUNIT005 = 2047 - DUMMYUNIT006 = 2048 - DUMMYUNIT007 = 2049 - DUMMYUNIT008 = 2050 - DUMMYUNIT009 = 2051 - DUMMYUNIT010 = 2052 - DUMMYUNIT011 = 2053 - DUMMYUNIT012 = 2054 - DUMMYUNIT013 = 2055 - DUMMYUNIT014 = 2056 + COLLAPSIBLEROCKTOWERDEBRISRAMPLEFTGREEN = 1999 + COLLAPSIBLEROCKTOWERDEBRISRAMPRIGHTGREEN = 2000 + COLLAPSIBLEROCKTOWERPUSHUNITRAMPLEFTGREEN = 2001 + COLLAPSIBLEROCKTOWERPUSHUNITRAMPRIGHTGREEN = 2002 + COLLAPSIBLEROCKTOWERRAMPLEFTGREEN = 2003 + COLLAPSIBLEROCKTOWERRAMPRIGHTGREEN = 2004 def __repr__(self) -> str: return f"UnitTypeId.{self.name}" diff --git a/sc2/ids/upgrade_id.py b/sc2/ids/upgrade_id.py index 22415442..a2157636 100644 --- a/sc2/ids/upgrade_id.py +++ b/sc2/ids/upgrade_id.py @@ -1,10 +1,10 @@ +# pyre-ignore-all-errors[14] from __future__ import annotations - -import enum - # DO NOT EDIT! # This file was automatically generated by "generate_ids.py" +import enum + class UpgradeId(enum.Enum): NULL = 0 diff --git a/sc2/main.py b/sc2/main.py index 5e1b3621..aa590d92 100644 --- a/sc2/main.py +++ b/sc2/main.py @@ -1,9 +1,8 @@ -# pylint: disable=W0212 +# pyre-ignore-all-errors[6, 11, 16, 21, 29] from __future__ import annotations import asyncio import json -import os import platform import signal import sys @@ -11,7 +10,6 @@ from dataclasses import dataclass from io import BytesIO from pathlib import Path -from typing import Dict, List, Optional, Tuple, Union import mpyq import portpicker @@ -27,9 +25,9 @@ from sc2.maps import Map from sc2.player import AbstractPlayer, Bot, BotProcess, Human from sc2.portconfig import Portconfig -from sc2.protocol import ConnectionAlreadyClosed, ProtocolError +from sc2.protocol import ConnectionAlreadyClosedError, ProtocolError from sc2.proxy import Proxy -from sc2.sc2process import SC2Process, kill_switch +from sc2.sc2process import KillSwitch, SC2Process # Set the global logging level logger.remove() @@ -47,14 +45,14 @@ class GameMatch: """ map_sc2: Map - players: List[AbstractPlayer] + players: list[AbstractPlayer] realtime: bool = False - random_seed: int = None - disable_fog: bool = None - sc2_config: List[Dict] = None - game_time_limit: int = None + random_seed: int | None = None + disable_fog: bool | None = None + sc2_config: list[dict] | None = None + game_time_limit: int | None = None - def __post_init__(self): + 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: self.players[1].name += "2" @@ -66,14 +64,14 @@ def __post_init__(self): self.sc2_config = [{}] while len(self.sc2_config) < len(self.players): self.sc2_config += self.sc2_config - self.sc2_config = self.sc2_config[:len(self.players)] + self.sc2_config = self.sc2_config[: len(self.players)] @property def needed_sc2_count(self) -> int: return sum(player.needs_sc2 for player in self.players) @property - def host_game_kwargs(self) -> Dict: + def host_game_kwargs(self) -> dict: return { "map_settings": self.map_sc2, "players": self.players, @@ -82,7 +80,7 @@ def host_game_kwargs(self) -> Dict: "disable_fog": self.disable_fog, } - def __repr__(self): + def __repr__(self) -> str: p1 = self.players[0] p1 = p1.name if p1.name else p1 p2 = self.players[1] @@ -104,13 +102,12 @@ async def _play_game_human(client, player_id, realtime, game_time_limit): await client.step() -# pylint: disable=R0912,R0911,R0914 async def _play_game_ai( - client: Client, player_id: int, ai: BotAI, realtime: bool, game_time_limit: Optional[int] + client: Client, player_id: int, ai: BotAI, realtime: bool, game_time_limit: int | None ) -> Result: - gs: GameState = None + gs: GameState | None = None - async def initialize_first_step() -> Optional[Result]: + async def initialize_first_step() -> Result | None: nonlocal gs ai._initialize_variables() @@ -135,7 +132,7 @@ async def initialize_first_step() -> Optional[Result]: ai._prepare_first_step() await ai.on_start() # TODO Catching too general exception Exception (broad-except) - # pylint: disable=W0703 + except Exception as e: logger.exception(f"Caught unknown exception in AI on_start: {e}") logger.error("Resigning due to previous error") @@ -154,7 +151,7 @@ async def run_bot_iteration(iteration: int): # In on_step various errors can occur - log properly try: await ai.on_step(iteration) - except (AttributeError, ) as e: + except (AttributeError,) as e: logger.exception(f"Caught exception: {e}") raise except Exception as e: @@ -206,12 +203,7 @@ async def run_bot_iteration(iteration: int): async def _play_game( - player: AbstractPlayer, - client: Client, - realtime, - portconfig, - game_time_limit=None, - rgb_render_config=None + player: AbstractPlayer, client: Client, realtime, portconfig, game_time_limit=None, rgb_render_config=None ) -> Result: assert isinstance(realtime, bool), repr(realtime) @@ -233,7 +225,7 @@ async def _play_game( return result -async def _play_replay(client, ai, realtime=False, player_id=0): +async def _play_replay(client, ai, realtime: bool = False, player_id: int = 0): ai._initialize_variables() game_data = await client.get_game_data() @@ -257,7 +249,7 @@ async def _play_replay(client, ai, realtime=False, player_id=0): try: await ai.on_start() # TODO Catching too general exception Exception (broad-except) - # pylint: disable=W0703 + except Exception as e: logger.exception(f"Caught unknown exception in AI replay on_start: {e}") await ai.on_end(Result.Defeat) @@ -293,7 +285,6 @@ async def _play_replay(client, ai, realtime=False, player_id=0): await ai.on_step(iteration) await ai._after_step() - # pylint: disable=W0703 # TODO Catching too general exception Exception (broad-except) except Exception as e: if isinstance(e, ProtocolError) and e.is_game_over_error: @@ -313,10 +304,9 @@ async def _play_replay(client, ai, realtime=False, player_id=0): logger.debug("Running AI step: done") - if not realtime: - if not client.in_game: # Client left (resigned) the game - await ai.on_end(Result.Victory) - return Result.Victory + if not realtime and not client.in_game: # Client left (resigned) the game + await ai.on_end(Result.Victory) + return Result.Victory await client.step() # unindent one line to work in realtime @@ -340,7 +330,7 @@ async def _setup_host_game( async def _host_game( map_settings, players, - realtime=False, + realtime: bool = False, portconfig=None, save_replay_as=None, game_time_limit=None, @@ -349,10 +339,9 @@ async def _host_game( sc2_version=None, disable_fog=None, ): - assert players, "Can't create a game without players" - assert any(isinstance(p, (Human, Bot)) for p in players) + assert any((isinstance(p, (Human, Bot))) for p in players) async with SC2Process( fullscreen=players[0].fullscreen, render=rgb_render_config is not None, sc2_version=sc2_version @@ -371,7 +360,7 @@ async def _host_game( await client.save_replay(client.save_replay_path) try: await client.leave() - except ConnectionAlreadyClosed: + except ConnectionAlreadyClosedError: logger.error("Connection was closed before the game ended") await client.quit() @@ -404,7 +393,7 @@ async def _host_game_aiter( if save_replay_as is not None: await client.save_replay(save_replay_as) await client.leave() - except ConnectionAlreadyClosed: + except ConnectionAlreadyClosedError: logger.error("Connection was closed before the game ended") return @@ -440,7 +429,7 @@ async def _join_game( await client.save_replay(save_replay_as) try: await client.leave() - except ConnectionAlreadyClosed: + except ConnectionAlreadyClosedError: logger.error("Connection was closed before the game ended") await client.quit() @@ -459,8 +448,8 @@ async def _host_replay(replay_path, ai, realtime, _portconfig, base_build, data_ return result -def get_replay_version(replay_path: Union[str, Path]) -> Tuple[str, str]: - with open(replay_path, 'rb') as f: +def get_replay_version(replay_path: str | Path) -> tuple[str, str]: + with Path(replay_path).open("rb") as f: replay_data = f.read() replay_io = BytesIO() replay_io.write(replay_data) @@ -471,7 +460,7 @@ def get_replay_version(replay_path: Union[str, Path]) -> Tuple[str, str]: # TODO Deprecate run_game function in favor of run_multiple_games -def run_game(map_settings, players, **kwargs) -> Union[Result, List[Optional[Result]]]: +def run_game(map_settings, players, **kwargs) -> Result | list[Result | None]: """ 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". @@ -486,10 +475,10 @@ async def run_host_and_join(): return await asyncio.gather( _host_game(map_settings, players, **kwargs, portconfig=portconfig), _join_game(players, **join_kwargs, portconfig=portconfig), - return_exceptions=True + return_exceptions=True, ) - result: List[Result] = asyncio.run(run_host_and_join()) + result: list[Result] = asyncio.run(run_host_and_join()) assert isinstance(result, list) assert all(isinstance(r, Result) for r in result) else: @@ -498,12 +487,12 @@ async def run_host_and_join(): return result -def run_replay(ai, replay_path, realtime=False, observed_id=0): +def run_replay(ai, replay_path: Path | str, realtime: bool = False, observed_id: int = 0): portconfig = Portconfig() - assert os.path.isfile(replay_path), f"Replay does not exist at the given path: {replay_path}" - assert os.path.isabs( + assert Path(replay_path).is_file(), f"Replay does not exist at the given path: {replay_path}" + assert Path( replay_path - ), f'Replay path has to be an absolute path, e.g. "C:/replays/my_replay.SC2Replay" but given path was "{replay_path}"' + ).is_absolute(), f'Replay path has to be an absolute path, e.g. "C:/replays/my_replay.SC2Replay" but given path was "{replay_path}"' base_build, data_version = get_replay_version(replay_path) result = asyncio.get_event_loop().run_until_complete( _host_replay(replay_path, ai, realtime, portconfig, base_build, data_version, observed_id) @@ -512,13 +501,13 @@ def run_replay(ai, replay_path, realtime=False, observed_id=0): async def play_from_websocket( - ws_connection: Union[str, ClientWebSocketResponse], + ws_connection: str | ClientWebSocketResponse, player: AbstractPlayer, realtime: bool = False, - portconfig: Portconfig = None, - save_replay_as=None, - game_time_limit: int = None, - should_close=True, + portconfig: Portconfig | None = None, + save_replay_as: str | None = None, + game_time_limit: int | None = None, + should_close: bool = True, ): """Use this to play when the match is handled externally e.g. for bot ladder games. Portconfig MUST be specified if not playing vs Computer. @@ -537,7 +526,7 @@ async def play_from_websocket( result = await _play_game(player, client, realtime, portconfig, game_time_limit=game_time_limit) if save_replay_as is not None: await client.save_replay(save_replay_as) - except ConnectionAlreadyClosed: + except ConnectionAlreadyClosedError: logger.error("Connection was closed before the game ended") return None finally: @@ -549,7 +538,7 @@ async def play_from_websocket( return result -async def run_match(controllers: List[Controller], match: GameMatch, close_ws=True): +async def run_match(controllers: list[Controller], match: GameMatch, close_ws: bool = True): await _setup_host_game(controllers[0], **match.host_game_kwargs) # Setup portconfig beforehand, so all players use the same ports @@ -595,9 +584,9 @@ async def run_match(controllers: List[Controller], match: GameMatch, close_ws=Tr return process_results(match.players, async_results) -def process_results(players: List[AbstractPlayer], async_results: List[Result]) -> Dict[AbstractPlayer, Result]: +def process_results(players: list[AbstractPlayer], async_results: list[Result]) -> dict[AbstractPlayer, Result]: opp_res = {Result.Victory: Result.Defeat, Result.Defeat: Result.Victory, Result.Tie: Result.Tie} - result: Dict[AbstractPlayer, Result] = {} + result: dict[AbstractPlayer, Result] = {} i = 0 for player in players: if player.needs_sc2: @@ -615,8 +604,7 @@ def process_results(players: List[AbstractPlayer], async_results: List[Result]) return result -# pylint: disable=R0912 -async def maintain_SCII_count(count: int, controllers: List[Controller], proc_args: List[Dict] = None): +async def maintain_SCII_count(count: int, controllers: list[Controller], proc_args: list[dict] | None = None) -> None: """Modifies the given list of controllers to reflect the desired amount of SCII processes""" # kill unhealthy ones. if controllers: @@ -639,8 +627,8 @@ async def maintain_SCII_count(count: int, controllers: List[Controller], proc_ar i += 1 for c in to_remove: c._process._clean(verbose=False) - if c._process in kill_switch._to_kill: - kill_switch._to_kill.remove(c._process) + if c._process in KillSwitch._to_kill: + KillSwitch._to_kill.remove(c._process) controllers.remove(c) # spawn more @@ -656,14 +644,13 @@ async def maintain_SCII_count(count: int, controllers: List[Controller], proc_ar for _ in range(3): if platform.system() == "Linux": # Works on linux: start one client after the other - # pylint: disable=C2801 + new_controllers = [await asyncio.wait_for(sc.__aenter__(), timeout=50) for sc in extra] else: # Doesnt seem to work on linux: starting 2 clients nearly at the same time new_controllers = await asyncio.wait_for( - # pylint: disable=C2801 asyncio.gather(*[sc.__aenter__() for sc in extra], return_exceptions=True), - timeout=50 + timeout=50, ) controllers.extend(c for c in new_controllers if isinstance(c, Controller)) @@ -684,17 +671,18 @@ async def maintain_SCII_count(count: int, controllers: List[Controller], proc_ar logger.info(f"Removing SCII listening to {proc._port}") await proc._close_connection() proc._clean(verbose=False) - if proc in kill_switch._to_kill: - kill_switch._to_kill.remove(proc) + if proc in KillSwitch._to_kill: + KillSwitch._to_kill.remove(proc) -def run_multiple_games(matches: List[GameMatch]): +def run_multiple_games(matches: list[GameMatch]): return asyncio.get_event_loop().run_until_complete(a_run_multiple_games(matches)) # TODO Catching too general exception Exception (broad-except) -# pylint: disable=W0703 -async def a_run_multiple_games(matches: List[GameMatch]) -> List[Dict[AbstractPlayer, Result]]: + + +async def a_run_multiple_games(matches: list[GameMatch]) -> list[dict[AbstractPlayer, Result]]: """Run multiple matches. Non-python bots are supported. When playing bot vs bot, this is less likely to fatally crash than repeating run_game() @@ -719,13 +707,14 @@ async def a_run_multiple_games(matches: List[GameMatch]) -> List[Dict[AbstractPl 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) - kill_switch.kill_all() + KillSwitch.kill_all() return results # TODO Catching too general exception Exception (broad-except) -# pylint: disable=W0703 -async def a_run_multiple_games_nokill(matches: List[GameMatch]) -> List[Dict[AbstractPlayer, Result]]: + + +async def a_run_multiple_games_nokill(matches: list[GameMatch]) -> list[dict[AbstractPlayer, Result]]: """Run multiple matches while reusing SCII processes. Prone to crashes and stalls """ @@ -762,7 +751,7 @@ async def a_run_multiple_games_nokill(matches: List[GameMatch]) -> List[Dict[Abs # Fire the killswitch manually, instead of letting the winning player fire it. await asyncio.wait_for(asyncio.gather(*(c._process._close_connection() for c in controllers)), timeout=50) - kill_switch.kill_all() + KillSwitch.kill_all() signal.signal(signal.SIGINT, signal.SIG_DFL) return results diff --git a/sc2/maps.py b/sc2/maps.py index 0cbf624c..f5e4e2cb 100644 --- a/sc2/maps.py +++ b/sc2/maps.py @@ -21,8 +21,7 @@ def get(name: str) -> Map: class Map: - - def __init__(self, path: Path): + def __init__(self, path: Path) -> None: self.path = path if self.path.is_absolute(): @@ -35,15 +34,15 @@ def __init__(self, path: Path): self.relative_path = self.path @property - def name(self): + def name(self) -> str: return self.path.stem @property - def data(self): - with open(self.path, "rb") as f: + def data(self) -> bytes: + with Path(self.path).open("rb") as f: return f.read() - def __repr__(self): + def __repr__(self) -> str: return f"Map({self.path})" @classmethod diff --git a/sc2/observer_ai.py b/sc2/observer_ai.py index 1f049039..0f45ddff 100644 --- a/sc2/observer_ai.py +++ b/sc2/observer_ai.py @@ -1,12 +1,12 @@ +# 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. """ -# pylint: disable=W0201,W0212 from __future__ import annotations -from typing import TYPE_CHECKING, List, Union +from typing import TYPE_CHECKING from sc2.bot_ai_internal import BotAIInternal from sc2.data import Alert, Result @@ -27,28 +27,28 @@ class ObserverAI(BotAIInternal): @property def time(self) -> float: - """ Returns time in seconds, assumes the game is played on 'faster' """ + """Returns time in seconds, assumes the game is played on 'faster'""" return self.state.game_loop / 22.4 # / (1/1.4) * (1/16) @property def time_formatted(self) -> str: - """ Returns time as string in min:sec format """ + """Returns time as string in min:sec format""" t = self.time return f"{int(t // 60):02}:{int(t % 60):02}" @property def game_info(self) -> GameInfo: - """ See game_info.py """ + """See game_info.py""" return self._game_info @property def game_data(self) -> GameData: - """ See game_data.py """ + """See game_data.py""" return self._game_data @property def client(self) -> Client: - """ See client.py """ + """See client.py""" return self._client def alert(self, alert_code: Alert) -> bool: @@ -101,13 +101,13 @@ def start_location(self) -> Point2: return self.game_info.player_start_location @property - def enemy_start_locations(self) -> List[Point2]: + def enemy_start_locations(self) -> list[Point2]: """Possible start locations for enemies.""" return self.game_info.start_locations async def get_available_abilities( - self, units: Union[List[Unit], Units], ignore_resource_requirements: bool = False - ) -> List[List[AbilityId]]: + self, units: list[Unit] | Units, ignore_resource_requirements: bool = False + ) -> list[list[AbilityId]]: """Returns available abilities of one or more units. Right now only checks cooldown, energy cost, and whether the ability has been researched. Examples:: @@ -122,7 +122,7 @@ async def get_available_abilities( :param ignore_resource_requirements:""" return await self.client.query_available_abilities(units, ignore_resource_requirements) - async def on_unit_destroyed(self, unit_tag: int): + async def on_unit_destroyed(self, unit_tag: int) -> None: """ Override this in your bot class. This will event will be called when a unit (or structure, friendly or enemy) dies. @@ -131,12 +131,12 @@ async def on_unit_destroyed(self, unit_tag: int): :param unit_tag: """ - async def on_unit_created(self, unit: Unit): + async def on_unit_created(self, unit: Unit) -> None: """Override this in your bot class. This function is called when a unit is created. :param unit:""" - async def on_building_construction_started(self, unit: Unit): + async def on_building_construction_started(self, unit: Unit) -> None: """ Override this in your bot class. This function is called when a building construction has started. @@ -144,7 +144,7 @@ async def on_building_construction_started(self, unit: Unit): :param unit: """ - async def on_building_construction_complete(self, unit: Unit): + async def on_building_construction_complete(self, unit: Unit) -> None: """ Override this in your bot class. This function is called when a building construction is completed. @@ -152,14 +152,14 @@ async def on_building_construction_complete(self, unit: Unit): :param unit: """ - async def on_upgrade_complete(self, upgrade: UpgradeId): + async def on_upgrade_complete(self, upgrade: UpgradeId) -> None: """ Override this in your bot class. This function is called with the upgrade id of an upgrade that was not finished last step and is now. :param upgrade: """ - async def on_start(self): + async def on_start(self) -> None: """ Override this in your bot class. This function is called after "on_start". At this point, game_data, game_info and the first iteration of game_state (self.state) are available. @@ -175,7 +175,7 @@ async def on_step(self, iteration: int): """ raise NotImplementedError - async def on_end(self, game_result: Result): + 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. :param game_result:""" diff --git a/sc2/paths.py b/sc2/paths.py index 79131cde..10ec26bb 100644 --- a/sc2/paths.py +++ b/sc2/paths.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os import platform import re @@ -18,7 +20,7 @@ "WineLinux": "~/.wine/drive_c/Program Files (x86)/StarCraft II", } -USERPATH = { +USERPATH: dict[str, str | None] = { "Windows": "Documents\\StarCraft II\\ExecuteInfo.txt", "WSL1": "Documents/StarCraft II/ExecuteInfo.txt", "WSL2": "Documents/StarCraft II/ExecuteInfo.txt", @@ -36,7 +38,7 @@ "WineLinux": "SC2_x64.exe", } -CWD = { +CWD: dict[str, str | None] = { "Windows": "Support64", "WSL1": "Support64", "WSL2": "Support64", @@ -67,20 +69,20 @@ 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])) - if os.path.isfile(einfo): - with open(einfo) as f: + if Path(einfo).is_file(): + with Path(einfo).open() as f: content = f.read() if content: base = re.search(r" = (.*)Versions", content).group(1) if PF in {"WSL1", "WSL2"}: base = str(wsl.win_path_to_wsl_path(base)) - if os.path.exists(base): + if Path(base).exists(): return base return None -def get_env(): +def get_env() -> None: # TODO: Linux env conf from: https://github.com/deepmind/pysc2/blob/master/pysc2/run_configs/platforms.py return None @@ -122,35 +124,32 @@ def latest_executeble(versions_dir, base_build=None): class _MetaPaths(type): - """"Lazily loads paths to allow importing the library even if SC2 isn't installed.""" + """ "Lazily loads paths to allow importing the library even if SC2 isn't installed.""" - # pylint: disable=C0203 - def __setup(self): + def __setup(cls): if PF not in BASEDIR: logger.critical(f"Unsupported platform '{PF}'") sys.exit(1) try: base = os.environ.get("SC2PATH") or get_user_sc2_install() or BASEDIR[PF] - self.BASE = Path(base).expanduser() - self.EXECUTABLE = latest_executeble(self.BASE / "Versions") - self.CWD = self.BASE / CWD[PF] if CWD[PF] else None + cls.BASE = Path(base).expanduser() + cls.EXECUTABLE = latest_executeble(cls.BASE / "Versions") + cls.CWD = cls.BASE / CWD[PF] if CWD[PF] else None - self.REPLAYS = self.BASE / "Replays" + cls.REPLAYS = cls.BASE / "Replays" - if (self.BASE / "maps").exists(): - self.MAPS = self.BASE / "maps" + if (cls.BASE / "maps").exists(): + cls.MAPS = cls.BASE / "maps" else: - self.MAPS = self.BASE / "Maps" + cls.MAPS = cls.BASE / "Maps" except FileNotFoundError as e: logger.critical(f"SC2 installation not found: File '{e.filename}' does not exist.") sys.exit(1) - # pylint: disable=C0203 - def __getattr__(self, attr): - # pylint: disable=E1120 - self.__setup() - return getattr(self, attr) + def __getattr__(cls, attr): + cls.__setup() + return getattr(cls, attr) class Paths(metaclass=_MetaPaths): diff --git a/sc2/pixel_map.py b/sc2/pixel_map.py index 30dd40d7..672ddee6 100644 --- a/sc2/pixel_map.py +++ b/sc2/pixel_map.py @@ -1,5 +1,7 @@ +from __future__ import annotations + +from collections.abc import Callable from pathlib import Path -from typing import Callable, FrozenSet, List, Set, Tuple, Union import numpy as np @@ -7,8 +9,7 @@ class PixelMap: - - def __init__(self, proto, in_bits: bool = False): + def __init__(self, proto, in_bits: bool = False) -> None: """ :param proto: :param in_bits: @@ -41,14 +42,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: - """ Example usage: is_pathable = self._game_info.pathing_grid[Point2((20, 20))] != 0 """ + def __getitem__(self, pos: tuple[int, int]) -> 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}" return int(self.data_numpy[pos[1], pos[0]]) - def __setitem__(self, pos: Tuple[int, int], value: int): - """ Example usage: self._game_info.pathing_grid[Point2((20, 20))] = 255 """ + def __setitem__(self, pos: tuple[int, int], 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}" assert ( @@ -57,18 +58,18 @@ def __setitem__(self, pos: Tuple[int, int], value: int): assert isinstance(value, int), f"value is of type {type(value)}, it should be an integer" self.data_numpy[pos[1], pos[0]] = value - def is_set(self, p: Tuple[int, int]) -> bool: + def is_set(self, p: tuple[int, int]) -> bool: return self[p] != 0 - def is_empty(self, p: Tuple[int, int]) -> bool: + def is_empty(self, p: tuple[int, int]) -> bool: return not self.is_set(p) - def copy(self) -> "PixelMap": + def copy(self) -> PixelMap: return PixelMap(self._proto, in_bits=self._in_bits) - def flood_fill(self, start_point: Point2, pred: Callable[[int], bool]) -> Set[Point2]: - nodes: Set[Point2] = set() - queue: List[Point2] = [start_point] + def flood_fill(self, start_point: Point2, pred: Callable[[int], bool]) -> set[Point2]: + nodes: set[Point2] = set() + queue: list[Point2] = [start_point] while queue: x, y = queue.pop() @@ -84,8 +85,8 @@ def flood_fill(self, start_point: Point2, pred: Callable[[int], bool]) -> Set[Po 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)] return nodes - def flood_fill_all(self, pred: Callable[[int], bool]) -> Set[FrozenSet[Point2]]: - groups: Set[FrozenSet[Point2]] = set() + def flood_fill_all(self, pred: Callable[[int], bool]) -> set[frozenset[Point2]]: + groups: set[frozenset[Point2]] = set() for x in range(self.width): for y in range(self.height): @@ -103,17 +104,16 @@ def print(self, wide: bool = False) -> None: print("#" if self.is_set((x, y)) else " ", end=(" " if wide else "")) print("") - def save_image(self, filename: Union[str, Path]): + def save_image(self, filename: str | Path) -> None: data = [(0, 0, self[x, y]) for y in range(self.height) for x in range(self.width)] - # pylint: disable=C0415 + from PIL import Image im = Image.new("RGB", (self.width, self.height)) - im.putdata(data) # type: ignore + im.putdata(data) im.save(filename) - def plot(self): - # pylint: disable=C0415 + def plot(self) -> None: import matplotlib.pyplot as plt plt.imshow(self.data_numpy, origin="lower") diff --git a/sc2/player.py b/sc2/player.py index e1259198..c71ce98d 100644 --- a/sc2/player.py +++ b/sc2/player.py @@ -1,22 +1,23 @@ +# pyre-ignore-all-errors[6, 11, 16, 29] +from __future__ import annotations + from abc import ABC from pathlib import Path -from typing import List, Union from sc2.bot_ai import BotAI from sc2.data import AIBuild, Difficulty, PlayerType, Race class AbstractPlayer(ABC): - def __init__( self, p_type: PlayerType, race: Race = None, - name: str = None, + name: str | None = None, difficulty=None, ai_build=None, - fullscreen=False - ): + fullscreen: bool = False, + ) -> None: assert isinstance(p_type, PlayerType), f"p_type is of type {type(p_type)}" assert name is None or isinstance(name, str), f"name is of type {type(name)}" @@ -44,24 +45,22 @@ def __init__( assert ai_build is None @property - def needs_sc2(self): + def needs_sc2(self) -> bool: return not isinstance(self, Computer) class Human(AbstractPlayer): - - def __init__(self, race, name=None, fullscreen=False): + def __init__(self, race, name: str | None = None, fullscreen: bool = False) -> None: super().__init__(PlayerType.Participant, race, name=name, fullscreen=fullscreen) - def __str__(self): + def __str__(self) -> str: if self.name is not None: return f"Human({self.race._name_}, name={self.name !r})" return f"Human({self.race._name_})" class Bot(AbstractPlayer): - - def __init__(self, race, ai, name=None, fullscreen=False): + def __init__(self, race, ai, name: str | None = None, fullscreen: bool = False) -> None: """ AI can be None if this player object is just used to inform the server about player types. @@ -70,39 +69,45 @@ def __init__(self, race, ai, name=None, fullscreen=False): super().__init__(PlayerType.Participant, race, name=name, fullscreen=fullscreen) self.ai = ai - def __str__(self): + def __str__(self) -> str: if self.name is not None: return f"Bot {self.ai.__class__.__name__}({self.race._name_}), name={self.name !r})" return f"Bot {self.ai.__class__.__name__}({self.race._name_})" class Computer(AbstractPlayer): - - def __init__(self, race, difficulty=Difficulty.Easy, ai_build=AIBuild.RandomBuild): + def __init__(self, race, difficulty=Difficulty.Easy, ai_build=AIBuild.RandomBuild) -> None: super().__init__(PlayerType.Computer, race, difficulty=difficulty, ai_build=ai_build) - def __str__(self): + def __str__(self) -> str: return f"Computer {self.difficulty._name_}({self.race._name_}, {self.ai_build.name})" class Observer(AbstractPlayer): - - def __init__(self): + def __init__(self) -> None: super().__init__(PlayerType.Observer) - def __str__(self): + def __str__(self) -> str: return "Observer" class Player(AbstractPlayer): - - def __init__(self, player_id, p_type, requested_race, difficulty=None, actual_race=None, name=None, ai_build=None): + def __init__( + self, + player_id: int, + p_type, + requested_race, + difficulty=None, + actual_race=None, + name: str | None = None, + ai_build=None, + ) -> None: super().__init__(p_type, requested_race, difficulty=difficulty, name=name, ai_build=ai_build) self.id: int = player_id self.actual_race: Race = actual_race @classmethod - def from_proto(cls, proto): + def from_proto(cls, proto) -> Player: if PlayerType(proto.type) == PlayerType.Observer: return cls(proto.player_id, PlayerType(proto.type), None, None, None) return cls( @@ -136,17 +141,17 @@ class BotProcess(AbstractPlayer): def __init__( self, - path: Union[str, Path], - launch_list: List[str], + path: str | Path, + launch_list: list[str], race: Race, - name=None, - sc2port_arg="--GamePort", - hostaddress_arg="--LadderServer", - match_arg="--StartPort", - realtime_arg="--RealTime", - other_args: str = None, - stdout: str = None, - ): + name: str | None = None, + sc2port_arg: str = "--GamePort", + hostaddress_arg: str = "--LadderServer", + match_arg: str = "--StartPort", + realtime_arg: str = "--RealTime", + other_args: str | None = None, + stdout: str | None = None, + ) -> None: super().__init__(PlayerType.Participant, race, name=name) assert Path(path).exists() self.path = path @@ -158,16 +163,12 @@ def __init__( self.other_args = other_args self.stdout = stdout - def __repr__(self): + def __repr__(self) -> str: if self.name is not None: return f"Bot {self.name}({self.race.name} from {self.launch_list})" return f"Bot({self.race.name} from {self.launch_list})" - def cmd_line(self, - sc2port: Union[int, str], - matchport: Union[int, str], - hostaddress: str, - realtime: bool = False) -> List[str]: + def cmd_line(self, sc2port: int | str, matchport: int | str, hostaddress: str, realtime: bool = False) -> list[str]: """ :param sc2port: the port that the launched sc2 instance listens to diff --git a/sc2/portconfig.py b/sc2/portconfig.py index 78011d89..2e646faf 100644 --- a/sc2/portconfig.py +++ b/sc2/portconfig.py @@ -1,5 +1,8 @@ +from __future__ import annotations + import json +# pyre-fixme[21] import portpicker @@ -22,7 +25,7 @@ class Portconfig: E.g. for 1v1, there will be only 1 guest. For 2v2 (coming soonTM), there would be 3 guests. """ - def __init__(self, guests=1, server_ports=None, player_ports=None): + def __init__(self, guests: int = 1, server_ports=None, player_ports=None) -> None: self.shared = None self._picked_ports = [] if server_ports: @@ -36,19 +39,19 @@ def __init__(self, guests=1, server_ports=None, player_ports=None): 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) - def clean(self): + def clean(self) -> None: while self._picked_ports: portpicker.return_port(self._picked_ports.pop()) - def __str__(self): + def __str__(self) -> str: return f"Portconfig(shared={self.shared}, server={self.server}, players={self.players})" @property - def as_json(self): + def as_json(self) -> str: return json.dumps({"shared": self.shared, "server": self.server, "players": self.players}) @classmethod - def contiguous_ports(cls, guests=1, attempts=40): + def contiguous_ports(cls, guests: int = 1, attempts: int = 40) -> Portconfig: """Returns a Portconfig with adjacent ports""" for _ in range(attempts): start = portpicker.pick_unused_port() @@ -64,6 +67,6 @@ def contiguous_ports(cls, guests=1, attempts=40): raise portpicker.NoFreePortFoundError() @classmethod - def from_json(cls, json_data): + def from_json(cls, json_data: bytearray | bytes | str) -> Portconfig: data = json.loads(json_data) return cls(server_ports=data["server"], player_ports=data["players"]) diff --git a/sc2/position.py b/sc2/position.py index 67d802c7..36a0922f 100644 --- a/sc2/position.py +++ b/sc2/position.py @@ -1,37 +1,39 @@ +# pyre-ignore-all-errors[6, 14, 15, 58] from __future__ import annotations import itertools import math import random -from typing import TYPE_CHECKING, Iterable, List, Set, Tuple, Union +from collections.abc import Iterable +from typing import TYPE_CHECKING, SupportsFloat, SupportsIndex +# pyre-fixme[21] from s2clientprotocol import common_pb2 as common_pb if TYPE_CHECKING: from sc2.unit import Unit from sc2.units import Units -EPSILON = 10**-8 +EPSILON: float = 10**-8 -def _sign(num): +def _sign(num: SupportsFloat | SupportsIndex) -> float: return math.copysign(1, num) class Pointlike(tuple): - @property def position(self) -> Pointlike: return self - def distance_to(self, target: Union[Unit, Point2]) -> float: + def distance_to(self, target: Unit | Point2) -> float: """Calculate a single distance from a point or unit to another point or unit :param target:""" p = target.position return math.hypot(self[0] - p[0], self[1] - p[1]) - def distance_to_point2(self, p: Union[Point2, Tuple[float, float]]) -> float: + def distance_to_point2(self, p: Point2 | tuple[float, float]) -> float: """Same as the function above, but should be a bit faster because of the dropped asserts and conversion. @@ -43,9 +45,9 @@ def _distance_squared(self, p2: Point2) -> float: This is to speed up the sorting process. :param p2:""" - return (self[0] - p2[0])**2 + (self[1] - p2[1])**2 + return (self[0] - p2[0]) ** 2 + (self[1] - p2[1]) ** 2 - def sort_by_distance(self, ps: Union[Units, Iterable[Point2]]) -> List[Point2]: + def sort_by_distance(self, ps: Units | Iterable[Point2]) -> list[Point2]: """This returns the target points sorted as list. You should not pass a set or dict since those are not sortable. If you want to sort your units towards a point, use 'units.sorted_by_distance_to(point)' instead. @@ -53,15 +55,15 @@ def sort_by_distance(self, ps: Union[Units, Iterable[Point2]]) -> List[Point2]: :param ps:""" return sorted(ps, key=lambda p: self.distance_to_point2(p.position)) - def closest(self, ps: Union[Units, Iterable[Point2]]) -> Union[Unit, Point2]: + def closest(self, ps: Units | Iterable[Point2]) -> Unit | Point2: """This function assumes the 2d distance is meant :param ps:""" assert ps, "ps is empty" - # pylint: disable=W0108 + return min(ps, key=lambda p: self.distance_to(p)) - def distance_to_closest(self, ps: Union[Units, Iterable[Point2]]) -> float: + def distance_to_closest(self, ps: Units | Iterable[Point2]) -> float: """This function assumes the 2d distance is meant :param ps:""" assert ps, "ps is empty" @@ -73,15 +75,15 @@ def distance_to_closest(self, ps: Union[Units, Iterable[Point2]]) -> float: closest_distance = distance return closest_distance - def furthest(self, ps: Union[Units, Iterable[Point2]]) -> Union[Unit, Pointlike]: + def furthest(self, ps: Units | Iterable[Point2]) -> Unit | Pointlike: """This function assumes the 2d distance is meant :param ps: Units object, or iterable of Unit or Point2""" assert ps, "ps is empty" - # pylint: disable=W0108 + return max(ps, key=lambda p: self.distance_to(p)) - def distance_to_furthest(self, ps: Union[Units, Iterable[Point2]]) -> float: + def distance_to_furthest(self, ps: Units | Iterable[Point2]) -> float: """This function assumes the 2d distance is meant :param ps:""" @@ -99,16 +101,16 @@ def offset(self, p) -> Pointlike: :param p: """ - return self.__class__(a + b for a, b in itertools.zip_longest(self, p[:len(self)], fillvalue=0)) + return self.__class__(a + b for a, b in itertools.zip_longest(self, p[: len(self)], fillvalue=0)) - def unit_axes_towards(self, p): + def unit_axes_towards(self, p) -> Pointlike: """ :param p: """ - return self.__class__(_sign(b - a) for a, b in itertools.zip_longest(self, p[:len(self)], fillvalue=0)) + return self.__class__(_sign(b - a) for a, b in itertools.zip_longest(self, p[: len(self)], fillvalue=0)) - def towards(self, p: Union[Unit, Pointlike], distance: Union[int, float] = 1, limit: bool = False) -> Pointlike: + def towards(self, p: Unit | Pointlike, distance: int | float = 1, limit: bool = False) -> Pointlike: """ :param p: @@ -125,22 +127,20 @@ def towards(self, p: Union[Unit, Pointlike], distance: Union[int, float] = 1, li if limit: distance = min(d, distance) return self.__class__( - a + (b - a) / d * distance for a, b in itertools.zip_longest(self, p[:len(self)], fillvalue=0) + a + (b - a) / d * distance for a, b in itertools.zip_longest(self, p[: len(self)], fillvalue=0) ) - def __eq__(self, other): + def __eq__(self, other: object) -> bool: try: return all(abs(a - b) <= EPSILON for a, b in itertools.zip_longest(self, other, fillvalue=0)) except TypeError: return False - def __hash__(self): + def __hash__(self) -> int: return hash(tuple(self)) -# pylint: disable=R0904 class Point2(Pointlike): - @classmethod def from_proto(cls, data) -> Point2: """ @@ -149,10 +149,12 @@ def from_proto(cls, data) -> Point2: return cls((data.x, data.y)) @property + # pyre-fixme[11] def as_Point2D(self) -> common_pb.Point2D: return common_pb.Point2D(x=self.x, y=self.y) @property + # pyre-fixme[11] def as_PointI(self) -> common_pb.PointI: """Represents points on the minimap. Values must be between 0 and 64.""" return common_pb.PointI(x=self.x, y=self.y) @@ -163,12 +165,12 @@ def rounded(self) -> Point2: @property def length(self) -> float: - """ This property exists in case Point2 is used as a vector. """ + """This property exists in case Point2 is used as a vector.""" return math.hypot(self[0], self[1]) @property def normalized(self) -> Point2: - """ This property exists in case Point2 is used as a vector. """ + """This property exists in case Point2 is used as a vector.""" length = self.length # Cannot normalize if length is zero assert length @@ -209,40 +211,40 @@ def random_on_distance(self, distance) -> Point2: def towards_with_random_angle( self, - p: Union[Point2, Point3], - distance: Union[int, float] = 1, - max_difference: Union[int, float] = (math.pi / 4), + p: Point2 | Point3, + distance: int | float = 1, + max_difference: int | float = (math.pi / 4), ) -> Point2: tx, ty = self.to2.towards(p.to2, 1) angle = math.atan2(ty - self.y, tx - self.x) angle = (angle - max_difference) + max_difference * 2 * random.random() return Point2((self.x + math.cos(angle) * distance, self.y + math.sin(angle) * distance)) - def circle_intersection(self, p: Point2, r: Union[int, float]) -> Set[Point2]: + def circle_intersection(self, p: Point2, r: int | float) -> set[Point2]: """self is point1, p is point2, r is the radius for circles originating in both points Used in ramp finding :param p: :param r:""" assert self != p, "self is equal to p" - distanceBetweenPoints = self.distance_to(p) - assert r >= distanceBetweenPoints / 2 + distance_between_points = self.distance_to(p) + assert r >= distance_between_points / 2 # remaining distance from center towards the intersection, using pythagoras - remainingDistanceFromCenter = (r**2 - (distanceBetweenPoints / 2)**2)**0.5 + remaining_distance_from_center = (r**2 - (distance_between_points / 2) ** 2) ** 0.5 # center of both points - offsetToCenter = Point2(((p.x - self.x) / 2, (p.y - self.y) / 2)) - center = self.offset(offsetToCenter) + offset_to_center = Point2(((p.x - self.x) / 2, (p.y - self.y) / 2)) + center = self.offset(offset_to_center) # stretch offset vector in the ratio of remaining distance from center to intersection - vectorStretchFactor = remainingDistanceFromCenter / (distanceBetweenPoints / 2) - v = offsetToCenter - offsetToCenterStretched = Point2((v.x * vectorStretchFactor, v.y * vectorStretchFactor)) + vector_stretch_factor = remaining_distance_from_center / (distance_between_points / 2) + v = offset_to_center + offset_to_center_stretched = Point2((v.x * vector_stretch_factor, v.y * vector_stretch_factor)) # rotate vector by 90° and -90° - vectorRotated1 = Point2((offsetToCenterStretched.y, -offsetToCenterStretched.x)) - vectorRotated2 = Point2((-offsetToCenterStretched.y, offsetToCenterStretched.x)) - intersect1 = center.offset(vectorRotated1) - intersect2 = center.offset(vectorRotated2) + vector_rotated_1 = Point2((offset_to_center_stretched.y, -offset_to_center_stretched.x)) + vector_rotated_2 = Point2((-offset_to_center_stretched.y, offset_to_center_stretched.x)) + intersect1 = center.offset(vector_rotated_1) + intersect2 = center.offset(vector_rotated_2) return {intersect1, intersect2} @property @@ -279,29 +281,28 @@ def __abs__(self) -> float: return math.hypot(self.x, self.y) def __bool__(self) -> bool: - if self.x != 0 or self.y != 0: - return True - return False + return self.x != 0 or self.y != 0 - def __mul__(self, other: Union[int, float, Point2]) -> Point2: + def __mul__(self, other: int | float | Point2) -> Point2: try: + # pyre-ignore[16] return self.__class__((self.x * other.x, self.y * other.y)) except AttributeError: return self.__class__((self.x * other, self.y * other)) - def __rmul__(self, other: Union[int, float, Point2]) -> Point2: + def __rmul__(self, other: int | float | Point2) -> Point2: return self.__mul__(other) - def __truediv__(self, other: Union[int, float, Point2]) -> Point2: + def __truediv__(self, other: int | float | Point2) -> Point2: if isinstance(other, self.__class__): return self.__class__((self.x / other.x, self.y / other.y)) return self.__class__((self.x / other, self.y / other)) - def is_same_as(self, other: Point2, dist=0.001) -> bool: + def is_same_as(self, other: Point2, dist: float = 0.001) -> bool: return self.distance_to_point2(other) <= dist def direction_vector(self, other: Point2) -> Point2: - """ Converts a vector to a direction that can face vertically, horizontally or diagonal or be zero, e.g. (0, 0), (1, -1), (1, 0) """ + """Converts a vector to a direction that can face vertically, horizontally or diagonal or be zero, e.g. (0, 0), (1, -1), (1, 0)""" return self.__class__((_sign(other.x - self.x), _sign(other.y - self.y))) def manhattan_distance(self, other: Point2) -> float: @@ -311,7 +312,7 @@ def manhattan_distance(self, other: Point2) -> float: return abs(other.x - self.x) + abs(other.y - self.y) @staticmethod - def center(points: List[Point2]) -> Point2: + def center(points: list[Point2]) -> Point2: """Returns the central point for points in list :param points:""" @@ -322,7 +323,6 @@ def center(points: List[Point2]) -> Point2: class Point3(Point2): - @classmethod def from_proto(cls, data) -> Point3: """ @@ -331,6 +331,7 @@ def from_proto(cls, data) -> Point3: return cls((data.x, data.y, data.z)) @property + # pyre-fixme[11] def as_Point(self) -> common_pb.Point: return common_pb.Point(x=self.x, y=self.y, z=self.z) @@ -346,14 +347,14 @@ def z(self) -> float: def to3(self) -> Point3: return Point3(self) - def __add__(self, other: Union[Point2, Point3]) -> Point3: + def __add__(self, other: Point2 | Point3) -> Point3: if not isinstance(other, Point3) and isinstance(other, Point2): return Point3((self.x + other.x, self.y + other.y, self.z)) + # pyre-ignore[16] return Point3((self.x + other.x, self.y + other.y, self.z + other.z)) class Size(Point2): - @property def width(self) -> float: return self[0] @@ -364,9 +365,8 @@ def height(self) -> float: class Rect(tuple): - @classmethod - def from_proto(cls, data): + def from_proto(cls, data) -> Rect: """ :param data: """ @@ -391,12 +391,12 @@ def height(self) -> float: @property def right(self) -> float: - """ Returns the x-coordinate of the rectangle of its right side. """ + """Returns the x-coordinate of the rectangle of its right side.""" return self.x + self.width @property def top(self) -> float: - """ Returns the y-coordinate of the rectangle of its top side. """ + """Returns the y-coordinate of the rectangle of its top side.""" return self.y + self.height @property @@ -407,5 +407,5 @@ def size(self) -> Size: def center(self) -> Point2: return Point2((self.x + self.width / 2, self.y + self.height / 2)) - def offset(self, p): + def offset(self, p) -> Rect: return self.__class__((self[0] + p[0], self[1] + p[1], self[2], self[3])) diff --git a/sc2/power_source.py b/sc2/power_source.py index b2b534c0..8c64bb62 100644 --- a/sc2/power_source.py +++ b/sc2/power_source.py @@ -1,5 +1,6 @@ +from __future__ import annotations + from dataclasses import dataclass -from typing import List from sc2.position import Point2 @@ -10,26 +11,26 @@ class PowerSource: radius: float unit_tag: int - def __post_init__(self): + def __post_init__(self) -> None: assert self.radius > 0 @classmethod - def from_proto(cls, proto): + def from_proto(cls, proto) -> PowerSource: return PowerSource(Point2.from_proto(proto.pos), proto.radius, proto.tag) def covers(self, position: Point2) -> bool: return self.position.distance_to(position) <= self.radius - def __repr__(self): + def __repr__(self) -> str: return f"PowerSource({self.position}, {self.radius})" @dataclass class PsionicMatrix: - sources: List[PowerSource] + sources: list[PowerSource] @classmethod - def from_proto(cls, proto): + def from_proto(cls, proto) -> PsionicMatrix: return PsionicMatrix([PowerSource.from_proto(p) for p in proto]) def covers(self, position: Point2) -> bool: diff --git a/sc2/protocol.py b/sc2/protocol.py index 8fc72684..0c44cb53 100644 --- a/sc2/protocol.py +++ b/sc2/protocol.py @@ -1,35 +1,38 @@ +from __future__ import annotations + import asyncio import sys from contextlib import suppress -from aiohttp import ClientWebSocketResponse +from aiohttp.client_ws import ClientWebSocketResponse from loguru import logger + +# pyre-fixme[21] from s2clientprotocol import sc2api_pb2 as sc_pb from sc2.data import Status class ProtocolError(Exception): - @property def is_game_over_error(self) -> bool: return self.args[0] in ["['Game has already ended']", "['Not supported if game has already ended']"] -class ConnectionAlreadyClosed(ProtocolError): +class ConnectionAlreadyClosedError(ProtocolError): pass class Protocol: - - def __init__(self, ws): + def __init__(self, ws: ClientWebSocketResponse) -> None: """ A class for communicating with an SCII application. :param ws: the websocket (type: aiohttp.ClientWebSocketResponse) used to communicate with a specific SCII app """ assert ws self._ws: ClientWebSocketResponse = ws - self._status: Status = None + # pyre-fixme[11] + self._status: Status | None = None async def __request(self, request): logger.debug(f"Sending request: {request !r}") @@ -37,7 +40,7 @@ async def __request(self, request): await self._ws.send_bytes(request.SerializeToString()) except TypeError as exc: logger.exception("Cannot send: Connection already closed.") - raise ConnectionAlreadyClosed("Connection already closed.") from exc + raise ConnectionAlreadyClosedError("Connection already closed.") from exc logger.debug("Request sent") response = sc_pb.Response() @@ -46,9 +49,9 @@ async def __request(self, request): except TypeError as exc: if self._status == Status.ended: logger.info("Cannot receive: Game has already ended.") - raise ConnectionAlreadyClosed("Game has already ended") from exc + raise ConnectionAlreadyClosedError("Game has already ended") from exc logger.error("Cannot receive: Connection already closed.") - raise ConnectionAlreadyClosed("Connection already closed.") from exc + raise ConnectionAlreadyClosedError("Connection already closed.") from exc except asyncio.CancelledError: # If request is sent, the response must be received before reraising cancel try: @@ -82,6 +85,6 @@ async def ping(self): result = await self._execute(ping=sc_pb.RequestPing()) return result - async def quit(self): - with suppress(ConnectionAlreadyClosed, ConnectionResetError): + async def quit(self) -> None: + with suppress(ConnectionAlreadyClosedError, ConnectionResetError): await self._execute(quit=sc_pb.RequestQuit()) diff --git a/sc2/proxy.py b/sc2/proxy.py index 2e204178..88242b24 100644 --- a/sc2/proxy.py +++ b/sc2/proxy.py @@ -1,13 +1,19 @@ -# pylint: disable=W0212 +# pyre-ignore-all-errors[16, 29] +from __future__ import annotations + import asyncio import os import platform import subprocess import time import traceback +from pathlib import Path from aiohttp import WSMsgType, web +from aiohttp.web_ws import WebSocketResponse from loguru import logger + +# pyre-fixme[21] from s2clientprotocol import sc2api_pb2 as sc_pb from sc2.controller import Controller @@ -26,9 +32,9 @@ def __init__( controller: Controller, player: BotProcess, proxyport: int, - game_time_limit: int = None, + game_time_limit: int | None = None, realtime: bool = False, - ): + ) -> None: self.controller = controller self.player = player self.port = proxyport @@ -39,10 +45,10 @@ def __init__( ) self.result = None - self.player_id: int = None + self.player_id: int | None = None self.done = False - async def parse_request(self, msg): + async def parse_request(self, msg) -> None: request = sc_pb.Request() request.ParseFromString(msg.data) if request.HasField("quit"): @@ -58,7 +64,7 @@ async def parse_request(self, msg): await self.controller._ws.send_bytes(request.SerializeToString()) # TODO Catching too general exception Exception (broad-except) - # pylint: disable=W0703 + async def get_response(self): response_bytes = None try: @@ -91,38 +97,34 @@ async def parse_response(self, response_bytes): logger.info(f"Controller({self.player.name}): {self.controller._status}->{new_status}") self.controller._status = new_status - if self.player_id is None: - if response.HasField("join_game"): - self.player_id = response.join_game.player_id - logger.info(f"Proxy({self.player.name}): got join_game for {self.player_id}") - - if self.result is None: - if response.HasField("observation"): - obs: sc_pb.ResponseObservation = response.observation - if obs.player_result: - self.result = {pr.player_id: Result(pr.result) for pr in obs.player_result} - elif ( - self.timeout_loop and obs.HasField("observation") and obs.observation.game_loop > self.timeout_loop - ): - self.result = {i: Result.Tie for i in range(1, 3)} - logger.info(f"Proxy({self.player.name}) timing out") - act = [sc_pb.Action(action_chat=sc_pb.ActionChat(message="Proxy: Timing out"))] - await self.controller._execute(action=sc_pb.RequestAction(actions=act)) + if self.player_id is None and response.HasField("join_game"): + self.player_id = response.join_game.player_id + logger.info(f"Proxy({self.player.name}): got join_game for {self.player_id}") + + if self.result is None and response.HasField("observation"): + obs: sc_pb.ResponseObservation = response.observation + if obs.player_result: + self.result = {pr.player_id: Result(pr.result) for pr in obs.player_result} + elif self.timeout_loop and obs.HasField("observation") and obs.observation.game_loop > self.timeout_loop: + self.result = {i: Result.Tie for i in range(1, 3)} + logger.info(f"Proxy({self.player.name}) timing out") + act = [sc_pb.Action(action_chat=sc_pb.ActionChat(message="Proxy: Timing out"))] + await self.controller._execute(action=sc_pb.RequestAction(actions=act)) return response - async def get_result(self): + async def get_result(self) -> None: try: res = await self.controller.ping() if res.status in {Status.in_game, Status.in_replay, Status.ended}: res = await self.controller._execute(observation=sc_pb.RequestObservation()) if res.HasField("observation") and res.observation.player_result: self.result = {pr.player_id: Result(pr.result) for pr in res.observation.player_result} - # pylint: disable=W0703 + # TODO Catching too general exception Exception (broad-except) except Exception as e: logger.exception(f"Caught unknown exception: {e}") - async def proxy_handler(self, request): + async def proxy_handler(self, request) -> WebSocketResponse: bot_ws = web.WebSocketResponse(receive_timeout=30) await bot_ws.prepare(request) try: @@ -130,7 +132,6 @@ async def proxy_handler(self, request): if msg.data is None: raise TypeError(f"data is None, {msg}") if msg.data and msg.type == WSMsgType.BINARY: - await self.parse_request(msg) response_bytes = await self.get_response() @@ -144,7 +145,7 @@ async def proxy_handler(self, request): logger.error("Client shutdown") else: logger.error("Incorrect message type") - # pylint: disable=W0703 + # TODO Catching too general exception Exception (broad-except) except Exception as e: logger.exception(f"Caught unknown exception: {e}") @@ -157,14 +158,13 @@ async def proxy_handler(self, request): if self.controller._status in {Status.in_game, Status.in_replay}: await self.controller._execute(leave_game=sc_pb.RequestLeaveGame()) await bot_ws.close() - # pylint: disable=W0703 + # TODO Catching too general exception Exception (broad-except) except Exception as e: logger.exception(f"Caught unknown exception during surrender: {e}") self.done = True return bot_ws - # pylint: disable=R0912 async def play_with_proxy(self, startport): logger.info(f"Proxy({self.port}): Starting app") app = web.Application() @@ -185,7 +185,7 @@ async def play_with_proxy(self, startport): if self.player.stdout is None: bot_process = subprocess.Popen(player_command_line, stdout=subprocess.DEVNULL, **subproc_args) else: - with open(self.player.stdout, "w+") as out: + with Path(self.player.stdout).open("w+") as out: bot_process = subprocess.Popen(player_command_line, stdout=out, **subproc_args) while self.result is None: @@ -209,8 +209,8 @@ async def play_with_proxy(self, startport): if isinstance(bot_process, subprocess.Popen): if bot_process.stdout and not bot_process.stdout.closed: # should not run anymore logger.info(f"==================output for player {self.player.name}") - for l in bot_process.stdout.readlines(): - logger.opt(raw=True).info(l.decode("utf-8")) + for line in bot_process.stdout.readlines(): + logger.opt(raw=True).info(line.decode("utf-8")) bot_process.stdout.close() logger.info("==================") bot_process.terminate() @@ -223,7 +223,7 @@ async def play_with_proxy(self, startport): bot_process.wait() try: await apprunner.cleanup() - # pylint: disable=W0703 + # TODO Catching too general exception Exception (broad-except) except Exception as e: logger.exception(f"Caught unknown exception during cleaning: {e}") diff --git a/sc2/renderer.py b/sc2/renderer.py index 60aceb0c..4d9f94ff 100644 --- a/sc2/renderer.py +++ b/sc2/renderer.py @@ -1,13 +1,13 @@ import datetime +# pyre-ignore[21] from s2clientprotocol import score_pb2 as score_pb from sc2.position import Point2 class Renderer: - - def __init__(self, client, map_size, minimap_size): + def __init__(self, client, map_size, minimap_size) -> None: self._client = client self._window = None @@ -22,7 +22,7 @@ def __init__(self, client, map_size, minimap_size): self._text_score = None self._text_time = None - async def render(self, observation): + async def render(self, observation) -> None: render_data = observation.observation.render_data map_size = render_data.map.size @@ -37,14 +37,16 @@ async def render(self, observation): minimap_pitch = -minimap_width * 3 if not self._window: - # pylint: disable=C0415 from pyglet.image import ImageData from pyglet.text import Label from pyglet.window import Window self._window = Window(width=map_width, height=map_height) + # pyre-fixme[16] self._window.on_mouse_press = self._on_mouse_press + # pyre-fixme[16] self._window.on_mouse_release = self._on_mouse_release + # pyre-fixme[16] 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) @@ -107,7 +109,6 @@ async def render(self, observation): 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"): - # pylint: disable=W0212 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() @@ -116,7 +117,7 @@ async def render(self, observation): await self._client.move_camera_spatial(Point2((self._mouse_x, self._minimap_size[0] - self._mouse_y))) self._mouse_x, self._mouse_y = None, None - async def _update_window(self): + async def _update_window(self) -> None: self._window.switch_to() self._window.dispatch_events() @@ -132,21 +133,21 @@ async def _update_window(self): self._window.flip() - def _on_mouse_press(self, x, y, button, _modifiers): + def _on_mouse_press(self, x, y, button, _modifiers) -> None: if button != 1: # 1: mouse.LEFT return if x > self._minimap_size[0] or y > self._minimap_size[1]: return self._mouse_x, self._mouse_y = x, y - def _on_mouse_release(self, x, y, button, _modifiers): + def _on_mouse_release(self, x, y, button, _modifiers) -> None: if button != 1: # 1: mouse.LEFT return if x > self._minimap_size[0] or y > self._minimap_size[1]: return self._mouse_x, self._mouse_y = x, y - def _on_mouse_drag(self, x, y, _dx, _dy, buttons, _modifiers): + def _on_mouse_drag(self, x, y, _dx, _dy, buttons, _modifiers) -> None: if not buttons & 1: # 1: mouse.LEFT return if x > self._minimap_size[0] or y > self._minimap_size[1]: diff --git a/sc2/sc2process.py b/sc2/sc2process.py index 1c25d9c2..ad8c0dae 100644 --- a/sc2/sc2process.py +++ b/sc2/sc2process.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import asyncio import os import os.path @@ -8,10 +10,14 @@ import tempfile import time from contextlib import suppress -from typing import Any, Dict, List, Optional, Tuple, Union +from pathlib import Path +from typing import Any import aiohttp + +# pyre-ignore[21] import portpicker +from aiohttp.client_ws import ClientWebSocketResponse from loguru import logger from sc2 import paths, wsl @@ -20,19 +26,18 @@ from sc2.versions import VERSIONS -class kill_switch: - _to_kill: List[Any] = [] +class KillSwitch: + _to_kill: list[Any] = [] @classmethod - def add(cls, value): + def add(cls, value) -> None: logger.debug("kill_switch: Add switch") cls._to_kill.append(value) @classmethod - def kill_all(cls): + def kill_all(cls) -> None: logger.info(f"kill_switch: Process cleanup for {len(cls._to_kill)} processes") for p in cls._to_kill: - # pylint: disable=W0212 p._clean(verbose=False) @@ -54,21 +59,21 @@ class SC2Process: def __init__( self, - host: Optional[str] = None, - port: Optional[int] = None, + host: str | None = None, + port: int | None = None, fullscreen: bool = False, - resolution: Optional[Union[List[int], Tuple[int, int]]] = None, - placement: Optional[Union[List[int], Tuple[int, int]]] = None, + resolution: list[int] | tuple[int, int] | None = None, + placement: list[int] | tuple[int, int] | None = None, render: bool = False, - sc2_version: str = None, - base_build: str = None, - data_hash: str = None, + sc2_version: str | None = None, + base_build: str | None = None, + data_hash: str | None = None, ) -> None: assert isinstance(host, str) or host is None assert isinstance(port, int) or port is None self._render = render - self._arguments: Dict[str, str] = {"-displayMode": str(int(fullscreen))} + self._arguments: dict[str, str] = {"-displayMode": str(int(fullscreen))} if not fullscreen: if resolution and len(resolution) == 2: self._arguments["-windowwidth"] = str(resolution[0]) @@ -86,7 +91,7 @@ def __init__( self._port = port self._used_portpicker = bool(port is None) self._tmp_dir = tempfile.mkdtemp(prefix="SC2_") - self._process: subprocess = None + self._process: subprocess.Popen | None = None self._session = None self._ws = None self._sc2_version = sc2_version @@ -94,12 +99,12 @@ def __init__( self._data_hash = data_hash async def __aenter__(self) -> Controller: - kill_switch.add(self) + KillSwitch.add(self) def signal_handler(*_args): # unused arguments: signal handling library expects all signal # callback handlers to accept two positional arguments - kill_switch.kill_all() + KillSwitch.kill_all() signal.signal(signal.SIGINT, signal_handler) @@ -113,13 +118,13 @@ def signal_handler(*_args): return Controller(self._ws, self) - async def __aexit__(self, *args): + async def __aexit__(self, *args) -> None: await self._close_connection() - kill_switch.kill_all() + KillSwitch.kill_all() signal.signal(signal.SIGINT, signal.SIG_DFL) @property - def ws_url(self): + def ws_url(self) -> str: return f"ws://{self._host}:{self._port}/sc2api" @property @@ -128,8 +133,8 @@ def versions(self): https://github.com/Blizzard/s2client-proto/blob/master/buildinfo/versions.json""" return VERSIONS - def find_data_hash(self, target_sc2_version: str) -> Optional[str]: - """ Returns the data hash from the matching version string. """ + 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: @@ -161,11 +166,8 @@ def _launch(self): if self._sc2_version: def special_match(strg: str): - """ Tests if the specified version is in the versions.py dict. """ - for version in self.versions: - if version["label"] == strg: - return True - return False + """Tests if the specified version is in the versions.py dict.""" + return any(version["label"] == strg for version in self.versions) valid_version_string = special_match(self._sc2_version) if valid_version_string: @@ -197,11 +199,11 @@ def special_match(strg: str): args, cwd=sc2_cwd, # Suppress Wine error messages - stderr=subprocess.DEVNULL + stderr=subprocess.DEVNULL, # , env=run_config.env ) - async def _connect(self): + async def _connect(self) -> ClientWebSocketResponse: # How long it waits for SC2 to start (in seconds) for i in range(180): if self._process is None: @@ -227,7 +229,7 @@ async def _connect(self): logger.debug("Websocket connection to SC2 process timed out") raise TimeoutError("Websocket") - async def _close_connection(self): + async def _close_connection(self) -> None: logger.info(f"Closing connection at {self._port}...") if self._ws is not None: @@ -236,12 +238,12 @@ async def _close_connection(self): if self._session is not None: await self._session.close() - # pylint: disable=R0912 - def _clean(self, verbose=True): + def _clean(self, verbose: bool = True) -> None: if verbose: logger.info("Cleaning up...") if self._process is not None: + assert isinstance(self._process, subprocess.Popen) if paths.PF in {"WSL1", "WSL2"}: if wsl.kill(self._process): logger.error("KILLED") @@ -258,11 +260,10 @@ def _clean(self, verbose=True): # Try to kill wineserver on linux if paths.PF in {"Linux", "WineLinux"}: # Command wineserver not detected - with suppress(FileNotFoundError): - with subprocess.Popen(["wineserver", "-k"]) as p: - p.wait() + with suppress(FileNotFoundError), subprocess.Popen(["wineserver", "-k"]) as p: + p.wait() - if os.path.exists(self._tmp_dir): + if Path(self._tmp_dir).exists(): shutil.rmtree(self._tmp_dir) self._process = None diff --git a/sc2/score.py b/sc2/score.py index 808ee938..9b8f5f2c 100644 --- a/sc2/score.py +++ b/sc2/score.py @@ -1,10 +1,9 @@ -# pylint: disable=R0904 class ScoreDetails: """Accessable in self.state.score during step function For more information, see https://github.com/Blizzard/s2client-proto/blob/master/s2clientprotocol/score.proto """ - def __init__(self, proto): + def __init__(self, proto) -> None: self._data = proto self._proto = proto.score_details diff --git a/sc2/unit.py b/sc2/unit.py index 01d65d28..f679a2ed 100644 --- a/sc2/unit.py +++ b/sc2/unit.py @@ -1,11 +1,11 @@ -# pylint: disable=W0212 +# pyre-ignore-all-errors[11, 16, 29] from __future__ import annotations import math import warnings from dataclasses import dataclass from functools import cached_property -from typing import TYPE_CHECKING, Any, FrozenSet, List, Optional, Set, Tuple, Union +from typing import TYPE_CHECKING, Any from sc2.cache import CacheDict from sc2.constants import ( @@ -68,7 +68,7 @@ @dataclass class RallyTarget: point: Point2 - tag: Optional[int] = None + tag: int | None = None @classmethod def from_proto(cls, proto: Any) -> RallyTarget: @@ -81,12 +81,12 @@ def from_proto(cls, proto: Any) -> RallyTarget: @dataclass class UnitOrder: ability: AbilityData # TODO: Should this be AbilityId instead? - target: Optional[Union[int, Point2]] = None + target: int | Point2 | None = None progress: float = 0 @classmethod def from_proto(cls, proto: Any, bot_object: BotAI) -> UnitOrder: - target: Optional[Union[int, Point2]] = proto.target_unit_tag + target: int | Point2 | None = proto.target_unit_tag if proto.HasField("target_world_space_pos"): target = Point2.from_proto(proto.target_world_space_pos) elif proto.HasField("target_unit_tag"): @@ -101,7 +101,6 @@ def __repr__(self) -> str: return f"UnitOrder({self.ability}, {self.target}, {self.progress})" -# pylint: disable=R0904 class Unit: class_cache = CacheDict() @@ -111,7 +110,7 @@ def __init__( bot_object: BotAI, distance_calculation_index: int = -1, base_build: int = -1, - ): + ) -> None: """ :param proto_data: :param bot_object: @@ -126,84 +125,84 @@ def __init__( self.distance_calculation_index: int = distance_calculation_index def __repr__(self) -> str: - """ Returns string of this form: Unit(name='SCV', tag=4396941328). """ + """Returns string of this form: Unit(name='SCV', tag=4396941328).""" return f"Unit(name={self.name !r}, tag={self.tag})" @property def type_id(self) -> UnitTypeId: - """ UnitTypeId found in sc2/ids/unit_typeid. """ + """UnitTypeId found in sc2/ids/unit_typeid.""" unit_type: int = self._proto.unit_type return self.class_cache.retrieve_and_set(unit_type, lambda: UnitTypeId(unit_type)) @cached_property def _type_data(self) -> UnitTypeData: - """ Provides the unit type data. """ + """Provides the unit type data.""" return self._bot_object.game_data.units[self._proto.unit_type] @cached_property - def _creation_ability(self) -> AbilityData: - """ Provides the AbilityData of the creation ability of this unit. """ + def _creation_ability(self) -> AbilityData | None: + """Provides the AbilityData of the creation ability of this unit.""" return self._type_data.creation_ability @property def name(self) -> str: - """ Returns the name of the unit. """ + """Returns the name of the unit.""" return self._type_data.name @cached_property def race(self) -> Race: - """ Returns the race of the unit """ + """Returns the race of the unit""" return Race(self._type_data._proto.race) @property def tag(self) -> int: - """ Returns the unique tag of the unit. """ + """Returns the unique tag of the unit.""" return self._proto.tag @property def is_structure(self) -> bool: - """ Checks if the unit is a structure. """ + """Checks if the unit is a structure.""" return IS_STRUCTURE in self._type_data.attributes @property def is_light(self) -> bool: - """ Checks if the unit has the 'light' attribute. """ + """Checks if the unit has the 'light' attribute.""" return IS_LIGHT in self._type_data.attributes @property def is_armored(self) -> bool: - """ Checks if the unit has the 'armored' attribute. """ + """Checks if the unit has the 'armored' attribute.""" return IS_ARMORED in self._type_data.attributes @property def is_biological(self) -> bool: - """ Checks if the unit has the 'biological' attribute. """ + """Checks if the unit has the 'biological' attribute.""" return IS_BIOLOGICAL in self._type_data.attributes @property def is_mechanical(self) -> bool: - """ Checks if the unit has the 'mechanical' attribute. """ + """Checks if the unit has the 'mechanical' attribute.""" return IS_MECHANICAL in self._type_data.attributes @property def is_massive(self) -> bool: - """ Checks if the unit has the 'massive' attribute. """ + """Checks if the unit has the 'massive' attribute.""" return IS_MASSIVE in self._type_data.attributes @property def is_psionic(self) -> bool: - """ Checks if the unit has the 'psionic' attribute. """ + """Checks if the unit has the 'psionic' attribute.""" return IS_PSIONIC in self._type_data.attributes @cached_property - def tech_alias(self) -> Optional[List[UnitTypeId]]: + def tech_alias(self) -> list[UnitTypeId] | None: """Building tech equality, e.g. OrbitalCommand is the same as CommandCenter For Hive, this returns [UnitTypeId.Hatchery, UnitTypeId.Lair] For SCV, this returns None""" return self._type_data.tech_alias @cached_property - def unit_alias(self) -> Optional[UnitTypeId]: + def unit_alias(self) -> UnitTypeId | None: """Building type equality, e.g. FlyingOrbitalCommand is the same as OrbitalCommand For flying OrbitalCommand, this returns UnitTypeId.OrbitalCommand For SCV, this returns None""" @@ -211,23 +210,23 @@ def unit_alias(self) -> Optional[UnitTypeId]: @cached_property def _weapons(self): - """ Returns the weapons of the unit. """ + """Returns the weapons of the unit.""" return self._type_data._proto.weapons @cached_property def can_attack(self) -> bool: - """ Checks if the unit can attack at all. """ + """Checks if the unit can attack at all.""" # TODO BATTLECRUISER doesnt have weapons in proto?! return bool(self._weapons) or self.type_id in {UNIT_BATTLECRUISER, UNIT_ORACLE} @property def can_attack_both(self) -> bool: - """ Checks if the unit can attack both ground and air units. """ + """Checks if the unit can attack both ground and air units.""" return self.can_attack_ground and self.can_attack_air @cached_property def can_attack_ground(self) -> bool: - """ Checks if the unit can attack ground units. """ + """Checks if the unit can attack ground units.""" if self.type_id in {UNIT_BATTLECRUISER, UNIT_ORACLE}: return True if self._weapons: @@ -236,7 +235,7 @@ def can_attack_ground(self) -> bool: @cached_property def ground_dps(self) -> float: - """ Returns the dps against ground units. Does not include upgrades. """ + """Returns the dps against ground units. Does not include upgrades.""" if self.can_attack_ground: weapon = next((weapon for weapon in self._weapons if weapon.type in TARGET_GROUND), None) if weapon: @@ -245,7 +244,7 @@ def ground_dps(self) -> float: @cached_property def ground_range(self) -> float: - """ Returns the range against ground units. Does not include upgrades. """ + """Returns the range against ground units. Does not include upgrades.""" if self.type_id == UNIT_ORACLE: return 4 if self.type_id == UNIT_BATTLECRUISER: @@ -258,7 +257,7 @@ def ground_range(self) -> float: @cached_property def can_attack_air(self) -> bool: - """ Checks if the unit can air attack at all. Does not include upgrades. """ + """Checks if the unit can air attack at all. Does not include upgrades.""" if self.type_id == UNIT_BATTLECRUISER: return True if self._weapons: @@ -267,7 +266,7 @@ def can_attack_air(self) -> bool: @cached_property def air_dps(self) -> float: - """ Returns the dps against air units. Does not include upgrades. """ + """Returns the dps against air units. Does not include upgrades.""" if self.can_attack_air: weapon = next((weapon for weapon in self._weapons if weapon.type in TARGET_AIR), None) if weapon: @@ -276,7 +275,7 @@ def air_dps(self) -> float: @cached_property def air_range(self) -> float: - """ Returns the range against air units. Does not include upgrades. """ + """Returns the range against air units. Does not include upgrades.""" if self.type_id == UNIT_BATTLECRUISER: return 6 if self.can_attack_air: @@ -286,7 +285,7 @@ def air_range(self) -> float: return 0 @cached_property - def bonus_damage(self) -> Optional[Tuple[int, str]]: + def bonus_damage(self) -> tuple[int, 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). @@ -299,12 +298,12 @@ def bonus_damage(self) -> Optional[Tuple[int, str]]: @property def armor(self) -> float: - """ Returns the armor of the unit. Does not include upgrades """ + """Returns the armor of the unit. Does not include upgrades""" return self._type_data._proto.armor @property def sight_range(self) -> float: - """ Returns the sight range of the unit. """ + """Returns the sight range of the unit.""" return self._type_data._proto.sight_range @property @@ -316,10 +315,10 @@ def movement_speed(self) -> float: @cached_property def real_speed(self) -> float: - """ See 'calculate_speed'. """ + """See 'calculate_speed'.""" return self.calculate_speed() - def calculate_speed(self, upgrades: Set[UpgradeId] = None) -> float: + def calculate_speed(self, upgrades: set[UpgradeId] | None = None) -> float: """Calculates the movement speed of the unit including buffs and upgrades. Note: Upgrades only work with own units. Use "upgrades" param to set expected enemy upgrades. @@ -333,7 +332,7 @@ def calculate_speed(self, upgrades: Set[UpgradeId] = None) -> float: upgrades = self._bot_object.state.upgrades if upgrades and unit_type in SPEED_UPGRADE_DICT: - upgrade_id: Optional[UpgradeId] = SPEED_UPGRADE_DICT.get(unit_type, None) + upgrade_id: UpgradeId | None = SPEED_UPGRADE_DICT.get(unit_type, None) if upgrade_id and upgrade_id in upgrades: speed *= SPEED_INCREASE_DICT.get(unit_type, 1) @@ -346,7 +345,7 @@ def calculate_speed(self, upgrades: Set[UpgradeId] = None) -> float: # Off creep upgrades elif upgrades: - upgrade_id2: Optional[UpgradeId] = OFF_CREEP_SPEED_UPGRADE_DICT.get(unit_type, None) + upgrade_id2: UpgradeId | None = OFF_CREEP_SPEED_UPGRADE_DICT.get(unit_type, None) if upgrade_id2: speed *= OFF_CREEP_SPEED_INCREASE_DICT[unit_type] @@ -375,49 +374,49 @@ def distance_per_step(self) -> float: @property def distance_to_weapon_ready(self) -> float: - """ Distance a unit can travel before it's weapon is ready to be fired again.""" + """Distance a unit can travel before it's weapon is ready to be fired again.""" return (self.real_speed / 22.4) * self.weapon_cooldown @property def is_mineral_field(self) -> bool: - """ Checks if the unit is a mineral field. """ + """Checks if the unit is a mineral field.""" return self._type_data.has_minerals @property def is_vespene_geyser(self) -> bool: - """ Checks if the unit is a non-empty vespene geyser or gas extraction building. """ + """Checks if the unit is a non-empty vespene geyser or gas extraction building.""" return self._type_data.has_vespene @property def health(self) -> float: - """ Returns the health of the unit. Does not include shields. """ + """Returns the health of the unit. Does not include shields.""" return self._proto.health @property def health_max(self) -> float: - """ Returns the maximum health of the unit. Does not include shields. """ + """Returns the maximum health of the unit. Does not include shields.""" return self._proto.health_max @cached_property def health_percentage(self) -> float: - """ Returns the percentage of health the unit has. Does not include shields. """ + """Returns the percentage of health the unit has. Does not include shields.""" if not self._proto.health_max: return 0 return self._proto.health / self._proto.health_max @property def shield(self) -> float: - """ Returns the shield points the unit has. Returns 0 for non-protoss units. """ + """Returns the shield points the unit has. Returns 0 for non-protoss units.""" return self._proto.shield @property def shield_max(self) -> float: - """ Returns the maximum shield points the unit can have. Returns 0 for non-protoss units. """ + """Returns the maximum shield points the unit can have. Returns 0 for non-protoss units.""" return self._proto.shield_max @cached_property def shield_percentage(self) -> float: - """ Returns the percentage of shield points the unit has. Returns 0 for non-protoss units. """ + """Returns the percentage of shield points the unit has. Returns 0 for non-protoss units.""" if not self._proto.shield_max: return 0 return self._proto.shield / self._proto.shield_max @@ -433,34 +432,34 @@ def shield_health_percentage(self) -> float: @property def energy(self) -> float: - """ Returns the amount of energy the unit has. Returns 0 for units without energy. """ + """Returns the amount of energy the unit has. Returns 0 for units without energy.""" return self._proto.energy @property def energy_max(self) -> float: - """ Returns the maximum amount of energy the unit can have. Returns 0 for units without energy. """ + """Returns the maximum amount of energy the unit can have. Returns 0 for units without energy.""" return self._proto.energy_max @cached_property def energy_percentage(self) -> float: - """ Returns the percentage of amount of energy the unit has. Returns 0 for units without energy. """ + """Returns the percentage of amount of energy the unit has. Returns 0 for units without energy.""" if not self._proto.energy_max: return 0 return self._proto.energy / self._proto.energy_max @property def age_in_frames(self) -> int: - """ Returns how old the unit object data is (in game frames). This age does not reflect the unit was created / trained / morphed! """ + """Returns how old the unit object data is (in game frames). This age does not reflect the unit was created / trained / morphed!""" return self._bot_object.state.game_loop - self.game_loop @property def age(self) -> float: - """ Returns how old the unit object data is (in game seconds). This age does not reflect when the unit was created / trained / morphed! """ + """Returns how old the unit object data is (in game seconds). This age does not reflect when the unit was created / trained / morphed!""" return (self._bot_object.state.game_loop - self.game_loop) / 22.4 @property def is_memory(self) -> bool: - """ Returns True if this Unit object is referenced from the future and is outdated. """ + """Returns True if this Unit object is referenced from the future and is outdated.""" return self.game_loop != self._bot_object.state.game_loop @cached_property @@ -504,50 +503,50 @@ def is_placeholder(self) -> bool: @property def alliance(self) -> Alliance: - """ Returns the team the unit belongs to. """ + """Returns the team the unit belongs to.""" return self._proto.alliance @property def is_mine(self) -> bool: - """ Checks if the unit is controlled by the bot. """ + """Checks if the unit is controlled by the bot.""" return self._proto.alliance == IS_MINE @property def is_enemy(self) -> bool: - """ Checks if the unit is hostile. """ + """Checks if the unit is hostile.""" return self._proto.alliance == IS_ENEMY @property def owner_id(self) -> int: - """ Returns the owner of the unit. This is a value of 1 or 2 in a two player game. """ + """Returns the owner of the unit. This is a value of 1 or 2 in a two player game.""" return self._proto.owner @property - def position_tuple(self) -> Tuple[float, float]: - """ Returns the 2d position of the unit as tuple without conversion to Point2. """ + def position_tuple(self) -> tuple[float, float]: + """Returns the 2d position of the unit as tuple without conversion to Point2.""" return self._proto.pos.x, self._proto.pos.y @cached_property def position(self) -> Point2: - """ Returns the 2d position of the unit. """ + """Returns the 2d position of the unit.""" return Point2.from_proto(self._proto.pos) @cached_property def position3d(self) -> Point3: - """ Returns the 3d position of the unit. """ + """Returns the 3d position of the unit.""" return Point3.from_proto(self._proto.pos) - def distance_to(self, p: Union[Unit, Point2]) -> float: + def distance_to(self, p: Unit | Point2) -> float: """Using the 2d distance between self and p. To calculate the 3d distance, use unit.position3d.distance_to(p) :param p: """ if isinstance(p, Unit): - return self._bot_object._distance_squared_unit_to_unit(self, p)**0.5 + return self._bot_object._distance_squared_unit_to_unit(self, p) ** 0.5 return self._bot_object.distance_math_hypot(self.position_tuple, p) - def distance_to_squared(self, p: Union[Unit, Point2]) -> float: + def distance_to_squared(self, p: Unit | Point2) -> float: """Using the 2d distance squared between self and p. Slightly faster than distance_to, so when filtering a lot of units, this function is recommended to be used. To calculate the 3d distance, use unit.position3d.distance_to(p) @@ -572,13 +571,11 @@ def target_in_range(self, target: Unit, bonus_distance: float = 0) -> bool: else: return False return ( - self._bot_object._distance_squared_unit_to_unit(self, target) <= - (self.radius + target.radius + unit_attack_range + bonus_distance)**2 + self._bot_object._distance_squared_unit_to_unit(self, target) + <= (self.radius + target.radius + unit_attack_range + bonus_distance) ** 2 ) - def in_ability_cast_range( - self, ability_id: AbilityId, target: Union[Unit, Point2], bonus_distance: float = 0 - ) -> bool: + def in_ability_cast_range(self, ability_id: AbilityId, target: Unit | Point2, bonus_distance: float = 0) -> bool: """Test if a unit is able to cast an ability on the target without checking ability cooldown (like stalker blink) or if ability is made available through research (like HT storm). :param ability_id: @@ -589,32 +586,27 @@ def in_ability_cast_range( assert cast_range > 0, f"Checking for an ability ({ability_id}) that has no cast range" ability_target_type = self._bot_object.game_data.abilities[ability_id.value]._proto.target # For casting abilities that target other units, like transfuse, feedback, snipe, yamato - if ( - ability_target_type in {Target.Unit.value, Target.PointOrUnit.value} # type: ignore - and isinstance(target, Unit) - ): + if ability_target_type in {Target.Unit.value, Target.PointOrUnit.value} and isinstance(target, Unit): return ( - self._bot_object._distance_squared_unit_to_unit(self, target) <= - (cast_range + self.radius + target.radius + bonus_distance)**2 + self._bot_object._distance_squared_unit_to_unit(self, target) + <= (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} # type: ignore - and isinstance(target, (Point2, tuple)) + if ability_target_type in {Target.Point.value, Target.PointOrUnit.value} and isinstance( + target, (Point2, tuple) ): return ( - self._bot_object._distance_pos_to_pos(self.position_tuple, target) <= - cast_range + self.radius + bonus_distance + self._bot_object._distance_pos_to_pos(self.position_tuple, target) + <= cast_range + self.radius + bonus_distance ) return False - # pylint: disable=R0912,R0911 def calculate_damage_vs_target( self, target: Unit, ignore_armor: bool = False, include_overkill_damage: bool = True, - ) -> Tuple[float, float, float]: + ) -> tuple[float, float, float]: """Returns a tuple of: [potential damage against target, attack speed, attack range] Returns the properly calculated damage per full-attack against the target unit. Returns (0, 0, 0) if this unit can't attack the target unit. @@ -652,7 +644,8 @@ def calculate_damage_vs_target( enemy_shield_armor = target.shield_upgrade_level # Ultralisk armor upgrade, only works if target belongs to the bot calling this function if ( - target.type_id in {UnitTypeId.ULTRALISK, UnitTypeId.ULTRALISKBURROWED} and target.is_mine + target.type_id in {UnitTypeId.ULTRALISK, UnitTypeId.ULTRALISKBURROWED} + and target.is_mine and UpgradeId.CHITINOUSPLATING in target._bot_object.state.upgrades ): enemy_armor += 2 @@ -674,20 +667,22 @@ def calculate_damage_vs_target( return weapon_damage, 0.224, 6 # Fast return for bunkers, since they don't have a weapon similar to BCs - if self.type_id == UnitTypeId.BUNKER: - if self.is_enemy: - if self.is_active: - # Expect fully loaded bunker with marines - return (24, 0.854, 6) - return (0, 0, 0) + if self.type_id == UnitTypeId.BUNKER and self.is_enemy: + if self.is_active: + # Expect fully loaded bunker with marines + return (24, 0.854, 6) + return (0, 0, 0) # TODO if bunker belongs to us, use passengers and upgrade level to calculate damage - required_target_type: Set[int] = ( + required_target_type: set[int] = ( TARGET_BOTH - if target.type_id == UnitTypeId.COLOSSUS else TARGET_GROUND if not target.is_flying else TARGET_AIR + if target.type_id == UnitTypeId.COLOSSUS + else TARGET_GROUND + if not target.is_flying + else TARGET_AIR ) # Contains total damage, attack speed and attack range - damages: List[Tuple[float, float, float]] = [] + damages: list[tuple[float, float, float]] = [] for weapon in self._weapons: if weapon.type not in required_target_type: continue @@ -697,26 +692,29 @@ def calculate_damage_vs_target( weapon_speed: float = weapon.speed weapon_range: float = weapon.range bonus_damage_per_upgrade = ( - 0 if not self.attack_upgrade_level else - DAMAGE_BONUS_PER_UPGRADE.get(self.type_id, {}).get(weapon.type, {}).get(None, 1) + 0 + if not self.attack_upgrade_level + else DAMAGE_BONUS_PER_UPGRADE.get(self.type_id, {}).get(weapon.type, {}).get(None, 1) ) damage_per_attack: float = weapon.damage + self.attack_upgrade_level * bonus_damage_per_upgrade # Remaining damage after all damage is dealt to shield remaining_damage: float = 0 # Calculate bonus damage against target - boni: List[float] = [] + boni: list[float] = [] # TODO: hardcode hellbats when they have blueflame or attack upgrades for bonus in weapon.damage_bonus: # More about damage bonus https://github.com/Blizzard/s2client-proto/blob/b73eb59ac7f2c52b2ca585db4399f2d3202e102a/s2clientprotocol/data.proto#L55 if bonus.attribute in target._type_data.attributes: bonus_damage_per_upgrade = ( - 0 if not self.attack_upgrade_level else - DAMAGE_BONUS_PER_UPGRADE.get(self.type_id, {}).get(weapon.type, {}).get(bonus.attribute, 0) + 0 + if not self.attack_upgrade_level + else DAMAGE_BONUS_PER_UPGRADE.get(self.type_id, {}).get(weapon.type, {}).get(bonus.attribute, 0) ) # Hardcode blueflame damage bonus from hellions if ( - bonus.attribute == IS_LIGHT and self.type_id == UnitTypeId.HELLION + bonus.attribute == IS_LIGHT + and self.type_id == UnitTypeId.HELLION and UpgradeId.HIGHCAPACITYBARRELS in self._bot_object.state.upgrades ): bonus_damage_per_upgrade += 5 @@ -768,11 +766,12 @@ def calculate_damage_vs_target( UnitTypeId.MISSILETURRET, UnitTypeId.AUTOTURRET, }: - upgrades: Set[UpgradeId] = self._bot_object.state.upgrades + upgrades: set[UpgradeId] = self._bot_object.state.upgrades if ( self.type_id == UnitTypeId.ZERGLING # Attack speed calculation only works for our unit - and self.is_mine and UpgradeId.ZERGLINGATTACKSPEED in upgrades + and self.is_mine + and UpgradeId.ZERGLINGATTACKSPEED in upgrades ): # 0.696044921875 for zerglings divided through 1.4 equals (+40% attack speed bonus from the upgrade): weapon_speed /= 1.4 @@ -796,7 +795,8 @@ def calculate_damage_vs_target( weapon_range += 2 elif ( self.type_id in {UnitTypeId.PLANETARYFORTRESS, UnitTypeId.MISSILETURRET, UnitTypeId.AUTOTURRET} - and self.is_mine and UpgradeId.HISECAUTOTRACKING in upgrades + and self.is_mine + and UpgradeId.HISECAUTOTRACKING in upgrades ): weapon_range += 1 @@ -821,8 +821,9 @@ def calculate_dps_vs_target( :param ignore_armor: :param include_overkill_damage: """ - calc_tuple: Tuple[float, float, - float] = self.calculate_damage_vs_target(target, ignore_armor, include_overkill_damage) + calc_tuple: tuple[float, float, float] = self.calculate_damage_vs_target( + target, ignore_armor, include_overkill_damage + ) # TODO fix for real time? The result may have to be multiplied by 1.4 because of game_speed=normal if calc_tuple[1] == 0: return 0 @@ -849,7 +850,7 @@ def is_facing(self, other_unit: Unit, angle_error: float = 0.05) -> bool: return angle_difference < angle_error @property - def footprint_radius(self) -> Optional[float]: + def footprint_radius(self) -> float | None: """For structures only. For townhalls this returns 2.5 For barracks, spawning pool, gateway, this returns 1.5 @@ -862,17 +863,17 @@ def footprint_radius(self) -> Optional[float]: @property def radius(self) -> float: - """ Half of unit size. See https://liquipedia.net/starcraft2/Unit_Statistics_(Legacy_of_the_Void) """ + """Half of unit size. See https://liquipedia.net/starcraft2/Unit_Statistics_(Legacy_of_the_Void)""" return self._proto.radius @property def build_progress(self) -> float: - """ Returns completion in range [0,1].""" + """Returns completion in range [0,1].""" return self._proto.build_progress @property def is_ready(self) -> bool: - """ Checks if the unit is completed. """ + """Checks if the unit is completed.""" return self.build_progress == 1 @property @@ -884,42 +885,42 @@ def cloak(self) -> CloakState: @property def is_cloaked(self) -> bool: - """ Checks if the unit is cloaked. """ + """Checks if the unit is cloaked.""" return self._proto.cloak in IS_CLOAKED @property def is_revealed(self) -> bool: - """ Checks if the unit is revealed. """ + """Checks if the unit is revealed.""" return self._proto.cloak == IS_REVEALED @property def can_be_attacked(self) -> bool: - """ Checks if the unit is revealed or not cloaked and therefore can be attacked. """ + """Checks if the unit is revealed or not cloaked and therefore can be attacked.""" return self._proto.cloak in CAN_BE_ATTACKED @cached_property - def buffs(self) -> FrozenSet[BuffId]: - """ Returns the set of current buffs the unit has. """ + def buffs(self) -> frozenset[BuffId]: + """Returns the set of current buffs the unit has.""" return frozenset(BuffId(buff_id) for buff_id in self._proto.buff_ids) @cached_property def is_carrying_minerals(self) -> bool: - """ Checks if a worker or MULE is carrying (gold-)minerals. """ + """Checks if a worker or MULE is carrying (gold-)minerals.""" return not IS_CARRYING_MINERALS.isdisjoint(self.buffs) @cached_property def is_carrying_vespene(self) -> bool: - """ Checks if a worker is carrying vespene gas. """ + """Checks if a worker is carrying vespene gas.""" return not IS_CARRYING_VESPENE.isdisjoint(self.buffs) @cached_property def is_carrying_resource(self) -> bool: - """ Checks if a worker is carrying a resource. """ + """Checks if a worker is carrying a resource.""" return not IS_CARRYING_RESOURCES.isdisjoint(self.buffs) @property def detect_range(self) -> float: - """ Returns the detection distance of the unit. """ + """Returns the detection distance of the unit.""" return self._proto.detect_range @cached_property @@ -934,39 +935,39 @@ def radar_range(self) -> float: @property def is_selected(self) -> bool: - """ Checks if the unit is currently selected. """ + """Checks if the unit is currently selected.""" return self._proto.is_selected @property def is_on_screen(self) -> bool: - """ Checks if the unit is on the screen. """ + """Checks if the unit is on the screen.""" return self._proto.is_on_screen @property def is_blip(self) -> bool: - """ Checks if the unit is detected by a sensor tower. """ + """Checks if the unit is detected by a sensor tower.""" return self._proto.is_blip @property def is_powered(self) -> bool: - """ Checks if the unit is powered by a pylon or warppism. """ + """Checks if the unit is powered by a pylon or warppism.""" return self._proto.is_powered @property def is_active(self) -> bool: - """ Checks if the unit has an order (e.g. unit is currently moving or attacking, structure is currently training or researching). """ + """Checks if the unit has an order (e.g. unit is currently moving or attacking, structure is currently training or researching).""" return self._proto.is_active # PROPERTIES BELOW THIS COMMENT ARE NOT POPULATED FOR SNAPSHOTS @property def mineral_contents(self) -> int: - """ Returns the amount of minerals remaining in a mineral field. """ + """Returns the amount of minerals remaining in a mineral field.""" return self._proto.mineral_contents @property def vespene_contents(self) -> int: - """ Returns the amount of gas remaining in a geyser. """ + """Returns the amount of gas remaining in a geyser.""" return self._proto.vespene_contents @property @@ -977,17 +978,17 @@ def has_vespene(self) -> bool: @property def is_flying(self) -> bool: - """ Checks if the unit is flying. """ + """Checks if the unit is flying.""" return self._proto.is_flying or self.has_buff(BuffId.GRAVITONBEAM) @property def is_burrowed(self) -> bool: - """ Checks if the unit is burrowed. """ + """Checks if the unit is burrowed.""" return self._proto.is_burrowed @property def is_hallucination(self) -> bool: - """ Returns True if the unit is your own hallucination or detected. """ + """Returns True if the unit is your own hallucination or detected.""" return self._proto.is_hallucination @property @@ -998,7 +999,7 @@ def attack_upgrade_level(self) -> int: @property def armor_upgrade_level(self) -> int: - """ Returns the upgrade level of the units armor. """ + """Returns the upgrade level of the units armor.""" return self._proto.armor_upgrade_level @property @@ -1022,13 +1023,13 @@ def buff_duration_max(self) -> int: # PROPERTIES BELOW THIS COMMENT ARE NOT POPULATED FOR ENEMIES @cached_property - def orders(self) -> List[UnitOrder]: - """ Returns the a list of the current orders. """ + def orders(self) -> list[UnitOrder]: + """Returns the a list of the current orders.""" # TODO: add examples on how to use unit orders return [UnitOrder.from_proto(order, self._bot_object) for order in self._proto.orders] @cached_property - def order_target(self) -> Optional[Union[int, Point2]]: + def order_target(self) -> int | Point2 | None: """Returns the target tag (if it is a Unit) or Point2 (if it is a Position) from the first order, returns None if the unit is idle""" if self.orders: @@ -1040,10 +1041,10 @@ def order_target(self) -> Optional[Union[int, Point2]]: @property def is_idle(self) -> bool: - """ Checks if unit is idle. """ + """Checks if unit is idle.""" return not self._proto.orders - def is_using_ability(self, abilities: Union[AbilityId, Set[AbilityId]]) -> bool: + def is_using_ability(self, abilities: AbilityId | set[AbilityId]) -> bool: """Check if the unit is using one of the given abilities. Only works for own units.""" if not self.orders: @@ -1113,17 +1114,17 @@ def add_on_tag(self) -> int: @property def has_add_on(self) -> bool: - """ Checks if unit has an addon attached. """ + """Checks if unit has an addon attached.""" return bool(self._proto.add_on_tag) @cached_property def has_techlab(self) -> bool: - """Check if a structure is connected to a techlab addon. This should only ever return True for BARRACKS, FACTORY, STARPORT. """ + """Check if a structure is connected to a techlab addon. This should only ever return True for BARRACKS, FACTORY, STARPORT.""" return self.add_on_tag in self._bot_object.techlab_tags @cached_property def has_reactor(self) -> bool: - """Check if a structure is connected to a reactor addon. This should only ever return True for BARRACKS, FACTORY, STARPORT. """ + """Check if a structure is connected to a reactor addon. This should only ever return True for BARRACKS, FACTORY, STARPORT.""" return self.add_on_tag in self._bot_object.reactor_tags @cached_property @@ -1149,13 +1150,13 @@ def add_on_position(self) -> Point2: return self.position.offset(Point2((2.5, -0.5))) @cached_property - def passengers(self) -> Set[Unit]: - """ Returns the units inside a Bunker, CommandCenter, PlanetaryFortress, Medivac, Nydus, Overlord or WarpPrism. """ + def passengers(self) -> set[Unit]: + """Returns the units inside a Bunker, CommandCenter, PlanetaryFortress, Medivac, Nydus, Overlord or WarpPrism.""" return {Unit(unit, self._bot_object) for unit in self._proto.passengers} @cached_property - def passengers_tags(self) -> Set[int]: - """ Returns the tags of the units inside a Bunker, CommandCenter, PlanetaryFortress, Medivac, Nydus, Overlord or WarpPrism. """ + def passengers_tags(self) -> set[int]: + """Returns the tags of the units inside a Bunker, CommandCenter, PlanetaryFortress, Medivac, Nydus, Overlord or WarpPrism.""" return {unit.tag for unit in self._proto.passengers} @property @@ -1166,27 +1167,27 @@ def cargo_used(self) -> int: @property def has_cargo(self) -> bool: - """ Checks if this unit has any units loaded. """ + """Checks if this unit has any units loaded.""" return bool(self._proto.cargo_space_taken) @property def cargo_size(self) -> int: - """ Returns the amount of cargo space the unit needs. """ + """Returns the amount of cargo space the unit needs.""" return self._type_data.cargo_size @property def cargo_max(self) -> int: - """ How much cargo space is available at maximum. """ + """How much cargo space is available at maximum.""" return self._proto.cargo_space_max @property def cargo_left(self) -> int: - """ Returns how much cargo space is currently left in the unit. """ + """Returns how much cargo space is currently left in the unit.""" return self._proto.cargo_space_max - self._proto.cargo_space_taken @property def assigned_harvesters(self) -> int: - """ Returns the number of workers currently gathering resources at a geyser or mining base.""" + """Returns the number of workers currently gathering resources at a geyser or mining base.""" return self._proto.assigned_harvesters @property @@ -1229,8 +1230,8 @@ def engaged_target_tag(self) -> int: return self._proto.engaged_target_tag @cached_property - def rally_targets(self) -> List[RallyTarget]: - """ Returns the queue of rallytargets of the structure. """ + def rally_targets(self) -> list[RallyTarget]: + """Returns the queue of rallytargets of the structure.""" return [RallyTarget.from_proto(rally_target) for rally_target in self._proto.rally_targets] # Unit functions @@ -1248,7 +1249,7 @@ def train( unit: UnitTypeId, queue: bool = False, can_afford_check: bool = False, - ) -> Union[UnitCommand, bool]: + ) -> UnitCommand | bool: """Orders unit to train another 'unit'. Usage: COMMANDCENTER.train(SCV) @@ -1266,14 +1267,15 @@ def train( def build( self, unit: UnitTypeId, - position: Point2 = None, + position: Point2 | Unit | None = None, queue: bool = False, can_afford_check: bool = False, - ) -> Union[UnitCommand, bool]: + ) -> UnitCommand | bool: """Orders unit to build another 'unit' at 'position'. Usage:: 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) @@ -1299,7 +1301,7 @@ def build_gas( target_geysir: Unit, queue: bool = False, can_afford_check: bool = False, - ) -> Union[UnitCommand, bool]: + ) -> UnitCommand | bool: """Orders unit to build another 'unit' at 'position'. Usage:: @@ -1327,7 +1329,7 @@ def research( upgrade: UpgradeId, queue: bool = False, can_afford_check: bool = False, - ) -> Union[UnitCommand, bool]: + ) -> UnitCommand | bool: """Orders unit to research 'upgrade'. Requires UpgradeId to be passed instead of AbilityId. @@ -1347,7 +1349,7 @@ def warp_in( unit: UnitTypeId, position: Point2, can_afford_check: bool = False, - ) -> Union[UnitCommand, bool]: + ) -> UnitCommand | bool: """Orders Warpgate to warp in 'unit' at 'position'. :param unit: @@ -1363,7 +1365,7 @@ def warp_in( can_afford_check=can_afford_check, ) - def attack(self, target: Union[Unit, Point2], queue: bool = False) -> Union[UnitCommand, bool]: + def attack(self, target: Unit | Point2, queue: bool = False) -> UnitCommand | bool: """Orders unit to attack. Target can be a Unit or Point2. Attacking a position will make the unit move there and attack everything on its way. @@ -1372,7 +1374,7 @@ def attack(self, target: Union[Unit, Point2], queue: bool = False) -> Union[Unit """ return self(AbilityId.ATTACK, target=target, queue=queue) - def smart(self, target: Union[Unit, Point2], queue: bool = False) -> Union[UnitCommand, bool]: + def smart(self, target: Unit | Point2, queue: bool = False) -> UnitCommand | bool: """Orders the smart command. Equivalent to a right-click order. :param target: @@ -1380,7 +1382,7 @@ def smart(self, target: Union[Unit, Point2], queue: bool = False) -> Union[UnitC """ return self(AbilityId.SMART, target=target, queue=queue) - def gather(self, target: Unit, queue: bool = False) -> Union[UnitCommand, bool]: + def gather(self, target: Unit, queue: bool = False) -> UnitCommand | bool: """Orders a unit to gather minerals or gas. 'Target' must be a mineral patch or a gas extraction building. @@ -1389,15 +1391,14 @@ def gather(self, target: Unit, queue: bool = False) -> Union[UnitCommand, bool]: """ return self(AbilityId.HARVEST_GATHER, target=target, queue=queue) - def return_resource(self, target: Unit = None, queue: bool = False) -> Union[UnitCommand, bool]: - """Orders the unit to return resource. Does not need a 'target'. + def return_resource(self, queue: bool = False) -> UnitCommand | bool: + """Orders the unit to return resource to the nearest townhall. - :param target: :param queue: """ - return self(AbilityId.HARVEST_RETURN, target=target, queue=queue) + return self(AbilityId.HARVEST_RETURN, target=None, queue=queue) - def move(self, position: Union[Unit, Point2], queue: bool = False) -> Union[UnitCommand, bool]: + def move(self, position: Unit | Point2, queue: bool = False) -> UnitCommand | bool: """Orders the unit to move to 'position'. Target can be a Unit (to follow that unit) or Point2. @@ -1406,14 +1407,14 @@ def move(self, position: Union[Unit, Point2], queue: bool = False) -> Union[Unit """ return self(AbilityId.MOVE_MOVE, target=position, queue=queue) - def hold_position(self, queue: bool = False) -> Union[UnitCommand, bool]: + def hold_position(self, queue: bool = False) -> UnitCommand | bool: """Orders a unit to stop moving. It will not move until it gets new orders. :param queue: """ return self(AbilityId.HOLDPOSITION, queue=queue) - def stop(self, queue: bool = False) -> Union[UnitCommand, bool]: + def stop(self, queue: bool = False) -> UnitCommand | bool: """Orders a unit to stop, but can start to move on its own if it is attacked, enemy unit is in range or other friendly units need the space. @@ -1422,7 +1423,7 @@ def stop(self, queue: bool = False) -> Union[UnitCommand, bool]: """ return self(AbilityId.STOP, queue=queue) - def patrol(self, position: Point2, queue: bool = False) -> Union[UnitCommand, bool]: + def patrol(self, position: Point2, queue: bool = False) -> UnitCommand | bool: """Orders a unit to patrol between position it has when the command starts and the target position. Can be queued up to seven patrol points. If the last point is the same as the starting point, the unit will patrol in a circle. @@ -1432,7 +1433,7 @@ def patrol(self, position: Point2, queue: bool = False) -> Union[UnitCommand, bo """ return self(AbilityId.PATROL, target=position, queue=queue) - def repair(self, repair_target: Unit, queue: bool = False) -> Union[UnitCommand, bool]: + def repair(self, repair_target: Unit, queue: bool = False) -> UnitCommand | bool: """Order an SCV or MULE to repair. :param repair_target: @@ -1443,7 +1444,7 @@ def repair(self, repair_target: Unit, queue: bool = False) -> Union[UnitCommand, def __hash__(self) -> int: return self.tag - def __eq__(self, other: Union[Unit, Any]) -> bool: + def __eq__(self, other: Unit | Any) -> bool: """ :param other: """ @@ -1452,13 +1453,13 @@ def __eq__(self, other: Union[Unit, Any]) -> bool: def __call__( self, ability: AbilityId, - target: Optional[Union[Point2, Unit]] = None, + target: Point2 | Unit | None = None, queue: bool = False, subtract_cost: bool = False, subtract_supply: bool = False, can_afford_check: bool = False, - ) -> Union[UnitCommand, bool]: - """ Deprecated: Stop using self.do() - This may be removed in the future. + ) -> UnitCommand | bool: + """Deprecated: Stop using self.do() - This may be removed in the future. :param ability: :param target: diff --git a/sc2/unit_command.py b/sc2/unit_command.py index 00a47406..3f17d96a 100644 --- a/sc2/unit_command.py +++ b/sc2/unit_command.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Tuple, Union +from typing import TYPE_CHECKING from sc2.constants import COMBINEABLE_ABILITIES from sc2.ids.ability_id import AbilityId @@ -11,8 +11,9 @@ class UnitCommand: - - def __init__(self, ability: AbilityId, unit: Unit, target: Union[Unit, Point2] = None, queue: bool = False): + def __init__( + self, ability: AbilityId, unit: Unit, target: Unit | Point2 | None = None, queue: bool = False + ) -> None: """ :param ability: :param unit: @@ -35,8 +36,8 @@ def __init__(self, ability: AbilityId, unit: Unit, target: Union[Unit, Point2] = self.queue = queue @property - def combining_tuple(self) -> Tuple[AbilityId, Union[Unit, Point2], bool, bool]: + def combining_tuple(self) -> tuple[AbilityId, Unit | Point2 | None, bool, bool]: return self.ability, self.target, self.queue, self.ability in COMBINEABLE_ABILITIES - def __repr__(self): + def __repr__(self) -> str: return f"UnitCommand({self.ability}, {self.unit}, {self.target}, {self.queue})" diff --git a/sc2/units.py b/sc2/units.py index 377424ef..38813601 100644 --- a/sc2/units.py +++ b/sc2/units.py @@ -1,9 +1,10 @@ -# pylint: disable=W0212 +# pyre-ignore-all-errors[14, 15, 16] from __future__ import annotations import random +from collections.abc import Callable, Generator, Iterable from itertools import chain -from typing import TYPE_CHECKING, Any, Callable, Generator, Iterable, List, Optional, Set, Tuple, Union +from typing import TYPE_CHECKING, Any from sc2.ids.unit_typeid import UnitTypeId from sc2.position import Point2 @@ -13,16 +14,14 @@ from sc2.bot_ai import BotAI -# pylint: disable=R0904 class Units(list): """A collection of Unit objects. Makes it easy to select units by selectors.""" @classmethod - def from_proto(cls, units, bot_object: BotAI): - # pylint: disable=E1120 - return cls((Unit(raw_unit, bot_object=bot_object) for raw_unit in units)) + def from_proto(cls, units, bot_object: BotAI) -> Units: + return cls((Unit(raw_unit, bot_object=bot_object) for raw_unit in units), bot_object) - def __init__(self, units: Iterable[Unit], bot_object: BotAI): + def __init__(self, units: Iterable[Unit], bot_object: BotAI) -> None: """ :param units: :param bot_object: @@ -30,7 +29,7 @@ def __init__(self, units: Iterable[Unit], bot_object: BotAI): super().__init__(units) self._bot_object = bot_object - def __call__(self, unit_types: Union[UnitTypeId, Iterable[UnitTypeId]]) -> Units: + def __call__(self, unit_types: UnitTypeId | Iterable[UnitTypeId]) -> Units: """Creates a new mutable Units object from Units or list object. :param unit_types: @@ -104,7 +103,7 @@ def empty(self) -> bool: def exists(self) -> bool: return bool(self) - def find_by_tag(self, tag: int) -> Optional[Unit]: + def find_by_tag(self, tag: int) -> Unit | None: """ :param tag: """ @@ -140,11 +139,11 @@ def random(self) -> Unit: assert self, "Units object is empty" return random.choice(self) - def random_or(self, other: any) -> Unit: + def random_or(self, other: Any) -> Unit: return random.choice(self) if self else other def random_group_of(self, n: int) -> Units: - """ Returns self if n >= self.amount. """ + """Returns self if n >= self.amount.""" if n < 1: return Units([], self._bot_object) if n >= self.amount: @@ -176,7 +175,7 @@ def in_attack_range_of(self, unit: Unit, bonus_distance: float = 0) -> Units: """ return self.filter(lambda x: unit.target_in_range(x, bonus_distance=bonus_distance)) - def closest_distance_to(self, position: Union[Unit, Point2]) -> float: + def closest_distance_to(self, position: Unit | Point2) -> float: """Returns the distance between the closest unit from this group to the target unit. Example:: @@ -191,10 +190,10 @@ def closest_distance_to(self, position: Union[Unit, Point2]) -> float: """ assert self, "Units object is empty" if isinstance(position, Unit): - return min(self._bot_object._distance_squared_unit_to_unit(unit, position) for unit in self)**0.5 + return min(self._bot_object._distance_squared_unit_to_unit(unit, position) for unit in self) ** 0.5 return min(self._bot_object._distance_units_to_pos(self, position)) - def furthest_distance_to(self, position: Union[Unit, Point2]) -> float: + def furthest_distance_to(self, position: Unit | Point2) -> float: """Returns the distance between the furthest unit from this group to the target unit @@ -210,10 +209,10 @@ def furthest_distance_to(self, position: Union[Unit, Point2]) -> float: """ assert self, "Units object is empty" if isinstance(position, Unit): - return max(self._bot_object._distance_squared_unit_to_unit(unit, position) for unit in self)**0.5 + return max(self._bot_object._distance_squared_unit_to_unit(unit, position) for unit in self) ** 0.5 return max(self._bot_object._distance_units_to_pos(self, position)) - def closest_to(self, position: Union[Unit, Point2]) -> Unit: + def closest_to(self, position: Unit | Point2) -> Unit: """Returns the closest unit (from this Units object) to the target unit or position. Example:: @@ -236,7 +235,7 @@ def closest_to(self, position: Union[Unit, Point2]) -> Unit: distances = self._bot_object._distance_units_to_pos(self, position) return min(((unit, dist) for unit, dist in zip(self, distances)), key=lambda my_tuple: my_tuple[1])[0] - def furthest_to(self, position: Union[Unit, Point2]) -> Unit: + def furthest_to(self, position: Unit | Point2) -> Unit: """Returns the furhest unit (from this Units object) to the target unit or position. Example:: @@ -258,7 +257,7 @@ def furthest_to(self, position: Union[Unit, Point2]) -> Unit: distances = self._bot_object._distance_units_to_pos(self, position) return max(((unit, dist) for unit, dist in zip(self, distances)), key=lambda my_tuple: my_tuple[1])[0] - def closer_than(self, distance: float, position: Union[Unit, Point2]) -> Units: + def closer_than(self, distance: float, position: Unit | Point2) -> Units: """Returns all units (from this Units object) that are closer than 'distance' away from target unit or position. Example:: @@ -277,13 +276,14 @@ def closer_than(self, distance: float, position: Union[Unit, Point2]) -> Units: if isinstance(position, Unit): distance_squared = distance**2 return self.subgroup( - unit for unit in self + unit + for unit in self if self._bot_object._distance_squared_unit_to_unit(unit, position) < distance_squared ) distances = self._bot_object._distance_units_to_pos(self, position) return self.subgroup(unit for unit, dist in zip(self, distances) if dist < distance) - def further_than(self, distance: float, position: Union[Unit, Point2]) -> Units: + def further_than(self, distance: float, position: Unit | Point2) -> Units: """Returns all units (from this Units object) that are further than 'distance' away from target unit or position. Example:: @@ -302,14 +302,15 @@ def further_than(self, distance: float, position: Union[Unit, Point2]) -> Units: if isinstance(position, Unit): distance_squared = distance**2 return self.subgroup( - unit for unit in self + unit + for unit in self if distance_squared < self._bot_object._distance_squared_unit_to_unit(unit, position) ) distances = self._bot_object._distance_units_to_pos(self, position) return self.subgroup(unit for unit, dist in zip(self, distances) if distance < dist) def in_distance_between( - self, position: Union[Unit, Point2, Tuple[float, float]], distance1: float, distance2: float + self, position: Unit | Point2 | tuple[float, float], distance1: float, distance2: float ) -> Units: """Returns units that are further than distance1 and closer than distance2 to unit or position. @@ -331,13 +332,16 @@ def in_distance_between( distance1_squared = distance1**2 distance2_squared = distance2**2 return self.subgroup( - unit for unit in self if - distance1_squared < self._bot_object._distance_squared_unit_to_unit(unit, position) < distance2_squared + unit + for unit in self + if distance1_squared + < self._bot_object._distance_squared_unit_to_unit(unit, position) + < distance2_squared ) distances = self._bot_object._distance_units_to_pos(self, position) return self.subgroup(unit for unit, dist in zip(self, distances) if distance1 < dist < distance2) - def closest_n_units(self, position: Union[Unit, Point2], n: int) -> Units: + def closest_n_units(self, position: Unit | Point2, n: int) -> Units: """Returns the n closest units in distance to position. Example:: @@ -355,7 +359,7 @@ def closest_n_units(self, position: Union[Unit, Point2], n: int) -> Units: return self return self.subgroup(self._list_sorted_by_distance_to(position)[:n]) - def furthest_n_units(self, position: Union[Unit, Point2], n: int) -> Units: + def furthest_n_units(self, position: Unit | Point2, n: int) -> Units: """Returns the n furhest units in distance to position. Example:: @@ -393,7 +397,9 @@ def in_distance_of_group(self, other_units: Units, distance: float) -> Units: return self.subgroup([]) return self.subgroup( - self_unit for self_unit in self if any( + self_unit + for self_unit in self + if any( self._bot_object._distance_squared_unit_to_unit(self_unit, other_unit) < distance_squared for other_unit in other_units ) @@ -410,11 +416,12 @@ def in_closest_distance_to_group(self, other_units: Units) -> Unit: assert other_units, "Given units object is empty" return min( self, - key=lambda self_unit: - min(self._bot_object._distance_squared_unit_to_unit(self_unit, other_unit) for other_unit in other_units), + key=lambda self_unit: min( + self._bot_object._distance_squared_unit_to_unit(self_unit, other_unit) for other_unit in other_units + ), ) - def _list_sorted_closest_to_distance(self, position: Union[Unit, Point2], distance: float) -> List[Unit]: + def _list_sorted_closest_to_distance(self, position: Unit | Point2, distance: float) -> list[Unit]: """This function should be a bit faster than using units.sorted(key=lambda u: u.distance_to(position)) :param position: @@ -481,7 +488,7 @@ def filter(self, pred: Callable[[Unit], Any]) -> Units: def sorted(self, key: Callable[[Unit], Any], reverse: bool = False) -> Units: return self.subgroup(sorted(self, key=key, reverse=reverse)) - def _list_sorted_by_distance_to(self, position: Union[Unit, Point2], reverse: bool = False) -> List[Unit]: + def _list_sorted_by_distance_to(self, position: Unit | Point2, reverse: bool = False) -> list[Unit]: """This function should be a bit faster than using units.sorted(key=lambda u: u.distance_to(position)) :param position: @@ -495,7 +502,7 @@ def _list_sorted_by_distance_to(self, position: Union[Unit, Point2], reverse: bo unit_dist_dict = {unit.tag: dist for unit, dist in zip(self, distances)} return sorted(self, key=lambda unit2: unit_dist_dict[unit2.tag], reverse=reverse) - def sorted_by_distance_to(self, position: Union[Unit, Point2], reverse: bool = False) -> Units: + def sorted_by_distance_to(self, position: Unit | Point2, reverse: bool = False) -> Units: """This function should be a bit faster than using units.sorted(key=lambda u: u.distance_to(position)) :param position: @@ -531,7 +538,7 @@ def tags_not_in(self, other: Iterable[int]) -> Units: """ return self.filter(lambda unit: unit.tag not in other) - def of_type(self, other: Union[UnitTypeId, Iterable[UnitTypeId]]) -> Units: + def of_type(self, other: UnitTypeId | Iterable[UnitTypeId]) -> Units: """Filters all units that are of a specific type Example:: @@ -547,7 +554,7 @@ def of_type(self, other: Union[UnitTypeId, Iterable[UnitTypeId]]) -> Units: other = set(other) return self.filter(lambda unit: unit.type_id in other) - def exclude_type(self, other: Union[UnitTypeId, Iterable[UnitTypeId]]) -> Units: + def exclude_type(self, other: UnitTypeId | Iterable[UnitTypeId]) -> Units: """Filters all units that are not of a specific type Example:: @@ -563,7 +570,7 @@ def exclude_type(self, other: Union[UnitTypeId, Iterable[UnitTypeId]]) -> Units: other = set(other) return self.filter(lambda unit: unit.type_id not in other) - def same_tech(self, other: Set[UnitTypeId]) -> Units: + def same_tech(self, other: set[UnitTypeId]) -> Units: """Returns all structures that have the same base structure. Untested: This should return the equivalents for WarpPrism, Observer, Overseer, SupplyDepot and others @@ -587,20 +594,20 @@ def same_tech(self, other: Set[UnitTypeId]) -> Units: :param other: """ assert isinstance(other, set), ( - "Please use a set as this filter function is already fairly slow. For example" + - " 'self.units.same_tech({UnitTypeId.LAIR})'" + "Please use a set as this filter function is already fairly slow. For example" + + " 'self.units.same_tech({UnitTypeId.LAIR})'" ) - tech_alias_types: Set[int] = {u.value for u in other} + tech_alias_types: set[int] = {u.value for u in other} unit_data = self._bot_object.game_data.units for unit_type in other: for same in unit_data[unit_type.value]._proto.tech_alias: tech_alias_types.add(same) return self.filter( - lambda unit: unit._proto.unit_type in tech_alias_types or - any(same in tech_alias_types for same in unit._type_data._proto.tech_alias) + lambda unit: unit._proto.unit_type in tech_alias_types + or any(same in tech_alias_types for same in unit._type_data._proto.tech_alias) ) - def same_unit(self, other: Union[UnitTypeId, Iterable[UnitTypeId]]) -> Units: + def same_unit(self, other: UnitTypeId | Iterable[UnitTypeId]) -> Units: """Returns all units that have the same base unit while being in different modes. Untested: This should return the equivalents for WarpPrism, Observer, Overseer, SupplyDepot and other units that have different modes but still act as the same unit @@ -622,19 +629,19 @@ def same_unit(self, other: Union[UnitTypeId, Iterable[UnitTypeId]]) -> Units: """ if isinstance(other, UnitTypeId): other = {other} - unit_alias_types: Set[int] = {u.value for u in other} + unit_alias_types: set[int] = {u.value for u in other} unit_data = self._bot_object.game_data.units for unit_type in other: unit_alias_types.add(unit_data[unit_type.value]._proto.unit_alias) unit_alias_types.discard(0) return self.filter( - lambda unit: unit._proto.unit_type in unit_alias_types or unit._type_data._proto.unit_alias in - unit_alias_types + lambda unit: unit._proto.unit_type in unit_alias_types + or unit._type_data._proto.unit_alias in unit_alias_types ) @property def center(self) -> Point2: - """ Returns the central position of all units. """ + """Returns the central position of all units.""" assert self, "Units object is empty" return Point2( ( @@ -645,72 +652,72 @@ def center(self) -> Point2: @property def selected(self) -> Units: - """ Returns all units that are selected by the human player. """ + """Returns all units that are selected by the human player.""" return self.filter(lambda unit: unit.is_selected) @property - def tags(self) -> Set[int]: - """ Returns all unit tags as a set. """ + def tags(self) -> set[int]: + """Returns all unit tags as a set.""" return {unit.tag for unit in self} @property def ready(self) -> Units: - """ Returns all structures that are ready (construction complete). """ + """Returns all structures that are ready (construction complete).""" return self.filter(lambda unit: unit.is_ready) @property def not_ready(self) -> Units: - """ Returns all structures that are not ready (construction not complete). """ + """Returns all structures that are not ready (construction not complete).""" return self.filter(lambda unit: not unit.is_ready) @property def idle(self) -> Units: - """ Returns all units or structures that are doing nothing (unit is standing still, structure is doing nothing). """ + """Returns all units or structures that are doing nothing (unit is standing still, structure is doing nothing).""" return self.filter(lambda unit: unit.is_idle) @property def owned(self) -> Units: - """ Deprecated: All your units. """ + """Deprecated: All your units.""" return self.filter(lambda unit: unit.is_mine) @property def enemy(self) -> Units: - """ Deprecated: All enemy units.""" + """Deprecated: All enemy units.""" return self.filter(lambda unit: unit.is_enemy) @property def flying(self) -> Units: - """ Returns all units that are flying. """ + """Returns all units that are flying.""" return self.filter(lambda unit: unit.is_flying) @property def not_flying(self) -> Units: - """ Returns all units that not are flying. """ + """Returns all units that not are flying.""" return self.filter(lambda unit: not unit.is_flying) @property def structure(self) -> Units: - """ Deprecated: All structures. """ + """Deprecated: All structures.""" return self.filter(lambda unit: unit.is_structure) @property def not_structure(self) -> Units: - """ Deprecated: All units that are not structures. """ + """Deprecated: All units that are not structures.""" return self.filter(lambda unit: not unit.is_structure) @property def gathering(self) -> Units: - """ Returns all workers that are mining minerals or vespene (gather command). """ + """Returns all workers that are mining minerals or vespene (gather command).""" return self.filter(lambda unit: unit.is_gathering) @property def returning(self) -> Units: - """ Returns all workers that are carrying minerals or vespene and are returning to a townhall. """ + """Returns all workers that are carrying minerals or vespene and are returning to a townhall.""" return self.filter(lambda unit: unit.is_returning) @property def collecting(self) -> Units: - """ Returns all workers that are mining or returning resources. """ + """Returns all workers that are mining or returning resources.""" return self.filter(lambda unit: unit.is_collecting) @property @@ -721,15 +728,15 @@ def visible(self) -> Units: @property def mineral_field(self) -> Units: - """ Returns all units that are mineral fields. """ + """Returns all units that are mineral fields.""" return self.filter(lambda unit: unit.is_mineral_field) @property def vespene_geyser(self) -> Units: - """ Returns all units that are vespene geysers. """ + """Returns all units that are vespene geysers.""" return self.filter(lambda unit: unit.is_vespene_geyser) @property def prefer_idle(self) -> Units: - """ Sorts units based on if they are idle. Idle units come first. """ + """Sorts units based on if they are idle. Idle units come first.""" return self.sorted(lambda unit: unit.is_idle, reverse=True) diff --git a/sc2/versions.py b/sc2/versions.py index 0ce92329..f37146e2 100644 --- a/sc2/versions.py +++ b/sc2/versions.py @@ -5,468 +5,534 @@ "fixed-hash": "009BC85EF547B51EBF461C83A9CBAB30", "label": "3.13", "replay-hash": "47BFE9D10F26B0A8B74C637D6327BF3C", - "version": 52910 - }, { + "version": 52910, + }, + { "base-version": 53644, "data-hash": "CA275C4D6E213ED30F80BACCDFEDB1F5", "fixed-hash": "29198786619C9011735BCFD378E49CB6", "label": "3.14", "replay-hash": "5AF236FC012ADB7289DB493E63F73FD5", - "version": 53644 - }, { + "version": 53644, + }, + { "base-version": 54518, "data-hash": "BBF619CCDCC80905350F34C2AF0AB4F6", "fixed-hash": "D5963F25A17D9E1EA406FF6BBAA9B736", "label": "3.15", "replay-hash": "43530321CF29FD11482AB9CBA3EB553D", - "version": 54518 - }, { + "version": 54518, + }, + { "base-version": 54518, "data-hash": "6EB25E687F8637457538F4B005950A5E", "fixed-hash": "D5963F25A17D9E1EA406FF6BBAA9B736", "label": "3.15.1", "replay-hash": "43530321CF29FD11482AB9CBA3EB553D", - "version": 54724 - }, { + "version": 54724, + }, + { "base-version": 55505, "data-hash": "60718A7CA50D0DF42987A30CF87BCB80", "fixed-hash": "0189B2804E2F6BA4C4591222089E63B2", "label": "3.16", "replay-hash": "B11811B13F0C85C29C5D4597BD4BA5A4", - "version": 55505 - }, { + "version": 55505, + }, + { "base-version": 55958, "data-hash": "5BD7C31B44525DAB46E64C4602A81DC2", "fixed-hash": "717B05ACD26C108D18A219B03710D06D", "label": "3.16.1", "replay-hash": "21C8FA403BB1194E2B6EB7520016B958", - "version": 55958 - }, { + "version": 55958, + }, + { "base-version": 56787, "data-hash": "DFD1F6607F2CF19CB4E1C996B2563D9B", "fixed-hash": "4E1C17AB6A79185A0D87F68D1C673CD9", "label": "3.17", "replay-hash": "D0296961C9EA1356F727A2468967A1E2", - "version": 56787 - }, { + "version": 56787, + }, + { "base-version": 56787, "data-hash": "3F2FCED08798D83B873B5543BEFA6C4B", "fixed-hash": "4474B6B7B0D1423DAA76B9623EF2E9A9", "label": "3.17.1", "replay-hash": "D0296961C9EA1356F727A2468967A1E2", - "version": 57218 - }, { + "version": 57218, + }, + { "base-version": 56787, "data-hash": "C690FC543082D35EA0AAA876B8362BEA", "fixed-hash": "4474B6B7B0D1423DAA76B9623EF2E9A9", "label": "3.17.2", "replay-hash": "D0296961C9EA1356F727A2468967A1E2", - "version": 57490 - }, { + "version": 57490, + }, + { "base-version": 57507, "data-hash": "1659EF34997DA3470FF84A14431E3A86", "fixed-hash": "95666060F129FD267C5A8135A8920AA2", "label": "3.18", "replay-hash": "06D650F850FDB2A09E4B01D2DF8C433A", - "version": 57507 - }, { + "version": 57507, + }, + { "base-version": 58400, "data-hash": "2B06AEE58017A7DF2A3D452D733F1019", "fixed-hash": "2CFE1B8757DA80086DD6FD6ECFF21AC6", "label": "3.19", "replay-hash": "227B6048D55535E0FF5607746EBCC45E", - "version": 58400 - }, { + "version": 58400, + }, + { "base-version": 58400, "data-hash": "D9B568472880CC4719D1B698C0D86984", "fixed-hash": "CE1005E9B145BDFC8E5E40CDEB5E33BB", "label": "3.19.1", "replay-hash": "227B6048D55535E0FF5607746EBCC45E", - "version": 58600 - }, { + "version": 58600, + }, + { "base-version": 59587, "data-hash": "9B4FD995C61664831192B7DA46F8C1A1", "fixed-hash": "D5D5798A9CCD099932C8F855C8129A7C", "label": "4.0", "replay-hash": "BB4DA41B57D490BD13C13A594E314BA4", - "version": 59587 - }, { + "version": 59587, + }, + { "base-version": 60196, "data-hash": "1B8ACAB0C663D5510941A9871B3E9FBE", "fixed-hash": "9327F9AF76CF11FC43D20E3E038B1B7A", "label": "4.1", "replay-hash": "AEA0C2A9D56E02C6B7D21E889D6B9B2F", - "version": 60196 - }, { + "version": 60196, + }, + { "base-version": 60321, "data-hash": "5C021D8A549F4A776EE9E9C1748FFBBC", "fixed-hash": "C53FA3A7336EDF320DCEB0BC078AEB0A", "label": "4.1.1", "replay-hash": "8EE054A8D98C7B0207E709190A6F3953", - "version": 60321 - }, { + "version": 60321, + }, + { "base-version": 60321, "data-hash": "33D9FE28909573253B7FC352CE7AEA40", "fixed-hash": "FEE6F86A211380DF509F3BBA58A76B87", "label": "4.1.2", "replay-hash": "8EE054A8D98C7B0207E709190A6F3953", - "version": 60604 - }, { + "version": 60604, + }, + { "base-version": 60321, "data-hash": "F486693E00B2CD305B39E0AB254623EB", "fixed-hash": "AF7F5499862F497C7154CB59167FEFB3", "label": "4.1.3", "replay-hash": "8EE054A8D98C7B0207E709190A6F3953", - "version": 61021 - }, { + "version": 61021, + }, + { "base-version": 60321, "data-hash": "2E2A3F6E0BAFE5AC659C4D39F13A938C", "fixed-hash": "F9A68CF1FBBF867216FFECD9EAB72F4A", "label": "4.1.4", "replay-hash": "8EE054A8D98C7B0207E709190A6F3953", - "version": 61545 - }, { + "version": 61545, + }, + { "base-version": 62347, "data-hash": "C0C0E9D37FCDBC437CE386C6BE2D1F93", "fixed-hash": "A5C4BE991F37F1565097AAD2A707FC4C", "label": "4.2", "replay-hash": "2167A7733637F3AFC49B210D165219A7", - "version": 62347 - }, { + "version": 62347, + }, + { "base-version": 62848, "data-hash": "29BBAC5AFF364B6101B661DB468E3A37", "fixed-hash": "ABAF9318FE79E84485BEC5D79C31262C", "label": "4.2.1", "replay-hash": "A7ACEC5759ADB459A5CEC30A575830EC", - "version": 62848 - }, { + "version": 62848, + }, + { "base-version": 63454, "data-hash": "3CB54C86777E78557C984AB1CF3494A0", "fixed-hash": "A9DCDAA97F7DA07F6EF29C0BF4DFC50D", "label": "4.2.2", "replay-hash": "A7ACEC5759ADB459A5CEC30A575830EC", - "version": 63454 - }, { + "version": 63454, + }, + { "base-version": 64469, "data-hash": "C92B3E9683D5A59E08FC011F4BE167FF", "fixed-hash": "DDF3E0A6C00DC667F59BF90F793C71B8", "label": "4.3", "replay-hash": "6E80072968515101AF08D3953FE3EEBA", - "version": 64469 - }, { + "version": 64469, + }, + { "base-version": 65094, "data-hash": "E5A21037AA7A25C03AC441515F4E0644", "fixed-hash": "09EF8E9B96F14C5126F1DB5378D15F3A", "label": "4.3.1", "replay-hash": "DD9B57C516023B58F5B588377880D93A", - "version": 65094 - }, { + "version": 65094, + }, + { "base-version": 65384, "data-hash": "B6D73C85DFB70F5D01DEABB2517BF11C", "fixed-hash": "615C1705E4C7A5FD8690B3FD376C1AFE", "label": "4.3.2", "replay-hash": "DD9B57C516023B58F5B588377880D93A", - "version": 65384 - }, { + "version": 65384, + }, + { "base-version": 65895, "data-hash": "BF41339C22AE2EDEBEEADC8C75028F7D", "fixed-hash": "C622989A4C0AF7ED5715D472C953830B", "label": "4.4", "replay-hash": "441BBF1A222D5C0117E85B118706037F", - "version": 65895 - }, { + "version": 65895, + }, + { "base-version": 66668, "data-hash": "C094081D274A39219061182DBFD7840F", "fixed-hash": "1C236A42171AAC6DD1D5E50D779C522D", "label": "4.4.1", "replay-hash": "21D5B4B4D5175C562CF4C4A803C995C6", - "version": 66668 - }, { + "version": 66668, + }, + { "base-version": 67188, "data-hash": "2ACF84A7ECBB536F51FC3F734EC3019F", "fixed-hash": "2F0094C990E0D4E505570195F96C2A0C", "label": "4.5", "replay-hash": "E9873B3A3846F5878CEE0D1E2ADD204A", - "version": 67188 - }, { + "version": 67188, + }, + { "base-version": 67188, "data-hash": "6D239173B8712461E6A7C644A5539369", "fixed-hash": "A1BC35751ACC34CF887321A357B40158", "label": "4.5.1", "replay-hash": "E9873B3A3846F5878CEE0D1E2ADD204A", - "version": 67344 - }, { + "version": 67344, + }, + { "base-version": 67926, "data-hash": "7DE59231CBF06F1ECE9A25A27964D4AE", "fixed-hash": "570BEB69151F40D010E89DE1825AE680", "label": "4.6", "replay-hash": "DA662F9091DF6590A5E323C21127BA5A", - "version": 67926 - }, { + "version": 67926, + }, + { "base-version": 67926, "data-hash": "BEA99B4A8E7B41E62ADC06D194801BAB", "fixed-hash": "309E45F53690F8D1108F073ABB4D4734", "label": "4.6.1", "replay-hash": "DA662F9091DF6590A5E323C21127BA5A", - "version": 68195 - }, { + "version": 68195, + }, + { "base-version": 69232, "data-hash": "B3E14058F1083913B80C20993AC965DB", "fixed-hash": "21935E776237EF12B6CC73E387E76D6E", "label": "4.6.2", "replay-hash": "A230717B315D83ACC3697B6EC28C3FF6", - "version": 69232 - }, { + "version": 69232, + }, + { "base-version": 70154, "data-hash": "8E216E34BC61ABDE16A59A672ACB0F3B", "fixed-hash": "09CD819C667C67399F5131185334243E", "label": "4.7", "replay-hash": "9692B04D6E695EF08A2FB920979E776C", - "version": 70154 - }, { + "version": 70154, + }, + { "base-version": 70154, "data-hash": "94596A85191583AD2EBFAE28C5D532DB", "fixed-hash": "0AE50F82AC1A7C0DCB6A290D7FBA45DB", "label": "4.7.1", "replay-hash": "D74FBB3CB0897A3EE8F44E78119C4658", - "version": 70326 - }, { + "version": 70326, + }, + { "base-version": 71061, "data-hash": "760581629FC458A1937A05ED8388725B", "fixed-hash": "815C099DF1A17577FDC186FDB1381B16", "label": "4.8", "replay-hash": "BD692311442926E1F0B7C17E9ABDA34B", - "version": 71061 - }, { + "version": 71061, + }, + { "base-version": 71523, "data-hash": "FCAF3F050B7C0CC7ADCF551B61B9B91E", "fixed-hash": "4593CC331691620509983E92180A309A", "label": "4.8.1", "replay-hash": "BD692311442926E1F0B7C17E9ABDA34B", - "version": 71523 - }, { + "version": 71523, + }, + { "base-version": 71663, "data-hash": "FE90C92716FC6F8F04B74268EC369FA5", "fixed-hash": "1DBF3819F3A7367592648632CC0D5BFD", "label": "4.8.2", "replay-hash": "E43A9885B3EFAE3D623091485ECCCB6C", - "version": 71663 - }, { + "version": 71663, + }, + { "base-version": 72282, "data-hash": "0F14399BBD0BA528355FF4A8211F845B", "fixed-hash": "E9958B2CB666DCFE101D23AF87DB8140", "label": "4.8.3", "replay-hash": "3AF3657F55AB961477CE268F5CA33361", - "version": 72282 - }, { + "version": 72282, + }, + { "base-version": 73286, "data-hash": "CD040C0675FD986ED37A4CA3C88C8EB5", "fixed-hash": "62A146F7A0D19A8DD05BF011631B31B8", "label": "4.8.4", "replay-hash": "EE3A89F443BE868EBDA33A17C002B609", - "version": 73286 - }, { + "version": 73286, + }, + { "base-version": 73559, "data-hash": "B2465E73AED597C74D0844112D582595", "fixed-hash": "EF0A43C33413613BC7343B86C0A7CC92", "label": "4.8.5", "replay-hash": "147388D35E76861BD4F590F8CC5B7B0B", - "version": 73559 - }, { + "version": 73559, + }, + { "base-version": 73620, "data-hash": "AA18FEAD6573C79EF707DF44ABF1BE61", "fixed-hash": "4D76491CCAE756F0498D1C5B2973FF9C", "label": "4.8.6", "replay-hash": "147388D35E76861BD4F590F8CC5B7B0B", - "version": 73620 - }, { + "version": 73620, + }, + { "base-version": 74071, "data-hash": "70C74A2DCA8A0D8E7AE8647CAC68ACCA", "fixed-hash": "C4A3F01B4753245296DC94BC1B5E9B36", "label": "4.9", "replay-hash": "19D15E5391FACB379BFCA262CA8FD208", - "version": 74071 - }, { + "version": 74071, + }, + { "base-version": 74456, "data-hash": "218CB2271D4E2FA083470D30B1A05F02", "fixed-hash": "E82051387C591CAB1212B64073759826", "label": "4.9.1", "replay-hash": "1586ADF060C26219FF3404673D70245B", - "version": 74456 - }, { + "version": 74456, + }, + { "base-version": 74741, "data-hash": "614480EF79264B5BD084E57F912172FF", "fixed-hash": "500CC375B7031C8272546B78E9BE439F", "label": "4.9.2", "replay-hash": "A7FAC56F940382E05157EAB19C932E3A", - "version": 74741 - }, { + "version": 74741, + }, + { "base-version": 75025, "data-hash": "C305368C63621480462F8F516FB64374", "fixed-hash": "DEE7842C8BCB6874EC254AA3D45365F7", "label": "4.9.3", "replay-hash": "A7FAC56F940382E05157EAB19C932E3A", - "version": 75025 - }, { + "version": 75025, + }, + { "base-version": 75689, "data-hash": "B89B5D6FA7CBF6452E721311BFBC6CB2", "fixed-hash": "2B2097DC4AD60A2D1E1F38691A1FF111", "label": "4.10", "replay-hash": "6A60E59031A7DB1B272EE87E51E4C7CD", - "version": 75689 - }, { + "version": 75689, + }, + { "base-version": 75800, "data-hash": "DDFFF9EC4A171459A4F371C6CC189554", "fixed-hash": "1FB8FAF4A87940621B34F0B8F6FDDEA6", "label": "4.10.1", "replay-hash": "6A60E59031A7DB1B272EE87E51E4C7CD", - "version": 75800 - }, { + "version": 75800, + }, + { "base-version": 76052, "data-hash": "D0F1A68AA88BA90369A84CD1439AA1C3", "fixed-hash": "", "label": "4.10.2", "replay-hash": "", - "version": 76052 - }, { + "version": 76052, + }, + { "base-version": 76114, "data-hash": "CDB276D311F707C29BA664B7754A7293", "fixed-hash": "", "label": "4.10.3", "replay-hash": "", - "version": 76114 - }, { + "version": 76114, + }, + { "base-version": 76811, "data-hash": "FF9FA4EACEC5F06DEB27BD297D73ED67", "fixed-hash": "", "label": "4.10.4", "replay-hash": "", - "version": 76811 - }, { + "version": 76811, + }, + { "base-version": 77379, "data-hash": "70E774E722A58287EF37D487605CD384", "fixed-hash": "", "label": "4.11.0", "replay-hash": "", - "version": 77379 - }, { + "version": 77379, + }, + { "base-version": 77379, "data-hash": "F92D1127A291722120AC816F09B2E583", "fixed-hash": "", "label": "4.11.1", "replay-hash": "", - "version": 77474 - }, { + "version": 77474, + }, + { "base-version": 77535, "data-hash": "FC43E0897FCC93E4632AC57CBC5A2137", "fixed-hash": "", "label": "4.11.2", "replay-hash": "", - "version": 77535 - }, { + "version": 77535, + }, + { "base-version": 77661, "data-hash": "A15B8E4247434B020086354F39856C51", "fixed-hash": "", "label": "4.11.3", "replay-hash": "", - "version": 77661 - }, { + "version": 77661, + }, + { "base-version": 78285, "data-hash": "69493AFAB5C7B45DDB2F3442FD60F0CF", "fixed-hash": "21D2EBD5C79DECB3642214BAD4A7EF56", "label": "4.11.4", "replay-hash": "CAB5C056EDBDA415C552074BF363CC85", - "version": 78285 - }, { + "version": 78285, + }, + { "base-version": 79998, "data-hash": "B47567DEE5DC23373BFF57194538DFD3", "fixed-hash": "0A698A1B072BC4B087F44DDEF0BE361E", "label": "4.12.0", "replay-hash": "9E15AA09E15FE3AF3655126CEEC7FF42", - "version": 79998 - }, { + "version": 79998, + }, + { "base-version": 80188, "data-hash": "44DED5AED024D23177C742FC227C615A", "fixed-hash": "0A698A1B072BC4B087F44DDEF0BE361E", "label": "4.12.1", "replay-hash": "9E15AA09E15FE3AF3655126CEEC7FF42", - "version": 80188 - }, { + "version": 80188, + }, + { "base-version": 80949, "data-hash": "9AE39C332883B8BF6AA190286183ED72", "fixed-hash": "DACEAFAB8B983C08ACD31ABC085A0052", "label": "5.0.0", "replay-hash": "28C41277C5837AABF9838B64ACC6BDCF", - "version": 80949 - }, { + "version": 80949, + }, + { "base-version": 81009, "data-hash": "0D28678BC32E7F67A238F19CD3E0A2CE", "fixed-hash": "DACEAFAB8B983C08ACD31ABC085A0052", "label": "5.0.1", "replay-hash": "28C41277C5837AABF9838B64ACC6BDCF", - "version": 81009 - }, { + "version": 81009, + }, + { "base-version": 81102, "data-hash": "DC0A1182FB4ABBE8E29E3EC13CF46F68", "fixed-hash": "0C193BD5F63BBAB79D798278F8B2548E", "label": "5.0.2", "replay-hash": "08BB9D4CAE25B57160A6E4AD7B8E1A5A", - "version": 81102 - }, { + "version": 81102, + }, + { "base-version": 81433, "data-hash": "5FD8D4B6B52723B44862DF29F232CF31", "fixed-hash": "4FC35CEA63509AB06AA80AACC1B3B700", "label": "5.0.3", "replay-hash": "0920F1BD722655B41DA096B98CC0912D", - "version": 81433 - }, { + "version": 81433, + }, + { "base-version": 82457, "data-hash": "D2707E265785612D12B381AF6ED9DBF4", "fixed-hash": "ED05F0DB335D003FBC3C7DEF69911114", "label": "5.0.4", "replay-hash": "7D9EE968AAD81761334BD9076BFD9EFF", - "version": 82457 - }, { + "version": 82457, + }, + { "base-version": 82893, "data-hash": "D795328C01B8A711947CC62AA9750445", "fixed-hash": "ED05F0DB335D003FBC3C7DEF69911114", "label": "5.0.5", "replay-hash": "7D9EE968AAD81761334BD9076BFD9EFF", - "version": 82893 - }, { + "version": 82893, + }, + { "base-version": 83830, "data-hash": "B4745D6A4F982A3143C183D8ACB6C3E3", "fixed-hash": "ed05f0db335d003fbc3c7def69911114", "label": "5.0.6", "replay-hash": "7D9EE968AAD81761334BD9076BFD9EFF", - "version": 83830 - }, { + "version": 83830, + }, + { "base-version": 84643, "data-hash": "A389D1F7DF9DD792FBE980533B7119FF", "fixed-hash": "368DE29820A74F5BE747543AC02DB3F8", "label": "5.0.7", "replay-hash": "7D9EE968AAD81761334BD9076BFD9EFF", - "version": 84643 - }, { + "version": 84643, + }, + { "base-version": 86383, "data-hash": "22EAC562CD0C6A31FB2C2C21E3AA3680", "fixed-hash": "B19F4D8B87A2835F9447CA17EDD40C1E", "label": "5.0.8", "replay-hash": "7D9EE968AAD81761334BD9076BFD9EFF", - "version": 86383 - }, { + "version": 86383, + }, + { "base-version": 87702, "data-hash": "F799E093428D419FD634CCE9B925218C", "fixed-hash": "B19F4D8B87A2835F9447CA17EDD40C1E", "label": "5.0.9", "replay-hash": "7D9EE968AAD81761334BD9076BFD9EFF", - "version": 87702 - }, { + "version": 87702, + }, + { "base-version": 88500, "data-hash": "F38043A301B034A78AD13F558257DCF8", "fixed-hash": "F3853B6E3B6013415CAC30EF3B27564B", "label": "5.0.10", "replay-hash": "A79CD3B6C6DADB0ECAEFA06E6D18E47B", - "version": 88500 - } + "version": 88500, + }, ] diff --git a/sc2/wsl.py b/sc2/wsl.py index af3d614e..4f2a3cdd 100644 --- a/sc2/wsl.py +++ b/sc2/wsl.py @@ -1,4 +1,5 @@ -# pylint: disable=R0911,W1510 +from __future__ import annotations + import os import re import subprocess @@ -10,12 +11,12 @@ ## accessed directly by any bot clients -def win_path_to_wsl_path(path): +def win_path_to_wsl_path(path) -> Path: """Convert a path like C:\\foo to /mnt/c/foo""" return Path("/mnt") / PureWindowsPath(re.sub("^([A-Z]):", lambda m: m.group(1).lower(), path)) -def wsl_path_to_win_path(path): +def wsl_path_to_win_path(path) -> PureWindowsPath: """Convert a path like /mnt/c/foo to C:\\foo""" return PureWindowsPath(re.sub("^/mnt/([a-z])", lambda m: m.group(1).upper() + ":", path)) @@ -39,7 +40,7 @@ def get_wsl_home(): }""" -def run(popen_args, sc2_cwd): +def run(popen_args, sc2_cwd) -> subprocess.Popen[str]: """Run SC2 in Windows and get the pid so that it can be killed later.""" path = wsl_path_to_win_path(popen_args[0]) args = " ".join(popen_args[1:]) @@ -53,7 +54,7 @@ def run(popen_args, sc2_cwd): ) -def kill(wsl_process): +def kill(wsl_process) -> bool: """Needed to kill a process started with WSL. Returns true if killed successfully.""" # HACK: subprocess and WSL1 appear to have a nasty interaction where # any streams are never closed and the process is never considered killed, @@ -69,7 +70,7 @@ def kill(wsl_process): return proc.returncode == 0 # Returns 128 on failure -def detect(): +def detect() -> str | None: """Detect the current running version of WSL, and bail out if it doesn't exist""" # Allow disabling WSL detection with an environment variable if os.getenv("SC2_WSL_DETECT", "1") == "0": diff --git a/test/autotest_bot.py b/test/autotest_bot.py index d1570f08..10abb82e 100644 --- a/test/autotest_bot.py +++ b/test/autotest_bot.py @@ -1,7 +1,4 @@ -import os -import sys - -sys.path.append(os.path.join(os.path.dirname(__file__), "..")) +from __future__ import annotations from loguru import logger @@ -19,7 +16,6 @@ class TestBot(BotAI): - def __init__(self): BotAI.__init__(self) # The time the bot has to complete all tests, here: the number of game seconds @@ -28,7 +24,8 @@ def __init__(self): # Check how many test action functions we have # At least 4 tests because we test properties and variables self.action_tests = [ - getattr(self, f"test_botai_actions{index}") for index in range(4000) + getattr(self, f"test_botai_actions{index}") + for index in range(4000) if hasattr(getattr(self, f"test_botai_actions{index}", 0), "__call__") ] self.tests_done_by_name = set() @@ -68,7 +65,7 @@ async def on_step(self, iteration): # Exit bot if iteration > 100: - logger.info("Tests completed after {} seconds".format(round(self.time, 1))) + logger.info(f"Tests completed after {round(self.time, 1)} seconds") exit(0) async def clean_up_center(self): @@ -159,7 +156,10 @@ async def test_botai_actions2(self): def temp_filter(unit: Unit): return ( - unit.is_moving or unit.is_patrolling or unit.orders and unit.orders[0] == AbilityId.HOLDPOSITION_HOLD + unit.is_moving + or unit.is_patrolling + or unit.orders + and unit.orders[0] == AbilityId.HOLDPOSITION_HOLD or unit.is_attacking ) @@ -372,11 +372,8 @@ async def test_botai_actions10(self): bane_cocoons = self.units(UnitTypeId.BANELINGCOCOON) # Cheat money, need 10k/10k to morph 400 lings to 400 banes - if not banes and not bane_cocoons: - if self.minerals < 10_000: - await self.client.debug_all_resources() - elif self.vespene < 10_000: - await self.client.debug_all_resources() + if not banes and not bane_cocoons and (self.minerals < 10_000 or self.vespene < 10_000): + await self.client.debug_all_resources() # Spawn units if not bane_nests: @@ -482,12 +479,12 @@ async def test_botai_actions12(self): class EmptyBot(BotAI): - async def on_start(self): if self.units: 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 c1671c9f..d948244a 100644 --- a/test/battery_overcharge_bot.py +++ b/test/battery_overcharge_bot.py @@ -14,16 +14,13 @@ class BatteryOverchargeBot(BotAI): - async def on_start(self): - """ Spawn requires structures. """ + """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.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], ] ) @@ -38,15 +35,14 @@ async def on_step(self, iteration): nexus(AbilityId.BATTERYOVERCHARGE_BATTERYOVERCHARGE, battery) if iteration > 20: - logger.warning(f"Success, bot did not crash. Exiting bot.") + logger.warning("Success, bot did not crash. Exiting bot.") await self.client.leave() def main(): run_game( maps.get("AcropolisLE"), - [Bot(Race.Protoss, BatteryOverchargeBot()), - Computer(Race.Terran, Difficulty.Medium)], + [Bot(Race.Protoss, BatteryOverchargeBot()), Computer(Race.Terran, Difficulty.Medium)], realtime=False, disable_fog=True, ) diff --git a/test/benchmark_bot_ai_init.py b/test/benchmark_bot_ai_init.py index cc70f1cd..bbcc9129 100644 --- a/test/benchmark_bot_ai_init.py +++ b/test/benchmark_bot_ai_init.py @@ -1,15 +1,18 @@ +from __future__ import annotations + +from typing import Any + from test.test_pickled_data import MAPS, build_bot_object_from_pickle_data, load_map_pickle_data -from typing import Any, List, Tuple -def _test_run_bot_ai_init_on_all_maps(pickle_data: List[Tuple[Any, Any, Any]]): +def _test_run_bot_ai_init_on_all_maps(pickle_data: list[tuple[Any, Any, Any]]): for data in pickle_data: build_bot_object_from_pickle_data(*data) def test_bench_bot_ai_init(benchmark): # Load pickle files outside of benchmark - map_pickle_data: List[Tuple[Any, Any, Any]] = [load_map_pickle_data(path) for path in MAPS] + map_pickle_data: list[tuple[Any, Any, Any]] = [load_map_pickle_data(path) for path in MAPS] _result = benchmark(_test_run_bot_ai_init_on_all_maps, map_pickle_data) diff --git a/test/benchmark_distance_two_points.py b/test/benchmark_distance_two_points.py index 974cec57..9527a107 100644 --- a/test/benchmark_distance_two_points.py +++ b/test/benchmark_distance_two_points.py @@ -1,7 +1,9 @@ +# pyre-ignore-all-errors[21] +from __future__ import annotations + import math import platform import random -from typing import Union import numpy as np from scipy.spatial import distance as scipydistance @@ -10,15 +12,15 @@ from sc2.position import Point2 PYTHON_VERSION = platform.python_version_tuple() -USING_PYTHON_3_8: bool = ("3", "8") <= PYTHON_VERSION +USING_PYTHON_3_8: bool = PYTHON_VERSION >= ("3", "8") def distance_to_python_raw(s, p): - return ((s[0] - p[0])**2 + (s[1] - p[1])**2)**0.5 + return ((s[0] - p[0]) ** 2 + (s[1] - p[1]) ** 2) ** 0.5 def distance_to_squared_python_raw(s, p): - return (s[0] - p[0])**2 + (s[1] - p[1])**2 + return (s[0] - p[0]) ** 2 + (s[1] - p[1]) ** 2 if USING_PYTHON_3_8: @@ -31,35 +33,35 @@ def distance_to_math_hypot(s, p): return math.hypot((s[0] - p[0]), (s[1] - p[1])) -def distance_scipy_euclidean(p1, p2) -> Union[int, float]: - """ Distance calculation using scipy """ +def distance_scipy_euclidean(p1, p2) -> int | float: + """Distance calculation using scipy""" dist = scipydistance.euclidean(p1, p2) # dist = distance.cdist(p1.T, p2.T, "euclidean") return dist def distance_numpy_linalg_norm(p1, p2): - """ Distance calculation using numpy """ + """Distance calculation using numpy""" return np.linalg.norm(p1 - p2) -def distance_sum_squared_sqrt(p1, p2) -> Union[int, float]: - """ Distance calculation using numpy """ - return np.sqrt(np.sum((p1 - p2)**2)) +def distance_sum_squared_sqrt(p1, p2) -> int | float: + """Distance calculation using numpy""" + return np.sqrt(np.sum((p1 - p2) ** 2)) -def distance_sum_squared(p1, p2) -> Union[int, float]: - """ Distance calculation using numpy """ - return np.sum((p1 - p2)**2, axis=0) +def distance_sum_squared(p1, p2) -> int | float: + """Distance calculation using numpy""" + return np.sum((p1 - p2) ** 2, axis=0) # @njit -# def distance_python_raw_njit(p1: Point2, p2: Point2) -> Union[int, float]: +# def distance_python_raw_njit(p1: Point2, p2: Point2) -> int | float: # """ The built in Point2 distance function rewritten differently with njit, same structure as distance02 """ # return ((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2) ** 0.5 # @njit -# def distance_python_raw_square_njit(p1: Point2, p2: Point2) -> Union[int, float]: +# def distance_python_raw_square_njit(p1: Point2, p2: Point2) -> int | float: # """ The built in Point2 distance function rewritten differently with njit, same structure as distance02 """ # return (p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2 @@ -69,12 +71,12 @@ def distance_sum_squared(p1, p2) -> Union[int, float]: # return np.linalg.norm(p1 - p2) # @njit("float64(float64[:], float64[:])") -# def distance_numpy_square_sum_sqrt_njit(p1, p2) -> Union[int, float]: +# def distance_numpy_square_sum_sqrt_njit(p1, p2) -> int | float: # """ Distance calculation using numpy + numba, same structure as distance13 """ # return np.sqrt(np.sum((p1 - p2) ** 2)) # @njit("float64(float64[:], float64[:])") -# def distance_numpy_square_sum_njit(p1, p2) -> Union[int, float]: +# def distance_numpy_square_sum_njit(p1, p2) -> int | float: # """ Distance calculation using numpy + numba, same structure as distance13 """ # return np.sum((p1 - p2) ** 2, axis=0) @@ -90,9 +92,7 @@ def distance_sum_squared(p1, p2) -> Union[int, float]: def check_result(result1, result2, accuracy=1e-5): - if abs(result1 - result2) <= accuracy: - return True - return False + return abs(result1 - result2) <= accuracy if USING_PYTHON_3_8: diff --git a/test/benchmark_distances_cdist.py b/test/benchmark_distances_cdist.py index ae7f74b0..fdcfd7b8 100644 --- a/test/benchmark_distances_cdist.py +++ b/test/benchmark_distances_cdist.py @@ -1,3 +1,4 @@ +# pyre-ignore-all-errors[21] import random import numpy as np @@ -124,8 +125,7 @@ def distance_matrix_scipy_pdist_squared(ps): min_value = 0 max_value = 300 points = np.array( - [np.array([random.uniform(min_value, max_value), - random.uniform(min_value, max_value)]) for _ in range(amount)] + [np.array([random.uniform(min_value, max_value), random.uniform(min_value, max_value)]) for _ in range(amount)] ) diff --git a/test/benchmark_distances_points_to_point.py b/test/benchmark_distances_points_to_point.py index b523ca08..cd36c8d8 100644 --- a/test/benchmark_distances_points_to_point.py +++ b/test/benchmark_distances_points_to_point.py @@ -1,6 +1,8 @@ +# pyre-ignore-all-errors[21] +from __future__ import annotations + import math import random -from typing import List, Tuple import numpy as np from scipy.spatial.distance import cdist @@ -15,28 +17,28 @@ def distance_matrix_scipy_cdist_squared(ps, p1): def distance_numpy_basic_1(ps, p1): - """ Distance calculation using numpy """ + """Distance calculation using numpy""" flat_units = (item for sublist in ps for item in sublist) units_np = np.fromiter(flat_units, dtype=float, count=2 * len(ps)).reshape((-1, 2)) point_np = np.fromiter(p1, dtype=float, count=2).reshape((-1, 2)) # Subtract and then square the values - nppoints = (units_np - point_np)**2 + nppoints = (units_np - point_np) ** 2 # Calc the sum of each vector nppoints = nppoints.sum(axis=1) return nppoints def distance_numpy_basic_2(ps, p1): - """ Distance calculation using numpy """ + """Distance calculation using numpy""" flat_units = (item for sublist in ps for item in sublist) units_np = np.fromiter(flat_units, dtype=float, count=2 * len(ps)).reshape((-1, 2)) point_np = np.fromiter(p1, dtype=float, count=2).reshape((-1, 2)) - dist_2 = np.sum((units_np - point_np)**2, axis=1) + dist_2 = np.sum((units_np - point_np) ** 2, axis=1) return dist_2 def distance_numpy_einsum(ps, p1): - """ Distance calculation using numpy einstein sum """ + """Distance calculation using numpy einstein sum""" flat_units = (item for sublist in ps for item in sublist) units_np = np.fromiter(flat_units, dtype=float, count=2 * len(ps)).reshape((-1, 2)) point_np = np.fromiter(p1, dtype=float, count=2).reshape((-1, 2)) @@ -46,7 +48,7 @@ def distance_numpy_einsum(ps, p1): def distance_numpy_einsum_pre_converted(ps, p1): - """ Distance calculation using numpy einstein sum """ + """Distance calculation using numpy einstein sum""" deltas = ps - p1 dist_2 = np.einsum("ij,ij->i", deltas, deltas) return dist_2 @@ -83,18 +85,18 @@ def distance_numpy_einsum_pre_converted(ps, p1): def distance_pure_python(ps, p1): - """ Distance calculation using numpy with jit(nopython=True) """ + """Distance calculation using numpy with jit(nopython=True)""" distances = [] x1 = p1[0] y1 = p1[1] for x0, y0 in ps: - distance_squared = (x0 - x1)**2 + (y0 - y1)**2 + distance_squared = (x0 - x1) ** 2 + (y0 - y1) ** 2 distances.append(distance_squared) return distances def distance_math_hypot(ps, p1): - """ Distance calculation using math.hypot """ + """Distance calculation using math.hypot""" distances = [] x1 = p1[0] y1 = p1[1] @@ -110,8 +112,8 @@ def distance_math_hypot(ps, p1): min_value = 0 max_value = 250 -point: Tuple[float, float] = (random.uniform(min_value, max_value), random.uniform(min_value, max_value)) -units: List[Tuple[float, float]] = [ +point: tuple[float, float] = (random.uniform(min_value, max_value), random.uniform(min_value, max_value)) +units: list[tuple[float, float]] = [ (random.uniform(min_value, max_value), random.uniform(min_value, max_value)) for _ in range(amount) ] diff --git a/test/benchmark_distances_units.py b/test/benchmark_distances_units.py index 9fee71a4..11d81462 100644 --- a/test/benchmark_distances_units.py +++ b/test/benchmark_distances_units.py @@ -1,3 +1,4 @@ +# pyre-ignore-all-errors[21] import math import random @@ -30,8 +31,7 @@ def distance_matrix_scipy_pdist_squared(ps): min_value = 0 max_value = 250 points = np.array( - [np.array([random.uniform(min_value, max_value), - random.uniform(min_value, max_value)]) for _ in range(amount)] + [np.array([random.uniform(min_value, max_value), random.uniform(min_value, max_value)]) for _ in range(amount)] ) m1 = distance_matrix_scipy_cdist(points) @@ -41,7 +41,7 @@ def distance_matrix_scipy_pdist_squared(ps): def calc_row_idx(k, n): - return int(math.ceil((1 / 2.0) * (-((-8 * k + 4 * n**2 - 4 * n - 7)**0.5) + 2 * n - 1) - 1)) + return int(math.ceil((1 / 2.0) * (-((-8 * k + 4 * n**2 - 4 * n - 7) ** 0.5) + 2 * n - 1) - 1)) def elem_in_i_rows(i, n): diff --git a/test/benchmark_prepare_units.py b/test/benchmark_prepare_units.py index 716482c7..51aeb7d7 100644 --- a/test/benchmark_prepare_units.py +++ b/test/benchmark_prepare_units.py @@ -1,11 +1,14 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + from test.test_pickled_data import MAPS, get_map_specific_bot -from typing import TYPE_CHECKING, List if TYPE_CHECKING: from sc2.bot_ai import BotAI -def _run_prepare_units(bot_objects: List["BotAI"]): +def _run_prepare_units(bot_objects: list[BotAI]): for bot_object in bot_objects: bot_object._prepare_units() diff --git a/test/damagetest_bot.py b/test/damagetest_bot.py index c2ece1c9..a8c17bca 100644 --- a/test/damagetest_bot.py +++ b/test/damagetest_bot.py @@ -1,7 +1,5 @@ -import os -import sys +from __future__ import annotations -sys.path.append(os.path.join(os.path.dirname(__file__), "..")) import math from loguru import logger @@ -16,7 +14,6 @@ class TestBot(BotAI): - def __init__(self): # The time the bot has to complete all tests, here: the number of game seconds self.game_time_timeout_limit = 20 * 60 # 20 minutes ingame time @@ -24,7 +21,8 @@ def __init__(self): # Check how many test action functions we have # At least 4 tests because we test properties and variables self.action_tests = [ - getattr(self, f"test_botai_actions{index}") for index in range(4000) + getattr(self, f"test_botai_actions{index}") + for index in range(4000) if hasattr(getattr(self, f"test_botai_actions{index}", 0), "__call__") ] self.tests_target = 4 @@ -53,7 +51,7 @@ async def on_step(self, iteration): # Exit bot if iteration > 100: - logger.info("Tests completed after {} seconds".format(round(self.time, 1))) + logger.info(f"Tests completed after {round(self.time, 1)} seconds") exit(0) async def clean_up_center(self): @@ -186,7 +184,7 @@ def get_attacker_and_defender(): return attacker, defender def do_some_unit_property_tests(attacker: Unit, defender: Unit): - """ Some tests that are not covered by test_pickled_data.py """ + """Some tests that are not covered by test_pickled_data.py""" # TODO move unit unrelated tests elsewhere self.step_time self.units_created @@ -241,17 +239,15 @@ def do_some_unit_property_tests(attacker: Unit, defender: Unit): for attacker_type in attacker_units: for defender_type in defender_units: # DT, Thor, Tempest one-shots workers, so skip test - if ( - attacker_type in { - UnitTypeId.DARKTEMPLAR, - UnitTypeId.TEMPEST, - UnitTypeId.THOR, - UnitTypeId.THORAP, - UnitTypeId.LIBERATORAG, - UnitTypeId.PLANETARYFORTRESS, - UnitTypeId.ARCHON, - } and defender_type in {UnitTypeId.PROBE, UnitTypeId.DRONE, UnitTypeId.SCV, UnitTypeId.MULE} - ): + if attacker_type in { + UnitTypeId.DARKTEMPLAR, + UnitTypeId.TEMPEST, + UnitTypeId.THOR, + UnitTypeId.THORAP, + UnitTypeId.LIBERATORAG, + UnitTypeId.PLANETARYFORTRESS, + UnitTypeId.ARCHON, + } and defender_type in {UnitTypeId.PROBE, UnitTypeId.DRONE, UnitTypeId.SCV, UnitTypeId.MULE}: continue # Spawn units @@ -263,7 +259,9 @@ def do_some_unit_property_tests(attacker: Unit, defender: Unit): # Wait for units to spawn attacker, defender = get_attacker_and_defender() while ( - attacker is None or defender is None or attacker.type_id != attacker_type + attacker is None + or defender is None + or attacker.type_id != attacker_type or defender.type_id != defender_type ): await self._advance_steps(1) @@ -318,12 +316,12 @@ def do_some_unit_property_tests(attacker: Unit, defender: Unit): class EmptyBot(BotAI): - async def on_start(self): if self.units: 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 fd6143cd..2d420c7f 100644 --- a/test/generate_pickle_files_bot.py +++ b/test/generate_pickle_files_bot.py @@ -1,13 +1,16 @@ +# 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" """ + import lzma -import os import pickle -from typing import Set +from pathlib import Path from loguru import logger + +# pyre-ignore[21] from s2clientprotocol import sc2api_pb2 as sc_pb from sc2 import maps @@ -23,7 +26,6 @@ class ExporterBot(BotAI): - def __init__(self): BotAI.__init__(self) self.map_name: str = None @@ -31,18 +33,18 @@ def __init__(self): async def on_step(self, iteration): pass - def get_pickle_file_path(self) -> str: - folder_path = os.path.dirname(__file__) + def get_pickle_file_path(self) -> Path: + folder_path = Path(__file__).parent subfolder_name = "pickle_data" file_name = f"{self.map_name}.xz" - file_path = os.path.join(folder_path, subfolder_name, file_name) + file_path = folder_path / subfolder_name / file_name return file_path - def get_combat_file_path(self) -> str: - folder_path = os.path.dirname(__file__) + def get_combat_file_path(self) -> Path: + folder_path = Path(__file__).parent subfolder_name = "combat_data" file_name = f"{self.map_name}.xz" - file_path = os.path.join(folder_path, subfolder_name, file_name) + file_path = folder_path / subfolder_name / file_name return file_path async def store_data_to_file(self, file_path: str): @@ -60,7 +62,7 @@ async def store_data_to_file(self, file_path: str): _game_info = GameInfo(raw_game_info.game_info) _game_state = GameState(raw_observation) - os.makedirs(os.path.dirname(file_path), exist_ok=True) + Path(file_path).parent.mkdir(exist_ok=True, parents=True) with lzma.open(file_path, "wb") as f: pickle.dump([raw_game_data, raw_game_info, raw_observation], f) @@ -75,13 +77,15 @@ async def on_start(self): await self.client.debug_god() # Spawn one of each unit - valid_units: Set[UnitTypeId] = { + valid_units: set[UnitTypeId] = { UnitTypeId(unit_id) for unit_id, data in self.game_data.units.items() - if data._proto.race != Race.NoRace and data._proto.race != Race.Random and data._proto.available + if data._proto.race != Race.NoRace + and data._proto.race != Race.Random + and data._proto.available # Dont cloak units - and UnitTypeId(unit_id) != UnitTypeId.MOTHERSHIP and - (data._proto.mineral_cost or data._proto.movement_speed or data._proto.weapons) + and UnitTypeId(unit_id) != UnitTypeId.MOTHERSHIP + and (data._proto.mineral_cost or data._proto.movement_speed or data._proto.weapons) } # Create units for self @@ -100,7 +104,6 @@ async def on_start(self): def main(): - maps_ = [ "16-BitLE", "2000AtmospheresAIE", @@ -211,7 +214,7 @@ def main(): bot = ExporterBot() bot.map_name = map_ file_path = bot.get_pickle_file_path() - if os.path.isfile(file_path): + if Path(file_path).is_file(): logger.warning( f"Pickle file for map {map_} was already generated. Skipping. If you wish to re-generate files, please remove them first." ) diff --git a/test/queries_test_bot.py b/test/queries_test_bot.py index 11249bd1..2e4b830e 100644 --- a/test/queries_test_bot.py +++ b/test/queries_test_bot.py @@ -1,15 +1,13 @@ +# pyre-ignore-all-errors[16] """ This testbot's purpose is to test the query behavior of the API. These query functions are: self.can_place (RequestQueryBuildingPlacement) TODO: self.client.query_pathing (RequestQueryPathing) """ -import os -import sys - -sys.path.append(os.path.join(os.path.dirname(__file__), "..")) -from typing import List, Union +from __future__ import annotations +import sys from loguru import logger @@ -24,7 +22,6 @@ class TestBot(BotAI): - def __init__(self): # The time the bot has to complete all tests, here: the number of game seconds self.game_time_timeout_limit = 20 * 60 # 20 minutes ingame time @@ -46,7 +43,7 @@ async def on_step(self, iteration): sys.exit(0) async def clear_map_center(self): - """ Spawn observer in map center, remove all enemy units, remove all own units. """ + """Spawn observer in map center, remove all enemy units, remove all own units.""" map_center = self.game_info.map_center # Spawn observer to be able to see enemy invisible units @@ -69,16 +66,16 @@ async def clear_map_center(self): await self.client.debug_kill_unit(my_units) await self._advance_steps(10) - async def spawn_unit(self, unit_type: Union[UnitTypeId, List[UnitTypeId]]): + async def spawn_unit(self, unit_type: UnitTypeId | list[UnitTypeId]): await self._advance_steps(10) - if not isinstance(unit_type, List): + 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]]) - async def spawn_unit_enemy(self, unit_type: Union[UnitTypeId, List[UnitTypeId]]): + async def spawn_unit_enemy(self, unit_type: UnitTypeId | list[UnitTypeId]): await self._advance_steps(10) - if not isinstance(unit_type, List): + if not isinstance(unit_type, list): unit_type = [unit_type] for i in unit_type: if i == UnitTypeId.CREEPTUMOR: @@ -251,7 +248,6 @@ async def test_rally_points_with_smart_ability(self): class EmptyBot(BotAI): - async def on_step(self, iteration: int): for unit in self.units: unit.hold_position() diff --git a/test/real_time_worker_production.py b/test/real_time_worker_production.py index 0638ee29..d272e0ea 100644 --- a/test/real_time_worker_production.py +++ b/test/real_time_worker_production.py @@ -1,10 +1,9 @@ """ This bot tests if on 'realtime=True' any nexus has more than 1 probe in the queue. """ -import os -import sys -sys.path.append(os.path.join(os.path.dirname(__file__), "..")) +from __future__ import annotations + import asyncio from loguru import logger @@ -22,7 +21,6 @@ class RealTimeTestBot(BotAI): - async def on_before_start(self): mf = self.mineral_field for w in self.workers: @@ -36,7 +34,7 @@ async def on_before_start(self): await asyncio.sleep(1) async def on_start(self): - """ This function is run after the expansion locations and ramps are calculated. """ + """This function is run after the expansion locations and ramps are calculated.""" self.client.game_step = 1 async def on_step(self, iteration): @@ -94,6 +92,7 @@ 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 @@ -103,8 +102,7 @@ async def on_end(self, game_result: Result): def main(): run_game( maps.get("AcropolisLE"), - [Bot(Race.Protoss, RealTimeTestBot()), - Computer(Race.Terran, Difficulty.Medium)], + [Bot(Race.Protoss, RealTimeTestBot()), Computer(Race.Terran, Difficulty.Medium)], realtime=True, disable_fog=True, ) diff --git a/test/run_example_bots_vs_computer.py b/test/run_example_bots_vs_computer.py index 70ad9b12..ed3b4d01 100644 --- a/test/run_example_bots_vs_computer.py +++ b/test/run_example_bots_vs_computer.py @@ -1,13 +1,12 @@ +# pyre-ignore-all-errors[16] """ This script makes sure to run all bots in the examples folder to check if they can launch. """ -import os -import sys -sys.path.append(os.path.join(os.path.dirname(__file__), "..")) +from __future__ import annotations + import asyncio from importlib import import_module -from typing import List, Type from loguru import logger @@ -25,102 +24,103 @@ { "race": Race.Protoss, "path": "examples.protoss.cannon_rush", - "bot_class_name": "CannonRushBot" + "bot_class_name": "CannonRushBot", }, { "race": Race.Protoss, "path": "examples.protoss.find_adept_shades", - "bot_class_name": "FindAdeptShadesBot" + "bot_class_name": "FindAdeptShadesBot", }, { "race": Race.Protoss, "path": "examples.protoss.threebase_voidray", - "bot_class_name": "ThreebaseVoidrayBot" + "bot_class_name": "ThreebaseVoidrayBot", }, { "race": Race.Protoss, "path": "examples.protoss.warpgate_push", - "bot_class_name": "WarpGateBot" + "bot_class_name": "WarpGateBot", }, # Terran { "race": Race.Terran, "path": "examples.terran.cyclone_push", - "bot_class_name": "CyclonePush" + "bot_class_name": "CyclonePush", }, { "race": Race.Terran, "path": "examples.terran.mass_reaper", - "bot_class_name": "MassReaperBot" + "bot_class_name": "MassReaperBot", }, { "race": Race.Terran, "path": "examples.terran.onebase_battlecruiser", - "bot_class_name": "BCRushBot" + "bot_class_name": "BCRushBot", }, { "race": Race.Terran, "path": "examples.terran.proxy_rax", - "bot_class_name": "ProxyRaxBot" + "bot_class_name": "ProxyRaxBot", }, { "race": Race.Terran, "path": "examples.terran.ramp_wall", - "bot_class_name": "RampWallBot" + "bot_class_name": "RampWallBot", }, # Zerg { "race": Race.Zerg, "path": "examples.zerg.expand_everywhere", - "bot_class_name": "ExpandEverywhere" + "bot_class_name": "ExpandEverywhere", }, { "race": Race.Zerg, "path": "examples.zerg.hydralisk_push", - "bot_class_name": "Hydralisk" + "bot_class_name": "Hydralisk", }, { "race": Race.Zerg, "path": "examples.zerg.onebase_broodlord", - "bot_class_name": "BroodlordBot" + "bot_class_name": "BroodlordBot", }, { "race": Race.Zerg, "path": "examples.zerg.zerg_rush", - "bot_class_name": "ZergRushBot" + "bot_class_name": "ZergRushBot", }, # # Other { "race": Race.Protoss, "path": "examples.worker_stack_bot", - "bot_class_name": "WorkerStackBot" + "bot_class_name": "WorkerStackBot", }, { "race": Race.Zerg, "path": "examples.worker_rush", - "bot_class_name": "WorkerRushBot" + "bot_class_name": "WorkerRushBot", }, { "race": Race.Terran, "path": "examples.too_slow_bot", - "bot_class_name": "SlowBot" + "bot_class_name": "SlowBot", }, { "race": Race.Terran, "path": "examples.distributed_workers", - "bot_class_name": "TerranBot" + "bot_class_name": "TerranBot", }, ] -matches: List[GameMatch] = [] +matches: list[GameMatch] = [] # 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"] module = import_module(bot_path) - bot_class: Type[BotAI] = getattr(module, bot_class_name) + bot_class: type[BotAI] = getattr(module, bot_class_name) limit_match_duration = game_time_limit_vs_computer if bot_class_name in {"SlowBot", "RampWallBot"}: @@ -151,5 +151,5 @@ async def main(): logger.info("Checked all results") -if __name__ == '__main__': +if __name__ == "__main__": asyncio.run(main()) diff --git a/test/run_example_bots_vs_each_other.py b/test/run_example_bots_vs_each_other.py index 0f540db8..72969a0b 100644 --- a/test/run_example_bots_vs_each_other.py +++ b/test/run_example_bots_vs_each_other.py @@ -1,14 +1,13 @@ +# 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. """ -import os -import sys -sys.path.append(os.path.join(os.path.dirname(__file__), "..")) +from __future__ import annotations + import asyncio from importlib import import_module from itertools import combinations -from typing import List, Type from loguru import logger @@ -27,87 +26,88 @@ { "race": Race.Protoss, "path": "examples.protoss.cannon_rush", - "bot_class_name": "CannonRushBot" + "bot_class_name": "CannonRushBot", }, { "race": Race.Protoss, "path": "examples.protoss.find_adept_shades", - "bot_class_name": "FindAdeptShadesBot" + "bot_class_name": "FindAdeptShadesBot", }, { "race": Race.Protoss, "path": "examples.protoss.threebase_voidray", - "bot_class_name": "ThreebaseVoidrayBot" + "bot_class_name": "ThreebaseVoidrayBot", }, { "race": Race.Protoss, "path": "examples.protoss.warpgate_push", - "bot_class_name": "WarpGateBot" + "bot_class_name": "WarpGateBot", }, # Terran { "race": Race.Terran, "path": "examples.terran.cyclone_push", - "bot_class_name": "CyclonePush" + "bot_class_name": "CyclonePush", }, { "race": Race.Terran, "path": "examples.terran.mass_reaper", - "bot_class_name": "MassReaperBot" + "bot_class_name": "MassReaperBot", }, { "race": Race.Terran, "path": "examples.terran.onebase_battlecruiser", - "bot_class_name": "BCRushBot" + "bot_class_name": "BCRushBot", }, { "race": Race.Terran, "path": "examples.terran.proxy_rax", - "bot_class_name": "ProxyRaxBot" + "bot_class_name": "ProxyRaxBot", }, { "race": Race.Terran, "path": "examples.terran.ramp_wall", - "bot_class_name": "RampWallBot" + "bot_class_name": "RampWallBot", }, # Zerg { "race": Race.Zerg, "path": "examples.zerg.expand_everywhere", - "bot_class_name": "ExpandEverywhere" + "bot_class_name": "ExpandEverywhere", }, { "race": Race.Zerg, "path": "examples.zerg.hydralisk_push", - "bot_class_name": "Hydralisk" + "bot_class_name": "Hydralisk", }, { "race": Race.Zerg, "path": "examples.zerg.onebase_broodlord", - "bot_class_name": "BroodlordBot" + "bot_class_name": "BroodlordBot", }, { "race": Race.Zerg, "path": "examples.zerg.zerg_rush", - "bot_class_name": "ZergRushBot" + "bot_class_name": "ZergRushBot", }, ] -matches: List[GameMatch] = [] +matches: list[GameMatch] = [] # 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"] module = import_module(bot_path) - bot_class1: Type[BotAI] = getattr(module, bot_class_name) + 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"] module = import_module(bot_path) - bot_class2: Type[BotAI] = getattr(module, bot_class_name) + bot_class2: type[BotAI] = getattr(module, bot_class_name) for realtime in [True, False]: matches.append( @@ -138,5 +138,5 @@ async def main(): logger.info("Checked all results") -if __name__ == '__main__': +if __name__ == "__main__": asyncio.run(main()) diff --git a/test/test_directions.py b/test/test_directions.py index fec66cc2..64bf28a7 100644 --- a/test/test_directions.py +++ b/test/test_directions.py @@ -67,7 +67,9 @@ def test_towards_random_angle(): random.seed(1) def random_points(n=1000): - rs = lambda: 1 - random.random() * 2 + def rs(): + return 1 - random.random() * 2 + return {Point2((rs() * 1000, rs() * 1000)) for _ in range(n)} def verify(source, target, max_difference=(pi / 4), n=1000): diff --git a/test/test_expiring_dict.py b/test/test_expiring_dict.py index e3391c38..9fc6daa3 100644 --- a/test/test_expiring_dict.py +++ b/test/test_expiring_dict.py @@ -4,14 +4,11 @@ def test_class(): - class State: - def __init__(self): self.game_loop = 0 class BotAI: - def __init__(self): self.state = State() @@ -57,7 +54,7 @@ def increment(self, value=1): assert test.get(key, with_age=True)[1] in {0, 1} c = 0 - for _key in test.keys(): + for _key in test: c += 1 assert c == 4 @@ -81,7 +78,7 @@ def increment(self, value=1): assert len(test) == 0 - for _key in test.keys(): + for _key in test: assert False for _value in test.values(): diff --git a/test/test_pickled_data.py b/test/test_pickled_data.py index 84d21faf..a086ab59 100644 --- a/test/test_pickled_data.py +++ b/test/test_pickled_data.py @@ -8,6 +8,8 @@ All functions that require some kind of query or interaction with the API directly will have to be tested in the "autotest_bot.py" in a live game. """ +from __future__ import annotations + import lzma import math import pickle @@ -17,8 +19,9 @@ import unittest from contextlib import suppress from pathlib import Path -from typing import Any, List, Tuple +from typing import Any +# pyre-ignore[21] from google.protobuf.internal import api_implementation from hypothesis import given, settings from hypothesis import strategies as st @@ -40,12 +43,12 @@ from sc2.unit import Unit from sc2.units import Units -MAPS: List[Path] = [ +MAPS: list[Path] = [ map_path for map_path in (Path(__file__).parent / "pickle_data").iterdir() if map_path.suffix == ".xz" ] -def load_map_pickle_data(map_path: Path) -> Tuple[Any, Any, Any]: +def load_map_pickle_data(map_path: Path) -> tuple[Any, Any, Any]: with lzma.open(str(map_path.absolute()), "rb") as f: raw_game_data, raw_game_info, raw_observation = pickle.load(f) return raw_game_data, raw_game_info, raw_observation @@ -73,7 +76,7 @@ def get_map_specific_bot(map_path: Path) -> BotAI: def test_protobuf_implementation(): """Make sure that cpp is used as implementation""" # Doesn't seem to be implemented in newer python versions - if sys.version_info.major == 3 and sys.version_info.minor < 10 and sys.platform != "darwin": + if sys.version_info < (3, 10) and sys.platform != "darwin": assert api_implementation.Type() == "cpp" @@ -176,7 +179,7 @@ def test_bot_ai(): assert bot.already_pending_upgrade(UpgradeId.STIMPACK) == 0 assert bot.already_pending(UpgradeId.STIMPACK) == 0 assert bot.already_pending(UnitTypeId.SCV) == 0 - assert 0 < bot.get_terrain_height(worker) + assert bot.get_terrain_height(worker) > 0 assert bot.in_placement_grid(worker) assert bot.in_pathing_grid(worker) # The pickle data was created by a terran bot, so there is no creep under any worker @@ -290,12 +293,14 @@ 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) + return Cost(0, 0) def assert_cost(item_id, real_cost: Cost): assert calc_cost(item_id) == real_cost, f"Cost of {item_id} should be {real_cost} but is {calc_cost(item_id)}" @@ -911,7 +916,7 @@ def test_exact_creation_ability(): from sc2.dicts.unit_abilities import UNIT_ABILITIES from sc2.dicts.unit_unit_alias import UNIT_UNIT_ALIAS except ImportError: - logger.info(f"Import error: dict sc2/dicts/ are missing!") + logger.info("Import error: dict sc2/dicts/ are missing!") return test_case = unittest.TestCase() bot: BotAI = get_map_specific_bot(random.choice(MAPS)) @@ -938,7 +943,7 @@ def test_exact_creation_ability(): } unit_types = list(UNIT_UNIT_ALIAS) + list(UNIT_UNIT_ALIAS.values()) + list(UNIT_ABILITIES) + list(ALL_GAS) - unit_types_unique_sorted = sorted(set(t.name for t in unit_types)) + unit_types_unique_sorted = sorted({t.name for t in unit_types}) for unit_type_name in unit_types_unique_sorted: unit_type = UnitTypeId[unit_type_name] if unit_type in ignore_types: @@ -966,7 +971,7 @@ def test_dicts(): try: from sc2.dicts.unit_research_abilities import RESEARCH_INFO except ImportError: - logger.info(f"Import error: dict sc2/dicts/unit_research_abilities.py is missing!") + logger.info("Import error: dict sc2/dicts/unit_research_abilities.py is missing!") return # If on macOS or Linux: skip (fails on several upgrades on github actions) @@ -1005,10 +1010,10 @@ def test_position_pointlike(x1, y1, x2, y2, x3, y3): pos3 = Point2((x3, y3)) epsilon = 1e-3 assert pos1.position == pos1 - dist = ((x2 - x1)**2 + (y2 - y1)**2)**0.5 + dist = ((x2 - x1) ** 2 + (y2 - y1) ** 2) ** 0.5 assert abs(pos1.distance_to(pos2) - dist) <= epsilon assert abs(pos1.distance_to_point2(pos2) - dist) <= epsilon - assert abs(pos1._distance_squared(pos2)**0.5 - dist) <= epsilon + assert abs(pos1._distance_squared(pos2) ** 0.5 - dist) <= epsilon points = {pos2, pos3} points2 = {pos1, pos2, pos3} @@ -1017,20 +1022,20 @@ def test_position_pointlike(x1, y1, x2, y2, x3, y3): assert pos1.sort_by_distance(points2) == sorted(points2, key=lambda p: pos1._distance_squared(p)) assert pos1.closest(points2) == pos1 closest_point = min(points, key=lambda p: p._distance_squared(pos1)) - dist_closest_point = pos1._distance_squared(closest_point)**0.5 + dist_closest_point = pos1._distance_squared(closest_point) ** 0.5 furthest_point = max(points, key=lambda p: p._distance_squared(pos1)) - dist_furthest_point = pos1._distance_squared(furthest_point)**0.5 + dist_furthest_point = pos1._distance_squared(furthest_point) ** 0.5 # Distances between pos1-pos2 and pos1-pos3 might be the same, so the sorting might still be different, that's why I use a set here assert pos1.closest(points) in {p for p in points2 if abs(pos1.distance_to(p) - dist_closest_point) < epsilon} - assert abs(pos1.distance_to_closest(points) - pos1._distance_squared(closest_point)**0.5) < epsilon + assert abs(pos1.distance_to_closest(points) - pos1._distance_squared(closest_point) ** 0.5) < epsilon assert pos1.furthest(points) in {p for p in points2 if abs(pos1.distance_to(p) - dist_furthest_point) < epsilon} - assert abs(pos1.distance_to_furthest(points) - pos1._distance_squared(furthest_point)**0.5) < epsilon + assert abs(pos1.distance_to_furthest(points) - pos1._distance_squared(furthest_point) ** 0.5) < epsilon assert pos1.offset(pos2) == Point2((pos1.x + pos2.x, pos1.y + pos2.y)) if pos1 != pos2: assert pos1.unit_axes_towards(pos2) != Point2((0, 0)) - if 0 < x3: + if x3 > 0: temp_pos = pos1.towards(pos2, x3) if x3 <= pos1.distance_to(pos2): # Using "towards" function to go between pos1 and pos2 @@ -1072,13 +1077,13 @@ def test_position_point2(x1, y1, x2, y2): assert pos1.to2 == pos1 assert pos1.to3 == Point3((x1, y1, 0)) - length1 = (pos1.x**2 + pos1.y**2)**0.5 + length1 = (pos1.x**2 + pos1.y**2) ** 0.5 assert abs(pos1.length - length1) < 0.001 if length1: normalized1 = pos1 / length1 assert abs(pos1.normalized.is_same_as(pos1 / length1)) assert abs(normalized1.length - 1) < 0.001 - length2 = (pos2.x**2 + pos2.y**2)**0.5 + length2 = (pos2.x**2 + pos2.y**2) ** 0.5 assert abs(pos2.length - length2) < 0.001 if length2: normalized2 = pos2 / length2 @@ -1087,7 +1092,7 @@ def test_position_point2(x1, y1, x2, y2): assert isinstance(pos1.distance_to(pos2), float) assert isinstance(pos1.distance_to_point2(pos2), float) - if 0 < x2: + if x2 > 0: assert pos1.random_on_distance(x2) != pos1 assert pos1.towards_with_random_angle(pos2, x2) != pos1 assert pos1.towards_with_random_angle(pos2) != pos1 diff --git a/test/test_pickled_ramp.py b/test/test_pickled_ramp.py index 153308cf..a1294d2a 100644 --- a/test/test_pickled_ramp.py +++ b/test/test_pickled_ramp.py @@ -10,7 +10,6 @@ import time from pathlib import Path -from test.test_pickled_data import MAPS, get_map_specific_bot from loguru import logger @@ -18,6 +17,7 @@ from sc2.position import Point2 from sc2.unit import Unit from sc2.units import Units +from test.test_pickled_data import MAPS, get_map_specific_bot # From https://docs.pytest.org/en/latest/example/parametrize.html#a-quick-port-of-testscenarios @@ -28,7 +28,7 @@ def pytest_generate_tests(metafunc): idlist.append(scenario[0]) items = scenario[1].items() argnames = [x[0] for x in items] - argvalues.append(([x[1] for x in items])) + argvalues.append([x[1] for x in items]) metafunc.parametrize(argnames, argvalues, ids=idlist, scope="class") @@ -38,6 +38,7 @@ class TestClass: 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 @@ -103,18 +104,20 @@ def test_bot_ai(self, map_path: Path): ), f"Too many expansions found: {len(bot.expansion_locations_list)}" # 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 ["StargazersAIE", "Stasis LE"] else 0 assert ( len(bot.expansion_locations_list) % (len(bot.enemy_start_locations) + 1) == expect_even_expansion_count ), f"{bot.expansion_locations_list}" # Test if bot start location is in expansion locations - assert bot.townhalls.random.position in set( - bot.expansion_locations_list + assert ( + bot.townhalls.random.position in set(bot.expansion_locations_list) ), f'This error might occur if you are running the tests locally using command "pytest test/", possibly because you are using an outdated cache.py version, but it should not occur when using docker and uv.\n{bot.townhalls.random.position}, {bot.expansion_locations_list}' # Test if enemy start locations are in expansion locations 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/test_replays.py b/test/test_replays.py index 12212320..f0f7618b 100644 --- a/test/test_replays.py +++ b/test/test_replays.py @@ -3,10 +3,10 @@ from sc2.main import get_replay_version THIS_FOLDER = Path(__file__).parent -REPLAY_PATHS = [path for path in (THIS_FOLDER / 'replays').iterdir() if path.suffix == '.SC2Replay'] +REPLAY_PATHS = [path for path in (THIS_FOLDER / "replays").iterdir() if path.suffix == ".SC2Replay"] def test_get_replay_version(): for replay_path in REPLAY_PATHS: version = get_replay_version(replay_path) - assert version == ('Base86383', '22EAC562CD0C6A31FB2C2C21E3AA3680') + assert version == ("Base86383", "22EAC562CD0C6A31FB2C2C21E3AA3680") diff --git a/test/travis_test_script.py b/test/travis_test_script.py index 028ac0b8..44028820 100644 --- a/test/travis_test_script.py +++ b/test/travis_test_script.py @@ -10,6 +10,7 @@ Or if you want to run from windows: uv run python test/travis_test_script.py test/autotest_bot.py """ + import subprocess import sys import time @@ -48,16 +49,20 @@ # Break as the bot run was successful break + # pyre-ignore[16] if process.returncode is not None: # Reformat the output into a list - logger.info_output: str = result + # 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")], ] 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) logger.info("Travis test script, bot output:\r\n{}\r\nEnd of bot output".format("\r\n".join(output_as_list))) @@ -71,8 +76,8 @@ 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("Returncode: {}".format(process.returncode)) - logger.info("Game took {} real time seconds".format(round(time.time() - t0, 1))) + 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: # This will throw an error even if a bot is called Traceback diff --git a/test/upgradestest_bot.py b/test/upgradestest_bot.py index f7ad21c1..6267866b 100644 --- a/test/upgradestest_bot.py +++ b/test/upgradestest_bot.py @@ -1,8 +1,4 @@ -import os -import sys - -sys.path.append(os.path.join(os.path.dirname(__file__), "..")) -from typing import Dict, List +from __future__ import annotations from loguru import logger @@ -20,7 +16,6 @@ class TestBot(BotAI): - def __init__(self): BotAI.__init__(self) # The time the bot has to complete all tests, here: the number of game seconds @@ -29,7 +24,8 @@ def __init__(self): # Check how many test action functions we have # At least 4 tests because we test properties and variables self.action_tests = [ - getattr(self, f"test_botai_actions{index}") for index in range(4000) + getattr(self, f"test_botai_actions{index}") + for index in range(4000) if hasattr(getattr(self, f"test_botai_actions{index}", 0), "__call__") ] self.tests_done_by_name = set() @@ -63,7 +59,7 @@ async def on_step(self, iteration): # Exit bot if iteration > 100: - logger.info("Tests completed after {} seconds".format(round(self.time, 1))) + logger.info(f"Tests completed after {round(self.time, 1)} seconds") exit(0) async def clean_up_center(self): @@ -88,11 +84,11 @@ async def test_botai_actions1(self): from sc2.dicts.unit_research_abilities import RESEARCH_INFO from sc2.dicts.upgrade_researched_from import UPGRADE_RESEARCHED_FROM - structure_types: List[UnitTypeId] = sorted(set(UPGRADE_RESEARCHED_FROM.values()), key=lambda data: data.name) - upgrade_types: List[UpgradeId] = list(UPGRADE_RESEARCHED_FROM) + structure_types: list[UnitTypeId] = sorted(set(UPGRADE_RESEARCHED_FROM.values()), key=lambda data: data.name) + upgrade_types: list[UpgradeId] = list(UPGRADE_RESEARCHED_FROM) # TODO if *techlab in name -> spawn rax/ fact / starport next to it - addon_structures: Dict[str, UnitTypeId] = { + addon_structures: dict[str, UnitTypeId] = { "BARRACKS": UnitTypeId.BARRACKS, "FACTORY": UnitTypeId.FACTORY, "STARPORT": UnitTypeId.STARPORT, @@ -106,10 +102,9 @@ async def test_botai_actions1(self): if "TECHLAB" in structure_type.name: continue - structure_upgrade_types: Dict[UpgradeId, Dict[str, AbilityId]] = RESEARCH_INFO[structure_type] - data: Dict[str, AbilityId] + 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) @@ -128,7 +123,7 @@ async def test_botai_actions1(self): continue # Spawn structure and requirements - spawn_structures: List[UnitTypeId] = [] + spawn_structures: list[UnitTypeId] = [] if requires_power: spawn_structures.append(UnitTypeId.PYLON) spawn_structures.append(structure_type) @@ -153,7 +148,7 @@ async def test_botai_actions1(self): await self._advance_steps(2) # Research upgrade - assert upgrade_id in upgrade_types, f"Given upgrade is not in the list of upgrade types" + assert upgrade_id in upgrade_types, "Given upgrade is not in the list of upgrade types" assert self.structures(structure_type), f"Structure {structure_type} has not been spawned in time" # Try to research the upgrade @@ -174,12 +169,12 @@ async def test_botai_actions1(self): class EmptyBot(BotAI): - async def on_start(self): if self.units: 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 22213fb0..8d2dc6c1 100644 --- a/uv.lock +++ b/uv.lock @@ -240,6 +240,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bf/9b/08c0432272d77b04803958a4598a51e2a4b51c06640af8b8f0f908c18bf2/charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079", size = 49446 }, ] +[[package]] +name = "click" +version = "8.1.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "platform_system == 'Windows'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941 }, +] + [[package]] name = "colorama" version = "0.4.6" @@ -369,6 +381,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321 }, ] +[[package]] +name = "dataclasses-json" +version = "0.5.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "marshmallow" }, + { name = "marshmallow-enum" }, + { name = "typing-inspect" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/85/94/1b30216f84c48b9e0646833f6f2dd75f1169cc04dc45c48fe39e644c89d5/dataclasses-json-0.5.7.tar.gz", hash = "sha256:c2c11bc8214fbf709ffc369d11446ff6945254a7f09128154a7620613d8fda90", size = 30958 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/7e/2042610dfc8121e8119ad8b94db496d8697e4b0ef7a6e378018a2bd84435/dataclasses_json-0.5.7-py3-none-any.whl", hash = "sha256:bc285b5f892094c3a53d558858a88553dd6a61a11ab1a8128a0e554385dcc5dd", size = 25647 }, +] + [[package]] name = "dill" version = "0.3.9" @@ -703,6 +729,49 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8c/95/4a103776c265d13b3d2cd24fb0494d4e04ea435a8ef97e1b2c026d43250b/kiwisolver-1.4.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0c6c43471bc764fad4bc99c5c2d6d16a676b1abf844ca7c8702bdae92df01ee0", size = 55811 }, ] +[[package]] +name = "libcst" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/27/a6/a19b587108b15d3e0bfa8d0944265809581c8b8e161e22c9c9060afbbf4a/libcst-1.5.1.tar.gz", hash = "sha256:71cb294db84df9e410208009c732628e920111683c2f2b2e0c5b71b98464f365", size = 773387 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/46/468a892cdc218272925c3fc4b3ae81cd81f24eabe29a35ba5d017ee35ee1/libcst-1.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ab83633e61ee91df575a3838b1e73c371f19d4916bf1816554933235553d41ea", size = 2124113 }, + { url = "https://files.pythonhosted.org/packages/8c/b7/b8e7b24629b32e4ba4822e3291c19dc63f2f95fea40230e630ec8df0d3f1/libcst-1.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b58a49895d95ec1fd34fad041a142d98edf9b51fcaf632337c13befeb4d51c7c", size = 2032570 }, + { url = "https://files.pythonhosted.org/packages/d3/db/1e064189f75bc68091fa4fe5b0b062493384544e47d8d50520d00d7bfe1c/libcst-1.5.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d9ec764aa781ef35ab96b693569ac3dced16df9feb40ee6c274d13e86a1472e", size = 2173960 }, + { url = "https://files.pythonhosted.org/packages/02/86/b03471cae3e8372e8e5350f90645136106bc9780d87bb46939dc68c938b5/libcst-1.5.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99bbffd8596d192bc0e844a4cf3c4fc696979d4e20ab1c0774a01768a59b47ed", size = 2264452 }, + { url = "https://files.pythonhosted.org/packages/3b/66/729dcfbf82d64646f11b3875270177ad35057fe1908bc29366a6d530dddb/libcst-1.5.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec6ee607cfe4cc4cc93e56e0188fdb9e50399d61a1262d58229752946f288f5e", size = 2341370 }, + { url = "https://files.pythonhosted.org/packages/db/23/177ca265dcaf2af4665ca359dd9967f9000dc74fc78fd3b6a231301ab972/libcst-1.5.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:72132756f985a19ef64d702a821099d4afc3544974662772b44cbc55b7279727", size = 2219726 }, + { url = "https://files.pythonhosted.org/packages/48/b9/2b292403ea5343143dfb93ad04da17752db3c77e7796e1f5eee00247b2c3/libcst-1.5.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:40b75bf2d70fc0bc26b1fa73e61bdc46fef59f5c71aedf16128e7c33db8d5e40", size = 2325121 }, + { url = "https://files.pythonhosted.org/packages/f6/57/1d6ee6d1456baa856fe33c07e3f6b76219ba0af7afe51a85b0b016e4d18c/libcst-1.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:56c944acaa781b8e586df3019374f5cf117054d7fc98f85be1ba84fe810005dc", size = 2031807 }, + { url = "https://files.pythonhosted.org/packages/14/c1/83f7ff3a225ad09527b8d15b410e1bba168bafe0d134d93645b1d8b69859/libcst-1.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:db7711a762b0327b581be5a963908fecd74412bdda34db34553faa521563c22d", size = 2123894 }, + { url = "https://files.pythonhosted.org/packages/5b/70/7b765a0a8db8084703fe408ed1c583c434e99b8ec3e7c6192732a1959eb8/libcst-1.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aa524bd012aaae1f485fd44490ef5abf708b14d2addc0f06b28de3e4585c4b9e", size = 2032548 }, + { url = "https://files.pythonhosted.org/packages/3c/01/d4111674d3cfe817c12ef79f8d39b2058a3bd8cd01a307a7db62118cd0ed/libcst-1.5.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3ffb8135c09e41e8cf710b152c33e9b7f1d0d0b9f242bae0c502eb082fdb1fb", size = 2173948 }, + { url = "https://files.pythonhosted.org/packages/4e/3b/0e7698e7715d2ed44512718dd6f45d5d698498b5c9fa906b4028a369a7f6/libcst-1.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76a8ac7a84f9b6f678a668bff85b360e0a93fa8d7f25a74a206a28110734bb2a", size = 2264422 }, + { url = "https://files.pythonhosted.org/packages/0d/c4/a76444a28015fb7327cfdbde7d3f88f633e88fce2fe910c7aaa7d4780422/libcst-1.5.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:89c808bdb5fa9ca02df41dd234cbb0e9de0d2e0c029c7063d5435a9f6781cc10", size = 2341569 }, + { url = "https://files.pythonhosted.org/packages/54/1c/3f116e3baa47f71929467b404643c09e31af7acb77de8d2b3fe5d1b06212/libcst-1.5.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40fbbaa8b839bfbfa5b300623ca2b6b0768b58bbc31b341afbc99110c9bee232", size = 2219836 }, + { url = "https://files.pythonhosted.org/packages/ea/f7/746b6d91125cf1f398889d1b4488b10cc3df6b35d9762c2131294a1e8217/libcst-1.5.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c7021e3904d8d088c369afc3fe17c279883e583415ef07edacadba76cfbecd27", size = 2325108 }, + { url = "https://files.pythonhosted.org/packages/fc/82/260932412cd9d6c1ac60283889adc18c21ffc55c8b5b63309b95bc277f76/libcst-1.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:f053a5deb6a214972dbe9fa26ecd8255edb903de084a3d7715bf9e9da8821c50", size = 2031804 }, + { url = "https://files.pythonhosted.org/packages/8f/0c/eac92358d05e75516f15654fb1550c9af165ce5a19f2b8adf44916ebebc4/libcst-1.5.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:666813950b8637af0c0e96b1ca46f5d5f183d2fe50bbac2186f5b283a99f3529", size = 2122234 }, + { url = "https://files.pythonhosted.org/packages/b3/26/6925af831f039e27eb380ba64448f33aea255ab6ecae6b5deec6ec637197/libcst-1.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b7b58b36022ae77a5a00002854043ae95c03e92f6062ad08473eff326f32efa0", size = 2031324 }, + { url = "https://files.pythonhosted.org/packages/e0/87/1b593bdddcb0d38d2232dab96b1f92deb2481c72063394f0394f680ff5b3/libcst-1.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eeb13d7c598fe9a798a1d22eae56ab3d3d599b38b83436039bd6ae229fc854d7", size = 2172432 }, + { url = "https://files.pythonhosted.org/packages/88/27/966f9fe2652aa496a85503333559937e58979eef674f9803c995d6704c44/libcst-1.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5987daff8389b0df60b5c20499ff4fb73fc03cb3ae1f6a746eefd204ed08df85", size = 2263445 }, + { url = "https://files.pythonhosted.org/packages/ff/79/f172226edbdd5b3a31d3c270e4407b35e3f5b0c6e404967e42314f1b434e/libcst-1.5.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00f3d2f32ee081bad3394546b0b9ac5e31686d3b5cfe4892d716d2ba65f9ec08", size = 2343044 }, + { url = "https://files.pythonhosted.org/packages/91/f2/664ae80583c66bcc3a2debcc8bab04e6843c3a6ac02e94050dddb5e5909c/libcst-1.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1ff21005c33b634957a98db438e882522febf1cacc62fa716f29e163a3f5871a", size = 2217129 }, + { url = "https://files.pythonhosted.org/packages/8b/df/b6b506d50f0a00a49d4e6217fd521c208cbf8693687cd0ac5880507ca6d1/libcst-1.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:15697ea9f1edbb9a263364d966c72abda07195d1c1a6838eb79af057f1040770", size = 2322129 }, + { url = "https://files.pythonhosted.org/packages/eb/84/9c79a0aa5334f39a86844d32ef474491a817e9eefaa8f23fc81e7ad07d8b/libcst-1.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:cedd4c8336e01c51913113fbf5566b8f61a86d90f3d5cc5b1cb5049575622c5f", size = 2032278 }, + { url = "https://files.pythonhosted.org/packages/dd/ab/8845c34f8378696589327a8666cec5cd7294f50d03987468743eaa051429/libcst-1.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1cc7393aaac733e963f0ee00466d059db74a38e15fc7e6a46dddd128c5be8d08", size = 2123796 }, + { url = "https://files.pythonhosted.org/packages/53/8f/8e4d97fe2912767c5e648c3bc72c6347bebd7656b8e8737cc943fddc044e/libcst-1.5.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bbaf5755be50fa9b35a3d553d1e62293fbb2ee5ce2c16c7e7ffeb2746af1ab88", size = 2032463 }, + { url = "https://files.pythonhosted.org/packages/15/a9/501bf05edfd39e42450a0e6863f5d197297b5c2fe7007db13a5b761a39d2/libcst-1.5.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e397f5b6c0fc271acea44579f154b0f3ab36011050f6db75ab00cef47441946", size = 2173905 }, + { url = "https://files.pythonhosted.org/packages/41/f9/e62f1d3073061a6807c2e3ee7d3fd77b5fa07a2df7fd50511b826d5f522d/libcst-1.5.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1947790a4fd7d96bcc200a6ecaa528045fcb26a34a24030d5859c7983662289e", size = 2264403 }, + { url = "https://files.pythonhosted.org/packages/11/a8/3fddee4a12cd41c5f78fef983763ad3a6539c5b4ca3e805b6583605f9c53/libcst-1.5.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:697eabe9f5ffc40f76d6d02e693274e0a382826d0cf8183bd44e7407dfb0ab90", size = 2341287 }, + { url = "https://files.pythonhosted.org/packages/3c/57/8d5b0fb35966387ae750520225d3a708373bc26ccefb100d8b49aed656cf/libcst-1.5.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dc06b7c60d086ef1832aebfd31b64c3c8a645adf0c5638d6243e5838f6a9356e", size = 2219711 }, + { url = "https://files.pythonhosted.org/packages/18/99/e20d1ceeb910a7a6c19ccadb63d296066baffe7ef912883c03c3da0a1cb6/libcst-1.5.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:19e39cfef4316599ca20d1c821490aeb783b52e8a8543a824972a525322a85d0", size = 2324849 }, + { url = "https://files.pythonhosted.org/packages/c2/e3/f57a014ec44c11c6e142e612875b093fdeeb7e1462ed96d25ffc83964155/libcst-1.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:01e01c04f0641188160d3b99c6526436e93a3fbf9783dba970f9885a77ec9b38", size = 2031830 }, +] + [[package]] name = "loguru" version = "0.7.3" @@ -776,6 +845,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b3/73/085399401383ce949f727afec55ec3abd76648d04b9f22e1c0e99cb4bec3/MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a", size = 15506 }, ] +[[package]] +name = "marshmallow" +version = "3.23.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/30/14d8609f65c8aeddddd3181c06d2c9582da6278f063b27c910bbf9903441/marshmallow-3.23.1.tar.gz", hash = "sha256:3a8dfda6edd8dcdbf216c0ede1d1e78d230a6dc9c5a088f58c4083b974a0d468", size = 177488 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ac/a7/a78ff54e67ef92a3d12126b98eb98ab8abab3de4a8c46d240c87e514d6bb/marshmallow-3.23.1-py3-none-any.whl", hash = "sha256:fece2eb2c941180ea1b7fcbd4a83c51bfdd50093fdd3ad2585ee5e1df2508491", size = 49488 }, +] + +[[package]] +name = "marshmallow-enum" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "marshmallow" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8e/8c/ceecdce57dfd37913143087fffd15f38562a94f0d22823e3c66eac0dca31/marshmallow-enum-1.5.1.tar.gz", hash = "sha256:38e697e11f45a8e64b4a1e664000897c659b60aa57bfa18d44e226a9920b6e58", size = 4013 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/59/ef3a3dc499be447098d4a89399beb869f813fee1b5a57d5d79dee2c1bf51/marshmallow_enum-1.5.1-py2.py3-none-any.whl", hash = "sha256:57161ab3dbfde4f57adeb12090f39592e992b9c86d206d02f6bd03ebec60f072", size = 4186 }, +] + [[package]] name = "matplotlib" version = "3.9.4" @@ -1295,6 +1388,40 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/be/ec/2eb3cd785efd67806c46c13a17339708ddc346cbb684eade7a6e6f79536a/pyparsing-3.2.0-py3-none-any.whl", hash = "sha256:93d9577b88da0bbea8cc8334ee8b918ed014968fd2ec383e868fb8afb1ccef84", size = 106921 }, ] +[[package]] +name = "pyre-check" +version = "0.9.23" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "dataclasses-json" }, + { name = "libcst" }, + { name = "psutil" }, + { name = "pyre-extensions" }, + { name = "tabulate" }, + { name = "testslide" }, + { name = "typing-extensions" }, + { name = "typing-inspect" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a7/35/dd3da9b3d1798067e72e52824b67eaea405eae65a5de4b3a0a0dd2fbee9a/pyre-check-0.9.23.tar.gz", hash = "sha256:3f4baf99145e06af416a2444e50b9e90b183585c053ab476004729ed9ba6902c", size = 22706217 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/aa/6e93595136eeb35fd30da0e6fc445364d0cda64c298caa619f7cc6327b8d/pyre_check-0.9.23-py3-none-macosx_10_11_x86_64.whl", hash = "sha256:71ae076a75293a6fbb9025c3aa1e7a81a4dfd7a6da8a884f4c39deed2e4e3f3a", size = 23432827 }, + { url = "https://files.pythonhosted.org/packages/b2/61/7d8793cdcd3ecf64452b6b926e4bcdb5e32f0d15624131414566badb7936/pyre_check-0.9.23-py3-none-manylinux1_x86_64.whl", hash = "sha256:6362f0d8af2d513c90fc863a142009d8d7cbf0aa762ec37cad194684bd962ae5", size = 47944875 }, +] + +[[package]] +name = "pyre-extensions" +version = "0.0.32" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, + { name = "typing-inspect" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a7/53/5bc2532536e921c48366ad1047c1344ccef6afa5e84053f0f6e20a453767/pyre_extensions-0.0.32.tar.gz", hash = "sha256:5396715f14ea56c4d5fd0a88c57ca7e44faa468f905909edd7de4ad90ed85e55", size = 10852 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/7a/9812cb8be9828ab688203c5ac5f743c60652887f0c00995a6f6f19f912bd/pyre_extensions-0.0.32-py3-none-any.whl", hash = "sha256:a63ba6883ab02f4b1a9f372ed4eb4a2f4c6f3d74879aa2725186fdfcfe3e5c68", size = 12766 }, +] + [[package]] name = "pytest" version = "8.3.4" @@ -1434,6 +1561,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, ] +[[package]] +name = "ruff" +version = "0.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bf/5e/683c7ef7a696923223e7d95ca06755d6e2acbc5fd8382b2912a28008137c/ruff-0.8.3.tar.gz", hash = "sha256:5e7558304353b84279042fc584a4f4cb8a07ae79b2bf3da1a7551d960b5626d3", size = 3378522 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/c4/bfdbb8b9c419ff3b52479af8581026eeaac3764946fdb463dec043441b7d/ruff-0.8.3-py3-none-linux_armv6l.whl", hash = "sha256:8d5d273ffffff0acd3db5bf626d4b131aa5a5ada1276126231c4174543ce20d6", size = 10535860 }, + { url = "https://files.pythonhosted.org/packages/ef/c5/0aabdc9314b4b6f051168ac45227e2aa8e1c6d82718a547455e40c9c9faa/ruff-0.8.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e4d66a21de39f15c9757d00c50c8cdd20ac84f55684ca56def7891a025d7e939", size = 10346327 }, + { url = "https://files.pythonhosted.org/packages/1a/78/4843a59e7e7b398d6019cf91ab06502fd95397b99b2b858798fbab9151f5/ruff-0.8.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c356e770811858bd20832af696ff6c7e884701115094f427b64b25093d6d932d", size = 9942585 }, + { url = "https://files.pythonhosted.org/packages/91/5a/642ed8f1ba23ffc2dd347697e01eef3c42fad6ac76603be4a8c3a9d6311e/ruff-0.8.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c0a60a825e3e177116c84009d5ebaa90cf40dfab56e1358d1df4e29a9a14b13", size = 10797597 }, + { url = "https://files.pythonhosted.org/packages/30/25/2e654bc7226da09a49730a1a2ea6e89f843b362db80b4b2a7a4f948ac986/ruff-0.8.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:75fb782f4db39501210ac093c79c3de581d306624575eddd7e4e13747e61ba18", size = 10307244 }, + { url = "https://files.pythonhosted.org/packages/c0/2d/a224d56bcd4383583db53c2b8f410ebf1200866984aa6eb9b5a70f04e71f/ruff-0.8.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f26bc76a133ecb09a38b7868737eded6941b70a6d34ef53a4027e83913b6502", size = 11362439 }, + { url = "https://files.pythonhosted.org/packages/82/01/03e2857f9c371b8767d3e909f06a33bbdac880df17f17f93d6f6951c3381/ruff-0.8.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:01b14b2f72a37390c1b13477c1c02d53184f728be2f3ffc3ace5b44e9e87b90d", size = 12078538 }, + { url = "https://files.pythonhosted.org/packages/af/ae/ff7f97b355da16d748ceec50e1604a8215d3659b36b38025a922e0612e9b/ruff-0.8.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:53babd6e63e31f4e96ec95ea0d962298f9f0d9cc5990a1bbb023a6baf2503a82", size = 11616172 }, + { url = "https://files.pythonhosted.org/packages/6a/d0/6156d4d1e53ebd17747049afe801c5d7e3014d9b2f398b9236fe36ba4320/ruff-0.8.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ae441ce4cf925b7f363d33cd6570c51435972d697e3e58928973994e56e1452", size = 12919886 }, + { url = "https://files.pythonhosted.org/packages/4e/84/affcb30bacb94f6036a128ad5de0e29f543d3f67ee42b490b17d68e44b8a/ruff-0.8.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7c65bc0cadce32255e93c57d57ecc2cca23149edd52714c0c5d6fa11ec328cd", size = 11212599 }, + { url = "https://files.pythonhosted.org/packages/60/b9/5694716bdefd8f73df7c0104334156c38fb0f77673d2966a5a1345bab94d/ruff-0.8.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5be450bb18f23f0edc5a4e5585c17a56ba88920d598f04a06bd9fd76d324cb20", size = 10784637 }, + { url = "https://files.pythonhosted.org/packages/24/7e/0e8f835103ac7da81c3663eedf79dec8359e9ae9a3b0d704bae50be59176/ruff-0.8.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8faeae3827eaa77f5721f09b9472a18c749139c891dbc17f45e72d8f2ca1f8fc", size = 10390591 }, + { url = "https://files.pythonhosted.org/packages/27/da/180ec771fc01c004045962ce017ca419a0281f4bfaf867ed0020f555b56e/ruff-0.8.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:db503486e1cf074b9808403991663e4277f5c664d3fe237ee0d994d1305bb060", size = 10894298 }, + { url = "https://files.pythonhosted.org/packages/6d/f8/29f241742ed3954eb2222314b02db29f531a15cab3238d1295e8657c5f18/ruff-0.8.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:6567be9fb62fbd7a099209257fef4ad2c3153b60579818b31a23c886ed4147ea", size = 11275965 }, + { url = "https://files.pythonhosted.org/packages/79/e9/5b81dc9afc8a80884405b230b9429efeef76d04caead904bd213f453b973/ruff-0.8.3-py3-none-win32.whl", hash = "sha256:19048f2f878f3ee4583fc6cb23fb636e48c2635e30fb2022b3a1cd293402f964", size = 8807651 }, + { url = "https://files.pythonhosted.org/packages/ea/67/7291461066007617b59a707887b90e319b6a043c79b4d19979f86b7a20e7/ruff-0.8.3-py3-none-win_amd64.whl", hash = "sha256:f7df94f57d7418fa7c3ffb650757e0c2b96cf2501a0b192c18e4fb5571dfada9", size = 9625289 }, + { url = "https://files.pythonhosted.org/packages/03/8f/e4fa95288b81233356d9a9dcaed057e5b0adc6399aa8fd0f6d784041c9c3/ruff-0.8.3-py3-none-win_arm64.whl", hash = "sha256:fe2756edf68ea79707c8d68b78ca9a58ed9af22e430430491ee03e718b5e4936", size = 9078754 }, +] + [[package]] name = "s2clientprotocol" version = "5.0.14.93333.0" @@ -1471,11 +1623,13 @@ dev = [ { name = "pre-commit" }, { name = "pyglet" }, { name = "pylint" }, + { name = "pyre-check" }, { name = "pytest" }, { name = "pytest-asyncio" }, { name = "pytest-benchmark" }, { name = "pytest-cov" }, { name = "radon" }, + { name = "ruff" }, { name = "sphinx" }, { name = "sphinx-autodoc-typehints" }, { name = "sphinx-rtd-theme" }, @@ -1507,11 +1661,13 @@ dev = [ { name = "pre-commit", specifier = ">=4.0.1" }, { name = "pyglet", specifier = ">=2.0.20" }, { name = "pylint", specifier = ">=3.3.2" }, + { name = "pyre-check", specifier = ">=0.9.23" }, { name = "pytest", specifier = ">=8.3.4" }, { name = "pytest-asyncio", specifier = ">=0.25.0" }, { name = "pytest-benchmark", specifier = ">=5.1.0" }, { name = "pytest-cov", specifier = ">=6.0.0" }, { name = "radon", specifier = ">=6.0.1" }, + { name = "ruff", specifier = ">=0.8.3" }, { name = "sphinx", specifier = ">=7.4.7" }, { name = "sphinx-autodoc-typehints", specifier = ">=2.3.0" }, { name = "sphinx-rtd-theme", specifier = ">=3.0.2" }, @@ -1744,6 +1900,26 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072 }, ] +[[package]] +name = "tabulate" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/fe/802052aecb21e3797b8f7902564ab6ea0d60ff8ca23952079064155d1ae1/tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c", size = 81090 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252 }, +] + +[[package]] +name = "testslide" +version = "2.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "psutil" }, + { name = "pygments" }, + { name = "typeguard" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ee/6f/c8d6d60a597c693559dab3b3362bd01e2212530e9a163eb0164af81e1ec1/TestSlide-2.7.1.tar.gz", hash = "sha256:d25890d5c383f673fac44a5f9e2561b7118d04f29f2c2b3d4f549e6db94cb34d", size = 50255 } + [[package]] name = "toml" version = "0.10.2" @@ -1791,6 +1967,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f9/b6/a447b5e4ec71e13871be01ba81f5dfc9d0af7e473da256ff46bc0e24026f/tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde", size = 37955 }, ] +[[package]] +name = "typeguard" +version = "2.13.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/38/c61bfcf62a7b572b5e9363a802ff92559cb427ee963048e1442e3aef7490/typeguard-2.13.3.tar.gz", hash = "sha256:00edaa8da3a133674796cf5ea87d9f4b4c367d77476e185e80251cc13dfbb8c4", size = 40604 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/bb/d43e5c75054e53efce310e79d63df0ac3f25e34c926be5dffb7d283fb2a8/typeguard-2.13.3-py3-none-any.whl", hash = "sha256:5e3e3be01e887e7eafae5af63d1f36c849aaa94e3a0112097312aabfa16284f1", size = 17605 }, +] + [[package]] name = "typing-extensions" version = "4.12.2" @@ -1800,6 +1985,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, ] +[[package]] +name = "typing-inspect" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy-extensions" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/74/1789779d91f1961fa9438e9a8710cdae6bd138c80d7303996933d117264a/typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78", size = 13825 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/f3/107a22063bf27bdccf2024833d3445f4eea42b2e598abfbd46f6a63b6cb0/typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f", size = 8827 }, +] + [[package]] name = "urllib3" version = "2.2.3"