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..060a2711 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: poetry run ruff format . --check --diff + pass_filenames: false + + - id: ruff-lint + name: Lint files + stages: [push] + language: system + entry: poetry run ruff check . + pass_filenames: false + + - id: pyre + name: Static types checking with pyre + stages: [push] + language: system + entry: poetry run pyre + pass_filenames: false diff --git a/.pyre_configuration b/.pyre_configuration new file mode 100644 index 00000000..7c512daa --- /dev/null +++ b/.pyre_configuration @@ -0,0 +1,6 @@ +{ + "site_package_search_strategy": "pep561", + "source_directories": [ + "." + ] +} 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/examples/arcade_bot.py b/examples/arcade_bot.py index 860e7ec8..56bbad60 100644 --- a/examples/arcade_bot.py +++ b/examples/arcade_bot.py @@ -37,7 +37,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 +44,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 +54,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) @@ -105,7 +103,8 @@ def position_around_unit( 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 diff --git a/examples/competitive/__init__.py b/examples/competitive/__init__.py index 34aa07e2..51b77b5a 100644 --- a/examples/competitive/__init__.py +++ b/examples/competitive/__init__.py @@ -7,7 +7,7 @@ import sc2 from sc2.client import Client -from sc2.protocol import ConnectionAlreadyClosed +from sc2.protocol import ConnectionAlreadyClosedError # Run ladder game @@ -26,10 +26,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 +65,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..01fda4e6 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 diff --git a/examples/distributed_workers.py b/examples/distributed_workers.py index 724cc392..cf88bf8f 100644 --- a/examples/distributed_workers.py +++ b/examples/distributed_workers.py @@ -7,7 +7,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..21cdf38c 100644 --- a/examples/protoss/cannon_rush.py +++ b/examples/protoss/cannon_rush.py @@ -9,7 +9,6 @@ class CannonRushBot(BotAI): - # pylint: disable=R0912 async def on_step(self, iteration): if iteration == 0: @@ -64,8 +63,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..d4941de3 100644 --- a/examples/protoss/find_adept_shades.py +++ b/examples/protoss/find_adept_shades.py @@ -12,7 +12,6 @@ # pylint: disable=W0231 class FindAdeptShadesBot(BotAI): - def __init__(self): self.shaded = False self.shades_mapping = {} @@ -60,8 +59,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..14e974eb 100644 --- a/examples/protoss/threebase_voidray.py +++ b/examples/protoss/threebase_voidray.py @@ -9,7 +9,6 @@ class ThreebaseVoidrayBot(BotAI): - # pylint: disable=R0912 async def on_step(self, iteration): target_base_count = 3 @@ -54,8 +53,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 +111,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 +127,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..ae839e68 100644 --- a/examples/protoss/warpgate_push.py +++ b/examples/protoss/warpgate_push.py @@ -13,7 +13,6 @@ # pylint: disable=W0231 class WarpGateBot(BotAI): - def __init__(self): # Initialize inherited class self.proxy_built = False @@ -92,7 +91,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 +118,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..63c9602d 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,7 +22,7 @@ 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 @@ -58,7 +57,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..e6dc89b9 100644 --- a/examples/terran/mass_reaper.py +++ b/examples/terran/mass_reaper.py @@ -23,7 +23,6 @@ # 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 @@ -39,8 +38,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 +70,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 +91,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 +116,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 +130,14 @@ 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 + # 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,15 +159,15 @@ 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)} if retreat_points: @@ -180,12 +188,14 @@ async def on_step(self, iteration): # 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,8 +218,9 @@ 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)} if retreat_points: @@ -288,8 +299,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 +321,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 +352,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 +363,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..c673b11e 100644 --- a/examples/terran/onebase_battlecruiser.py +++ b/examples/terran/onebase_battlecruiser.py @@ -13,9 +13,8 @@ class BCRushBot(BotAI): - def select_target(self) -> Tuple[Point2, bool]: - """ Select an enemy target the units should attack. """ + """Select an enemy target the units should attack.""" targets: Units = self.enemy_structures if targets: return targets.random.position, True @@ -24,7 +23,7 @@ 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: return self.enemy_start_locations[0].position, False return self.mineral_field.random.position, False @@ -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( @@ -112,7 +112,7 @@ async def on_step(self, iteration): ) 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. """ + """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. """ + """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) @@ -149,8 +151,8 @@ def starport_land_positions(sp_position: Point2) -> List[Point2]: for target_land_position in possible_land_positions: land_and_addon_points: List[Point2] = starport_land_positions(target_land_position) if all( - self.in_map_bounds(land_pos) and self.in_placement_grid(land_pos) - and self.in_pathing_grid(land_pos) for land_pos in land_and_addon_points + 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..46805ece 100644 --- a/examples/terran/proxy_rax.py +++ b/examples/terran/proxy_rax.py @@ -10,7 +10,6 @@ class ProxyRaxBot(BotAI): - async def on_start(self): self.client.game_step = 2 @@ -45,8 +44,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..181b2193 100644 --- a/examples/terran/ramp_wall.py +++ b/examples/terran/ramp_wall.py @@ -17,7 +17,6 @@ class RampWallBot(BotAI): - # pylint: disable=W0231 def __init__(self): self.unit_command_uses_self_do = False @@ -87,8 +86,7 @@ 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 + d for d in depot_placement_positions if depots.closest_distance_to(d) > 1 } # Build depots @@ -248,7 +246,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..f4490aa5 100644 --- a/examples/worker_stack_bot.py +++ b/examples/worker_stack_bot.py @@ -29,7 +29,6 @@ # 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]] = {} @@ -44,10 +43,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: @@ -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..6567f3d7 100644 --- a/examples/zerg/banes_banes_banes.py +++ b/examples/zerg/banes_banes_banes.py @@ -28,9 +28,7 @@ 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 +43,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 +54,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 +69,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 +83,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 +93,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..b9f523a3 100644 --- a/examples/zerg/expand_everywhere.py +++ b/examples/zerg/expand_everywhere.py @@ -13,7 +13,6 @@ class ExpandEverywhere(BotAI): - async def on_start(self): self.client.game_step = 50 await self.client.debug_show_map() @@ -21,7 +20,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 +30,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) @@ -61,7 +62,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 +71,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..d470ba39 100644 --- a/examples/zerg/hydralisk_push.py +++ b/examples/zerg/hydralisk_push.py @@ -14,7 +14,6 @@ class Hydralisk(BotAI): - def select_target(self) -> Point2: if self.enemy_structures: return random.choice(self.enemy_structures).position @@ -39,11 +38,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..69a9c554 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,7 +14,6 @@ class BroodlordBot(BotAI): - def select_target(self) -> Point2: if self.enemy_structures: return random.choice(self.enemy_structures).position 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..5c4f22e7 100644 --- a/examples/zerg/zerg_rush.py +++ b/examples/zerg/zerg_rush.py @@ -17,7 +17,6 @@ # pylint: disable=W0231 class ZergRushBot(BotAI): - def __init__(self): self.on_end_called = False @@ -63,8 +62,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 +75,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 +102,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) diff --git a/generate_dicts_from_data_json.py b/generate_dicts_from_data_json.py index c64a27d3..b6743255 100644 --- a/generate_dicts_from_data_json.py +++ b/generate_dicts_from_data_json.py @@ -11,7 +11,6 @@ """ import json import lzma -import os import pickle from collections import OrderedDict from pathlib import Path @@ -32,19 +31,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()" @@ -115,7 +112,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, @@ -258,7 +257,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: @@ -459,7 +457,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) @@ -517,7 +515,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) @@ -533,8 +531,7 @@ def main(): 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, diff --git a/poetry.lock b/poetry.lock index cd18cd43..f1234bd4 100644 --- a/poetry.lock +++ b/poetry.lock @@ -133,25 +133,6 @@ files = [ {file = "alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65"}, ] -[[package]] -name = "astroid" -version = "2.15.8" -description = "An abstract syntax tree for Python with inference support." -optional = false -python-versions = ">=3.7.2" -files = [ - {file = "astroid-2.15.8-py3-none-any.whl", hash = "sha256:1aa149fc5c6589e3d0ece885b4491acd80af4f087baafa3fb5203b113e68cd3c"}, - {file = "astroid-2.15.8.tar.gz", hash = "sha256:6c107453dffee9055899705de3c9ead36e74119cee151e5a9aaf7f0b0e020a6a"}, -] - -[package.dependencies] -lazy-object-proxy = ">=1.4.0" -typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} -wrapt = [ - {version = ">=1.11,<2", markers = "python_version < \"3.11\""}, - {version = ">=1.14,<2", markers = "python_version >= \"3.11\""}, -] - [[package]] name = "async-timeout" version = "4.0.3" @@ -317,6 +298,20 @@ files = [ {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, ] +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + [[package]] name = "colorama" version = "0.4.6" @@ -474,19 +469,23 @@ docs = ["ipython", "matplotlib", "numpydoc", "sphinx"] tests = ["pytest", "pytest-cov", "pytest-xdist"] [[package]] -name = "dill" -version = "0.3.8" -description = "serialize all of Python" +name = "dataclasses-json" +version = "0.5.7" +description = "Easily serialize dataclasses to and from JSON" optional = false -python-versions = ">=3.8" +python-versions = ">=3.6" files = [ - {file = "dill-0.3.8-py3-none-any.whl", hash = "sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7"}, - {file = "dill-0.3.8.tar.gz", hash = "sha256:3ebe3c479ad625c4553aca177444d89b486b1d84982eeacded644afc0cf797ca"}, + {file = "dataclasses-json-0.5.7.tar.gz", hash = "sha256:c2c11bc8214fbf709ffc369d11446ff6945254a7f09128154a7620613d8fda90"}, + {file = "dataclasses_json-0.5.7-py3-none-any.whl", hash = "sha256:bc285b5f892094c3a53d558858a88553dd6a61a11ab1a8128a0e554385dcc5dd"}, ] +[package.dependencies] +marshmallow = ">=3.3.0,<4.0.0" +marshmallow-enum = ">=1.5.1,<2.0.0" +typing-inspect = ">=0.4.0" + [package.extras] -graph = ["objgraph (>=1.7.2)"] -profile = ["gprof2dot (>=2022.7.29)"] +dev = ["flake8", "hypothesis", "ipython", "mypy (>=0.710)", "portray", "pytest (>=6.2.3)", "simplejson", "types-dataclasses"] [[package]] name = "distlib" @@ -820,18 +819,17 @@ files = [ ] [[package]] -name = "isort" -version = "5.13.2" -description = "A Python utility / library to sort Python imports." +name = "intervaltree" +version = "3.1.0" +description = "Editable interval tree data structure for Python 2 and 3" optional = false -python-versions = ">=3.8.0" +python-versions = "*" files = [ - {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, - {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, + {file = "intervaltree-3.1.0.tar.gz", hash = "sha256:902b1b88936918f9b2a19e0e5eb7ccb430ae45cde4f39ea4b36932920d33952d"}, ] -[package.extras] -colors = ["colorama (>=0.4.6)"] +[package.dependencies] +sortedcontainers = ">=2.0,<3.0" [[package]] name = "jinja2" @@ -964,51 +962,45 @@ files = [ ] [[package]] -name = "lazy-object-proxy" -version = "1.10.0" -description = "A fast and thorough lazy object proxy." +name = "libcst" +version = "1.4.0" +description = "A concrete syntax tree with AST-like properties for Python 3.0 through 3.12 programs." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "lazy-object-proxy-1.10.0.tar.gz", hash = "sha256:78247b6d45f43a52ef35c25b5581459e85117225408a4128a3daf8bf9648ac69"}, - {file = "lazy_object_proxy-1.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:855e068b0358ab916454464a884779c7ffa312b8925c6f7401e952dcf3b89977"}, - {file = "lazy_object_proxy-1.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab7004cf2e59f7c2e4345604a3e6ea0d92ac44e1c2375527d56492014e690c3"}, - {file = "lazy_object_proxy-1.10.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc0d2fc424e54c70c4bc06787e4072c4f3b1aa2f897dfdc34ce1013cf3ceef05"}, - {file = "lazy_object_proxy-1.10.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e2adb09778797da09d2b5ebdbceebf7dd32e2c96f79da9052b2e87b6ea495895"}, - {file = "lazy_object_proxy-1.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b1f711e2c6dcd4edd372cf5dec5c5a30d23bba06ee012093267b3376c079ec83"}, - {file = "lazy_object_proxy-1.10.0-cp310-cp310-win32.whl", hash = "sha256:76a095cfe6045c7d0ca77db9934e8f7b71b14645f0094ffcd842349ada5c5fb9"}, - {file = "lazy_object_proxy-1.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:b4f87d4ed9064b2628da63830986c3d2dca7501e6018347798313fcf028e2fd4"}, - {file = "lazy_object_proxy-1.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fec03caabbc6b59ea4a638bee5fce7117be8e99a4103d9d5ad77f15d6f81020c"}, - {file = "lazy_object_proxy-1.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02c83f957782cbbe8136bee26416686a6ae998c7b6191711a04da776dc9e47d4"}, - {file = "lazy_object_proxy-1.10.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:009e6bb1f1935a62889ddc8541514b6a9e1fcf302667dcb049a0be5c8f613e56"}, - {file = "lazy_object_proxy-1.10.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:75fc59fc450050b1b3c203c35020bc41bd2695ed692a392924c6ce180c6f1dc9"}, - {file = "lazy_object_proxy-1.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:782e2c9b2aab1708ffb07d4bf377d12901d7a1d99e5e410d648d892f8967ab1f"}, - {file = "lazy_object_proxy-1.10.0-cp311-cp311-win32.whl", hash = "sha256:edb45bb8278574710e68a6b021599a10ce730d156e5b254941754a9cc0b17d03"}, - {file = "lazy_object_proxy-1.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:e271058822765ad5e3bca7f05f2ace0de58a3f4e62045a8c90a0dfd2f8ad8cc6"}, - {file = "lazy_object_proxy-1.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e98c8af98d5707dcdecc9ab0863c0ea6e88545d42ca7c3feffb6b4d1e370c7ba"}, - {file = "lazy_object_proxy-1.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:952c81d415b9b80ea261d2372d2a4a2332a3890c2b83e0535f263ddfe43f0d43"}, - {file = "lazy_object_proxy-1.10.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80b39d3a151309efc8cc48675918891b865bdf742a8616a337cb0090791a0de9"}, - {file = "lazy_object_proxy-1.10.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e221060b701e2aa2ea991542900dd13907a5c90fa80e199dbf5a03359019e7a3"}, - {file = "lazy_object_proxy-1.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:92f09ff65ecff3108e56526f9e2481b8116c0b9e1425325e13245abfd79bdb1b"}, - {file = "lazy_object_proxy-1.10.0-cp312-cp312-win32.whl", hash = "sha256:3ad54b9ddbe20ae9f7c1b29e52f123120772b06dbb18ec6be9101369d63a4074"}, - {file = "lazy_object_proxy-1.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:127a789c75151db6af398b8972178afe6bda7d6f68730c057fbbc2e96b08d282"}, - {file = "lazy_object_proxy-1.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9e4ed0518a14dd26092614412936920ad081a424bdcb54cc13349a8e2c6d106a"}, - {file = "lazy_object_proxy-1.10.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ad9e6ed739285919aa9661a5bbed0aaf410aa60231373c5579c6b4801bd883c"}, - {file = "lazy_object_proxy-1.10.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fc0a92c02fa1ca1e84fc60fa258458e5bf89d90a1ddaeb8ed9cc3147f417255"}, - {file = "lazy_object_proxy-1.10.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0aefc7591920bbd360d57ea03c995cebc204b424524a5bd78406f6e1b8b2a5d8"}, - {file = "lazy_object_proxy-1.10.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5faf03a7d8942bb4476e3b62fd0f4cf94eaf4618e304a19865abf89a35c0bbee"}, - {file = "lazy_object_proxy-1.10.0-cp38-cp38-win32.whl", hash = "sha256:e333e2324307a7b5d86adfa835bb500ee70bfcd1447384a822e96495796b0ca4"}, - {file = "lazy_object_proxy-1.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:cb73507defd385b7705c599a94474b1d5222a508e502553ef94114a143ec6696"}, - {file = "lazy_object_proxy-1.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:366c32fe5355ef5fc8a232c5436f4cc66e9d3e8967c01fb2e6302fd6627e3d94"}, - {file = "lazy_object_proxy-1.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2297f08f08a2bb0d32a4265e98a006643cd7233fb7983032bd61ac7a02956b3b"}, - {file = "lazy_object_proxy-1.10.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18dd842b49456aaa9a7cf535b04ca4571a302ff72ed8740d06b5adcd41fe0757"}, - {file = "lazy_object_proxy-1.10.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:217138197c170a2a74ca0e05bddcd5f1796c735c37d0eee33e43259b192aa424"}, - {file = "lazy_object_proxy-1.10.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9a3a87cf1e133e5b1994144c12ca4aa3d9698517fe1e2ca82977781b16955658"}, - {file = "lazy_object_proxy-1.10.0-cp39-cp39-win32.whl", hash = "sha256:30b339b2a743c5288405aa79a69e706a06e02958eab31859f7f3c04980853b70"}, - {file = "lazy_object_proxy-1.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:a899b10e17743683b293a729d3a11f2f399e8a90c73b089e29f5d0fe3509f0dd"}, - {file = "lazy_object_proxy-1.10.0-pp310.pp311.pp312.pp38.pp39-none-any.whl", hash = "sha256:80fa48bd89c8f2f456fc0765c11c23bf5af827febacd2f523ca5bc1893fcc09d"}, + {file = "libcst-1.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:279b54568ea1f25add50ea4ba3d76d4f5835500c82f24d54daae4c5095b986aa"}, + {file = "libcst-1.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3401dae41fe24565387a65baee3887e31a44e3e58066b0250bc3f3ccf85b1b5a"}, + {file = "libcst-1.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1989fa12d3cd79118ebd29ebe2a6976d23d509b1a4226bc3d66fcb7cb50bd5d"}, + {file = "libcst-1.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:addc6d585141a7677591868886f6bda0577529401a59d210aa8112114340e129"}, + {file = "libcst-1.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:17d71001cb25e94cfe8c3d997095741a8c4aa7a6d234c0f972bc42818c88dfaf"}, + {file = "libcst-1.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:2d47de16d105e7dd5f4e01a428d9f4dc1e71efd74f79766daf54528ce37f23c3"}, + {file = "libcst-1.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e6227562fc5c9c1efd15dfe90b0971ae254461b8b6b23c1b617139b6003de1c1"}, + {file = "libcst-1.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3399e6c95df89921511b44d8c5bf6a75bcbc2d51f1f6429763609ba005c10f6b"}, + {file = "libcst-1.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48601e3e590e2d6a7ab8c019cf3937c70511a78d778ab3333764531253acdb33"}, + {file = "libcst-1.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f42797309bb725f0f000510d5463175ccd7155395f09b5e7723971b0007a976d"}, + {file = "libcst-1.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb4e42ea107a37bff7f9fdbee9532d39f9ea77b89caa5c5112b37057b12e0838"}, + {file = "libcst-1.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:9d0cc3c5a2a51fa7e1d579a828c0a2e46b2170024fd8b1a0691c8a52f3abb2d9"}, + {file = "libcst-1.4.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7ece51d935bc9bf60b528473d2e5cc67cbb88e2f8146297e40ee2c7d80be6f13"}, + {file = "libcst-1.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:81653dea1cdfa4c6520a7c5ffb95fa4d220cbd242e446c7a06d42d8636bfcbba"}, + {file = "libcst-1.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6abce0e66bba2babfadc20530fd3688f672d565674336595b4623cd800b91ef"}, + {file = "libcst-1.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5da9d7dc83801aba3b8d911f82dc1a375db0d508318bad79d9fb245374afe068"}, + {file = "libcst-1.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c54aa66c86d8ece9c93156a2cf5ca512b0dce40142fe9e072c86af2bf892411"}, + {file = "libcst-1.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:62e2682ee1567b6a89c91853865372bf34f178bfd237853d84df2b87b446e654"}, + {file = "libcst-1.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b8ecdba8934632b4dadacb666cd3816627a6ead831b806336972ccc4ba7ca0e9"}, + {file = "libcst-1.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8e54c777b8d27339b70f304d16fc8bc8674ef1bd34ed05ea874bf4921eb5a313"}, + {file = "libcst-1.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:061d6855ef30efe38b8a292b7e5d57c8e820e71fc9ec9846678b60a934b53bbb"}, + {file = "libcst-1.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb0abf627ee14903d05d0ad9b2c6865f1b21eb4081e2c7bea1033f85db2b8bae"}, + {file = "libcst-1.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d024f44059a853b4b852cfc04fec33e346659d851371e46fc8e7c19de24d3da9"}, + {file = "libcst-1.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:3c6a8faab9da48c5b371557d0999b4ca51f4f2cbd37ee8c2c4df0ac01c781465"}, + {file = "libcst-1.4.0.tar.gz", hash = "sha256:449e0b16604f054fa7f27c3ffe86ea7ef6c409836fe68fe4e752a1894175db00"}, ] +[package.dependencies] +pyyaml = ">=5.2" + +[package.extras] +dev = ["Sphinx (>=5.1.1)", "black (==23.12.1)", "build (>=0.10.0)", "coverage (>=4.5.4)", "fixit (==2.1.0)", "flake8 (==7.0.0)", "hypothesis (>=4.36.0)", "hypothesmith (>=0.0.4)", "jinja2 (==3.1.4)", "jupyter (>=1.0.0)", "maturin (>=0.8.3,<1.6)", "nbsphinx (>=0.4.2)", "prompt-toolkit (>=2.0.9)", "pyre-check (==0.9.18)", "setuptools-rust (>=1.5.2)", "setuptools-scm (>=6.0.1)", "slotscheck (>=0.7.1)", "sphinx-rtd-theme (>=0.4.3)", "ufmt (==2.6.0)", "usort (==1.0.8.post1)"] + [[package]] name = "loguru" version = "0.6.0" @@ -1113,6 +1105,39 @@ files = [ {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, ] +[[package]] +name = "marshmallow" +version = "3.21.3" +description = "A lightweight library for converting complex datatypes to and from native Python datatypes." +optional = false +python-versions = ">=3.8" +files = [ + {file = "marshmallow-3.21.3-py3-none-any.whl", hash = "sha256:86ce7fb914aa865001a4b2092c4c2872d13bc347f3d42673272cabfdbad386f1"}, + {file = "marshmallow-3.21.3.tar.gz", hash = "sha256:4f57c5e050a54d66361e826f94fba213eb10b67b2fdb02c3e0343ce207ba1662"}, +] + +[package.dependencies] +packaging = ">=17.0" + +[package.extras] +dev = ["marshmallow[tests]", "pre-commit (>=3.5,<4.0)", "tox"] +docs = ["alabaster (==0.7.16)", "autodocsumm (==0.2.12)", "sphinx (==7.3.7)", "sphinx-issues (==4.1.0)", "sphinx-version-warning (==1.1.2)"] +tests = ["pytest", "pytz", "simplejson"] + +[[package]] +name = "marshmallow-enum" +version = "1.5.1" +description = "Enum field for Marshmallow" +optional = false +python-versions = "*" +files = [ + {file = "marshmallow-enum-1.5.1.tar.gz", hash = "sha256:38e697e11f45a8e64b4a1e664000897c659b60aa57bfa18d44e226a9920b6e58"}, + {file = "marshmallow_enum-1.5.1-py2.py3-none-any.whl", hash = "sha256:57161ab3dbfde4f57adeb12090f39592e992b9c86d206d02f6bd03ebec60f072"}, +] + +[package.dependencies] +marshmallow = ">=2.0.0" + [[package]] name = "matplotlib" version = "3.9.1" @@ -1166,17 +1191,6 @@ python-dateutil = ">=2.7" [package.extras] dev = ["meson-python (>=0.13.1)", "numpy (>=1.25)", "pybind11 (>=2.6)", "setuptools (>=64)", "setuptools_scm (>=7)"] -[[package]] -name = "mccabe" -version = "0.7.0" -description = "McCabe checker, plugin for flake8" -optional = false -python-versions = ">=3.6" -files = [ - {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, - {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, -] - [[package]] name = "mpyq" version = "0.2.5" @@ -1286,48 +1300,6 @@ files = [ {file = "multidict-6.0.5.tar.gz", hash = "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da"}, ] -[[package]] -name = "mypy" -version = "0.960" -description = "Optional static typing for Python" -optional = false -python-versions = ">=3.6" -files = [ - {file = "mypy-0.960-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3a3e525cd76c2c4f90f1449fd034ba21fcca68050ff7c8397bb7dd25dd8b8248"}, - {file = "mypy-0.960-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7a76dc4f91e92db119b1be293892df8379b08fd31795bb44e0ff84256d34c251"}, - {file = "mypy-0.960-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ffdad80a92c100d1b0fe3d3cf1a4724136029a29afe8566404c0146747114382"}, - {file = "mypy-0.960-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7d390248ec07fa344b9f365e6ed9d205bd0205e485c555bed37c4235c868e9d5"}, - {file = "mypy-0.960-cp310-cp310-win_amd64.whl", hash = "sha256:925aa84369a07846b7f3b8556ccade1f371aa554f2bd4fb31cb97a24b73b036e"}, - {file = "mypy-0.960-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:239d6b2242d6c7f5822163ee082ef7a28ee02e7ac86c35593ef923796826a385"}, - {file = "mypy-0.960-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f1ba54d440d4feee49d8768ea952137316d454b15301c44403db3f2cb51af024"}, - {file = "mypy-0.960-cp36-cp36m-win_amd64.whl", hash = "sha256:cb7752b24528c118a7403ee955b6a578bfcf5879d5ee91790667c8ea511d2085"}, - {file = "mypy-0.960-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:826a2917c275e2ee05b7c7b736c1e6549a35b7ea5a198ca457f8c2ebea2cbecf"}, - {file = "mypy-0.960-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3eabcbd2525f295da322dff8175258f3fc4c3eb53f6d1929644ef4d99b92e72d"}, - {file = "mypy-0.960-cp37-cp37m-win_amd64.whl", hash = "sha256:f47322796c412271f5aea48381a528a613f33e0a115452d03ae35d673e6064f8"}, - {file = "mypy-0.960-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2c7f8bb9619290836a4e167e2ef1f2cf14d70e0bc36c04441e41487456561409"}, - {file = "mypy-0.960-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fbfb873cf2b8d8c3c513367febde932e061a5f73f762896826ba06391d932b2a"}, - {file = "mypy-0.960-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cc537885891382e08129d9862553b3d00d4be3eb15b8cae9e2466452f52b0117"}, - {file = "mypy-0.960-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:481f98c6b24383188c928f33dd2f0776690807e12e9989dd0419edd5c74aa53b"}, - {file = "mypy-0.960-cp38-cp38-win_amd64.whl", hash = "sha256:29dc94d9215c3eb80ac3c2ad29d0c22628accfb060348fd23d73abe3ace6c10d"}, - {file = "mypy-0.960-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:33d53a232bb79057f33332dbbb6393e68acbcb776d2f571ba4b1d50a2c8ba873"}, - {file = "mypy-0.960-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8d645e9e7f7a5da3ec3bbcc314ebb9bb22c7ce39e70367830eb3c08d0140b9ce"}, - {file = "mypy-0.960-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:85cf2b14d32b61db24ade8ac9ae7691bdfc572a403e3cb8537da936e74713275"}, - {file = "mypy-0.960-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a85a20b43fa69efc0b955eba1db435e2ffecb1ca695fe359768e0503b91ea89f"}, - {file = "mypy-0.960-cp39-cp39-win_amd64.whl", hash = "sha256:0ebfb3f414204b98c06791af37a3a96772203da60636e2897408517fcfeee7a8"}, - {file = "mypy-0.960-py3-none-any.whl", hash = "sha256:bfd4f6536bd384c27c392a8b8f790fd0ed5c0cf2f63fc2fed7bce56751d53026"}, - {file = "mypy-0.960.tar.gz", hash = "sha256:d4fccf04c1acf750babd74252e0f2db6bd2ac3aa8fe960797d9f3ef41cf2bfd4"}, -] - -[package.dependencies] -mypy-extensions = ">=0.4.3" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = ">=3.10" - -[package.extras] -dmypy = ["psutil (>=4.0)"] -python2 = ["typed-ast (>=1.4.0,<2)"] -reports = ["lxml"] - [[package]] name = "mypy-extensions" version = "1.0.0" @@ -1644,35 +1616,6 @@ files = [ [package.extras] windows-terminal = ["colorama (>=0.4.6)"] -[[package]] -name = "pylint" -version = "2.17.7" -description = "python code static checker" -optional = false -python-versions = ">=3.7.2" -files = [ - {file = "pylint-2.17.7-py3-none-any.whl", hash = "sha256:27a8d4c7ddc8c2f8c18aa0050148f89ffc09838142193fdbe98f172781a3ff87"}, - {file = "pylint-2.17.7.tar.gz", hash = "sha256:f4fcac7ae74cfe36bc8451e931d8438e4a476c20314b1101c458ad0f05191fad"}, -] - -[package.dependencies] -astroid = ">=2.15.8,<=2.17.0-dev0" -colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} -dill = [ - {version = ">=0.2", markers = "python_version < \"3.11\""}, - {version = ">=0.3.6", markers = "python_version >= \"3.11\""}, -] -isort = ">=4.2.5,<6" -mccabe = ">=0.6,<0.8" -platformdirs = ">=2.2.0" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -tomlkit = ">=0.10.1" -typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} - -[package.extras] -spelling = ["pyenchant (>=3.2,<4.0)"] -testutils = ["gitpython (>3)"] - [[package]] name = "pyparsing" version = "3.1.2" @@ -1687,6 +1630,45 @@ files = [ [package.extras] diagrams = ["jinja2", "railroad-diagrams"] +[[package]] +name = "pyre-check" +version = "0.9.22" +description = "A performant type checker for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyre-check-0.9.22.tar.gz", hash = "sha256:e082f926dff71661959535c3936fca5ad40a44858b5fd3e99009a616a1b57083"}, + {file = "pyre_check-0.9.22-py3-none-macosx_10_11_x86_64.whl", hash = "sha256:4bbd61dad5669dfef00e875bf8a573866595ecbd8240f595339a9781e8a1e22e"}, + {file = "pyre_check-0.9.22-py3-none-manylinux1_x86_64.whl", hash = "sha256:d331e2687e194fa22505e0724b1536e61bf06fddc5416d7b83d542d2270c91ce"}, +] + +[package.dependencies] +click = ">=8.0" +dataclasses-json = "0.5.7" +intervaltree = "*" +libcst = "*" +psutil = "*" +pyre-extensions = ">=0.0.29" +tabulate = "*" +testslide = ">=2.7.0" +typing-extensions = "*" +typing-inspect = "*" + +[[package]] +name = "pyre-extensions" +version = "0.0.30" +description = "Type system extensions for use with the pyre type checker" +optional = false +python-versions = "*" +files = [ + {file = "pyre-extensions-0.0.30.tar.gz", hash = "sha256:ba7923c486e089afb37a10623a8f4ae82d73cff42426d711c48af070e5bc31b2"}, + {file = "pyre_extensions-0.0.30-py3-none-any.whl", hash = "sha256:32b37ede4eed0ea879fdd6d84e0c7811e129f19b76614f1be3a6b47f9a4b1fa0"}, +] + +[package.dependencies] +typing-extensions = "*" +typing-inspect = "*" + [[package]] name = "pytest" version = "7.4.4" @@ -1876,6 +1858,32 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "ruff" +version = "0.1.15" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.1.15-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:5fe8d54df166ecc24106db7dd6a68d44852d14eb0729ea4672bb4d96c320b7df"}, + {file = "ruff-0.1.15-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6f0bfbb53c4b4de117ac4d6ddfd33aa5fc31beeaa21d23c45c6dd249faf9126f"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0d432aec35bfc0d800d4f70eba26e23a352386be3a6cf157083d18f6f5881c8"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9405fa9ac0e97f35aaddf185a1be194a589424b8713e3b97b762336ec79ff807"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c66ec24fe36841636e814b8f90f572a8c0cb0e54d8b5c2d0e300d28a0d7bffec"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:6f8ad828f01e8dd32cc58bc28375150171d198491fc901f6f98d2a39ba8e3ff5"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86811954eec63e9ea162af0ffa9f8d09088bab51b7438e8b6488b9401863c25e"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fd4025ac5e87d9b80e1f300207eb2fd099ff8200fa2320d7dc066a3f4622dc6b"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b17b93c02cdb6aeb696effecea1095ac93f3884a49a554a9afa76bb125c114c1"}, + {file = "ruff-0.1.15-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ddb87643be40f034e97e97f5bc2ef7ce39de20e34608f3f829db727a93fb82c5"}, + {file = "ruff-0.1.15-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:abf4822129ed3a5ce54383d5f0e964e7fef74a41e48eb1dfad404151efc130a2"}, + {file = "ruff-0.1.15-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6c629cf64bacfd136c07c78ac10a54578ec9d1bd2a9d395efbee0935868bf852"}, + {file = "ruff-0.1.15-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1bab866aafb53da39c2cadfb8e1c4550ac5340bb40300083eb8967ba25481447"}, + {file = "ruff-0.1.15-py3-none-win32.whl", hash = "sha256:2417e1cb6e2068389b07e6fa74c306b2810fe3ee3476d5b8a96616633f40d14f"}, + {file = "ruff-0.1.15-py3-none-win_amd64.whl", hash = "sha256:3837ac73d869efc4182d9036b1405ef4c73d9b1f88da2413875e34e0d6919587"}, + {file = "ruff-0.1.15-py3-none-win_arm64.whl", hash = "sha256:9a933dfb1c14ec7a33cceb1e49ec4a16b51ce3c20fd42663198746efc0427360"}, + {file = "ruff-0.1.15.tar.gz", hash = "sha256:f6dfa8c1b21c913c326919056c390966648b680966febcb796cc9d1aaab8564e"}, +] + [[package]] name = "s2clientprotocol" version = "5.0.13.92440.3" @@ -2146,6 +2154,38 @@ lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] standalone = ["Sphinx (>=5)"] test = ["pytest"] +[[package]] +name = "tabulate" +version = "0.9.0" +description = "Pretty-print tabular data" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}, + {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, +] + +[package.extras] +widechars = ["wcwidth"] + +[[package]] +name = "testslide" +version = "2.7.1" +description = "A test framework for Python that makes mocking and iterating over code with tests a breeze" +optional = false +python-versions = "*" +files = [ + {file = "TestSlide-2.7.1.tar.gz", hash = "sha256:d25890d5c383f673fac44a5f9e2561b7118d04f29f2c2b3d4f549e6db94cb34d"}, +] + +[package.dependencies] +psutil = ">=5.6.7" +Pygments = ">=2.2.0" +typeguard = "<3.0" + +[package.extras] +build = ["black", "coverage", "coveralls", "flake8", "ipython", "isort (>=5.1,<6.0)", "mypy (==0.991)", "sphinx", "sphinx-autobuild", "sphinx-kr-theme", "twine"] + [[package]] name = "toml" version = "0.10.2" @@ -2169,16 +2209,20 @@ files = [ ] [[package]] -name = "tomlkit" -version = "0.13.0" -description = "Style preserving TOML library" +name = "typeguard" +version = "2.13.3" +description = "Run-time type checker for Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.5.3" files = [ - {file = "tomlkit-0.13.0-py3-none-any.whl", hash = "sha256:7075d3042d03b80f603482d69bf0c8f345c2b30e41699fd8883227f89972b264"}, - {file = "tomlkit-0.13.0.tar.gz", hash = "sha256:08ad192699734149f5b97b45f1f18dad7eb1b6d16bc72ad0c2335772650d7b72"}, + {file = "typeguard-2.13.3-py3-none-any.whl", hash = "sha256:5e3e3be01e887e7eafae5af63d1f36c849aaa94e3a0112097312aabfa16284f1"}, + {file = "typeguard-2.13.3.tar.gz", hash = "sha256:00edaa8da3a133674796cf5ea87d9f4b4c367d77476e185e80251cc13dfbb8c4"}, ] +[package.extras] +doc = ["sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["mypy", "pytest", "typing-extensions"] + [[package]] name = "typing-extensions" version = "4.12.2" @@ -2190,6 +2234,21 @@ files = [ {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] +[[package]] +name = "typing-inspect" +version = "0.9.0" +description = "Runtime inspection utilities for typing module." +optional = false +python-versions = "*" +files = [ + {file = "typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f"}, + {file = "typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78"}, +] + +[package.dependencies] +mypy-extensions = ">=0.3.0" +typing-extensions = ">=3.7.4" + [[package]] name = "urllib3" version = "2.2.2" @@ -2241,96 +2300,6 @@ files = [ [package.extras] dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"] -[[package]] -name = "wrapt" -version = "1.16.0" -description = "Module for decorators, wrappers and monkey patching." -optional = false -python-versions = ">=3.6" -files = [ - {file = "wrapt-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4"}, - {file = "wrapt-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020"}, - {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb2dee3874a500de01c93d5c71415fcaef1d858370d405824783e7a8ef5db440"}, - {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a88e6010048489cda82b1326889ec075a8c856c2e6a256072b28eaee3ccf487"}, - {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac83a914ebaf589b69f7d0a1277602ff494e21f4c2f743313414378f8f50a4cf"}, - {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:73aa7d98215d39b8455f103de64391cb79dfcad601701a3aa0dddacf74911d72"}, - {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:807cc8543a477ab7422f1120a217054f958a66ef7314f76dd9e77d3f02cdccd0"}, - {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bf5703fdeb350e36885f2875d853ce13172ae281c56e509f4e6eca049bdfb136"}, - {file = "wrapt-1.16.0-cp310-cp310-win32.whl", hash = "sha256:f6b2d0c6703c988d334f297aa5df18c45e97b0af3679bb75059e0e0bd8b1069d"}, - {file = "wrapt-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:decbfa2f618fa8ed81c95ee18a387ff973143c656ef800c9f24fb7e9c16054e2"}, - {file = "wrapt-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a5db485fe2de4403f13fafdc231b0dbae5eca4359232d2efc79025527375b09"}, - {file = "wrapt-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75ea7d0ee2a15733684badb16de6794894ed9c55aa5e9903260922f0482e687d"}, - {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a452f9ca3e3267cd4d0fcf2edd0d035b1934ac2bd7e0e57ac91ad6b95c0c6389"}, - {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43aa59eadec7890d9958748db829df269f0368521ba6dc68cc172d5d03ed8060"}, - {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72554a23c78a8e7aa02abbd699d129eead8b147a23c56e08d08dfc29cfdddca1"}, - {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d2efee35b4b0a347e0d99d28e884dfd82797852d62fcd7ebdeee26f3ceb72cf3"}, - {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6dcfcffe73710be01d90cae08c3e548d90932d37b39ef83969ae135d36ef3956"}, - {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:eb6e651000a19c96f452c85132811d25e9264d836951022d6e81df2fff38337d"}, - {file = "wrapt-1.16.0-cp311-cp311-win32.whl", hash = "sha256:66027d667efe95cc4fa945af59f92c5a02c6f5bb6012bff9e60542c74c75c362"}, - {file = "wrapt-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:aefbc4cb0a54f91af643660a0a150ce2c090d3652cf4052a5397fb2de549cd89"}, - {file = "wrapt-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5eb404d89131ec9b4f748fa5cfb5346802e5ee8836f57d516576e61f304f3b7b"}, - {file = "wrapt-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9090c9e676d5236a6948330e83cb89969f433b1943a558968f659ead07cb3b36"}, - {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94265b00870aa407bd0cbcfd536f17ecde43b94fb8d228560a1e9d3041462d73"}, - {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2058f813d4f2b5e3a9eb2eb3faf8f1d99b81c3e51aeda4b168406443e8ba809"}, - {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b5e1f498a8ca1858a1cdbffb023bfd954da4e3fa2c0cb5853d40014557248b"}, - {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:14d7dc606219cdd7405133c713f2c218d4252f2a469003f8c46bb92d5d095d81"}, - {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:49aac49dc4782cb04f58986e81ea0b4768e4ff197b57324dcbd7699c5dfb40b9"}, - {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:418abb18146475c310d7a6dc71143d6f7adec5b004ac9ce08dc7a34e2babdc5c"}, - {file = "wrapt-1.16.0-cp312-cp312-win32.whl", hash = "sha256:685f568fa5e627e93f3b52fda002c7ed2fa1800b50ce51f6ed1d572d8ab3e7fc"}, - {file = "wrapt-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:dcdba5c86e368442528f7060039eda390cc4091bfd1dca41e8046af7c910dda8"}, - {file = "wrapt-1.16.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d462f28826f4657968ae51d2181a074dfe03c200d6131690b7d65d55b0f360f8"}, - {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a33a747400b94b6d6b8a165e4480264a64a78c8a4c734b62136062e9a248dd39"}, - {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3646eefa23daeba62643a58aac816945cadc0afaf21800a1421eeba5f6cfb9c"}, - {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ebf019be5c09d400cf7b024aa52b1f3aeebeff51550d007e92c3c1c4afc2a40"}, - {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0d2691979e93d06a95a26257adb7bfd0c93818e89b1406f5a28f36e0d8c1e1fc"}, - {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:1acd723ee2a8826f3d53910255643e33673e1d11db84ce5880675954183ec47e"}, - {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bc57efac2da352a51cc4658878a68d2b1b67dbe9d33c36cb826ca449d80a8465"}, - {file = "wrapt-1.16.0-cp36-cp36m-win32.whl", hash = "sha256:da4813f751142436b075ed7aa012a8778aa43a99f7b36afe9b742d3ed8bdc95e"}, - {file = "wrapt-1.16.0-cp36-cp36m-win_amd64.whl", hash = "sha256:6f6eac2360f2d543cc875a0e5efd413b6cbd483cb3ad7ebf888884a6e0d2e966"}, - {file = "wrapt-1.16.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a0ea261ce52b5952bf669684a251a66df239ec6d441ccb59ec7afa882265d593"}, - {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bd2d7ff69a2cac767fbf7a2b206add2e9a210e57947dd7ce03e25d03d2de292"}, - {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9159485323798c8dc530a224bd3ffcf76659319ccc7bbd52e01e73bd0241a0c5"}, - {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a86373cf37cd7764f2201b76496aba58a52e76dedfaa698ef9e9688bfd9e41cf"}, - {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:73870c364c11f03ed072dda68ff7aea6d2a3a5c3fe250d917a429c7432e15228"}, - {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b935ae30c6e7400022b50f8d359c03ed233d45b725cfdd299462f41ee5ffba6f"}, - {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:db98ad84a55eb09b3c32a96c576476777e87c520a34e2519d3e59c44710c002c"}, - {file = "wrapt-1.16.0-cp37-cp37m-win32.whl", hash = "sha256:9153ed35fc5e4fa3b2fe97bddaa7cbec0ed22412b85bcdaf54aeba92ea37428c"}, - {file = "wrapt-1.16.0-cp37-cp37m-win_amd64.whl", hash = "sha256:66dfbaa7cfa3eb707bbfcd46dab2bc6207b005cbc9caa2199bcbc81d95071a00"}, - {file = "wrapt-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1dd50a2696ff89f57bd8847647a1c363b687d3d796dc30d4dd4a9d1689a706f0"}, - {file = "wrapt-1.16.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:44a2754372e32ab315734c6c73b24351d06e77ffff6ae27d2ecf14cf3d229202"}, - {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e9723528b9f787dc59168369e42ae1c3b0d3fadb2f1a71de14531d321ee05b0"}, - {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbed418ba5c3dce92619656802cc5355cb679e58d0d89b50f116e4a9d5a9603e"}, - {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:941988b89b4fd6b41c3f0bfb20e92bd23746579736b7343283297c4c8cbae68f"}, - {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a42cd0cfa8ffc1915aef79cb4284f6383d8a3e9dcca70c445dcfdd639d51267"}, - {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ca9b6085e4f866bd584fb135a041bfc32cab916e69f714a7d1d397f8c4891ca"}, - {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5e49454f19ef621089e204f862388d29e6e8d8b162efce05208913dde5b9ad6"}, - {file = "wrapt-1.16.0-cp38-cp38-win32.whl", hash = "sha256:c31f72b1b6624c9d863fc095da460802f43a7c6868c5dda140f51da24fd47d7b"}, - {file = "wrapt-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:490b0ee15c1a55be9c1bd8609b8cecd60e325f0575fc98f50058eae366e01f41"}, - {file = "wrapt-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9b201ae332c3637a42f02d1045e1d0cccfdc41f1f2f801dafbaa7e9b4797bfc2"}, - {file = "wrapt-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2076fad65c6736184e77d7d4729b63a6d1ae0b70da4868adeec40989858eb3fb"}, - {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5cd603b575ebceca7da5a3a251e69561bec509e0b46e4993e1cac402b7247b8"}, - {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b47cfad9e9bbbed2339081f4e346c93ecd7ab504299403320bf85f7f85c7d46c"}, - {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8212564d49c50eb4565e502814f694e240c55551a5f1bc841d4fcaabb0a9b8a"}, - {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5f15814a33e42b04e3de432e573aa557f9f0f56458745c2074952f564c50e664"}, - {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db2e408d983b0e61e238cf579c09ef7020560441906ca990fe8412153e3b291f"}, - {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:edfad1d29c73f9b863ebe7082ae9321374ccb10879eeabc84ba3b69f2579d537"}, - {file = "wrapt-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed867c42c268f876097248e05b6117a65bcd1e63b779e916fe2e33cd6fd0d3c3"}, - {file = "wrapt-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:eb1b046be06b0fce7249f1d025cd359b4b80fc1c3e24ad9eca33e0dcdb2e4a35"}, - {file = "wrapt-1.16.0-py3-none-any.whl", hash = "sha256:6906c4100a8fcbf2fa735f6059214bb13b97f75b1a61777fcf6432121ef12ef1"}, - {file = "wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d"}, -] - -[[package]] -name = "yapf" -version = "0.32.0" -description = "A formatter for Python code." -optional = false -python-versions = "*" -files = [ - {file = "yapf-0.32.0-py2.py3-none-any.whl", hash = "sha256:8fea849025584e486fd06d6ba2bed717f396080fd3cc236ba10cb97c4c51cf32"}, - {file = "yapf-0.32.0.tar.gz", hash = "sha256:a3f5085d37ef7e3e004c4ba9f9b3e40c54ff1901cd111f05145ae313a7c67d1b"}, -] - [[package]] name = "yarl" version = "1.9.4" @@ -2452,4 +2421,4 @@ test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", [metadata] lock-version = "2.0" python-versions = ">=3.9, <3.13" -content-hash = "d757311170736b71ecfb1f8620807ef27d77fb9f1455775a16d43f9efee85488" +content-hash = "11c16b4f5f0b3c7dd6f94fc3ff256b2a2e1c04a33c42950160411665ec38a6c1" diff --git a/pyproject.toml b/pyproject.toml index d17b01a7..0531e0e5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ license = "MIT" homepage = "https://github.com/Burnysc2/python-sc2" documentation = "https://burnysc2.github.io/python-sc2/docs/index.html" keywords = ["StarCraft", "StarCraft 2", "StarCraft II", "AI", "Bot"] -classifiers=[ +classifiers = [ "Intended Audience :: Developers", "Intended Audience :: Education", "Intended Audience :: Science/Research", @@ -25,9 +25,7 @@ classifiers=[ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", ] -packages = [ - { include = "sc2/**/*.py" }, -] +packages = [{ include = "sc2/**/*.py" }] [tool.poetry.dependencies] python = ">=3.9, <3.13" @@ -44,11 +42,9 @@ protobuf = "<4.0.0" coverage = "^7.2" hypothesis = "^6.23.1" matplotlib = "^3.4.3" -mypy = "^0.960" pillow = "^9.0" pre-commit = "^2.15.0" pyglet = "^2.0" -pylint = "^2.11.1" pytest = "^7.1.1" pytest-asyncio = "^0.18.3" pytest-benchmark = "^4.0.0" @@ -58,56 +54,58 @@ sphinx = "^7.0" sphinx-autodoc-typehints = "^1.18" sphinx-rtd-theme = "^2.0" toml = "^0.10.2" -yapf = "^0.32.0" +# Linter +ruff = "^0.1.14" +# Type checker +pyre-check = "^0.9.18" [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" -[tool.mypy] -python_version = "3.9" -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 = 'py38' +line-length = 120 +# 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 +] +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 +] + +[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..51c99289 100644 --- a/sc2/action.py +++ b/sc2/action.py @@ -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..84b1e931 100644 --- a/sc2/bot_ai.py +++ b/sc2/bot_ai.py @@ -44,12 +44,12 @@ 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}" @@ -150,10 +150,8 @@ def main_base_ramp(self) -> 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." + """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 @@ -164,9 +162,7 @@ def expansion_locations_dict(self) -> Dict[Point2, Units]: 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." + 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 @@ -298,21 +294,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: @@ -327,7 +322,8 @@ async def distribute_workers(self, resource_ratio: float = 2): # and need to send them to the closest patch 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 @@ -559,19 +555,23 @@ 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 @@ -596,7 +596,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 @@ -605,14 +607,15 @@ def select_build_worker(self, pos: Union[Unit, Point2], force: bool = False) -> return None async def can_place_single(self, building: Union[AbilityId, UnitTypeId], position: Point2) -> bool: - """ Checks the placement for only one position. """ + """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: Union[AbilityData, AbilityId, UnitTypeId], positions: List[Point2] + ) -> List[bool]: """Tests if a building can be placed in the given locations. Example:: @@ -680,9 +683,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 +693,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) @@ -780,8 +784,7 @@ def structure_type_build_progress(self, structure_type: Union[UnitTypeId, int]) 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()) + 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 +794,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 @@ -886,9 +889,11 @@ 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( @@ -935,11 +940,7 @@ async def build( return True def train( - self, - unit_type: UnitTypeId, - amount: int = 1, - closest_to: Point2 = None, - train_only_idle_buildings: bool = True + self, unit_type: UnitTypeId, amount: int = 1, closest_to: Point2 = 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 @@ -996,7 +997,8 @@ def train( 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} + u in train_structure_type + for u in {UnitTypeId.BARRACKS, UnitTypeId.FACTORY, UnitTypeId.STARPORT} ) # Sort structures closest to a point if closest_to is not None: @@ -1004,8 +1006,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 @@ -1169,9 +1171,12 @@ def in_map_bounds(self, pos: Union[Point2, tuple, list]) -> bool: :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. diff --git a/sc2/bot_ai_internal.py b/sc2/bot_ai_internal.py index a2a40e1b..c0dcc593 100644 --- a/sc2/bot_ai_internal.py +++ b/sc2/bot_ai_internal.py @@ -8,9 +8,8 @@ from abc import ABC from collections import Counter from contextlib import suppress -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Dict, Generator, Iterable, List, Set, Tuple, Union, final from typing import Counter as CounterType -from typing import Dict, Generator, Iterable, List, Set, Tuple, Union, final import numpy as np from loguru import logger @@ -53,7 +52,7 @@ class BotAIInternal(ABC): @final def _initialize_variables(self): - """ Called from main.py internally """ + """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 @@ -127,7 +126,7 @@ def _initialize_variables(self): @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 +137,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 +148,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, @@ -160,10 +159,8 @@ 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." + """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, @@ -173,7 +170,7 @@ def expansion_locations(self) -> Dict[Point2, Units]: @final def _find_expansion_locations(self): - """ Ran once at the start of the game to calculate expansion locations. """ + """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 @@ -181,7 +178,8 @@ def _find_expansion_locations(self): resource_spread_threshold: float = 8.5 # Create a group for every resource resource_groups: List[List[Unit]] = [ - [resource] for resource in self.resources + [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 +208,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 +225,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 @@ -276,32 +276,31 @@ def _abilities_count_and_build_progress(self) -> Tuple[CounterType[AbilityId], D 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. """ + """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() for structure in self.structures: @@ -500,8 +499,7 @@ def _prepare_step(self, state, proto_game_info): 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 + 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} @@ -633,7 +631,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 @@ -723,8 +721,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 @@ -768,7 +768,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 +776,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 +817,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( @@ -844,7 +844,7 @@ 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. """ + """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 @@ -878,8 +878,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 @@ -905,7 +905,7 @@ def _distance_units_to_pos( units: Units, pos: Union[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 @@ -914,7 +914,7 @@ def _distance_unit_to_points( unit: Unit, 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) diff --git a/sc2/cache.py b/sc2/cache.py index f807e112..7709927e 100644 --- a/sc2/cache.py +++ b/sc2/cache.py @@ -9,16 +9,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,7 +26,7 @@ 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 diff --git a/sc2/client.py b/sc2/client.py index e2dff958..45813561 100644 --- a/sc2/client.py +++ b/sc2/client.py @@ -1,5 +1,6 @@ from __future__ import annotations +from pathlib import Path from typing import Dict, Iterable, List, Optional, Set, Tuple, Union from loguru import logger @@ -16,7 +17,7 @@ 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 @@ -24,7 +25,6 @@ # pylint: disable=R0904 class Client(Protocol): - def __init__(self, ws, save_replay_path: str = None): """ :param ws: @@ -102,7 +102,7 @@ async def join_game(self, name=None, race=None, observed_player_id=None, portcon return result.join_game.player_id async def leave(self): - """ You can use 'await self.client.leave()' to surrender midst game. """ + """You can use 'await self.client.leave()' to surrender midst game.""" is_resign = self._game_result is None if is_resign: @@ -115,14 +115,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): 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 +151,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)) @@ -178,7 +178,7 @@ 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: @@ -202,8 +202,9 @@ 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: Union[Unit, Point2, Point3], end: Union[Point2, Point3] + ) -> Optional[Union[int, float]]: """Caution: returns "None" when path not found Try to combine queries with the function below because the pathing query is generally slow. @@ -266,10 +267,7 @@ async def _query_building_placement_fast( return [p.result == 1 for p in result.query.placements] async def query_building_placement( - self, - ability: AbilityData, - positions: List[Union[Point2, Point3]], - ignore_resources: bool = True + self, ability: AbilityData, positions: List[Union[Point2, Point3]], ignore_resources: bool = True ) -> List[ActionResult]: """This function might be deleted in favor of the function above (_query_building_placement_fast). @@ -292,7 +290,7 @@ async def query_building_placement( async def query_available_abilities( self, units: Union[List[Unit], Units], ignore_resource_requirements: bool = False ) -> List[List[AbilityId]]: - """ Query abilities of multiple units """ + """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 """ @@ -314,7 +312,7 @@ async def query_available_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 """ + """Query abilities of multiple units""" result = await self._execute( query=query_pb.RequestQuery( @@ -325,7 +323,7 @@ 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 """ + """Writes a message to the chat""" ch = ChatChannel.Team if team_only else ChatChannel.Broadcast await self._execute( action=sc_pb.RequestAction( @@ -348,8 +346,9 @@ 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) + ) ) ) ] @@ -380,7 +379,8 @@ 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 ) ) ) @@ -450,7 +450,7 @@ 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). """ + """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( @@ -597,14 +597,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, ) ) ] @@ -654,15 +658,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. """ + """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( @@ -671,51 +677,51 @@ 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. """ + """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. """ + """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. """ + """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. """ + """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. """ + """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. """ + """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. """ + """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. """ + """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. """ + """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. """ + """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. """ + """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. """ + """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): @@ -733,10 +739,9 @@ async def quick_load(self): class DrawItem: - @staticmethod def to_debug_color(color: Union[tuple, Point3]): - """ Helper function for color conversion """ + """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 @@ -755,7 +760,6 @@ 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): self._start_point: Point2 = start_point self._color: Point3 = color @@ -776,7 +780,6 @@ def __hash__(self): class DrawItemWorldText(DrawItem): - def __init__(self, start_point: Point3 = None, color: Point3 = None, text: str = "", font_size: int = 8): self._start_point: Point3 = start_point self._color: Point3 = color @@ -797,7 +800,6 @@ def __hash__(self): class DrawItemLine(DrawItem): - def __init__(self, start_point: Point3 = None, end_point: Point3 = None, color: Point3 = None): self._start_point: Point3 = start_point self._end_point: Point3 = end_point @@ -814,7 +816,6 @@ def __hash__(self): class DrawItemBox(DrawItem): - def __init__(self, start_point: Point3 = None, end_point: Point3 = None, color: Point3 = None): self._start_point: Point3 = start_point self._end_point: Point3 = end_point @@ -832,7 +833,6 @@ def __hash__(self): class DrawItemSphere(DrawItem): - def __init__(self, start_point: Point3 = None, radius: float = None, color: Point3 = None): self._start_point: Point3 = start_point self._radius: float = radius diff --git a/sc2/constants.py b/sc2/constants.py index d0c4067e..add59306 100644 --- a/sc2/constants.py +++ b/sc2/constants.py @@ -494,185 +494,50 @@ def return_NOTAUNIT() -> UnitTypeId: # # 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", diff --git a/sc2/controller.py b/sc2/controller.py index a3d53aef..5341da6a 100644 --- a/sc2/controller.py +++ b/sc2/controller.py @@ -9,7 +9,6 @@ class Controller(Protocol): - def __init__(self, ws, process): super().__init__(ws) self._process = process @@ -46,13 +45,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/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..90c5f126 100644 --- a/sc2/dicts/generic_redirect_abilities.py +++ b/sc2/dicts/generic_redirect_abilities.py @@ -300,5 +300,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..b03aac35 100644 --- a/sc2/dicts/unit_abilities.py +++ b/sc2/dicts/unit_abilities.py @@ -11,118 +11,233 @@ 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 +247,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 +342,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..5a17e630 100644 --- a/sc2/dicts/unit_research_abilities.py +++ b/sc2/dicts/unit_research_abilities.py @@ -12,445 +12,330 @@ 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 + "ability": AbilityId.FORGERESEARCH_PROTOSSSHIELDSLEVEL3, + "required_building": UnitTypeId.TWILIGHTCOUNCIL, + "required_upgrade": UpgradeId.PROTOSSSHIELDSLEVEL2, + "requires_power": True, }, - UpgradeId.LIBERATORAGRANGEUPGRADE: { - 'ability': AbilityId.FUSIONCORERESEARCH_RESEARCHBALLISTICRANGE - }, - 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..811752b8 100644 --- a/sc2/dicts/unit_tech_alias.py +++ b/sc2/dicts/unit_tech_alias.py @@ -40,5 +40,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..97230b73 100644 --- a/sc2/dicts/unit_train_build_abilities.py +++ b/sc2/dicts/unit_train_build_abilities.py @@ -12,595 +12,417 @@ 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 + "ability": AbilityId.BARRACKSTRAIN_GHOST, + "requires_techlab": True, + "required_building": UnitTypeId.GHOSTACADEMY, }, - UnitTypeId.MARAUDER: { - 'ability': AbilityId.BARRACKSTRAIN_MARAUDER, - 'requires_techlab': True - }, - 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 - }, - UnitTypeId.VIPER: { - 'ability': AbilityId.LARVATRAIN_VIPER, - 'required_building': UnitTypeId.HIVE + "ability": AbilityId.LARVATRAIN_ULTRALISK, + "required_building": UnitTypeId.ULTRALISKCAVERN, }, - 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 - }, - UnitTypeId.OBSERVER: { - 'ability': AbilityId.ROBOTICSFACILITYTRAIN_OBSERVER, - 'requires_power': True + "ability": AbilityId.TRAIN_DISRUPTOR, + "required_building": UnitTypeId.ROBOTICSBAY, + "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 - }, - UnitTypeId.MEDIVAC: { - 'ability': AbilityId.STARPORTTRAIN_MEDIVAC + "ability": AbilityId.STARPORTTRAIN_BATTLECRUISER, + "requires_techlab": True, + "required_building": UnitTypeId.FUSIONCORE, }, - 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..9b9f36aa 100644 --- a/sc2/dicts/unit_trained_from.py +++ b/sc2/dicts/unit_trained_from.py @@ -115,5 +115,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..6ad225de 100644 --- a/sc2/dicts/unit_unit_alias.py +++ b/sc2/dicts/unit_unit_alias.py @@ -48,5 +48,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..eb02ba36 100644 --- a/sc2/dicts/upgrade_researched_from.py +++ b/sc2/dicts/upgrade_researched_from.py @@ -96,5 +96,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..c800c645 100644 --- a/sc2/expiring_dict.py +++ b/sc2/expiring_dict.py @@ -43,7 +43,7 @@ def frame(self) -> int: 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] @@ -54,7 +54,7 @@ def __contains__(self, key) -> bool: return False def __getitem__(self, key, with_age=False) -> Any: - """ Return the item of the dict using d[key] """ + """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) @@ -66,12 +66,12 @@ def __getitem__(self, key, with_age=False) -> Any: raise KeyError(key) def __setitem__(self, key, value): - """ Set d[key] = value """ + """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 """ + """Printable version of the dict instead of getting memory adress""" print_list = [] with self.lock: for key, value in OrderedDict.items(self): @@ -84,7 +84,7 @@ 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() @@ -99,7 +99,7 @@ def __len__(self): return count def pop(self, key, default=None, with_age=False): - """ Return the item and remove it """ + """Return the item and remove it""" with self.lock: if OrderedDict.__contains__(self, key): item = OrderedDict.__getitem__(self, key) @@ -116,7 +116,7 @@ def pop(self, key, default=None, with_age=False): return default def get(self, key, default=None, with_age=False): - """ Return the value for key if key is in dict, else default """ + """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) @@ -137,21 +137,21 @@ def update(self, other_dict: dict): 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..31cde244 100644 --- a/sc2/game_data.py +++ b/sc2/game_data.py @@ -21,15 +21,13 @@ class GameData: - def __init__(self, data): """ :param data: """ - ids = set(a.value for a in AbilityId if a.value != 0) + 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 + 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} @@ -76,7 +74,6 @@ 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 @classmethod @@ -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,7 +131,6 @@ def cost(self) -> Cost: class UnitTypeData: - def __init__(self, game_data: GameData, proto): """ :param game_data: @@ -170,7 +166,7 @@ def creation_ability(self) -> Optional[AbilityData]: @property def footprint_radius(self) -> Optional[float]: - """ See unit.py footprint_radius """ + """See unit.py footprint_radius""" if self.creation_ability is None: return None return self.creation_ability._proto.footprint_radius @@ -193,12 +189,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 """ + """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: @@ -218,7 +214,7 @@ def tech_alias(self) -> Optional[List[UnitTypeId]]: @property def unit_alias(self) -> Optional[UnitTypeId]: - """ Building type equality, e.g. FlyingOrbitalCommand is the same as OrbitalCommand """ + """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: @@ -236,14 +232,14 @@ 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""" 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 """ + """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: @@ -278,7 +274,6 @@ def morph_cost(self) -> Optional[Cost]: class UpgradeData: - def __init__(self, game_data: GameData, proto): """ :param game_data: @@ -313,6 +308,7 @@ 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 diff --git a/sc2/game_info.py b/sc2/game_info.py index f4189dc8..577ed393 100644 --- a/sc2/game_info.py +++ b/sc2/game_info.py @@ -41,7 +41,7 @@ def height_at(self, p: Point2) -> int: @cached_property def upper(self) -> FrozenSet[Point2]: - """ Returns the upper points of a ramp. """ + """Returns the upper points of a ramp.""" current_max = -10000 result = set() for p in self.points: @@ -55,7 +55,7 @@ def upper(self) -> FrozenSet[Point2]: @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. """ + """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))) @@ -86,7 +86,7 @@ def bottom_center(self) -> Point2: @cached_property def barracks_in_middle(self) -> Optional[Point2]: - """ Barracks position in the middle of the 2 depots """ + """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: @@ -102,7 +102,7 @@ def barracks_in_middle(self) -> Optional[Point2]: @cached_property def depot_in_middle(self) -> Optional[Point2]: - """ Depot in the middle of the 3 depots """ + """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: @@ -122,7 +122,7 @@ def depot_in_middle(self) -> Optional[Point2]: @cached_property def corner_depots(self) -> FrozenSet[Point2]: - """ Finds the 2 depot positions on the outside """ + """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: @@ -141,7 +141,7 @@ def corner_depots(self) -> FrozenSet[Point2]: @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 @@ -150,7 +150,7 @@ def barracks_can_fit_addon(self) -> bool: @cached_property def barracks_correct_placement(self) -> Optional[Point2]: - """ Corrected placement so that an addon can fit """ + """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: @@ -217,7 +217,6 @@ def protoss_wall_warpin(self) -> Optional[Point2]: class GameInfo: - def __init__(self, proto): self._proto = proto self.players: List[Player] = [Player.from_proto(p) for p in self._proto.player_info] @@ -236,8 +235,7 @@ def __init__(self, proto): 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 + p.player_id: p.race_actual or p.race_requested for p in self._proto.player_info } self.start_locations: List[Point2] = [ Point2.from_proto(sl).round(decimals=1) for sl in self._proto.start_raw.start_locations @@ -251,15 +249,18 @@ def _find_ramps_and_vision_blockers(self) -> Tuple[List[Ramp], FrozenSet[Point2] 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)] diff --git a/sc2/game_state.py b/sc2/game_state.py index b2dbda44..9f1e92a4 100644 --- a/sc2/game_state.py +++ b/sc2/game_state.py @@ -25,7 +25,6 @@ class Blip: - def __init__(self, proto): """ :param proto: @@ -92,7 +91,6 @@ def __getattr__(self, attr): class EffectData: - def __init__(self, proto, fake=False): """ :param proto: @@ -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) @@ -194,7 +191,6 @@ class ActionError(AbilityLookupTemplateClass): class GameState: - def __init__(self, response_observation, previous_observation=None): """ :param response_observation: @@ -236,7 +232,7 @@ 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 """ + """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) diff --git a/sc2/generate_ids.py b/sc2/generate_ids.py index 23d9e1f4..9dc43ac5 100644 --- a/sc2/generate_ids.py +++ b/sc2/generate_ids.py @@ -17,7 +17,6 @@ class IdGenerator: - def __init__(self, game_data: GameData = None, game_version: str = None, verbose: bool = False): self.game_data: GameData = game_data self.game_version = game_version @@ -181,17 +180,17 @@ 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: + 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)) @@ -203,7 +202,6 @@ def update_ids_from_stableid_json(self): @staticmethod def reimport_ids(): - # 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"]) @@ -223,17 +221,15 @@ def reimport_ids(): 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) + ids = {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 + 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 + 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} diff --git a/sc2/main.py b/sc2/main.py index 5e1b3621..06cb5e72 100644 --- a/sc2/main.py +++ b/sc2/main.py @@ -3,7 +3,6 @@ import asyncio import json -import os import platform import signal import sys @@ -27,9 +26,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() @@ -66,7 +65,7 @@ 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: @@ -154,7 +153,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 +205,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) @@ -313,10 +307,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 @@ -349,7 +342,6 @@ 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) @@ -371,7 +363,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 +396,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 +432,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() @@ -460,7 +452,7 @@ async def _host_replay(replay_path, ai, realtime, _portconfig, base_build, data_ def get_replay_version(replay_path: Union[str, Path]) -> Tuple[str, str]: - with open(replay_path, 'rb') as f: + with Path(replay_path).open("rb") as f: replay_data = f.read() replay_io = BytesIO() replay_io.write(replay_data) @@ -486,7 +478,7 @@ 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()) @@ -500,10 +492,10 @@ async def run_host_and_join(): def run_replay(ai, replay_path, realtime=False, observed_id=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) @@ -537,7 +529,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: @@ -639,8 +631,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 @@ -663,7 +655,7 @@ async def maintain_SCII_count(count: int, controllers: List[Controller], proc_ar 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,8 +676,8 @@ 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]): @@ -719,7 +711,7 @@ 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 @@ -762,7 +754,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..ffcfa333 100644 --- a/sc2/maps.py +++ b/sc2/maps.py @@ -21,7 +21,6 @@ def get(name: str) -> Map: class Map: - def __init__(self, path: Path): self.path = path @@ -40,7 +39,7 @@ def name(self): @property def data(self): - with open(self.path, "rb") as f: + with Path(self.path).open("rb") as f: return f.read() def __repr__(self): diff --git a/sc2/observer_ai.py b/sc2/observer_ai.py index 1f049039..a8159563 100644 --- a/sc2/observer_ai.py +++ b/sc2/observer_ai.py @@ -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: diff --git a/sc2/paths.py b/sc2/paths.py index 79131cde..674cce6f 100644 --- a/sc2/paths.py +++ b/sc2/paths.py @@ -67,15 +67,15 @@ 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 @@ -122,35 +122,35 @@ 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): + def __getattr__(cls, attr): # pylint: disable=E1120 - self.__setup() - return getattr(self, 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..31442dca 100644 --- a/sc2/pixel_map.py +++ b/sc2/pixel_map.py @@ -7,7 +7,6 @@ class PixelMap: - def __init__(self, proto, in_bits: bool = False): """ :param proto: @@ -42,13 +41,13 @@ 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 """ + """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 """ + """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 ( @@ -109,7 +108,7 @@ def save_image(self, filename: Union[str, Path]): 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): diff --git a/sc2/player.py b/sc2/player.py index e1259198..96af7ba6 100644 --- a/sc2/player.py +++ b/sc2/player.py @@ -7,15 +7,8 @@ class AbstractPlayer(ABC): - def __init__( - self, - p_type: PlayerType, - race: Race = None, - name: str = None, - difficulty=None, - ai_build=None, - fullscreen=False + self, p_type: PlayerType, race: Race = None, name: str = None, difficulty=None, ai_build=None, fullscreen=False ): 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)}" @@ -49,7 +42,6 @@ def needs_sc2(self): class Human(AbstractPlayer): - def __init__(self, race, name=None, fullscreen=False): super().__init__(PlayerType.Participant, race, name=name, fullscreen=fullscreen) @@ -60,7 +52,6 @@ def __str__(self): class Bot(AbstractPlayer): - def __init__(self, race, ai, name=None, fullscreen=False): """ AI can be None if this player object is just used to inform the @@ -77,7 +68,6 @@ def __str__(self): class Computer(AbstractPlayer): - def __init__(self, race, difficulty=Difficulty.Easy, ai_build=AIBuild.RandomBuild): super().__init__(PlayerType.Computer, race, difficulty=difficulty, ai_build=ai_build) @@ -86,7 +76,6 @@ def __str__(self): class Observer(AbstractPlayer): - def __init__(self): super().__init__(PlayerType.Observer) @@ -95,7 +84,6 @@ def __str__(self): class Player(AbstractPlayer): - def __init__(self, player_id, p_type, requested_race, difficulty=None, actual_race=None, name=None, ai_build=None): super().__init__(p_type, requested_race, difficulty=difficulty, name=name, ai_build=ai_build) self.id: int = player_id @@ -163,11 +151,9 @@ def __repr__(self): 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: Union[int, str], matchport: Union[int, str], hostaddress: str, realtime: bool = False + ) -> List[str]: """ :param sc2port: the port that the launched sc2 instance listens to diff --git a/sc2/position.py b/sc2/position.py index 67d802c7..5f79f6c0 100644 --- a/sc2/position.py +++ b/sc2/position.py @@ -19,7 +19,6 @@ def _sign(num): class Pointlike(tuple): - @property def position(self) -> Pointlike: return self @@ -43,7 +42,7 @@ 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]: """This returns the target points sorted as list. @@ -99,14 +98,14 @@ 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): """ :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: """ @@ -125,7 +124,7 @@ 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): @@ -140,7 +139,6 @@ def __hash__(self): # pylint: disable=R0904 class Point2(Pointlike): - @classmethod def from_proto(cls, data) -> Point2: """ @@ -163,12 +161,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 @@ -225,24 +223,24 @@ def circle_intersection(self, p: Point2, r: Union[int, float]) -> Set[Point2]: :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 @@ -301,7 +299,7 @@ def is_same_as(self, other: Point2, dist=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: @@ -322,7 +320,6 @@ def center(points: List[Point2]) -> Point2: class Point3(Point2): - @classmethod def from_proto(cls, data) -> Point3: """ @@ -353,7 +350,6 @@ def __add__(self, other: Union[Point2, Point3]) -> Point3: class Size(Point2): - @property def width(self) -> float: return self[0] @@ -364,7 +360,6 @@ def height(self) -> float: class Rect(tuple): - @classmethod def from_proto(cls, data): """ @@ -391,12 +386,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 diff --git a/sc2/protocol.py b/sc2/protocol.py index 8fc72684..f0083661 100644 --- a/sc2/protocol.py +++ b/sc2/protocol.py @@ -10,18 +10,16 @@ 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): """ A class for communicating with an SCII application. @@ -37,7 +35,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 +44,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: @@ -83,5 +81,5 @@ async def ping(self): return result async def quit(self): - with suppress(ConnectionAlreadyClosed, ConnectionResetError): + with suppress(ConnectionAlreadyClosedError, ConnectionResetError): await self._execute(quit=sc_pb.RequestQuit()) diff --git a/sc2/proxy.py b/sc2/proxy.py index 2e204178..cf51ec1f 100644 --- a/sc2/proxy.py +++ b/sc2/proxy.py @@ -5,6 +5,7 @@ import subprocess import time import traceback +from pathlib import Path from aiohttp import WSMsgType, web from loguru import logger @@ -91,23 +92,19 @@ 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): @@ -130,7 +127,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() @@ -185,7 +181,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 +205,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() diff --git a/sc2/renderer.py b/sc2/renderer.py index 60aceb0c..a3a4f8c4 100644 --- a/sc2/renderer.py +++ b/sc2/renderer.py @@ -6,7 +6,6 @@ class Renderer: - def __init__(self, client, map_size, minimap_size): self._client = client diff --git a/sc2/sc2process.py b/sc2/sc2process.py index 1c25d9c2..4b49a621 100644 --- a/sc2/sc2process.py +++ b/sc2/sc2process.py @@ -8,6 +8,7 @@ import tempfile import time from contextlib import suppress +from pathlib import Path from typing import Any, Dict, List, Optional, Tuple, Union import aiohttp @@ -20,7 +21,7 @@ from sc2.versions import VERSIONS -class kill_switch: +class KillSwitch: _to_kill: List[Any] = [] @classmethod @@ -94,12 +95,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) @@ -115,7 +116,7 @@ def signal_handler(*_args): async def __aexit__(self, *args): await self._close_connection() - kill_switch.kill_all() + KillSwitch.kill_all() signal.signal(signal.SIGINT, signal.SIG_DFL) @property @@ -129,7 +130,7 @@ def versions(self): return VERSIONS def find_data_hash(self, target_sc2_version: str) -> Optional[str]: - """ Returns the data hash from the matching version string. """ + """Returns the data hash from the matching version string.""" version: dict for version in self.versions: if version["label"] == target_sc2_version: @@ -161,11 +162,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,7 +195,7 @@ def special_match(strg: str): args, cwd=sc2_cwd, # Suppress Wine error messages - stderr=subprocess.DEVNULL + stderr=subprocess.DEVNULL, # , env=run_config.env ) @@ -258,11 +256,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/unit.py b/sc2/unit.py index 01d65d28..ad58469d 100644 --- a/sc2/unit.py +++ b/sc2/unit.py @@ -126,73 +126,73 @@ 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. """ + """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 @@ -211,23 +211,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 +236,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 +245,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 +258,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 +267,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 +276,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: @@ -299,12 +299,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,7 +316,7 @@ 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: @@ -375,49 +375,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 +433,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,37 +504,37 @@ 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. """ + """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: @@ -544,7 +544,7 @@ def distance_to(self, p: Union[Unit, Point2]) -> float: :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: @@ -572,8 +572,8 @@ 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( @@ -589,22 +589,18 @@ 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 @@ -652,7 +648,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,17 +671,19 @@ 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] = ( 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]] = [] @@ -697,8 +696,9 @@ 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 @@ -711,12 +711,14 @@ def calculate_damage_vs_target( # 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 @@ -772,7 +774,8 @@ def calculate_damage_vs_target( 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 +799,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 +825,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 @@ -862,17 +867,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 +889,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. """ + """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 +939,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 +982,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 +1003,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 @@ -1023,7 +1028,7 @@ def buff_duration_max(self) -> int: @cached_property def orders(self) -> List[UnitOrder]: - """ Returns the a list of the current orders. """ + """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] @@ -1040,7 +1045,7 @@ 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: @@ -1113,17 +1118,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 @@ -1150,12 +1155,12 @@ def add_on_position(self) -> Point2: @cached_property def passengers(self) -> Set[Unit]: - """ Returns the units inside a Bunker, CommandCenter, PlanetaryFortress, Medivac, Nydus, Overlord or WarpPrism. """ + """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. """ + """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 +1171,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 @@ -1230,7 +1235,7 @@ def engaged_target_tag(self) -> int: @cached_property def rally_targets(self) -> List[RallyTarget]: - """ Returns the queue of rallytargets of the structure. """ + """Returns the queue of rallytargets of the structure.""" return [RallyTarget.from_proto(rally_target) for rally_target in self._proto.rally_targets] # Unit functions @@ -1458,7 +1463,7 @@ def __call__( subtract_supply: bool = False, can_afford_check: bool = False, ) -> Union[UnitCommand, bool]: - """ Deprecated: Stop using self.do() - This may be removed in the future. + """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..b84e2417 100644 --- a/sc2/unit_command.py +++ b/sc2/unit_command.py @@ -11,7 +11,6 @@ class UnitCommand: - def __init__(self, ability: AbilityId, unit: Unit, target: Union[Unit, Point2] = None, queue: bool = False): """ :param ability: diff --git a/sc2/units.py b/sc2/units.py index 377424ef..b6ce714d 100644 --- a/sc2/units.py +++ b/sc2/units.py @@ -20,7 +20,7 @@ class Units(list): @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)) + return cls(Unit(raw_unit, bot_object=bot_object) for raw_unit in units) def __init__(self, units: Iterable[Unit], bot_object: BotAI): """ @@ -144,7 +144,7 @@ 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: @@ -191,7 +191,7 @@ 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: @@ -210,7 +210,7 @@ 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: @@ -277,7 +277,8 @@ 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) @@ -302,7 +303,8 @@ 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) @@ -331,8 +333,11 @@ 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) @@ -393,7 +398,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,8 +417,9 @@ 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]: @@ -587,8 +595,8 @@ 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} unit_data = self._bot_object.game_data.units @@ -596,8 +604,8 @@ def same_tech(self, other: Set[UnitTypeId]) -> Units: 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: @@ -628,13 +636,13 @@ def same_unit(self, other: Union[UnitTypeId, Iterable[UnitTypeId]]) -> Units: 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 +653,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. """ + """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 +729,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/test/autotest_bot.py b/test/autotest_bot.py index d1570f08..fe71bf6e 100644 --- a/test/autotest_bot.py +++ b/test/autotest_bot.py @@ -1,7 +1,7 @@ -import os import sys +from pathlib import Path -sys.path.append(os.path.join(os.path.dirname(__file__), "..")) +sys.path.append(Path(__file__).parent) from loguru import logger @@ -19,7 +19,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 +27,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 +68,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 +159,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 +375,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,7 +482,6 @@ 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) 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_distance_two_points.py b/test/benchmark_distance_two_points.py index 986d5183..66c89640 100644 --- a/test/benchmark_distance_two_points.py +++ b/test/benchmark_distance_two_points.py @@ -10,15 +10,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: @@ -32,25 +32,25 @@ def distance_to_math_hypot(s, p): def distance_scipy_euclidean(p1, p2) -> Union[int, float]: - """ Distance calculation using scipy """ + """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)) + """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) + """Distance calculation using numpy""" + return np.sum((p1 - p2) ** 2, axis=0) # @njit diff --git a/test/benchmark_distances_cdist.py b/test/benchmark_distances_cdist.py index bbca2f78..8541243b 100644 --- a/test/benchmark_distances_cdist.py +++ b/test/benchmark_distances_cdist.py @@ -124,8 +124,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 b3a55a40..d11fb233 100644 --- a/test/benchmark_distances_points_to_point.py +++ b/test/benchmark_distances_points_to_point.py @@ -15,28 +15,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 +46,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 +83,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] diff --git a/test/benchmark_distances_units.py b/test/benchmark_distances_units.py index 3b01e052..1006355e 100644 --- a/test/benchmark_distances_units.py +++ b/test/benchmark_distances_units.py @@ -30,8 +30,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 +40,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/damagetest_bot.py b/test/damagetest_bot.py index c2ece1c9..09f46a6a 100644 --- a/test/damagetest_bot.py +++ b/test/damagetest_bot.py @@ -1,7 +1,7 @@ -import os import sys +from pathlib import Path -sys.path.append(os.path.join(os.path.dirname(__file__), "..")) +sys.path.append(Path(__file__).parent) import math from loguru import logger @@ -16,7 +16,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 +23,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 +53,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 +186,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 +241,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 +261,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,7 +318,6 @@ 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) diff --git a/test/generate_pickle_files_bot.py b/test/generate_pickle_files_bot.py index fd6143cd..d190b379 100644 --- a/test/generate_pickle_files_bot.py +++ b/test/generate_pickle_files_bot.py @@ -3,8 +3,8 @@ These will then be used to run tests from the test script "test_pickled_data.py" """ import lzma -import os import pickle +from pathlib import Path from typing import Set from loguru import logger @@ -23,7 +23,6 @@ class ExporterBot(BotAI): - def __init__(self): BotAI.__init__(self) self.map_name: str = None @@ -32,17 +31,17 @@ async def on_step(self, iteration): pass def get_pickle_file_path(self) -> str: - folder_path = os.path.dirname(__file__) + 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__) + 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 +59,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) @@ -78,10 +77,12 @@ async def on_start(self): 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 +101,6 @@ async def on_start(self): def main(): - maps_ = [ "16-BitLE", "2000AtmospheresAIE", @@ -211,7 +211,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..c7ceaaa0 100644 --- a/test/queries_test_bot.py +++ b/test/queries_test_bot.py @@ -4,10 +4,10 @@ self.can_place (RequestQueryBuildingPlacement) TODO: self.client.query_pathing (RequestQueryPathing) """ -import os import sys +from pathlib import Path -sys.path.append(os.path.join(os.path.dirname(__file__), "..")) +sys.path.append(Path(__file__).parent) from typing import List, Union @@ -24,7 +24,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 +45,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 @@ -251,7 +250,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..6226ef43 100644 --- a/test/real_time_worker_production.py +++ b/test/real_time_worker_production.py @@ -1,10 +1,10 @@ """ This bot tests if on 'realtime=True' any nexus has more than 1 probe in the queue. """ -import os import sys +from pathlib import Path -sys.path.append(os.path.join(os.path.dirname(__file__), "..")) +sys.path.append(Path(__file__).parent) import asyncio from loguru import logger @@ -22,7 +22,6 @@ class RealTimeTestBot(BotAI): - async def on_before_start(self): mf = self.mineral_field for w in self.workers: @@ -36,7 +35,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): @@ -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..1df70f0a 100644 --- a/test/run_example_bots_vs_computer.py +++ b/test/run_example_bots_vs_computer.py @@ -1,10 +1,10 @@ """ This script makes sure to run all bots in the examples folder to check if they can launch. """ -import os import sys +from pathlib import Path -sys.path.append(os.path.join(os.path.dirname(__file__), "..")) +sys.path.append(Path(__file__).parent) import asyncio from importlib import import_module from typing import List, Type @@ -25,90 +25,90 @@ { "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", }, ] @@ -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..f94ed4ad 100644 --- a/test/run_example_bots_vs_each_other.py +++ b/test/run_example_bots_vs_each_other.py @@ -1,10 +1,10 @@ """ 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 +from pathlib import Path -sys.path.append(os.path.join(os.path.dirname(__file__), "..")) +sys.path.append(Path(__file__).parent) import asyncio from importlib import import_module from itertools import combinations @@ -27,69 +27,69 @@ { "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", }, ] @@ -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 a8326726..acc172d9 100644 --- a/test/test_pickled_data.py +++ b/test/test_pickled_data.py @@ -72,7 +72,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: + if sys.version_info < (3, 10): assert api_implementation.Type() == "cpp" @@ -175,7 +175,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 @@ -910,7 +910,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)) @@ -937,7 +937,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: @@ -965,7 +965,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 bot: BotAI = get_map_specific_bot(random.choice(MAPS)) @@ -1000,10 +1000,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} @@ -1012,20 +1012,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 @@ -1067,13 +1067,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 @@ -1082,7 +1082,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 3c9f2b0a..1d6d160d 100644 --- a/test/test_pickled_ramp.py +++ b/test/test_pickled_ramp.py @@ -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") @@ -108,8 +108,8 @@ def test_bot_ai(self, map_path: Path): 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 poetry.\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: 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 47139a5c..0a366d13 100644 --- a/test/travis_test_script.py +++ b/test/travis_test_script.py @@ -71,8 +71,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..f3bbb3c3 100644 --- a/test/upgradestest_bot.py +++ b/test/upgradestest_bot.py @@ -1,7 +1,7 @@ -import os import sys +from pathlib import Path -sys.path.append(os.path.join(os.path.dirname(__file__), "..")) +sys.path.append(Path(__file__).parent) from typing import Dict, List from loguru import logger @@ -20,7 +20,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 +28,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 +63,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): @@ -109,7 +109,6 @@ async def test_botai_actions1(self): 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) @@ -153,7 +152,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,7 +173,6 @@ 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)