from math import floor from typing import TYPE_CHECKING, Set, Optional, Callable, Dict, Tuple, Iterable from BaseClasses import CollectionState, Location from .item.item_groups import kerrigan_non_ulimates, kerrigan_logic_active_abilities from .item.item_names import PROGRESSIVE_PROTOSS_AIR_WEAPON, PROGRESSIVE_PROTOSS_AIR_ARMOR, PROGRESSIVE_PROTOSS_SHIELDS from .options import ( RequiredTactics, kerrigan_unit_available, AllInMap, GrantStoryTech, GrantStoryLevels, SpearOfAdunPassiveAbilityPresence, SpearOfAdunPresence, MissionOrder, EnableMorphling, NovaGhostOfAChanceVariant, get_enabled_campaigns, get_enabled_races, ) from .item.item_tables import ( tvx_defense_ratings, tvz_defense_ratings, tvx_air_defense_ratings, kerrigan_levels, get_full_item_list, zvx_air_defense_ratings, zvx_defense_ratings, pvx_defense_ratings, pvz_defense_ratings, no_logic_basic_units, advanced_basic_units, basic_units, upgrade_bundle_inverted_lookup, WEAPON_ARMOR_UPGRADE_MAX_LEVEL, soa_ultimate_ratings, soa_energy_ratings, terran_passive_ratings, soa_passive_ratings, zerg_passive_ratings, protoss_passive_ratings, ) from .mission_tables import SC2Race, SC2Campaign from .item import item_groups, item_names if TYPE_CHECKING: from . import SC2World def min2(a: int, b: int) -> int: """`min()` that only takes two values; faster than baseline int by about 2x""" if a <= b: return a return b class SC2Logic: def __init__(self, world: Optional["SC2World"]) -> None: # Note: Don't store a reference to the world so we can cache this object on the world object self.player = -1 if world is None else world.player self.logic_level: int = world.options.required_tactics.value if world else RequiredTactics.default self.advanced_tactics = self.logic_level != RequiredTactics.option_standard self.take_over_ai_allies = bool(world and world.options.take_over_ai_allies) self.kerrigan_unit_available = ( (True if world is None else (world.options.kerrigan_presence.value in kerrigan_unit_available)) and SC2Campaign.HOTS in get_enabled_campaigns(world) and SC2Race.ZERG in get_enabled_races(world) ) self.kerrigan_levels_per_mission_completed = 0 if world is None else world.options.kerrigan_levels_per_mission_completed.value self.kerrigan_levels_per_mission_completed_cap = -1 if world is None else world.options.kerrigan_levels_per_mission_completed_cap.value self.kerrigan_total_level_cap = -1 if world is None else world.options.kerrigan_total_level_cap.value self.morphling_enabled = False if world is None else (world.options.enable_morphling.value == EnableMorphling.option_true) self.grant_story_tech = GrantStoryTech.option_no_grant if world is None else (world.options.grant_story_tech.value) self.story_levels_granted = False if world is None else (world.options.grant_story_levels.value != GrantStoryLevels.option_disabled) self.basic_terran_units = get_basic_units(self.logic_level, SC2Race.TERRAN) self.basic_zerg_units = get_basic_units(self.logic_level, SC2Race.ZERG) self.basic_protoss_units = get_basic_units(self.logic_level, SC2Race.PROTOSS) self.spear_of_adun_presence = SpearOfAdunPresence.default if world is None else world.options.spear_of_adun_presence.value self.spear_of_adun_passive_presence = ( SpearOfAdunPassiveAbilityPresence.default if world is None else world.options.spear_of_adun_passive_ability_presence.value ) self.enabled_campaigns = get_enabled_campaigns(world) self.mission_order = MissionOrder.default if world is None else world.options.mission_order.value self.generic_upgrade_missions = 0 if world is None else world.options.generic_upgrade_missions.value self.all_in_map = AllInMap.option_ground if world is None else world.options.all_in_map.value self.nova_ghost_of_a_chance_variant = NovaGhostOfAChanceVariant.option_wol if world is None else world.options.nova_ghost_of_a_chance_variant.value self.war_council_upgrades = True if world is None else not world.options.war_council_nerfs.value self.base_power_rating = 2 if self.advanced_tactics else 0 # Must be set externally for accurate logic checking of upgrade level when generic_upgrade_missions is checked self.total_mission_count = 1 # Must be set externally self.nova_used = True # Conditionally set to False by the world after culling items self.has_barracks_unit: bool = True self.has_factory_unit: bool = True self.has_starport_unit: bool = True self.has_zerg_melee_unit: bool = True self.has_zerg_ranged_unit: bool = True self.has_zerg_air_unit: bool = True self.has_protoss_ground_unit: bool = True self.has_protoss_air_unit: bool = True self.unit_count_functions: Dict[Tuple[SC2Race, int], Callable[[CollectionState], bool]] = {} """Cache of logic functions used by any_units logic level""" # Super Globals def is_item_placement(self, state: CollectionState) -> bool: """ Tells if it's item placement or item pool filter :return: True for item placement, False for pool filter """ # has_group with count = 0 is always true for item placement and always false for SC2 item filtering return state.has_group("Missions", self.player, 0) def get_very_hard_required_upgrade_level(self) -> bool: return 2 if self.advanced_tactics else 3 def weapon_armor_upgrade_count(self, upgrade_item: str, state: CollectionState) -> int: assert upgrade_item in upgrade_bundle_inverted_lookup.keys() count: int = 0 if self.generic_upgrade_missions > 0: if (not self.is_item_placement(state)) or self.logic_level == RequiredTactics.option_no_logic: # Item pool filtering, W/A upgrades aren't items # No Logic: Don't care about W/A in this case return WEAPON_ARMOR_UPGRADE_MAX_LEVEL else: count += floor((100 / self.generic_upgrade_missions) * (state.count_group("Missions", self.player) / self.total_mission_count)) count += state.count(upgrade_item, self.player) count += state.count_from_list(upgrade_bundle_inverted_lookup[upgrade_item], self.player) if upgrade_item == item_names.PROGRESSIVE_PROTOSS_SHIELDS: count += max( state.count(item_names.PROGRESSIVE_PROTOSS_GROUND_UPGRADE, self.player), state.count(item_names.PROGRESSIVE_PROTOSS_AIR_UPGRADE, self.player), ) if upgrade_item in item_groups.protoss_generic_upgrades and state.has(item_names.QUATRO, self.player): count += 1 return count def soa_power_rating(self, state: CollectionState) -> bool: power_rating = 0 # Spear of Adun Ultimates (Strongest) for item, rating in soa_ultimate_ratings.items(): if state.has(item, self.player): power_rating += rating break # Spear of Adun ability that consumes energy (Strongest, then second strongest) found_main_weapon = False for item, rating in soa_energy_ratings.items(): count = 1 if item == item_names.SOA_PROGRESSIVE_PROXY_PYLON: count = 2 if state.has(item, self.player, count): if not found_main_weapon: power_rating += rating found_main_weapon = True else: power_rating += rating // 2 break # Mass Recall (Negligible energy cost) if state.has(item_names.SOA_MASS_RECALL, self.player): power_rating += 2 return power_rating # Global Terran def terran_power_rating(self, state: CollectionState) -> int: power_score = self.base_power_rating # Passive Score (Economic upgrades and global army upgrades) power_score += sum((rating for item, rating in terran_passive_ratings.items() if state.has(item, self.player))) # Spear of Adun if self.spear_of_adun_presence == SpearOfAdunPresence.option_everywhere: power_score += self.soa_power_rating(state) if self.spear_of_adun_passive_presence == SpearOfAdunPassiveAbilityPresence.option_everywhere: power_score += sum((rating for item, rating in soa_passive_ratings.items() if state.has(item, self.player))) return power_score def terran_army_weapon_armor_upgrade_min_level(self, state: CollectionState) -> int: """ Minimum W/A upgrade level for unit classes present in the world """ count: int = WEAPON_ARMOR_UPGRADE_MAX_LEVEL if self.has_barracks_unit: count = min( count, self.weapon_armor_upgrade_count(item_names.PROGRESSIVE_TERRAN_INFANTRY_WEAPON, state), self.weapon_armor_upgrade_count(item_names.PROGRESSIVE_TERRAN_INFANTRY_ARMOR, state), ) if self.has_factory_unit: count = min( count, self.weapon_armor_upgrade_count(item_names.PROGRESSIVE_TERRAN_VEHICLE_WEAPON, state), self.weapon_armor_upgrade_count(item_names.PROGRESSIVE_TERRAN_VEHICLE_ARMOR, state), ) if self.has_starport_unit: count = min( count, self.weapon_armor_upgrade_count(item_names.PROGRESSIVE_TERRAN_SHIP_WEAPON, state), self.weapon_armor_upgrade_count(item_names.PROGRESSIVE_TERRAN_SHIP_ARMOR, state), ) return count def terran_very_hard_mission_weapon_armor_level(self, state: CollectionState) -> bool: return self.terran_army_weapon_armor_upgrade_min_level(state) >= self.get_very_hard_required_upgrade_level() # WoL def terran_common_unit(self, state: CollectionState) -> bool: return state.has_any(self.basic_terran_units, self.player) def terran_early_tech(self, state: CollectionState) -> bool: """ Basic combat unit that can be deployed quickly from mission start :param state :return: """ return state.has_any( {item_names.MARINE, item_names.DOMINION_TROOPER, item_names.FIREBAT, item_names.MARAUDER, item_names.REAPER, item_names.HELLION}, self.player, ) or ( self.advanced_tactics and state.has_any({item_names.GOLIATH, item_names.DIAMONDBACK, item_names.VIKING, item_names.BANSHEE}, self.player) ) def terran_air(self, state: CollectionState) -> bool: """ Air units or drops on advanced tactics """ return ( state.has_any({item_names.VIKING, item_names.WRAITH, item_names.BANSHEE, item_names.BATTLECRUISER}, self.player) or state.has_all((item_names.VALKYRIE, item_names.VALKYRIE_FLECHETTE_MISSILES), self.player) or state.has_all((item_names.LIBERATOR, item_names.LIBERATOR_RAID_ARTILLERY), self.player) or ( self.advanced_tactics and ( (state.has_any({item_names.HERCULES, item_names.MEDIVAC}, self.player) and self.terran_common_unit(state)) or (state.has_all((item_names.RAVEN, item_names.RAVEN_HUNTER_SEEKER_WEAPON), self.player)) ) ) ) def terran_air_anti_air(self, state: CollectionState) -> bool: """ Air-to-air """ return ( state.has(item_names.VIKING, self.player) or state.has_all({item_names.WRAITH, item_names.WRAITH_ADVANCED_LASER_TECHNOLOGY}, self.player) or state.has_all({item_names.BATTLECRUISER, item_names.BATTLECRUISER_ATX_LASER_BATTERY}, self.player) or ( self.advanced_tactics and state.has_any({item_names.WRAITH, item_names.VALKYRIE, item_names.BATTLECRUISER}, self.player) and self.weapon_armor_upgrade_count(item_names.PROGRESSIVE_TERRAN_SHIP_WEAPON, state) >= 2 ) ) def terran_any_air_unit(self, state: CollectionState) -> bool: return state.has_any( { item_names.VIKING, item_names.MEDIVAC, item_names.RAVEN, item_names.BANSHEE, item_names.SCIENCE_VESSEL, item_names.BATTLECRUISER, item_names.WRAITH, item_names.HERCULES, item_names.LIBERATOR, item_names.VALKYRIE, item_names.SKY_FURY, item_names.NIGHT_HAWK, item_names.EMPERORS_GUARDIAN, item_names.NIGHT_WOLF, item_names.PRIDE_OF_AUGUSTRGRAD, }, self.player, ) def terran_competent_ground_to_air(self, state: CollectionState) -> bool: """ Ground-to-air """ return ( state.has(item_names.GOLIATH, self.player) or ( state.has_any({item_names.MARINE, item_names.DOMINION_TROOPER}, self.player) and self.terran_bio_heal(state) and self.weapon_armor_upgrade_count(item_names.PROGRESSIVE_TERRAN_INFANTRY_WEAPON, state) >= 2 ) or self.advanced_tactics and ( state.has(item_names.CYCLONE, self.player) or state.has_all((item_names.THOR, item_names.THOR_PROGRESSIVE_HIGH_IMPACT_PAYLOAD), self.player) ) ) def terran_competent_anti_air(self, state: CollectionState) -> bool: """ Good AA unit """ return self.terran_competent_ground_to_air(state) or self.terran_air_anti_air(state) def terran_any_anti_air(self, state: CollectionState) -> bool: return ( state.has_any( ( # Barracks item_names.MARINE, item_names.WAR_PIGS, item_names.SON_OF_KORHAL, item_names.DOMINION_TROOPER, item_names.GHOST, item_names.SPECTRE, item_names.EMPERORS_SHADOW, # Factory item_names.GOLIATH, item_names.SPARTAN_COMPANY, item_names.BULWARK_COMPANY, item_names.CYCLONE, item_names.WIDOW_MINE, item_names.THOR, item_names.JOTUN, item_names.BLACKHAMMER, # Ships item_names.WRAITH, item_names.WINGED_NIGHTMARES, item_names.NIGHT_HAWK, item_names.VIKING, item_names.HELS_ANGELS, item_names.SKY_FURY, item_names.LIBERATOR, item_names.MIDNIGHT_RIDERS, item_names.EMPERORS_GUARDIAN, item_names.VALKYRIE, item_names.BRYNHILDS, item_names.BATTLECRUISER, item_names.JACKSONS_REVENGE, item_names.PRIDE_OF_AUGUSTRGRAD, item_names.RAVEN, # Buildings item_names.MISSILE_TURRET, ), self.player, ) or state.has_all((item_names.REAPER, item_names.REAPER_JET_PACK_OVERDRIVE), self.player) or state.has_all((item_names.PLANETARY_FORTRESS, item_names.PLANETARY_FORTRESS_IBIKS_TRACKING_SCANNERS), self.player) or ( state.has(item_names.MEDIVAC, self.player) and state.has_any((item_names.SIEGE_TANK, item_names.SIEGE_BREAKERS, item_names.SHOCK_DIVISION), self.player) and state.count(item_names.SIEGE_TANK_PROGRESSIVE_TRANSPORT_HOOK, self.player) >= 2 ) ) def terran_any_anti_air_or_science_vessels(self, state: CollectionState) -> bool: return self.terran_any_anti_air(state) or state.has(item_names.SCIENCE_VESSEL, self.player) def terran_moderate_anti_air(self, state: CollectionState) -> bool: return self.terran_competent_anti_air(state) or ( state.has_any( ( item_names.MARINE, item_names.DOMINION_TROOPER, item_names.THOR, item_names.CYCLONE, item_names.BATTLECRUISER, item_names.WRAITH, item_names.VALKYRIE, ), self.player, ) or ( state.has_all((item_names.MEDIVAC, item_names.SIEGE_TANK), self.player) and state.count(item_names.SIEGE_TANK_PROGRESSIVE_TRANSPORT_HOOK, self.player) >= 2 ) or (self.advanced_tactics and state.has_any((item_names.GHOST, item_names.SPECTRE, item_names.LIBERATOR), self.player)) ) def terran_basic_anti_air(self, state: CollectionState) -> bool: """ Basic AA to deal with few air units """ return ( state.has_any( ( item_names.MISSILE_TURRET, item_names.WAR_PIGS, item_names.SPARTAN_COMPANY, item_names.HELS_ANGELS, item_names.WINGED_NIGHTMARES, item_names.BRYNHILDS, item_names.SKY_FURY, item_names.SON_OF_KORHAL, item_names.BULWARK_COMPANY, ), self.player, ) or self.terran_moderate_anti_air(state) or self.advanced_tactics and ( state.has_any( ( item_names.WIDOW_MINE, item_names.PRIDE_OF_AUGUSTRGRAD, item_names.BLACKHAMMER, item_names.EMPERORS_SHADOW, item_names.EMPERORS_GUARDIAN, item_names.NIGHT_HAWK, ), self.player, ) ) ) def terran_defense_rating(self, state: CollectionState, zerg_enemy: bool, air_enemy: bool = True) -> int: """ Ability to handle defensive missions :param state: :param zerg_enemy: Whether the enemy is zerg :param air_enemy: Whether the enemy attacks with air :return: """ defense_score = sum((tvx_defense_ratings[item] for item in tvx_defense_ratings if state.has(item, self.player))) # Manned Bunker if state.has_any({item_names.MARINE, item_names.DOMINION_TROOPER, item_names.MARAUDER}, self.player) and state.has( item_names.BUNKER, self.player ): defense_score += 3 elif zerg_enemy and state.has(item_names.FIREBAT, self.player) and state.has(item_names.BUNKER, self.player): defense_score += 2 # Siege Tank upgrades if state.has_all({item_names.SIEGE_TANK, item_names.SIEGE_TANK_MAELSTROM_ROUNDS}, self.player): defense_score += 2 if state.has_all({item_names.SIEGE_TANK, item_names.SIEGE_TANK_GRADUATING_RANGE}, self.player): defense_score += 1 # Widow Mine upgrade if state.has_all({item_names.WIDOW_MINE, item_names.WIDOW_MINE_CONCEALMENT}, self.player): defense_score += 1 # Viking with splash if state.has_all({item_names.VIKING, item_names.VIKING_SHREDDER_ROUNDS}, self.player): defense_score += 2 # General enemy-based rules if zerg_enemy: defense_score += sum((tvz_defense_ratings[item] for item in tvz_defense_ratings if state.has(item, self.player))) if air_enemy: # Capped at 2 defense_score += min(sum((tvx_air_defense_ratings[item] for item in tvx_air_defense_ratings if state.has(item, self.player))), 2) if air_enemy and zerg_enemy and state.has(item_names.VALKYRIE, self.player): # Valkyries shred mass Mutas, the most common air enemy that's massed in these cases defense_score += 2 # Advanced Tactics bumps defense rating requirements down by 2 if self.advanced_tactics: defense_score += 2 return defense_score def terran_competent_comp(self, state: CollectionState, upgrade_level: int = 1) -> bool: # All competent comps require anti-air if not self.terran_competent_anti_air(state): return False # Infantry with Healing infantry_weapons = self.weapon_armor_upgrade_count(item_names.PROGRESSIVE_TERRAN_INFANTRY_WEAPON, state) infantry_armor = self.weapon_armor_upgrade_count(item_names.PROGRESSIVE_TERRAN_INFANTRY_ARMOR, state) infantry = state.has_any({item_names.MARINE, item_names.DOMINION_TROOPER, item_names.MARAUDER}, self.player) if infantry_weapons >= upgrade_level + 1 and infantry_armor >= upgrade_level and infantry and self.terran_bio_heal(state): return True # Mass Air-To-Ground ship_weapons = self.weapon_armor_upgrade_count(item_names.PROGRESSIVE_TERRAN_SHIP_WEAPON, state) ship_armor = self.weapon_armor_upgrade_count(item_names.PROGRESSIVE_TERRAN_SHIP_ARMOR, state) if ship_weapons >= upgrade_level and ship_armor >= upgrade_level: air = ( state.has_any({item_names.BANSHEE, item_names.BATTLECRUISER}, self.player) or state.has_all({item_names.LIBERATOR, item_names.LIBERATOR_RAID_ARTILLERY}, self.player) or state.has_all({item_names.WRAITH, item_names.WRAITH_ADVANCED_LASER_TECHNOLOGY}, self.player) or state.has_all({item_names.VALKYRIE, item_names.VALKYRIE_FLECHETTE_MISSILES}, self.player) and ship_weapons >= 2 ) if air and self.terran_mineral_dump(state): return True # Strong Mech vehicle_weapons = self.weapon_armor_upgrade_count(item_names.PROGRESSIVE_TERRAN_VEHICLE_WEAPON, state) vehicle_armor = self.weapon_armor_upgrade_count(item_names.PROGRESSIVE_TERRAN_VEHICLE_ARMOR, state) if vehicle_weapons >= upgrade_level and vehicle_armor >= upgrade_level: strong_vehicle = state.has_any({item_names.THOR, item_names.SIEGE_TANK}, self.player) light_frontline = state.has_any( {item_names.MARINE, item_names.DOMINION_TROOPER, item_names.HELLION, item_names.VULTURE}, self.player ) or state.has_all({item_names.REAPER, item_names.REAPER_RESOURCE_EFFICIENCY}, self.player) if strong_vehicle and light_frontline: return True # Mech with Healing vehicle = state.has_any({item_names.GOLIATH, item_names.WARHOUND}, self.player) micro_gas_vehicle = self.advanced_tactics and state.has_any({item_names.DIAMONDBACK, item_names.CYCLONE}, self.player) if self.terran_sustainable_mech_heal(state) and (vehicle or (micro_gas_vehicle and light_frontline)): return True return False def terran_mineral_dump(self, state: CollectionState) -> bool: """ Can build something using only minerals """ return ( state.has_any({item_names.MARINE, item_names.VULTURE, item_names.HELLION, item_names.SON_OF_KORHAL}, self.player) or state.has_all({item_names.REAPER, item_names.REAPER_RESOURCE_EFFICIENCY}, self.player) or (self.advanced_tactics and state.has_any({item_names.PERDITION_TURRET, item_names.DEVASTATOR_TURRET}, self.player)) ) def terran_beats_protoss_deathball(self, state: CollectionState) -> bool: """ Ability to deal with Immortals, Colossi with some air support """ return ( ( state.has_any({item_names.BANSHEE, item_names.BATTLECRUISER}, self.player) or state.has_all({item_names.LIBERATOR, item_names.LIBERATOR_RAID_ARTILLERY}, self.player) ) and self.terran_competent_anti_air(state) or self.terran_competent_comp(state) and self.terran_air_anti_air(state) ) and self.terran_army_weapon_armor_upgrade_min_level(state) >= 2 def marine_medic_upgrade(self, state: CollectionState) -> bool: """ Infantry upgrade to infantry-only no-build segments """ return ( state.has_any({item_names.MARINE_COMBAT_SHIELD, item_names.MARINE_MAGRAIL_MUNITIONS, item_names.MEDIC_STABILIZER_MEDPACKS}, self.player) or (state.count(item_names.MARINE_PROGRESSIVE_STIMPACK, self.player) >= 2 and state.has_group("Missions", self.player, 1)) or self.advanced_tactics and state.has(item_names.MARINE_LASER_TARGETING_SYSTEM, self.player) ) def marine_medic_firebat_upgrade(self, state: CollectionState) -> bool: return ( self.marine_medic_upgrade(state) or state.count(item_names.FIREBAT_PROGRESSIVE_STIMPACK, self.player) >= 2 or state.has_any((item_names.FIREBAT_NANO_PROJECTORS, item_names.FIREBAT_JUGGERNAUT_PLATING), self.player) ) def terran_bio_heal(self, state: CollectionState) -> bool: """ Ability to heal bio units """ return state.has_any({item_names.MEDIC, item_names.MEDIVAC, item_names.FIELD_RESPONSE_THETA}, self.player) or ( self.advanced_tactics and state.has_all({item_names.RAVEN, item_names.RAVEN_BIO_MECHANICAL_REPAIR_DRONE}, self.player) ) def terran_base_trasher(self, state: CollectionState) -> bool: """ Can attack heavily defended bases """ if not self.terran_competent_comp(state): return False if not self.terran_very_hard_mission_weapon_armor_level(state): return False return ( state.has_all((item_names.SIEGE_TANK, item_names.SIEGE_TANK_JUMP_JETS), self.player) or state.has_all({item_names.BATTLECRUISER, item_names.BATTLECRUISER_ATX_LASER_BATTERY}, self.player) or state.has_all({item_names.LIBERATOR, item_names.LIBERATOR_RAID_ARTILLERY}, self.player) or ( self.advanced_tactics and (state.has_all({item_names.RAVEN, item_names.RAVEN_HUNTER_SEEKER_WEAPON}, self.player)) and ( state.has_all({item_names.VIKING, item_names.VIKING_SHREDDER_ROUNDS}, self.player) or state.has_all({item_names.BANSHEE, item_names.BANSHEE_SHOCKWAVE_MISSILE_BATTERY}, self.player) ) ) ) def terran_mobile_detector(self, state: CollectionState) -> bool: return state.has_any({item_names.RAVEN, item_names.SCIENCE_VESSEL, item_names.COMMAND_CENTER_SCANNER_SWEEP}, self.player) def can_nuke(self, state: CollectionState) -> bool: """ Ability to launch nukes """ return self.advanced_tactics and ( state.has_any({item_names.GHOST, item_names.SPECTRE}, self.player) or state.has_all({item_names.THOR, item_names.THOR_BUTTON_WITH_A_SKULL_ON_IT}, self.player) ) def terran_sustainable_mech_heal(self, state: CollectionState) -> bool: """ Can heal mech units without spending resources """ return ( state.has(item_names.SCIENCE_VESSEL, self.player) or ( state.has_any({item_names.MEDIC, item_names.FIELD_RESPONSE_THETA}, self.player) and state.has(item_names.MEDIC_ADAPTIVE_MEDPACKS, self.player) ) or state.count(item_names.PROGRESSIVE_REGENERATIVE_BIO_STEEL, self.player) >= 3 or ( self.advanced_tactics and ( state.has_all({item_names.RAVEN, item_names.RAVEN_BIO_MECHANICAL_REPAIR_DRONE}, self.player) or state.count(item_names.PROGRESSIVE_REGENERATIVE_BIO_STEEL, self.player) >= 2 ) ) ) def terran_cliffjumper(self, state: CollectionState) -> bool: return ( state.has(item_names.REAPER, self.player) or state.has_all({item_names.GOLIATH, item_names.GOLIATH_JUMP_JETS}, self.player) or state.has_all({item_names.SIEGE_TANK, item_names.SIEGE_TANK_JUMP_JETS}, self.player) ) def nova_any_nobuild_damage(self, state: CollectionState) -> bool: return state.has_any( ( item_names.NOVA_C20A_CANISTER_RIFLE, item_names.NOVA_HELLFIRE_SHOTGUN, item_names.NOVA_PLASMA_RIFLE, item_names.NOVA_MONOMOLECULAR_BLADE, item_names.NOVA_BLAZEFIRE_GUNBLADE, item_names.NOVA_PULSE_GRENADES, item_names.NOVA_DOMINATION, ), self.player, ) def nova_any_weapon(self, state: CollectionState) -> bool: return state.has_any( { item_names.NOVA_C20A_CANISTER_RIFLE, item_names.NOVA_HELLFIRE_SHOTGUN, item_names.NOVA_PLASMA_RIFLE, item_names.NOVA_MONOMOLECULAR_BLADE, item_names.NOVA_BLAZEFIRE_GUNBLADE, }, self.player, ) def nova_ranged_weapon(self, state: CollectionState) -> bool: return state.has_any({item_names.NOVA_C20A_CANISTER_RIFLE, item_names.NOVA_HELLFIRE_SHOTGUN, item_names.NOVA_PLASMA_RIFLE}, self.player) def nova_anti_air_weapon(self, state: CollectionState) -> bool: return state.has_any({item_names.NOVA_C20A_CANISTER_RIFLE, item_names.NOVA_PLASMA_RIFLE, item_names.NOVA_BLAZEFIRE_GUNBLADE}, self.player) def nova_splash(self, state: CollectionState) -> bool: return state.has_any({item_names.NOVA_HELLFIRE_SHOTGUN, item_names.NOVA_PULSE_GRENADES}, self.player) or ( self.advanced_tactics and state.has_any({item_names.NOVA_PLASMA_RIFLE, item_names.NOVA_MONOMOLECULAR_BLADE}, self.player) ) def nova_dash(self, state: CollectionState) -> bool: return state.has_any({item_names.NOVA_MONOMOLECULAR_BLADE, item_names.NOVA_BLINK}, self.player) def nova_full_stealth(self, state: CollectionState) -> bool: return state.count(item_names.NOVA_PROGRESSIVE_STEALTH_SUIT_MODULE, self.player) >= 2 def nova_heal(self, state: CollectionState) -> bool: return state.has_any({item_names.NOVA_ARMORED_SUIT_MODULE, item_names.NOVA_STIM_INFUSION}, self.player) def nova_escape_assist(self, state: CollectionState) -> bool: return state.has_any({item_names.NOVA_BLINK, item_names.NOVA_HOLO_DECOY, item_names.NOVA_IONIC_FORCE_FIELD}, self.player) def nova_beat_stone(self, state: CollectionState) -> bool: """ Used for any units logic for beating Stone. Shotgun may not be possible; may need feedback. """ return ( state.has_any(( item_names.NOVA_DOMINATION, item_names.NOVA_BLAZEFIRE_GUNBLADE, item_names.NOVA_C20A_CANISTER_RIFLE, ), self.player) or (( state.has_any(( item_names.NOVA_PLASMA_RIFLE, item_names.NOVA_MONOMOLECULAR_BLADE, ), self.player) or state.has_all(( item_names.NOVA_HELLFIRE_SHOTGUN, item_names.NOVA_STIM_INFUSION ), self.player) ) and state.has_any(( item_names.NOVA_JUMP_SUIT_MODULE, item_names.NOVA_ARMORED_SUIT_MODULE, item_names.NOVA_ENERGY_SUIT_MODULE, ), self.player) and state.has_any(( item_names.NOVA_FLASHBANG_GRENADES, item_names.NOVA_STIM_INFUSION, item_names.NOVA_BLINK, item_names.NOVA_IONIC_FORCE_FIELD, ), self.player) ) ) # Global Zerg def zerg_power_rating(self, state: CollectionState) -> int: power_score = self.base_power_rating # Passive Score (Economic upgrades and global army upgrades) power_score += sum((rating for item, rating in zerg_passive_ratings.items() if state.has(item, self.player))) # Spear of Adun if self.spear_of_adun_presence == SpearOfAdunPresence.option_everywhere: power_score += self.soa_power_rating(state) if self.spear_of_adun_passive_presence == SpearOfAdunPassiveAbilityPresence.option_everywhere: power_score += sum((rating for item, rating in soa_passive_ratings.items() if state.has(item, self.player))) return power_score def zerg_defense_rating(self, state: CollectionState, zerg_enemy: bool, air_enemy: bool = True) -> int: """ Ability to handle defensive missions :param state: :param zerg_enemy: Whether the enemy is zerg :param air_enemy: Whether the enemy attacks with air """ defense_score = sum((zvx_defense_ratings[item] for item in zvx_defense_ratings if state.has(item, self.player))) # Twin Drones if state.has(item_names.TWIN_DRONES, self.player): if state.has(item_names.SPINE_CRAWLER, self.player): defense_score += 1 if state.has(item_names.SPORE_CRAWLER, self.player) and air_enemy: defense_score += 1 # Impaler if self.morph_impaler(state): defense_score += 3 if state.has(item_names.IMPALER_SUNKEN_SPINES, self.player): defense_score += 1 if zerg_enemy: defense_score += -1 # Lurker if self.morph_lurker(state): defense_score += 2 if state.has(item_names.LURKER_SEISMIC_SPINES, self.player): defense_score += 2 if state.has(item_names.LURKER_ADAPTED_SPINES, self.player) and not zerg_enemy: defense_score += 1 if zerg_enemy: defense_score += 1 # Brood Lord if self.morph_brood_lord(state): defense_score += 2 # Corpser Roach if state.has_all({item_names.ROACH, item_names.ROACH_CORPSER_STRAIN}, self.player): defense_score += 1 if zerg_enemy: defense_score += 1 # Igniter if self.morph_igniter(state) and zerg_enemy: defense_score += 2 # Creep Tumors if self.spread_creep(state, False): if not zerg_enemy: defense_score += 1 if state.has(item_names.MALIGNANT_CREEP, self.player): defense_score += 1 # Infested Siege Tanks if self.zerg_infested_tank_with_ammo(state): defense_score += 5 # Infested Liberators if state.has_all((item_names.INFESTED_LIBERATOR, item_names.INFESTED_LIBERATOR_DEFENDER_MODE), self.player): defense_score += 3 # Bile Launcher upgrades if state.has_all((item_names.BILE_LAUNCHER, item_names.BILE_LAUNCHER_RAPID_BOMBARMENT), self.player): defense_score += 2 # General enemy-based rules if air_enemy: # Capped at 2 defense_score += min(sum((zvx_air_defense_ratings[item] for item in zvx_air_defense_ratings if state.has(item, self.player))), 2) # Advanced Tactics bumps defense rating requirements down by 2 if self.advanced_tactics: defense_score += 2 return defense_score def zerg_army_weapon_armor_upgrade_min_level(self, state: CollectionState) -> int: count: int = WEAPON_ARMOR_UPGRADE_MAX_LEVEL if self.has_zerg_melee_unit: count = min2(count, self.zerg_melee_weapon_armor_upgrade_min_level(state)) if self.has_zerg_ranged_unit: count = min2(count, self.zerg_ranged_weapon_armor_upgrade_min_level(state)) if self.has_zerg_air_unit: count = min2(count, self.zerg_flyer_weapon_armor_upgrade_min_level(state)) return count def zerg_melee_weapon_armor_upgrade_min_level(self, state: CollectionState) -> int: return min2( self.weapon_armor_upgrade_count(item_names.PROGRESSIVE_ZERG_MELEE_ATTACK, state), self.weapon_armor_upgrade_count(item_names.PROGRESSIVE_ZERG_GROUND_CARAPACE, state), ) def zerg_ranged_weapon_armor_upgrade_min_level(self, state: CollectionState) -> int: return min2( self.weapon_armor_upgrade_count(item_names.PROGRESSIVE_ZERG_MISSILE_ATTACK, state), self.weapon_armor_upgrade_count(item_names.PROGRESSIVE_ZERG_GROUND_CARAPACE, state), ) def zerg_flyer_weapon_armor_upgrade_min_level(self, state: CollectionState) -> int: return min2( self.weapon_armor_upgrade_count(item_names.PROGRESSIVE_ZERG_FLYER_ATTACK, state), self.weapon_armor_upgrade_count(item_names.PROGRESSIVE_ZERG_FLYER_CARAPACE, state), ) def zerg_can_collect_pickup_across_gap(self, state: CollectionState) -> bool: """Any way for zerg to get any ground unit across gaps longer than viper yoink range to collect a pickup.""" return ( state.has_any( ( item_names.NYDUS_WORM, item_names.ECHIDNA_WORM, item_names.OVERLORD_VENTRAL_SACS, item_names.YGGDRASIL, item_names.INFESTED_BANSHEE, ), self.player, ) or (self.morph_ravager(state) and state.has(item_names.RAVAGER_DEEP_TUNNEL, self.player)) or state.has_all( ( item_names.INFESTED_SIEGE_TANK, item_names.INFESTED_SIEGE_TANK_DEEP_TUNNEL, item_names.OVERLORD_GENERATE_CREEP, ), self.player, ) or state.has_all((item_names.SWARM_QUEEN_DEEP_TUNNEL, item_names.OVERLORD_OVERSEER_ASPECT), self.player) # Deep tunnel to a creep tumor ) def zerg_has_infested_scv(self, state: CollectionState) -> bool: return ( state.has_any(( item_names.INFESTED_MARINE, item_names.INFESTED_BUNKER, item_names.INFESTED_DIAMONDBACK, item_names.INFESTED_SIEGE_TANK, item_names.INFESTED_BANSHEE, item_names.BULLFROG, item_names.INFESTED_LIBERATOR, item_names.INFESTED_MISSILE_TURRET, ), self.player) ) def zerg_very_hard_mission_weapon_armor_level(self, state: CollectionState) -> bool: return self.zerg_army_weapon_armor_upgrade_min_level(state) >= self.get_very_hard_required_upgrade_level() def zerg_common_unit(self, state: CollectionState) -> bool: return state.has_any(self.basic_zerg_units, self.player) def zerg_competent_anti_air(self, state: CollectionState) -> bool: return state.has_any({item_names.HYDRALISK, item_names.MUTALISK, item_names.CORRUPTOR, item_names.BROOD_QUEEN}, self.player) or ( self.advanced_tactics and state.has(item_names.INFESTOR, self.player) ) def zerg_moderate_anti_air(self, state: CollectionState) -> bool: return ( self.zerg_competent_anti_air(state) or self.zerg_basic_air_to_air(state) or ( state.has(item_names.SWARM_QUEEN, self.player) or state.has_all({item_names.SWARM_HOST, item_names.SWARM_HOST_PRESSURIZED_GLANDS}, self.player) or (self.spread_creep(state, True) and state.has(item_names.INFESTED_BUNKER, self.player)) ) or (self.advanced_tactics and state.has(item_names.INFESTED_MARINE, self.player)) ) def zerg_kerrigan_or_any_anti_air(self, state: CollectionState) -> bool: return self.kerrigan_unit_available or self.zerg_any_anti_air(state) def zerg_any_anti_air(self, state: CollectionState) -> bool: return ( state.has_any( ( item_names.HYDRALISK, item_names.SWARM_QUEEN, item_names.BROOD_QUEEN, item_names.MUTALISK, item_names.CORRUPTOR, item_names.SCOURGE, item_names.INFESTOR, item_names.INFESTED_MARINE, item_names.INFESTED_LIBERATOR, item_names.SPORE_CRAWLER, item_names.INFESTED_MISSILE_TURRET, item_names.INFESTED_BUNKER, item_names.HUNTER_KILLERS, item_names.CAUSTIC_HORRORS, ), self.player, ) or state.has_all((item_names.SWARM_HOST, item_names.SWARM_HOST_PRESSURIZED_GLANDS), self.player) or state.has_all((item_names.ABERRATION, item_names.ABERRATION_PROGRESSIVE_BANELING_LAUNCH), self.player) or state.has_all((item_names.INFESTED_DIAMONDBACK, item_names.INFESTED_DIAMONDBACK_PROGRESSIVE_FUNGAL_SNARE), self.player) or self.morph_ravager(state) or self.morph_viper(state) or self.morph_devourer(state) or (self.morph_guardian(state) and state.has(item_names.GUARDIAN_PRIMAL_ADAPTATION, self.player)) ) def zerg_basic_anti_air(self, state: CollectionState) -> bool: return self.zerg_basic_kerriganless_anti_air(state) or self.kerrigan_unit_available def zerg_basic_kerriganless_anti_air(self, state: CollectionState) -> bool: return ( self.zerg_moderate_anti_air(state) or state.has_any((item_names.HUNTER_KILLERS, item_names.CAUSTIC_HORRORS), self.player) or (self.advanced_tactics and state.has_any({item_names.SPORE_CRAWLER, item_names.INFESTED_MISSILE_TURRET}, self.player)) ) def zerg_basic_air_to_air(self, state: CollectionState) -> bool: return ( state.has_any( {item_names.MUTALISK, item_names.CORRUPTOR, item_names.BROOD_QUEEN, item_names.SCOURGE, item_names.INFESTED_LIBERATOR}, self.player ) or self.morph_devourer(state) or self.morph_viper(state) or (self.morph_guardian(state) and state.has(item_names.GUARDIAN_PRIMAL_ADAPTATION, self.player)) ) def zerg_basic_air_to_ground(self, state: CollectionState) -> bool: return ( state.has_any({item_names.MUTALISK, item_names.INFESTED_BANSHEE}, self.player) or self.morph_guardian(state) or self.morph_brood_lord(state) or (self.morph_devourer(state) and state.has(item_names.DEVOURER_PRESCIENT_SPORES, self.player)) ) def zerg_versatile_air(self, state: CollectionState) -> bool: return self.zerg_basic_air_to_air(state) and self.zerg_basic_air_to_ground(state) def zerg_infested_tank_with_ammo(self, state: CollectionState) -> bool: return state.has(item_names.INFESTED_SIEGE_TANK, self.player) and ( state.has_all({item_names.INFESTOR, item_names.INFESTOR_INFESTED_TERRAN}, self.player) or state.has(item_names.INFESTED_BUNKER, self.player) or (self.advanced_tactics and state.has(item_names.INFESTED_MARINE, self.player)) or state.count(item_names.INFESTED_SIEGE_TANK_PROGRESSIVE_AUTOMATED_MITOSIS, self.player) >= (1 if self.advanced_tactics else 2) ) def morph_baneling(self, state: CollectionState) -> bool: return (state.has(item_names.ZERGLING, self.player) or self.morphling_enabled) and state.has(item_names.ZERGLING_BANELING_ASPECT, self.player) def morph_ravager(self, state: CollectionState) -> bool: return (state.has(item_names.ROACH, self.player) or self.morphling_enabled) and state.has(item_names.ROACH_RAVAGER_ASPECT, self.player) def morph_brood_lord(self, state: CollectionState) -> bool: return (state.has_any({item_names.MUTALISK, item_names.CORRUPTOR}, self.player) or self.morphling_enabled) and state.has( item_names.MUTALISK_CORRUPTOR_BROOD_LORD_ASPECT, self.player ) def morph_guardian(self, state: CollectionState) -> bool: return (state.has_any({item_names.MUTALISK, item_names.CORRUPTOR}, self.player) or self.morphling_enabled) and state.has( item_names.MUTALISK_CORRUPTOR_GUARDIAN_ASPECT, self.player ) def morph_viper(self, state: CollectionState) -> bool: return (state.has_any({item_names.MUTALISK, item_names.CORRUPTOR}, self.player) or self.morphling_enabled) and state.has( item_names.MUTALISK_CORRUPTOR_VIPER_ASPECT, self.player ) def morph_devourer(self, state: CollectionState) -> bool: return (state.has_any({item_names.MUTALISK, item_names.CORRUPTOR}, self.player) or self.morphling_enabled) and state.has( item_names.MUTALISK_CORRUPTOR_DEVOURER_ASPECT, self.player ) def morph_impaler(self, state: CollectionState) -> bool: return (state.has(item_names.HYDRALISK, self.player) or self.morphling_enabled) and state.has( item_names.HYDRALISK_IMPALER_ASPECT, self.player ) def morph_lurker(self, state: CollectionState) -> bool: return (state.has(item_names.HYDRALISK, self.player) or self.morphling_enabled) and state.has(item_names.HYDRALISK_LURKER_ASPECT, self.player) def morph_impaler_or_lurker(self, state: CollectionState) -> bool: return self.morph_impaler(state) or self.morph_lurker(state) def morph_igniter(self, state: CollectionState) -> bool: return (state.has(item_names.ROACH, self.player) or self.morphling_enabled) and state.has(item_names.ROACH_PRIMAL_IGNITER_ASPECT, self.player) def morph_tyrannozor(self, state: CollectionState) -> bool: return state.has(item_names.ULTRALISK_TYRANNOZOR_ASPECT, self.player) and ( state.has(item_names.ULTRALISK, self.player) or self.morphling_enabled ) def zerg_competent_comp(self, state: CollectionState) -> bool: if self.zerg_army_weapon_armor_upgrade_min_level(state) < 2: return False advanced = self.advanced_tactics core_unit = state.has_any( {item_names.ROACH, item_names.ABERRATION, item_names.ZERGLING, item_names.INFESTED_DIAMONDBACK}, self.player ) or self.morph_igniter(state) support_unit = ( state.has_any({item_names.SWARM_QUEEN, item_names.HYDRALISK, item_names.INFESTED_BANSHEE}, self.player) or self.morph_brood_lord(state) or state.has_all((item_names.MUTALISK, item_names.MUTALISK_SEVERING_GLAIVE, item_names.MUTALISK_VICIOUS_GLAIVE), self.player) or advanced and (state.has_any({item_names.INFESTOR, item_names.DEFILER}, self.player) or self.morph_viper(state)) ) if core_unit and support_unit: return True vespene_unit = ( state.has_any({item_names.ULTRALISK, item_names.ABERRATION}, self.player) or ( self.morph_guardian(state) and state.has_any( (item_names.GUARDIAN_SORONAN_ACID, item_names.GUARDIAN_EXPLOSIVE_SPORES, item_names.GUARDIAN_PRIMORDIAL_FURY), self.player ) ) or advanced and self.morph_viper(state) ) return vespene_unit and state.has_any({item_names.ZERGLING, item_names.SWARM_QUEEN}, self.player) def zerg_common_unit_basic_aa(self, state: CollectionState) -> bool: return self.zerg_common_unit(state) and self.zerg_basic_anti_air(state) def zerg_common_unit_competent_aa(self, state: CollectionState) -> bool: return self.zerg_common_unit(state) and self.zerg_competent_anti_air(state) def zerg_competent_comp_basic_aa(self, state: CollectionState) -> bool: return self.zerg_competent_comp(state) and self.zerg_basic_anti_air(state) def zerg_competent_comp_competent_aa(self, state: CollectionState) -> bool: return self.zerg_competent_comp(state) and self.zerg_competent_anti_air(state) def spread_creep(self, state: CollectionState, free_creep_tumor=True) -> bool: return (self.advanced_tactics and free_creep_tumor) or state.has_any( {item_names.SWARM_QUEEN, item_names.OVERLORD_OVERSEER_ASPECT}, self.player ) def zerg_mineral_dump(self, state: CollectionState) -> bool: return ( state.has_any({item_names.ZERGLING, item_names.PYGALISK, item_names.INFESTED_BUNKER}, self.player) or state.has_all({item_names.SWARM_QUEEN, item_names.SWARM_QUEEN_RESOURCE_EFFICIENCY}, self.player) or (self.advanced_tactics and self.spread_creep(state) and state.has(item_names.SPINE_CRAWLER, self.player)) ) def zerg_big_monsters(self, state: CollectionState) -> bool: """ Durable units with some capacity for damage """ return ( self.morph_tyrannozor(state) or state.has_any((item_names.ABERRATION, item_names.ULTRALISK), self.player) or (self.spread_creep(state, False) and state.has(item_names.INFESTED_BUNKER, self.player)) ) def zerg_base_buster(self, state: CollectionState) -> bool: """Powerful and sustainable zerg anti-ground for busting big bases; anti-air not included""" if not self.zerg_competent_comp(state): return False return ( ( self.zerg_melee_weapon_armor_upgrade_min_level(state) >= self.get_very_hard_required_upgrade_level() and ( self.morph_tyrannozor(state) or ( state.has(item_names.ULTRALISK, self.player) and state.has_any((item_names.ULTRALISK_TORRASQUE_STRAIN, item_names.ULTRALISK_CHITINOUS_PLATING), self.player) ) or (self.morph_baneling(state) and state.has(item_names.BANELING_SPLITTER_STRAIN, self.player)) ) and state.has(item_names.SWARM_QUEEN, self.player) # Healing to sustain the frontline ) or ( self.zerg_ranged_weapon_armor_upgrade_min_level(state) >= self.get_very_hard_required_upgrade_level() and ( self.morph_impaler(state) or self.morph_lurker(state) and state.has_all((item_names.LURKER_SEISMIC_SPINES, item_names.LURKER_ADAPTED_SPINES), self.player) or state.has_all( ( item_names.ROACH, item_names.ROACH_CORPSER_STRAIN, item_names.ROACH_ADAPTIVE_PLATING, item_names.ROACH_GLIAL_RECONSTITUTION, ), self.player, ) or self.morph_igniter(state) and state.has(item_names.PRIMAL_IGNITER_PRIMAL_TENACITY, self.player) or state.has_all((item_names.INFESTOR, item_names.INFESTOR_INFESTED_TERRAN), self.player) or self.spread_creep(state, False) and state.has(item_names.INFESTED_BUNKER, self.player) or self.zerg_infested_tank_with_ammo(state) # Highly-upgraded swarm hosts may also work, but that would require promoting many upgrades to progression ) ) or ( self.zerg_flyer_weapon_armor_upgrade_min_level(state) >= self.get_very_hard_required_upgrade_level() and ( self.morph_brood_lord(state) or self.morph_guardian(state) and state.has_all((item_names.GUARDIAN_PROPELLANT_SACS, item_names.GUARDIAN_SORONAN_ACID), self.player) or state.has_all((item_names.INFESTED_BANSHEE, item_names.INFESTED_BANSHEE_FLESHFUSED_TARGETING_OPTICS), self.player) # Highly-upgraded anti-ground devourers would also be good ) ) ) def zergling_hydra_roach_start(self, state: CollectionState) -> bool: """ Created mainly for engine of destruction start, but works for other missions with no-build starts. """ return state.has_any(( item_names.ZERGLING_ADRENAL_OVERLOAD, item_names.HYDRALISK_FRENZY, item_names.ROACH_HYDRIODIC_BILE, item_names.ZERGLING_RAPTOR_STRAIN, item_names.ROACH_CORPSER_STRAIN, ), self.player) def kerrigan_levels(self, state: CollectionState, target: int, story_levels_available=True) -> bool: if (story_levels_available and self.story_levels_granted) or not self.kerrigan_unit_available: return True # Levels are granted if ( self.kerrigan_levels_per_mission_completed > 0 and self.kerrigan_levels_per_mission_completed_cap != 0 and not self.is_item_placement(state) ): # Levels can be granted from mission completion. # Item pool filtering isn't aware of missions beaten. Assume that missions beaten will fulfill this rule. return True # Levels from missions beaten levels = self.kerrigan_levels_per_mission_completed * state.count_group("Missions", self.player) if self.kerrigan_levels_per_mission_completed_cap != -1: levels = min2(levels, self.kerrigan_levels_per_mission_completed_cap) # Levels from items for kerrigan_level_item in kerrigan_levels: level_amount = get_full_item_list()[kerrigan_level_item].number item_count = state.count(kerrigan_level_item, self.player) levels += item_count * level_amount # Total level cap if self.kerrigan_total_level_cap != -1: levels = min2(levels, self.kerrigan_total_level_cap) return levels >= target def basic_kerrigan(self, state: CollectionState, story_tech_available=True) -> bool: if story_tech_available and self.grant_story_tech == GrantStoryTech.option_grant: return True # One active ability that can be used to defeat enemies directly if not state.has_any( ( item_names.KERRIGAN_LEAPING_STRIKE, item_names.KERRIGAN_KINETIC_BLAST, item_names.KERRIGAN_SPAWN_BANELINGS, item_names.KERRIGAN_PSIONIC_SHIFT, item_names.KERRIGAN_CRUSHING_GRIP, ), self.player, ): return False # Two non-ultimate abilities count = 0 for item in kerrigan_non_ulimates: if state.has(item, self.player): count += 1 if count >= 2: return True return False def two_kerrigan_actives(self, state: CollectionState, story_tech_available=True) -> bool: if story_tech_available and self.grant_story_tech == GrantStoryTech.option_grant: return True count = 0 for i in range(7): if state.has_any(kerrigan_logic_active_abilities, self.player): count += 1 return count >= 2 # Global Protoss def protoss_power_rating(self, state: CollectionState) -> int: power_score = self.base_power_rating # War Council Upgrades (all units are improved) if self.war_council_upgrades: power_score += 3 # Passive Score (Economic upgrades and global army upgrades) power_score += sum((rating for item, rating in protoss_passive_ratings.items() if state.has(item, self.player))) # Spear of Adun if self.spear_of_adun_presence in (SpearOfAdunPresence.option_everywhere, SpearOfAdunPresence.option_protoss): power_score += self.soa_power_rating(state) if self.spear_of_adun_passive_presence in (SpearOfAdunPassiveAbilityPresence.option_everywhere, SpearOfAdunPresence.option_protoss): power_score += sum((rating for item, rating in soa_passive_ratings.items() if state.has(item, self.player))) return power_score def protoss_army_weapon_armor_upgrade_min_level(self, state: CollectionState) -> int: count: int = WEAPON_ARMOR_UPGRADE_MAX_LEVEL + 1 # +1 for Quatro if self.has_protoss_ground_unit: count = min( count, self.weapon_armor_upgrade_count(item_names.PROGRESSIVE_PROTOSS_GROUND_WEAPON, state), self.weapon_armor_upgrade_count(item_names.PROGRESSIVE_PROTOSS_GROUND_ARMOR, state), ) if self.has_protoss_air_unit: count = min( count, self.weapon_armor_upgrade_count(item_names.PROGRESSIVE_PROTOSS_AIR_WEAPON, state), self.weapon_armor_upgrade_count(item_names.PROGRESSIVE_PROTOSS_AIR_ARMOR, state), ) if self.has_protoss_ground_unit or self.has_protoss_air_unit: count = min( count, self.weapon_armor_upgrade_count(item_names.PROGRESSIVE_PROTOSS_SHIELDS, state), ) return count def protoss_very_hard_mission_weapon_armor_level(self, state: CollectionState) -> bool: return self.protoss_army_weapon_armor_upgrade_min_level(state) >= self.get_very_hard_required_upgrade_level() def protoss_defense_rating(self, state: CollectionState, zerg_enemy: bool) -> int: """ Ability to handle defensive missions :param state: :param zerg_enemy: Whether the enemy is zerg """ defense_score = sum((pvx_defense_ratings[item] for item in pvx_defense_ratings if state.has(item, self.player))) # Vanguard + rapid fire if state.has_all((item_names.VANGUARD, item_names.VANGUARD_RAPIDFIRE_CANNON), self.player): defense_score += 1 # Fire Colossus if state.has_all((item_names.COLOSSUS, item_names.COLOSSUS_FIRE_LANCE), self.player): defense_score += 2 if zerg_enemy: defense_score += 2 if ( state.has_any((item_names.PHOTON_CANNON, item_names.KHAYDARIN_MONOLITH, item_names.NEXUS_OVERCHARGE), self.player) and state.has(item_names.SHIELD_BATTERY, self.player) ): defense_score += 2 # No anti-air defense dict here, use an existing logic rule instead if zerg_enemy: defense_score += sum((pvz_defense_ratings[item] for item in pvz_defense_ratings if state.has(item, self.player))) # Advanced Tactics bumps defense rating requirements down by 2 if self.advanced_tactics: defense_score += 2 return defense_score def protoss_common_unit(self, state: CollectionState) -> bool: return state.has_any(self.basic_protoss_units, self.player) def protoss_any_gap_transport(self, state: CollectionState) -> bool: """Can get ground units across large gaps, larger than blink range""" return ( state.has_any( ( item_names.WARP_PRISM, item_names.ARBITER, ), self.player, ) or state.has(item_names.SOA_PROGRESSIVE_PROXY_PYLON, self.player, count=2) or state.has_all((item_names.MISTWING, item_names.MISTWING_PILOT), self.player) or ( state.has(item_names.SOA_PROGRESSIVE_PROXY_PYLON, self.player) and ( state.has_any(item_groups.gateway_units + [item_names.ELDER_PROBES, item_names.PROBE_WARPIN], self.player) or (state.has(item_names.WARP_HARMONIZATION, self.player) and state.has_any(item_groups.protoss_ground_wa, self.player)) ) ) ) def protoss_any_anti_air_unit_or_soa_any_protoss(self, state: CollectionState) -> bool: return self.protoss_any_anti_air_unit(state) or ( self.spear_of_adun_presence in (SpearOfAdunPresence.option_everywhere, SpearOfAdunPresence.option_protoss) and self.protoss_any_anti_air_soa(state) ) def protoss_any_anti_air_unit_or_soa(self, state: CollectionState) -> bool: return self.protoss_any_anti_air_unit(state) or self.protoss_any_anti_air_soa(state) def protoss_any_anti_air_soa(self, state: CollectionState) -> bool: return ( state.has_any( ( item_names.SOA_ORBITAL_STRIKE, item_names.SOA_SOLAR_LANCE, item_names.SOA_SOLAR_BOMBARDMENT, item_names.SOA_PURIFIER_BEAM, item_names.SOA_PYLON_OVERCHARGE, ), self.player, ) or state.has(item_names.SOA_PROGRESSIVE_PROXY_PYLON, self.player, 2) # Warp-In Reinforcements ) def protoss_any_anti_air_unit(self, state: CollectionState) -> bool: return ( state.has_any( ( # Gateway item_names.STALKER, item_names.SLAYER, item_names.INSTIGATOR, item_names.DRAGOON, item_names.ADEPT, item_names.SENTRY, item_names.ENERGIZER, item_names.HIGH_TEMPLAR, item_names.SIGNIFIER, item_names.ASCENDANT, item_names.DARK_ARCHON, # Robo item_names.ANNIHILATOR, # Stargate item_names.PHOENIX, item_names.MIRAGE, item_names.CORSAIR, item_names.SCOUT, item_names.MISTWING, item_names.CALADRIUS, item_names.OPPRESSOR, item_names.ARBITER, item_names.VOID_RAY, item_names.DESTROYER, item_names.PULSAR, item_names.CARRIER, item_names.SKYLORD, item_names.TEMPEST, item_names.MOTHERSHIP, # Buildings item_names.NEXUS_OVERCHARGE, item_names.PHOTON_CANNON, item_names.KHAYDARIN_MONOLITH, ), self.player, ) or state.has_all((item_names.SUPPLICANT, item_names.SUPPLICANT_ZENITH_PITCH), self.player) or state.has_all((item_names.WARP_PRISM, item_names.WARP_PRISM_PHASE_BLASTER), self.player) or state.has_all((item_names.WRATHWALKER, item_names.WRATHWALKER_AERIAL_TRACKING), self.player) or state.has_all((item_names.DISRUPTOR, item_names.DISRUPTOR_PERFECTED_POWER), self.player) or state.has_all((item_names.IMMORTAL, item_names.IMMORTAL_ANNIHILATOR_ADVANCED_TARGETING), self.player) or state.has_all((item_names.SKIRMISHER, item_names.SKIRMISHER_PEER_CONTEMPT), self.player) or state.has_all((item_names.TRIREME, item_names.TRIREME_SOLAR_BEAM), self.player) or ( state.has(item_names.DARK_TEMPLAR, self.player) and state.has_any((item_names.DARK_TEMPLAR_DARK_ARCHON_MELD, item_names.DARK_TEMPLAR_ARCHON_MERGE), self.player) ) ) def protoss_basic_anti_air(self, state: CollectionState) -> bool: return ( self.protoss_competent_anti_air(state) or state.has_any( { item_names.PHOENIX, item_names.MIRAGE, item_names.CORSAIR, item_names.CARRIER, item_names.SKYLORD, item_names.SCOUT, item_names.DARK_ARCHON, item_names.MOTHERSHIP, item_names.MISTWING, item_names.CALADRIUS, item_names.OPPRESSOR, item_names.PULSAR, item_names.DRAGOON, }, self.player, ) or state.has_all({item_names.TRIREME, item_names.TRIREME_SOLAR_BEAM}, self.player) or state.has_all({item_names.WRATHWALKER, item_names.WRATHWALKER_AERIAL_TRACKING}, self.player) or state.has_all({item_names.WARP_PRISM, item_names.WARP_PRISM_PHASE_BLASTER}, self.player) or self.advanced_tactics and state.has_any({item_names.HIGH_TEMPLAR, item_names.SIGNIFIER, item_names.SENTRY, item_names.ENERGIZER}, self.player) or self.protoss_can_merge_archon(state) or self.protoss_can_merge_dark_archon(state) ) def protoss_anti_armor_anti_air(self, state: CollectionState) -> bool: return ( self.protoss_competent_anti_air(state) or state.has_any((item_names.SCOUT, item_names.MISTWING, item_names.DRAGOON), self.player) or ( state.has_any({item_names.IMMORTAL, item_names.ANNIHILATOR}, self.player) and state.has(item_names.IMMORTAL_ANNIHILATOR_ADVANCED_TARGETING, self.player) ) or state.has_all({item_names.WRATHWALKER, item_names.WRATHWALKER_AERIAL_TRACKING}, self.player) ) def protoss_anti_light_anti_air(self, state: CollectionState) -> bool: return ( self.protoss_competent_anti_air(state) or state.has_any( { item_names.PHOENIX, item_names.MIRAGE, item_names.CORSAIR, item_names.CARRIER, }, self.player, ) or state.has_all((item_names.SKIRMISHER, item_names.SKIRMISHER_PEER_CONTEMPT), self.player) ) def protoss_moderate_anti_air(self, state: CollectionState) -> bool: return ( self.protoss_competent_anti_air(state) or self.protoss_anti_light_anti_air(state) or self.protoss_anti_armor_anti_air(state) or state.has(item_names.SKYLORD, self.player) ) def protoss_common_unit_basic_aa(self, state: CollectionState) -> bool: return self.protoss_common_unit(state) and self.protoss_basic_anti_air(state) def protoss_common_unit_anti_light_air(self, state: CollectionState) -> bool: return self.protoss_common_unit(state) and self.protoss_anti_light_anti_air(state) def protoss_common_unit_anti_armor_air(self, state: CollectionState) -> bool: return self.protoss_common_unit(state) and self.protoss_anti_armor_anti_air(state) def protoss_competent_anti_air(self, state: CollectionState) -> bool: return ( state.has_any( { item_names.STALKER, item_names.SLAYER, item_names.INSTIGATOR, item_names.ADEPT, item_names.VOID_RAY, item_names.DESTROYER, item_names.TEMPEST, item_names.CALADRIUS, }, self.player, ) or ( ( state.has_any( { item_names.PHOENIX, item_names.MIRAGE, item_names.CORSAIR, item_names.CARRIER, }, self.player, ) or state.has_all((item_names.SKIRMISHER, item_names.SKIRMISHER_PEER_CONTEMPT), self.player) ) and ( state.has_any((item_names.SCOUT, item_names.MISTWING, item_names.DRAGOON), self.player) or state.has_all({item_names.WRATHWALKER, item_names.WRATHWALKER_AERIAL_TRACKING}, self.player) or ( state.has_any({item_names.IMMORTAL, item_names.ANNIHILATOR}, self.player) and state.has(item_names.IMMORTAL_ANNIHILATOR_ADVANCED_TARGETING, self.player) ) ) ) or ( self.advanced_tactics and state.has_any({item_names.IMMORTAL, item_names.ANNIHILATOR}, self.player) and state.has(item_names.IMMORTAL_ANNIHILATOR_ADVANCED_TARGETING, self.player) ) ) def protoss_has_blink(self, state: CollectionState) -> bool: return ( state.has_any({item_names.STALKER, item_names.INSTIGATOR}, self.player) or state.has_all({item_names.SLAYER, item_names.SLAYER_PHASE_BLINK}, self.player) or ( state.has(item_names.DARK_TEMPLAR_AVENGER_BLOOD_HUNTER_BLINK, self.player) and state.has_any({item_names.DARK_TEMPLAR, item_names.BLOOD_HUNTER, item_names.AVENGER}, self.player) ) ) def protoss_fleet(self, state: CollectionState) -> bool: return ( ( state.has_any( { item_names.CARRIER, item_names.SKYLORD, item_names.TEMPEST, item_names.VOID_RAY, item_names.DESTROYER, }, self.player, ) ) or ( state.has_all((item_names.TRIREME, item_names.TRIREME_SOLAR_BEAM), self.player) and ( state.has_any((item_names.PHOENIX, item_names.MIRAGE, item_names.CORSAIR), self.player) or state.has_all((item_names.SKIRMISHER, item_names.SKIRMISHER_PEER_CONTEMPT), self.player) ) ) and self.weapon_armor_upgrade_count(PROGRESSIVE_PROTOSS_AIR_WEAPON, state) >= 2 and self.weapon_armor_upgrade_count(PROGRESSIVE_PROTOSS_AIR_ARMOR, state) >= 2 and self.weapon_armor_upgrade_count(PROGRESSIVE_PROTOSS_SHIELDS, state) >= 2 ) def protoss_hybrid_counter(self, state: CollectionState) -> bool: """ Ground Hybrids """ return ( state.has_any( { item_names.ANNIHILATOR, item_names.ASCENDANT, item_names.TEMPEST, item_names.CARRIER, item_names.TRIREME, item_names.VOID_RAY, item_names.WRATHWALKER, }, self.player, ) or state.has_all((item_names.VANGUARD, item_names.VANGUARD_FUSION_MORTARS), self.player) or ( (state.has(item_names.IMMORTAL, self.player) or self.advanced_tactics) and (state.has_any({item_names.STALKER, item_names.DRAGOON, item_names.ADEPT, item_names.INSTIGATOR, item_names.SLAYER}, self.player)) ) or (self.advanced_tactics and state.has_all((item_names.OPPRESSOR, item_names.OPPRESSOR_VULCAN_BLASTER), self.player)) ) def protoss_basic_splash(self, state: CollectionState) -> bool: return ( state.has_any(( item_names.COLOSSUS, item_names.VANGUARD, item_names.HIGH_TEMPLAR, item_names.SIGNIFIER, item_names.REAVER, item_names.ASCENDANT, item_names.DAWNBRINGER, ), self.player) or state.has_all((item_names.ZEALOT, item_names.ZEALOT_WHIRLWIND), self.player) or ( state.has_all( (item_names.DARK_TEMPLAR, item_names.DARK_TEMPLAR_LESSER_SHADOW_FURY, item_names.DARK_TEMPLAR_GREATER_SHADOW_FURY), self.player ) ) or ( state.has(item_names.DESTROYER, self.player) and ( state.has_any(( item_names.DESTROYER_REFORGED_BLOODSHARD_CORE, item_names.DESTROYER_RESOURCE_EFFICIENCY, ), self.player) ) ) ) def protoss_static_defense(self, state: CollectionState) -> bool: return state.has_any({item_names.PHOTON_CANNON, item_names.KHAYDARIN_MONOLITH}, self.player) def protoss_can_merge_archon(self, state: CollectionState) -> bool: return ( state.has_any({item_names.HIGH_TEMPLAR, item_names.SIGNIFIER}, self.player) or state.has_all({item_names.ASCENDANT, item_names.ASCENDANT_ARCHON_MERGE}, self.player) or state.has_all({item_names.DARK_TEMPLAR, item_names.DARK_TEMPLAR_ARCHON_MERGE}, self.player) ) def protoss_can_merge_dark_archon(self, state: CollectionState) -> bool: return state.has(item_names.DARK_ARCHON, self.player) or state.has_all( {item_names.DARK_TEMPLAR, item_names.DARK_TEMPLAR_DARK_ARCHON_MELD}, self.player ) def protoss_competent_comp(self, state: CollectionState) -> bool: if not self.protoss_competent_anti_air(state): return False if self.protoss_fleet(state) and self.protoss_mineral_dump(state): return True if self.protoss_deathball(state): return True core_unit: bool = state.has_any( ( item_names.ZEALOT, item_names.CENTURION, item_names.SENTINEL, item_names.STALKER, item_names.INSTIGATOR, item_names.SLAYER, item_names.ADEPT, ), self.player, ) support_unit: bool = ( state.has_any( ( item_names.SENTRY, item_names.ENERGIZER, item_names.IMMORTAL, item_names.VANGUARD, item_names.COLOSSUS, item_names.REAVER, item_names.VOID_RAY, item_names.PHOENIX, item_names.CORSAIR, ), self.player, ) or state.has_all((item_names.MIRAGE, item_names.MIRAGE_GRAVITON_BEAM), self.player) or state.has_all( (item_names.DARK_TEMPLAR, item_names.DARK_TEMPLAR_LESSER_SHADOW_FURY, item_names.DARK_TEMPLAR_GREATER_SHADOW_FURY), self.player ) or ( self.advanced_tactics and ( state.has_any( ( item_names.HIGH_TEMPLAR, item_names.SIGNIFIER, item_names.ASCENDANT, item_names.ANNIHILATOR, item_names.WRATHWALKER, item_names.SKIRMISHER, item_names.ARBITER, ), self.player, ) ) ) ) if core_unit and support_unit: return True return False def protoss_deathball(self, state: CollectionState) -> bool: return ( self.protoss_common_unit(state) and self.protoss_competent_anti_air(state) and self.protoss_hybrid_counter(state) and self.protoss_basic_splash(state) and self.protoss_army_weapon_armor_upgrade_min_level(state) >= 2 ) def protoss_heal(self, state: CollectionState) -> bool: return state.has_any((item_names.SENTRY, item_names.SHIELD_BATTERY, item_names.RECONSTRUCTION_BEAM), self.player) or state.has_all( (item_names.CARRIER, item_names.CARRIER_REPAIR_DRONES), self.player ) def protoss_mineral_dump(self, state: CollectionState) -> bool: return ( state.has_any((item_names.ZEALOT, item_names.SENTINEL, item_names.PHOTON_CANNON), self.player) or state.has_all((item_names.CENTURION, item_names.CENTURION_RESOURCE_EFFICIENCY), self.player) or self.advanced_tactics and state.has_any((item_names.SUPPLICANT, item_names.SHIELD_BATTERY), self.player) ) def zealot_sentry_slayer_start(self, state: CollectionState) -> bool: """ Created mainly for engine of destruction start, but works for other missions with no-build starts. """ return state.has_any(( item_names.ZEALOT_WHIRLWIND, item_names.SENTRY_DOUBLE_SHIELD_RECHARGE, item_names.SLAYER_PHASE_BLINK, item_names.STALKER_INSTIGATOR_SLAYER_DISINTEGRATING_PARTICLES, item_names.STALKER_INSTIGATOR_SLAYER_PARTICLE_REFLECTION, ), self.player) # Mission-specific rules def ghost_of_a_chance_requirement(self, state: CollectionState) -> bool: return ( self.grant_story_tech == GrantStoryTech.option_grant or self.nova_ghost_of_a_chance_variant == NovaGhostOfAChanceVariant.option_wol or not self.nova_used or ( self.nova_ranged_weapon(state) and state.has_any({item_names.NOVA_DOMINATION, item_names.NOVA_C20A_CANISTER_RIFLE}, self.player) and (self.nova_full_stealth(state) or self.nova_heal(state)) and self.nova_anti_air_weapon(state) ) ) def terran_outbreak_requirement(self, state: CollectionState) -> bool: """Outbreak mission requirement""" return self.terran_defense_rating(state, True, False) >= 4 and (self.terran_common_unit(state) or state.has(item_names.REAPER, self.player)) def zerg_outbreak_requirement(self, state: CollectionState) -> bool: """ Outbreak mission requirement. Need to boot out Aberration-based comp """ return ( self.zerg_defense_rating(state, True, False) >= 4 and self.zerg_common_unit(state) and ( state.has_any( ( item_names.SWARM_QUEEN, item_names.HYDRALISK, item_names.ROACH, item_names.MUTALISK, item_names.INFESTED_BANSHEE, ), self.player, ) or self.morph_lurker(state) or self.morph_brood_lord(state) or ( self.advanced_tactics and ( self.morph_impaler(state) or self.morph_igniter(state) or state.has_any((item_names.INFESTED_DIAMONDBACK, item_names.INFESTED_SIEGE_TANK), self.player) ) ) ) ) def protoss_outbreak_requirement(self, state: CollectionState) -> bool: """ Outbreak mission requirement Something other than Zealot-based comp is required. """ return ( self.protoss_defense_rating(state, True) >= 4 and self.protoss_common_unit(state) and self.protoss_basic_splash(state) and ( state.has_any( ( item_names.STALKER, item_names.SLAYER, item_names.INSTIGATOR, item_names.ADEPT, item_names.COLOSSUS, item_names.VANGUARD, item_names.SKIRMISHER, item_names.OPPRESSOR, item_names.CARRIER, item_names.SKYLORD, item_names.TRIREME, item_names.DAWNBRINGER, ), self.player, ) or (self.advanced_tactics and (state.has_any((item_names.VOID_RAY, item_names.DESTROYER), self.player))) ) ) def terran_safe_haven_requirement(self, state: CollectionState) -> bool: """Safe Haven mission requirement""" return self.terran_common_unit(state) and self.terran_competent_anti_air(state) def terran_havens_fall_requirement(self, state: CollectionState) -> bool: """Haven's Fall mission requirement""" return self.terran_common_unit(state) and ( self.terran_competent_comp(state) or ( self.terran_competent_anti_air(state) and ( state.has_any((item_names.VIKING, item_names.BATTLECRUISER), self.player) or state.has_all((item_names.WRAITH, item_names.WRAITH_ADVANCED_LASER_TECHNOLOGY), self.player) or state.has_all((item_names.LIBERATOR, item_names.LIBERATOR_RAID_ARTILLERY), self.player) ) ) ) def terran_respond_to_colony_infestations(self, state: CollectionState) -> bool: """ Can deal quickly with Brood Lords and Mutas in Haven's Fall and being able to progress the mission """ return self.terran_havens_fall_requirement(state) and ( self.terran_air_anti_air(state) or ( state.has_any({item_names.BATTLECRUISER, item_names.VALKYRIE}, self.player) and self.weapon_armor_upgrade_count(item_names.PROGRESSIVE_TERRAN_SHIP_WEAPON, state) >= 2 ) ) def zerg_havens_fall_requirement(self, state: CollectionState) -> bool: return ( self.zerg_common_unit(state) and self.zerg_competent_anti_air(state) and (state.has(item_names.MUTALISK, self.player) or self.zerg_competent_comp(state)) ) def zerg_respond_to_colony_infestations(self, state: CollectionState) -> bool: """ Can deal quickly with Brood Lords and Mutas in Haven's Fall and being able to progress the mission """ return self.zerg_havens_fall_requirement(state) and ( self.morph_devourer(state) or state.has_any({item_names.MUTALISK, item_names.CORRUPTOR}, self.player) or self.advanced_tactics and (self.morph_viper(state) or state.has_any({item_names.BROOD_QUEEN, item_names.SCOURGE}, self.player)) ) def protoss_havens_fall_requirement(self, state: CollectionState) -> bool: return ( self.protoss_common_unit(state) and self.protoss_competent_anti_air(state) and ( self.protoss_competent_comp(state) or ( state.has_any((item_names.TEMPEST, item_names.SKYLORD, item_names.DESTROYER), self.player) or ( self.weapon_armor_upgrade_count(item_names.PROGRESSIVE_PROTOSS_AIR_WEAPON, state) >= 2 and state.has(item_names.CARRIER, self.player) or state.has_all((item_names.SKIRMISHER, item_names.SKIRMISHER_PEER_CONTEMPT), self.player) ) ) ) ) def protoss_respond_to_colony_infestations(self, state: CollectionState) -> bool: """ Can deal quickly with Brood Lords and Mutas in Haven's Fall and being able to progress the mission """ return self.protoss_havens_fall_requirement(state) and ( state.has_any({item_names.CARRIER, item_names.SKYLORD, item_names.DESTROYER, item_names.TEMPEST}, self.player) # handle mutas or ( state.has_any( { item_names.PHOENIX, item_names.MIRAGE, item_names.CORSAIR, }, self.player, ) or state.has_all((item_names.SKIRMISHER, item_names.SKIRMISHER_PEER_CONTEMPT), self.player) ) # handle brood lords and virophages and ( state.has_any( { item_names.VOID_RAY, }, self.player, ) or self.advanced_tactics and state.has_all({item_names.SCOUT, item_names.MISTWING}, self.player) ) ) def terran_gates_of_hell_requirement(self, state: CollectionState) -> bool: """Gates of Hell mission requirement""" return self.terran_competent_comp(state) and (self.terran_defense_rating(state, True) > 6) def zerg_gates_of_hell_requirement(self, state: CollectionState) -> bool: """Gates of Hell mission requirement""" return self.zerg_competent_comp_competent_aa(state) and (self.zerg_defense_rating(state, True) > 8) def protoss_gates_of_hell_requirement(self, state: CollectionState) -> bool: """Gates of Hell mission requirement""" return self.protoss_competent_comp(state) and (self.protoss_defense_rating(state, True) > 6) def terran_welcome_to_the_jungle_requirement(self, state: CollectionState) -> bool: """ Welcome to the Jungle requirements - able to deal with Scouts, Void Rays, Zealots and Stalkers """ if self.terran_power_rating(state) < 5: return False return (self.terran_common_unit(state) and self.terran_competent_ground_to_air(state)) or ( self.advanced_tactics and state.has_any({item_names.MARINE, item_names.DOMINION_TROOPER, item_names.VULTURE}, self.player) and self.terran_air_anti_air(state) ) def zerg_welcome_to_the_jungle_requirement(self, state: CollectionState) -> bool: """ Welcome to the Jungle requirements - able to deal with Scouts, Void Rays, Zealots and Stalkers """ if self.zerg_power_rating(state) < 5: return False return (self.zerg_competent_comp(state) and state.has_any({item_names.HYDRALISK, item_names.MUTALISK}, self.player)) or ( self.advanced_tactics and self.zerg_common_unit(state) and ( state.has_any({item_names.MUTALISK, item_names.INFESTOR}, self.player) or (self.morph_devourer(state) and state.has_any({item_names.HYDRALISK, item_names.SWARM_QUEEN}, self.player)) or (self.morph_viper(state) and state.has(item_names.VIPER_PARASITIC_BOMB, self.player)) ) and self.zerg_army_weapon_armor_upgrade_min_level(state) >= 1 ) def protoss_welcome_to_the_jungle_requirement(self, state: CollectionState) -> bool: """ Welcome to the Jungle requirements - able to deal with Scouts, Void Rays, Zealots and Stalkers """ if self.protoss_power_rating(state) < 5: return False return self.protoss_common_unit(state) and self.protoss_anti_armor_anti_air(state) def terran_can_grab_ghosts_in_the_fog_east_rock_formation(self, state: CollectionState) -> bool: """ Able to shoot by a long range or from air to claim the rock formation separated by a chasm """ return ( state.has_any( { item_names.MEDIVAC, item_names.HERCULES, item_names.VIKING, item_names.BANSHEE, item_names.WRAITH, item_names.SIEGE_TANK, item_names.BATTLECRUISER, item_names.NIGHT_HAWK, item_names.NIGHT_WOLF, item_names.SHOCK_DIVISION, item_names.SKY_FURY, }, self.player, ) or state.has_all({item_names.VALKYRIE, item_names.VALKYRIE_FLECHETTE_MISSILES}, self.player) or state.has_all({item_names.RAVEN, item_names.RAVEN_HUNTER_SEEKER_WEAPON}, self.player) or ( state.has_any({item_names.LIBERATOR, item_names.EMPERORS_GUARDIAN}, self.player) and state.has(item_names.LIBERATOR_RAID_ARTILLERY, self.player) ) or ( self.advanced_tactics and ( state.has_any( { item_names.HELS_ANGELS, item_names.DUSK_WINGS, item_names.WINGED_NIGHTMARES, item_names.SIEGE_BREAKERS, item_names.BRYNHILDS, item_names.JACKSONS_REVENGE, }, self.player, ) ) or state.has_all({item_names.MIDNIGHT_RIDERS, item_names.LIBERATOR_RAID_ARTILLERY}, self.player) ) ) def terran_great_train_robbery_train_stopper(self, state: CollectionState) -> bool: """ Ability to deal with trains (moving target with a lot of HP) """ return state.has_any( {item_names.SIEGE_TANK, item_names.DIAMONDBACK, item_names.MARAUDER, item_names.CYCLONE, item_names.BANSHEE}, self.player ) or ( self.advanced_tactics and ( state.has_all({item_names.REAPER, item_names.REAPER_G4_CLUSTERBOMB}, self.player) or state.has_all({item_names.SPECTRE, item_names.SPECTRE_PSIONIC_LASH}, self.player) or state.has_any({item_names.VULTURE, item_names.LIBERATOR}, self.player) ) ) def zerg_great_train_robbery_train_stopper(self, state: CollectionState) -> bool: """ Ability to deal with trains (moving target with a lot of HP) """ return ( state.has_any( ( item_names.ABERRATION, item_names.INFESTED_DIAMONDBACK, item_names.INFESTED_BANSHEE, ), self.player, ) or state.has_all({item_names.MUTALISK, item_names.MUTALISK_SUNDERING_GLAIVE}, self.player) or state.has_all((item_names.HYDRALISK, item_names.HYDRALISK_MUSCULAR_AUGMENTS), self.player) or ( state.has(item_names.ZERGLING, self.player) and ( state.has_any( (item_names.ZERGLING_SHREDDING_CLAWS, item_names.ZERGLING_SHREDDING_CLAWS, item_names.ZERGLING_RAPTOR_STRAIN), self.player ) ) and (self.advanced_tactics or state.has_any((item_names.ZERGLING_METABOLIC_BOOST, item_names.ZERGLING_RAPTOR_STRAIN), self.player)) ) or self.zerg_infested_tank_with_ammo(state) or (self.advanced_tactics and (self.morph_tyrannozor(state))) ) def protoss_great_train_robbery_train_stopper(self, state: CollectionState) -> bool: """ Ability to deal with trains (moving target with a lot of HP) """ return ( state.has_any( (item_names.ANNIHILATOR, item_names.IMMORTAL, item_names.STALKER, item_names.WRATHWALKER, item_names.VOID_RAY, item_names.DESTROYER), self.player, ) or state.has_all({item_names.SLAYER, item_names.SLAYER_PHASE_BLINK}, self.player) or state.has_all((item_names.REAVER, item_names.REAVER_KHALAI_REPLICATORS), self.player) or state.has_all({item_names.VANGUARD, item_names.VANGUARD_FUSION_MORTARS}, self.player) or ( state.has(item_names.INSTIGATOR, self.player) and state.has_any((item_names.INSTIGATOR_BLINK_OVERDRIVE, item_names.INSTIGATOR_MODERNIZED_SERVOS), self.player) ) or (state.has_all((item_names.OPPRESSOR, item_names.SCOUT_GRAVITIC_THRUSTERS, item_names.SCOUT_ADVANCED_PHOTON_BLASTERS), self.player)) or state.has_all((item_names.ORACLE, item_names.ORACLE_TEMPORAL_ACCELERATION_BEAM), self.player) or ( self.advanced_tactics and ( state.has(item_names.TEMPEST, self.player) or state.has_all((item_names.ADEPT, item_names.ADEPT_RESONATING_GLAIVES), self.player) or state.has_all({item_names.VANGUARD, item_names.VANGUARD_RAPIDFIRE_CANNON}, self.player) or state.has_all((item_names.OPPRESSOR, item_names.SCOUT_GRAVITIC_THRUSTERS, item_names.OPPRESSOR_VULCAN_BLASTER), self.player) or state.has_all((item_names.ASCENDANT, item_names.ASCENDANT_POWER_OVERWHELMING, item_names.SUPPLICANT), self.player) or state.has_all( (item_names.DARK_TEMPLAR, item_names.DARK_TEMPLAR_LESSER_SHADOW_FURY, item_names.DARK_TEMPLAR_GREATER_SHADOW_FURY), self.player, ) or ( state.has(item_names.DARK_TEMPLAR_AVENGER_BLOOD_HUNTER_BLINK, self.player) and ( state.has_any((item_names.DARK_TEMPLAR, item_names.AVENGER), self.player) or state.has_all((item_names.BLOOD_HUNTER, item_names.BLOOD_HUNTER_BRUTAL_EFFICIENCY), self.player) ) ) ) ) ) def terran_can_rescue(self, state) -> bool: """ Rescuing in The Moebius Factor """ return state.has_any({item_names.MEDIVAC, item_names.HERCULES, item_names.RAVEN, item_names.VIKING}, self.player) or self.advanced_tactics def terran_supernova_requirement(self, state) -> bool: return self.terran_beats_protoss_deathball(state) and self.terran_power_rating(state) >= 6 def zerg_supernova_requirement(self, state) -> bool: return ( self.zerg_common_unit(state) and self.zerg_power_rating(state) >= 6 and (self.advanced_tactics or state.has(item_names.YGGDRASIL, self.player)) ) def protoss_supernova_requirement(self, state: CollectionState) -> bool: return ( ( state.count(item_names.PROGRESSIVE_WARP_RELOCATE, self.player) >= 2 or (self.advanced_tactics and state.has(item_names.PROGRESSIVE_WARP_RELOCATE, self.player)) ) and self.protoss_competent_anti_air(state) and (self.protoss_fleet(state) or (self.protoss_competent_comp(state) and self.protoss_power_rating(state) >= 6)) ) def terran_maw_requirement(self, state: CollectionState) -> bool: """ Ability to deal with large areas with environment damage """ return ( state.has(item_names.BATTLECRUISER, self.player) and ( self.weapon_armor_upgrade_count(item_names.PROGRESSIVE_TERRAN_SHIP_WEAPON, state) >= 2 or state.has(item_names.BATTLECRUISER_ATX_LASER_BATTERY, self.player) ) ) or ( self.terran_air(state) and ( # Avoid dropping Troopers or units that do barely damage state.has_any( ( item_names.GOLIATH, item_names.THOR, item_names.WARHOUND, item_names.VIKING, item_names.BANSHEE, item_names.WRAITH, item_names.BATTLECRUISER, ), self.player, ) or state.has_all((item_names.LIBERATOR, item_names.LIBERATOR_RAID_ARTILLERY), self.player) or state.has_all((item_names.VALKYRIE, item_names.VALKYRIE_FLECHETTE_MISSILES), self.player) or (state.has(item_names.MARAUDER, self.player) and self.terran_bio_heal(state)) ) and ( # Can deal damage to air units inside rip fields state.has_any((item_names.GOLIATH, item_names.CYCLONE, item_names.VIKING), self.player) or ( state.has_any((item_names.WRAITH, item_names.VALKYRIE, item_names.BATTLECRUISER), self.player) and self.weapon_armor_upgrade_count(item_names.PROGRESSIVE_TERRAN_SHIP_WEAPON, state) >= 2 ) or state.has_all((item_names.THOR, item_names.THOR_PROGRESSIVE_HIGH_IMPACT_PAYLOAD), self.player) ) and self.terran_competent_comp(state) and self.terran_competent_anti_air(state) and self.terran_sustainable_mech_heal(state) ) def zerg_maw_requirement(self, state: CollectionState) -> bool: """ Ability to cross defended gaps, deal with skytoss, and avoid costly losses. """ if self.advanced_tactics and state.has(item_names.INFESTOR, self.player): return True usable_muta = ( state.has_all((item_names.MUTALISK, item_names.MUTALISK_RAPID_REGENERATION), self.player) and state.has_any((item_names.MUTALISK_SEVERING_GLAIVE, item_names.MUTALISK_VICIOUS_GLAIVE), self.player) and ( state.has(item_names.MUTALISK_SUNDERING_GLAIVE, self.player) or state.has_all((item_names.MUTALISK_SEVERING_GLAIVE, item_names.MUTALISK_VICIOUS_GLAIVE), self.player) ) ) return ( # Heal ( state.has(item_names.SWARM_QUEEN, self.player) or self.advanced_tactics and ((self.morph_tyrannozor(state) and state.has(item_names.TYRANNOZOR_HEALING_ADAPTATION, self.player)) or (usable_muta)) ) # Cross the gap and ( state.has_any((item_names.NYDUS_WORM, item_names.OVERLORD_VENTRAL_SACS), self.player) or (self.advanced_tactics and state.has(item_names.YGGDRASIL, self.player)) ) # Air to ground and (self.morph_brood_lord(state) or self.morph_guardian(state) or usable_muta) # Ground to air and ( state.has(item_names.INFESTOR, self.player) or self.morph_tyrannozor(state) or state.has_all( {item_names.SWARM_HOST, item_names.SWARM_HOST_RESOURCE_EFFICIENCY, item_names.SWARM_HOST_PRESSURIZED_GLANDS}, self.player ) or state.has_all({item_names.HYDRALISK, item_names.HYDRALISK_RESOURCE_EFFICIENCY}, self.player) or state.has_all({item_names.INFESTED_DIAMONDBACK, item_names.INFESTED_DIAMONDBACK_PROGRESSIVE_FUNGAL_SNARE}, self.player) ) # Survives rip-field and ( state.has_any({item_names.ABERRATION, item_names.ROACH, item_names.ULTRALISK}, self.player) or self.morph_tyrannozor(state) or (self.advanced_tactics and usable_muta) ) # Air-to-air and (state.has_any({item_names.MUTALISK, item_names.CORRUPTOR, item_names.INFESTED_LIBERATOR, item_names.BROOD_QUEEN}, self.player)) # Upgrades / general and self.zerg_competent_anti_air(state) and self.zerg_competent_comp(state) ) def protoss_maw_requirement(self, state: CollectionState) -> bool: """ Ability to cross defended gaps and deal with skytoss. """ return ( ( state.has(item_names.WARP_PRISM, self.player) or ( self.advanced_tactics and (state.has(item_names.ARBITER, self.player) or state.has_all((item_names.MISTWING, item_names.MISTWING_PILOT), self.player)) ) ) and self.protoss_common_unit_anti_armor_air(state) and self.protoss_fleet(state) ) def terran_engine_of_destruction_requirement(self, state: CollectionState) -> bool: power_rating = self.terran_power_rating(state) if power_rating < 3 or not self.marine_medic_upgrade(state) or not self.terran_common_unit(state): return False if power_rating >= 7 and self.terran_competent_comp(state): return True else: return ( state.has_any((item_names.WRAITH, item_names.BATTLECRUISER), self.player) or self.terran_air_anti_air(state) and state.has_any((item_names.BANSHEE, item_names.LIBERATOR), self.player) ) def zerg_engine_of_destruction_requirement(self, state: CollectionState) -> bool: power_rating = self.zerg_power_rating(state) if ( power_rating < 3 or not self.zergling_hydra_roach_start(state) or not self.zerg_common_unit(state) or not self.zerg_competent_anti_air(state) or not self.zerg_repair_odin(state) ): return False if power_rating >= 7 and self.zerg_competent_comp(state): return True else: return self.zerg_base_buster(state) def protoss_engine_of_destruction_requirement(self, state: CollectionState) -> bool: return ( self.zealot_sentry_slayer_start(state) and self.protoss_repair_odin(state) and (self.protoss_deathball(state) or self.protoss_fleet(state)) ) def zerg_repair_odin(self, state: CollectionState) -> bool: return ( self.zerg_has_infested_scv(state) or state.has_all({item_names.SWARM_QUEEN_BIO_MECHANICAL_TRANSFUSION, item_names.SWARM_QUEEN}, self.player) or (self.advanced_tactics and state.has(item_names.SWARM_QUEEN, self.player)) ) def protoss_repair_odin(self, state: CollectionState) -> bool: return ( state.has(item_names.SENTRY, self.player) or state.has_all((item_names.CARRIER, item_names.CARRIER_REPAIR_DRONES), self.player) or ( self.spear_of_adun_passive_presence in [SpearOfAdunPassiveAbilityPresence.option_protoss, SpearOfAdunPassiveAbilityPresence.option_everywhere] and state.has(item_names.RECONSTRUCTION_BEAM, self.player) ) or (self.advanced_tactics and state.has_all({item_names.SHIELD_BATTERY, item_names.KHALAI_INGENUITY}, self.player)) ) def terran_in_utter_darkness_requirement(self, state: CollectionState) -> bool: return self.terran_competent_comp(state) and self.terran_defense_rating(state, True, True) >= 8 def zerg_in_utter_darkness_requirement(self, state: CollectionState) -> bool: return self.zerg_competent_comp(state) and self.zerg_competent_anti_air(state) and self.zerg_defense_rating(state, True, True) >= 8 def protoss_in_utter_darkness_requirement(self, state: CollectionState) -> bool: return self.protoss_competent_comp(state) and self.protoss_defense_rating(state, True) >= 4 def terran_all_in_requirement(self, state: CollectionState) -> bool: """ All-in """ if not self.terran_very_hard_mission_weapon_armor_level(state): return False beats_kerrigan = ( state.has_any({item_names.MARINE, item_names.DOMINION_TROOPER, item_names.BANSHEE}, self.player) or state.has_all({item_names.REAPER, item_names.REAPER_RESOURCE_EFFICIENCY}, self.player) or (self.all_in_map == AllInMap.option_air and state.has_all((item_names.VALKYRIE, item_names.VALKYRIE_FLECHETTE_MISSILES), self.player)) or (self.advanced_tactics and state.has_all((item_names.GHOST, item_names.GHOST_EMP_ROUNDS), self.player)) ) if not beats_kerrigan: return False if not self.terran_competent_comp(state): return False if self.all_in_map == AllInMap.option_ground: # Ground defense_rating = self.terran_defense_rating(state, True, False) if state.has_any({item_names.BATTLECRUISER, item_names.BANSHEE}, self.player): defense_rating += 2 return defense_rating >= 13 else: # Air defense_rating = self.terran_defense_rating(state, True, True) return ( defense_rating >= 9 and self.terran_competent_anti_air(state) and state.has_any({item_names.VIKING, item_names.BATTLECRUISER, item_names.VALKYRIE}, self.player) and state.has_any({item_names.HIVE_MIND_EMULATOR, item_names.PSI_DISRUPTER, item_names.MISSILE_TURRET}, self.player) ) def zerg_all_in_requirement(self, state: CollectionState) -> bool: """ All-in (Zerg) """ if not self.zerg_very_hard_mission_weapon_armor_level(state): return False beats_kerrigan = ( state.has_any({item_names.INFESTED_MARINE, item_names.INFESTED_BANSHEE, item_names.INFESTED_BUNKER}, self.player) or state.has_all({item_names.SWARM_HOST, item_names.SWARM_HOST_RESOURCE_EFFICIENCY}, self.player) or self.morph_brood_lord(state) ) if not beats_kerrigan: return False if not self.zerg_competent_comp(state): return False if self.all_in_map == AllInMap.option_ground: # Ground defense_rating = self.zerg_defense_rating(state, True, False) if ( state.has_any({item_names.MUTALISK, item_names.INFESTED_BANSHEE}, self.player) or self.morph_brood_lord(state) or self.morph_guardian(state) ): defense_rating += 3 if state.has(item_names.SPINE_CRAWLER, self.player): defense_rating += 2 return defense_rating >= 13 else: # Air defense_rating = self.zerg_defense_rating(state, True, True) return ( defense_rating >= 9 and state.has_any({item_names.MUTALISK, item_names.CORRUPTOR}, self.player) and state.has_any({item_names.SPORE_CRAWLER, item_names.INFESTED_MISSILE_TURRET}, self.player) ) def protoss_all_in_requirement(self, state: CollectionState) -> bool: """ All-in (Protoss) """ if not self.protoss_very_hard_mission_weapon_armor_level(state): return False beats_kerrigan = ( # cheap units with multiple small attacks, or anything with Feedback state.has_any({item_names.ZEALOT, item_names.SENTINEL, item_names.SKIRMISHER, item_names.HIGH_TEMPLAR}, self.player) or state.has_all((item_names.CENTURION, item_names.CENTURION_RESOURCE_EFFICIENCY), self.player) or state.has_all({item_names.SIGNIFIER, item_names.SIGNIFIER_FEEDBACK}, self.player) or (self.protoss_can_merge_archon(state) and state.has(item_names.ARCHON_HIGH_ARCHON, self.player)) or (self.protoss_can_merge_dark_archon(state) and state.has(item_names.DARK_ARCHON_FEEDBACK, self.player)) ) if not beats_kerrigan: return False if not self.protoss_competent_comp(state): return False if self.all_in_map == AllInMap.option_ground: # Ground defense_rating = self.protoss_defense_rating(state, True) if ( state.has_any({item_names.SKIRMISHER, item_names.DARK_TEMPLAR, item_names.TEMPEST, item_names.TRIREME}, self.player) or state.has_all((item_names.BLOOD_HUNTER, item_names.BLOOD_HUNTER_BRUTAL_EFFICIENCY), self.player) or state.has_all((item_names.AVENGER, item_names.AVENGER_KRYHAS_CLOAK), self.player) ): defense_rating += 2 if state.has(item_names.PHOTON_CANNON, self.player): defense_rating += 2 return defense_rating >= 13 else: # Air defense_rating = self.protoss_defense_rating(state, True) if state.has(item_names.KHAYDARIN_MONOLITH, self.player): defense_rating += 2 if state.has(item_names.PHOTON_CANNON, self.player): defense_rating += 2 return defense_rating >= 9 and (state.has_any({item_names.TEMPEST, item_names.SKYLORD, item_names.VOID_RAY}, self.player)) def zerg_can_grab_ghosts_in_the_fog_east_rock_formation(self, state: CollectionState) -> bool: return ( state.has_any({item_names.MUTALISK, item_names.INFESTED_BANSHEE, item_names.OVERLORD_VENTRAL_SACS, item_names.INFESTOR}, self.player) or (self.morph_devourer(state) and state.has(item_names.DEVOURER_PRESCIENT_SPORES, self.player)) or (self.morph_guardian(state) and state.has(item_names.GUARDIAN_PRIMAL_ADAPTATION, self.player)) or ((self.morph_guardian(state) or self.morph_brood_lord(state)) and self.zerg_basic_air_to_air(state)) or ( self.advanced_tactics and ( state.has_any({item_names.INFESTED_SIEGE_BREAKERS, item_names.INFESTED_DUSK_WINGS}, self.player) or (state.has(item_names.HUNTERLING, self.player) and self.zerg_basic_air_to_air(state)) ) ) ) def zerg_any_units_back_in_the_saddle_requirement(self, state: CollectionState) -> bool: return ( self.grant_story_tech == GrantStoryTech.option_grant # Note(mm): This check isn't necessary as self.kerrigan_levels cover it, # and it's not fully desirable in future when we support non-grant story tech + kerriganless. # or not self.kerrigan_presence or state.has_any(( # Cases tested by Snarky item_names.KERRIGAN_KINETIC_BLAST, item_names.KERRIGAN_LEAPING_STRIKE, item_names.KERRIGAN_CRUSHING_GRIP, item_names.KERRIGAN_PSIONIC_SHIFT, item_names.KERRIGAN_SPAWN_BANELINGS, item_names.KERRIGAN_FURY, item_names.KERRIGAN_APOCALYPSE, item_names.KERRIGAN_DROP_PODS, item_names.KERRIGAN_SPAWN_LEVIATHAN, item_names.KERRIGAN_IMMOBILIZATION_WAVE, # Involves a 1-minute cooldown wait before the ultra item_names.KERRIGAN_MEND, # See note from THE EV below ), self.player) or self.kerrigan_levels(state, 20) or (self.kerrigan_levels(state, 10) and state.has(item_names.KERRIGAN_CHAIN_REACTION, self.player)) # Tested by THE EV, "facetank with Kerrigan and stutter step to the end with >10s left" # > have to lure the first group of Zerg in the 2nd timed section into the first room of the second area # > (with the heal box) so you can kill them before the timer starts. # # phaneros: Technically possible without the levels, but adding them in for safety margin and to hopefully # make generation force this branch less often or (state.has_any((item_names.KERRIGAN_HEROIC_FORTITUDE, item_names.KERRIGAN_INFEST_BROODLINGS), self.player) and self.kerrigan_levels(state, 5) ) # Insufficient: Wild Mutation, Assimilation Aura ) def zerg_pass_vents(self, state: CollectionState) -> bool: return ( self.grant_story_tech == GrantStoryTech.option_grant or state.has_any({item_names.ZERGLING, item_names.HYDRALISK, item_names.ROACH}, self.player) or (self.advanced_tactics and state.has(item_names.INFESTOR, self.player)) ) def supreme_requirement(self, state: CollectionState) -> bool: return ( self.grant_story_tech == GrantStoryTech.option_grant or not self.kerrigan_unit_available or (self.grant_story_tech == GrantStoryTech.option_allow_substitutes and state.has_any(( item_names.KERRIGAN_LEAPING_STRIKE, item_names.OVERLORD_VENTRAL_SACS, item_names.YGGDRASIL, item_names.MUTALISK_CORRUPTOR_VIPER_ASPECT, item_names.NYDUS_WORM, item_names.BULLFROG, ), self.player) and state.has_any(( item_names.KERRIGAN_MEND, item_names.SWARM_QUEEN, item_names.INFESTED_MEDICS, ), self.player) and self.kerrigan_levels(state, 35) ) or (state.has_all((item_names.KERRIGAN_LEAPING_STRIKE, item_names.KERRIGAN_MEND), self.player) and self.kerrigan_levels(state, 35)) ) def terran_infested_garrison_claimer(self, state: CollectionState) -> bool: return state.has_any((item_names.GHOST, item_names.SPECTRE, item_names.EMPERORS_SHADOW), self.player) def protoss_infested_garrison_claimer(self, state: CollectionState) -> bool: return state.has_any( (item_names.HIGH_TEMPLAR, item_names.SIGNIFIER, item_names.ASCENDANT), self.player ) or self.protoss_can_merge_dark_archon(state) def terran_hand_of_darkness_requirement(self, state: CollectionState) -> bool: return self.terran_competent_comp(state) and self.terran_power_rating(state) >= 6 def zerg_hand_of_darkness_requirement(self, state: CollectionState) -> bool: return ( self.zerg_competent_comp(state) and (self.zerg_competent_anti_air(state) or self.advanced_tactics and self.zerg_moderate_anti_air(state)) and (self.basic_kerrigan(state, False) or self.zerg_power_rating(state) >= 4) ) def protoss_hand_of_darkness_requirement(self, state: CollectionState) -> bool: return self.protoss_competent_comp(state) and self.protoss_power_rating(state) >= 6 def terran_planetfall_requirement(self, state: CollectionState) -> bool: return self.terran_beats_protoss_deathball(state) and self.terran_power_rating(state) >= 8 def zerg_planetfall_requirement(self, state: CollectionState) -> bool: return self.zerg_competent_comp(state) and self.zerg_competent_anti_air(state) and self.zerg_power_rating(state) >= 8 def protoss_planetfall_requirement(self, state: CollectionState) -> bool: return self.protoss_deathball(state) and self.protoss_power_rating(state) >= 8 def zerg_the_reckoning_requirement(self, state: CollectionState) -> bool: if not (self.zerg_power_rating(state) >= 6 or self.basic_kerrigan(state, False)): return False if self.take_over_ai_allies: return ( self.terran_competent_comp(state) and self.zerg_competent_comp(state) and (self.zerg_competent_anti_air(state) or self.terran_competent_anti_air(state)) and self.terran_very_hard_mission_weapon_armor_level(state) and self.zerg_very_hard_mission_weapon_armor_level(state) ) else: return self.zerg_competent_comp(state) and self.zerg_competent_anti_air(state) and self.zerg_very_hard_mission_weapon_armor_level(state) def terran_the_reckoning_requirement(self, state: CollectionState) -> bool: return self.terran_very_hard_mission_weapon_armor_level(state) and self.terran_base_trasher(state) def protoss_the_reckoning_requirement(self, state: CollectionState) -> bool: return ( self.protoss_very_hard_mission_weapon_armor_level(state) and self.protoss_deathball(state) and (not self.take_over_ai_allies or (self.terran_competent_comp(state) and self.terran_very_hard_mission_weapon_armor_level(state))) ) def protoss_can_attack_behind_chasm(self, state: CollectionState) -> bool: return ( state.has_any(( item_names.SCOUT, item_names.SKIRMISHER, item_names.TEMPEST, item_names.CARRIER, item_names.SKYLORD, item_names.TRIREME, item_names.VOID_RAY, item_names.DESTROYER, item_names.PULSAR, item_names.DAWNBRINGER, item_names.MOTHERSHIP, ), self.player) or self.protoss_has_blink(state) or ( state.has(item_names.WARP_PRISM, self.player) and (self.protoss_common_unit(state) or state.has(item_names.WARP_PRISM_PHASE_BLASTER, self.player)) ) or (self.advanced_tactics and state.has_any({item_names.ORACLE, item_names.ARBITER}, self.player)) ) def the_infinite_cycle_requirement(self, state: CollectionState) -> bool: return ( self.kerrigan_levels(state, 70) and ( self.grant_story_tech == GrantStoryTech.option_grant or not self.kerrigan_unit_available or ( state.has_any( ( item_names.KERRIGAN_KINETIC_BLAST, item_names.KERRIGAN_SPAWN_BANELINGS, item_names.KERRIGAN_LEAPING_STRIKE, item_names.KERRIGAN_SPAWN_LEVIATHAN, ), self.player, ) and self.basic_kerrigan(state) ) ) ) def templars_return_phase_2_requirement(self, state: CollectionState) -> bool: return ( self.grant_story_tech == GrantStoryTech.option_grant or self.advanced_tactics or ( state.has_any( ( item_names.IMMORTAL, item_names.ANNIHILATOR, item_names.VANGUARD, item_names.COLOSSUS, item_names.WRATHWALKER, item_names.REAVER, item_names.DARK_TEMPLAR, item_names.HIGH_TEMPLAR, item_names.ENERGIZER, item_names.SENTRY, ), self.player, ) ) ) def templars_return_phase_3_reach_colossus_requirement(self, state: CollectionState) -> bool: return self.templars_return_phase_2_requirement(state) and ( self.grant_story_tech == GrantStoryTech.option_grant or self.advanced_tactics and state.has_any({item_names.ZEALOT_WHIRLWIND, item_names.VANGUARD_RAPIDFIRE_CANNON}, self.player) or state.has_all(( item_names.ZEALOT_WHIRLWIND, item_names.VANGUARD_RAPIDFIRE_CANNON ), self.player) ) def templars_return_phase_3_reach_dts_requirement(self, state: CollectionState) -> bool: return self.templars_return_phase_3_reach_colossus_requirement(state) and ( self.grant_story_tech == GrantStoryTech.option_grant or ( (self.advanced_tactics or state.has(item_names.ENERGIZER_MOBILE_CHRONO_BEAM, self.player)) and (state.has(item_names.COLOSSUS_FIRE_LANCE, self.player) or ( state.has_all( { item_names.COLOSSUS_PACIFICATION_PROTOCOL, item_names.ENERGIZER_MOBILE_CHRONO_BEAM, }, self.player, ) ) )) ) def terran_spear_of_adun_requirement(self, state: CollectionState) -> bool: return self.terran_common_unit(state) and self.terran_competent_anti_air(state) and self.terran_defense_rating(state, False, False) >= 5 def zerg_spear_of_adun_requirement(self, state: CollectionState) -> bool: return self.zerg_common_unit(state) and self.zerg_competent_anti_air(state) and self.zerg_defense_rating(state, False, False) >= 5 def protoss_spear_of_adun_requirement(self, state: CollectionState) -> bool: return ( self.protoss_common_unit(state) and self.protoss_anti_light_anti_air(state) and ( state.has_any((item_names.ZEALOT, item_names.CENTURION, item_names.SENTINEL, item_names.ADEPT), self.player) or self.protoss_basic_splash(state) ) and self.protoss_defense_rating(state, False) >= 5 ) def terran_sky_shield_requirement(self, state: CollectionState) -> bool: return self.terran_common_unit(state) and self.terran_competent_anti_air(state) and self.terran_power_rating(state) >= 7 def zerg_sky_shield_requirement(self, state: CollectionState) -> bool: return self.zerg_common_unit(state) and self.zerg_competent_anti_air(state) and self.zerg_power_rating(state) >= 7 def protoss_sky_shield_requirement(self, state: CollectionState) -> bool: return self.protoss_common_unit(state) and self.protoss_competent_anti_air(state) and self.protoss_power_rating(state) >= 7 def protoss_brothers_in_arms_requirement(self, state: CollectionState) -> bool: return (self.protoss_common_unit(state) and self.protoss_anti_armor_anti_air(state) and self.protoss_hybrid_counter(state)) or ( self.take_over_ai_allies and (self.terran_common_unit(state) or self.protoss_common_unit(state)) and (self.terran_competent_anti_air(state) or self.protoss_anti_armor_anti_air(state)) and ( self.protoss_hybrid_counter(state) or state.has_any({item_names.BATTLECRUISER, item_names.LIBERATOR, item_names.SIEGE_TANK}, self.player) or (self.advanced_tactics and state.has_all({item_names.SPECTRE, item_names.SPECTRE_PSIONIC_LASH}, self.player)) or ( state.has(item_names.IMMORTAL, self.player) and state.has_any({item_names.MARINE, item_names.DOMINION_TROOPER, item_names.MARAUDER}, self.player) and self.terran_bio_heal(state) ) ) ) def zerg_brothers_in_arms_requirement(self, state: CollectionState) -> bool: return ( self.zerg_common_unit(state) and self.zerg_competent_comp(state) and self.zerg_competent_anti_air(state) and self.zerg_big_monsters(state) ) or ( self.take_over_ai_allies and (self.zerg_common_unit(state) or self.terran_common_unit(state)) and (self.terran_competent_anti_air(state) or self.zerg_competent_anti_air(state)) and ( self.zerg_big_monsters(state) or state.has_any({item_names.BATTLECRUISER, item_names.LIBERATOR, item_names.SIEGE_TANK}, self.player) or (self.advanced_tactics and state.has_all({item_names.SPECTRE, item_names.SPECTRE_PSIONIC_LASH}, self.player)) or ( state.has(item_names.ABERRATION, self.player) and state.has_any({item_names.MARINE, item_names.DOMINION_TROOPER, item_names.MARAUDER}, self.player) and self.terran_bio_heal(state) ) ) ) def protoss_amons_reach_requirement(self, state: CollectionState) -> bool: return self.protoss_common_unit_anti_light_air(state) and self.protoss_basic_splash(state) and self.protoss_power_rating(state) >= 7 def protoss_last_stand_requirement(self, state: CollectionState) -> bool: return ( self.protoss_common_unit(state) and self.protoss_competent_anti_air(state) and self.protoss_static_defense(state) and self.protoss_defense_rating(state, False) >= 8 ) def terran_last_stand_requirement(self, state: CollectionState) -> bool: return ( self.terran_common_unit(state) and state.has_any({item_names.SIEGE_TANK, item_names.LIBERATOR}, self.player) and state.has_any({item_names.PERDITION_TURRET, item_names.DEVASTATOR_TURRET, item_names.PLANETARY_FORTRESS}, self.player) and self.terran_air_anti_air(state) and state.has_any({item_names.VIKING, item_names.BATTLECRUISER}, self.player) and self.terran_defense_rating(state, True, False) >= 10 and self.terran_army_weapon_armor_upgrade_min_level(state) >= 2 ) def zerg_last_stand_requirement(self, state: CollectionState) -> bool: return ( self.zerg_common_unit(state) and self.zerg_competent_anti_air(state) and state.has(item_names.SPINE_CRAWLER, self.player) and ( self.morph_lurker(state) or state.has_all({item_names.MUTALISK, item_names.MUTALISK_SEVERING_GLAIVE, item_names.MUTALISK_VICIOUS_GLAIVE}, self.player) or self.zerg_infested_tank_with_ammo(state) or self.advanced_tactics and state.has_all({item_names.ULTRALISK, item_names.ULTRALISK_CHITINOUS_PLATING, item_names.ULTRALISK_MONARCH_BLADES}, self.player) ) and ( self.morph_impaler(state) or state.has_all({item_names.INFESTED_LIBERATOR, item_names.INFESTED_LIBERATOR_DEFENDER_MODE}, self.player) or self.zerg_infested_tank_with_ammo(state) or state.has(item_names.BILE_LAUNCHER, self.player) ) and ( self.morph_devourer(state) or state.has_all({item_names.MUTALISK, item_names.MUTALISK_SUNDERING_GLAIVE}, self.player) or self.advanced_tactics and state.has(item_names.BROOD_QUEEN, self.player) ) and self.zerg_mineral_dump(state) and self.zerg_army_weapon_armor_upgrade_min_level(state) >= 2 ) def terran_temple_of_unification_requirement(self, state: CollectionState) -> bool: return self.terran_beats_protoss_deathball(state) and self.terran_power_rating(state) >= 10 def zerg_temple_of_unification_requirement(self, state: CollectionState) -> bool: # Don't be locked to roach/hydra return ( self.zerg_competent_comp(state) and self.zerg_competent_anti_air(state) and ( state.has(item_names.INFESTED_BANSHEE, self.player) or state.has_all((item_names.INFESTED_LIBERATOR, item_names.INFESTED_LIBERATOR_DEFENDER_MODE), self.player) or state.has_all({item_names.MUTALISK, item_names.MUTALISK_SUNDERING_GLAIVE}, self.player) or self.zerg_big_monsters(state) or ( self.advanced_tactics and (state.has_any({item_names.INFESTOR, item_names.DEFILER, item_names.BROOD_QUEEN}, self.player) or self.morph_viper(state)) ) ) and self.zerg_power_rating(state) >= 10 ) def protoss_temple_of_unification_requirement(self, state: CollectionState) -> bool: return self.protoss_competent_comp(state) and self.protoss_power_rating(state) >= 10 def protoss_harbinger_of_oblivion_requirement(self, state: CollectionState) -> bool: return ( self.protoss_anti_armor_anti_air(state) and ( self.take_over_ai_allies and (self.protoss_common_unit(state) or self.zerg_common_unit(state)) or (self.protoss_competent_comp(state) and self.protoss_hybrid_counter(state)) ) and self.protoss_power_rating(state) >= 6 ) def terran_harbinger_of_oblivion_requirement(self, state: CollectionState) -> bool: return ( self.terran_competent_anti_air(state) and ( self.take_over_ai_allies and (self.terran_common_unit(state) or self.zerg_common_unit(state)) or ( self.terran_beats_protoss_deathball(state) and state.has_any({item_names.BATTLECRUISER, item_names.LIBERATOR, item_names.SIEGE_TANK, item_names.THOR}, self.player) ) ) and self.terran_power_rating(state) >= 6 ) def zerg_harbinger_of_oblivion_requirement(self, state: CollectionState) -> bool: return ( self.zerg_competent_anti_air(state) and self.zerg_common_unit(state) and (self.take_over_ai_allies or (self.zerg_competent_comp(state) and self.zerg_big_monsters(state))) and self.zerg_power_rating(state) >= 6 ) def protoss_unsealing_the_past_ledge_requirement(self, state: CollectionState) -> bool: return ( state.has_any((item_names.COLOSSUS, item_names.WRATHWALKER), self.player) or self.protoss_can_attack_behind_chasm(state) ) def terran_unsealing_the_past_requirement(self, state: CollectionState) -> bool: return ( self.terran_competent_anti_air(state) and self.terran_competent_comp(state) and self.terran_power_rating(state) >= 6 and ( state.has_all((item_names.SIEGE_TANK, item_names.SIEGE_TANK_JUMP_JETS), self.player) or state.has_all( {item_names.BATTLECRUISER, item_names.BATTLECRUISER_ATX_LASER_BATTERY, item_names.BATTLECRUISER_MOIRAI_IMPULSE_DRIVE}, self.player ) or ( self.advanced_tactics and ( state.has_all({item_names.SIEGE_TANK, item_names.SIEGE_TANK_SMART_SERVOS}, self.player) or ( state.has_all({item_names.LIBERATOR, item_names.LIBERATOR_SMART_SERVOS}, self.player) and ( ( state.has_all({item_names.HELLION, item_names.HELLION_HELLBAT}, self.player) or state.has(item_names.FIREBAT, self.player) ) and self.terran_bio_heal(state) or state.has_all({item_names.VIKING, item_names.VIKING_SHREDDER_ROUNDS}, self.player) or state.has(item_names.BANSHEE, self.player) ) ) ) ) ) ) def zerg_unsealing_the_past_requirement(self, state: CollectionState) -> bool: return ( self.zerg_competent_comp(state) and self.zerg_competent_anti_air(state) and self.zerg_power_rating(state) >= 6 and ( self.morph_brood_lord(state) or self.zerg_big_monsters(state) or state.has_all({item_names.MUTALISK, item_names.MUTALISK_SEVERING_GLAIVE, item_names.MUTALISK_VICIOUS_GLAIVE}, self.player) or ( self.advanced_tactics and (self.morph_igniter(state) or (self.morph_lurker(state) and state.has(item_names.LURKER_SEISMIC_SPINES, self.player))) ) ) ) def terran_purification_requirement(self, state: CollectionState) -> bool: return ( self.terran_competent_comp(state) and self.terran_very_hard_mission_weapon_armor_level(state) and self.terran_defense_rating(state, True, False) >= 10 and ( state.has_any({item_names.LIBERATOR, item_names.THOR}, self.player) or ( state.has(item_names.SIEGE_TANK, self.player) and (self.advanced_tactics or state.has(item_names.SIEGE_TANK_MAELSTROM_ROUNDS, self.player)) ) ) and ( state.has_all({item_names.VIKING, item_names.VIKING_SHREDDER_ROUNDS}, self.player) or ( state.has(item_names.BANSHEE, self.player) and ( state.has(item_names.BANSHEE_SHOCKWAVE_MISSILE_BATTERY, self.player) or (self.advanced_tactics and state.has(item_names.BANSHEE_ROCKET_BARRAGE, self.player)) ) ) ) ) def zerg_purification_requirement(self, state: CollectionState) -> bool: return ( self.zerg_competent_comp(state) and self.zerg_competent_anti_air(state) and self.zerg_defense_rating(state, True, True) >= 5 and self.zerg_big_monsters(state) and (state.has(item_names.ULTRALISK, self.player) or self.morph_igniter(state) or self.morph_lurker(state)) ) def protoss_steps_of_the_rite_requirement(self, state: CollectionState) -> bool: return self.protoss_deathball(state) or self.protoss_fleet(state) def terran_steps_of_the_rite_requirement(self, state: CollectionState) -> bool: return ( self.terran_beats_protoss_deathball(state) and ( state.has_any({item_names.SIEGE_TANK, item_names.LIBERATOR}, self.player) or state.has_all({item_names.BATTLECRUISER, item_names.BATTLECRUISER_ATX_LASER_BATTERY}, self.player) or state.has_all((item_names.BANSHEE, item_names.BANSHEE_SHOCKWAVE_MISSILE_BATTERY), self.player) ) and ( state.has_all({item_names.BATTLECRUISER, item_names.BATTLECRUISER_ATX_LASER_BATTERY}, self.player) or state.has(item_names.VALKYRIE, self.player) or state.has_all((item_names.VIKING, item_names.VIKING_RIPWAVE_MISSILES), self.player) ) and self.terran_very_hard_mission_weapon_armor_level(state) ) def zerg_steps_of_the_rite_requirement(self, state: CollectionState) -> bool: return ( self.zerg_competent_comp(state) and self.zerg_competent_anti_air(state) and self.zerg_base_buster(state) and ( self.morph_lurker(state) or self.zerg_infested_tank_with_ammo(state) or state.has_all({item_names.INFESTED_LIBERATOR, item_names.INFESTED_LIBERATOR_DEFENDER_MODE}, self.player) or (state.has(item_names.SWARM_QUEEN, self.player) and self.zerg_big_monsters(state)) ) and ( state.has(item_names.INFESTED_LIBERATOR, self.player) or state.has_all({item_names.MUTALISK, item_names.MUTALISK_SEVERING_GLAIVE, item_names.MUTALISK_VICIOUS_GLAIVE}, self.player) or (state.has(item_names.MUTALISK, self.player) and self.morph_devourer(state)) ) ) def terran_rak_shir_requirement(self, state: CollectionState) -> bool: return self.terran_beats_protoss_deathball(state) and self.terran_power_rating(state) >= 10 def zerg_rak_shir_requirement(self, state: CollectionState) -> bool: return ( self.zerg_competent_comp(state) and self.zerg_competent_anti_air(state) and ( self.zerg_big_monsters(state) or state.has_all({item_names.INFESTED_LIBERATOR, item_names.INFESTED_LIBERATOR_DEFENDER_MODE}, self.player) or self.morph_impaler_or_lurker(state) ) and ( state.has_all({item_names.INFESTED_LIBERATOR, item_names.INFESTED_LIBERATOR_CLOUD_DISPERSAL}, self.player) or ( state.has(item_names.MUTALISK, self.player) and (state.has(item_names.MUTALISK_SUNDERING_GLAIVE, self.player) or self.morph_devourer(state)) ) or state.has(item_names.CORRUPTOR, self.player) or (self.advanced_tactics and state.has(item_names.INFESTOR, self.player)) ) and self.zerg_power_rating(state) >= 10 ) def protoss_rak_shir_requirement(self, state: CollectionState) -> bool: return (self.protoss_deathball(state) or self.protoss_fleet(state)) and self.protoss_power_rating(state) >= 10 def protoss_templars_charge_requirement(self, state: CollectionState) -> bool: return ( self.protoss_heal(state) and self.protoss_anti_armor_anti_air(state) and ( self.protoss_fleet(state) or ( self.advanced_tactics and self.protoss_competent_comp(state) and ( state.has_any((item_names.ARBITER, item_names.CORSAIR, item_names.PHOENIX), self.player) or state.has_all((item_names.MIRAGE, item_names.MIRAGE_GRAVITON_BEAM), self.player) ) ) ) ) def terran_templars_charge_requirement(self, state: CollectionState) -> bool: return self.terran_very_hard_mission_weapon_armor_level(state) and ( ( state.has_all({item_names.BATTLECRUISER, item_names.BATTLECRUISER_ATX_LASER_BATTERY}, self.player) and state.count(item_names.BATTLECRUISER_PROGRESSIVE_DEFENSIVE_MATRIX, self.player) >= 2 ) or ( self.terran_air_anti_air(state) and self.terran_sustainable_mech_heal(state) and ( state.has_any({item_names.BANSHEE, item_names.BATTLECRUISER}, self.player) or state.has_all({item_names.LIBERATOR, item_names.LIBERATOR_RAID_ARTILLERY}, self.player) or (self.advanced_tactics and (state.has_all({item_names.WRAITH, item_names.WRAITH_ADVANCED_LASER_TECHNOLOGY}, self.player))) ) ) ) def zerg_templars_charge_requirement(self, state: CollectionState) -> bool: return ( self.zerg_competent_comp(state) and self.zerg_competent_anti_air(state) and state.has(item_names.SWARM_QUEEN, self.player) and ( self.morph_guardian(state) or self.morph_brood_lord(state) or state.has(item_names.INFESTED_BANSHEE, self.player) or ( self.advanced_tactics and ( state.has_all( { item_names.MUTALISK, item_names.MUTALISK_SEVERING_GLAIVE, item_names.MUTALISK_VICIOUS_GLAIVE, item_names.MUTALISK_AERODYNAMIC_GLAIVE_SHAPE, }, self.player, ) or self.morph_viper(state) ) ) ) and ( state.has_all({item_names.INFESTED_LIBERATOR, item_names.INFESTED_LIBERATOR_CLOUD_DISPERSAL}, self.player) or (self.morph_devourer(state) and state.has(item_names.MUTALISK, self.player)) or state.has_all({item_names.MUTALISK, item_names.MUTALISK_SUNDERING_GLAIVE}, self.player) ) ) def protoss_the_host_requirement(self, state: CollectionState) -> bool: return ( self.protoss_fleet(state) and self.protoss_static_defense(state) and self.protoss_army_weapon_armor_upgrade_min_level(state) >= 2 ) or ( self.protoss_deathball(state) and state.has(item_names.SOA_TIME_STOP, self.player) or self.advanced_tactics and (state.has_any((item_names.SOA_SHIELD_OVERCHARGE, item_names.SOA_SOLAR_BOMBARDMENT), self.player)) ) def terran_the_host_requirement(self, state: CollectionState) -> bool: return ( self.terran_beats_protoss_deathball(state) and self.terran_very_hard_mission_weapon_armor_level(state) and ( ( state.has_all({item_names.BATTLECRUISER, item_names.BATTLECRUISER_ATX_LASER_BATTERY}, self.player) and state.count(item_names.BATTLECRUISER_PROGRESSIVE_DEFENSIVE_MATRIX, self.player) >= 2 ) or ( self.terran_air_anti_air(state) and self.terran_sustainable_mech_heal(state) and ( state.has_any({item_names.BANSHEE, item_names.BATTLECRUISER}, self.player) or state.has_all({item_names.LIBERATOR, item_names.LIBERATOR_RAID_ARTILLERY}, self.player) ) ) or ( self.spear_of_adun_presence == SpearOfAdunPresence.option_everywhere and state.has(item_names.SOA_TIME_STOP, self.player) or self.advanced_tactics and (state.has_any((item_names.SOA_SHIELD_OVERCHARGE, item_names.SOA_SOLAR_BOMBARDMENT), self.player)) ) ) ) def zerg_the_host_requirement(self, state: CollectionState) -> bool: return ( self.zerg_competent_comp(state) and self.zerg_competent_anti_air(state) and self.zerg_very_hard_mission_weapon_armor_level(state) and self.zerg_base_buster(state) and self.zerg_big_monsters(state) and ( (self.morph_brood_lord(state) or self.morph_guardian(state)) and ( (self.morph_devourer(state) and state.has(item_names.MUTALISK, self.player)) or state.has_all((item_names.INFESTED_LIBERATOR, item_names.INFESTED_LIBERATOR_CLOUD_DISPERSAL), self.player) ) or ( state.has_all( ( item_names.MUTALISK, item_names.MUTALISK_SEVERING_GLAIVE, item_names.MUTALISK_VICIOUS_GLAIVE, item_names.MUTALISK_SUNDERING_GLAIVE, item_names.MUTALISK_RAPID_REGENERATION, ), self.player, ) ) ) or ( self.spear_of_adun_presence == SpearOfAdunPresence.option_everywhere and state.has(item_names.SOA_TIME_STOP, self.player) or self.advanced_tactics and (state.has_any((item_names.SOA_SHIELD_OVERCHARGE, item_names.SOA_SOLAR_BOMBARDMENT), self.player)) ) ) def protoss_salvation_requirement(self, state: CollectionState) -> bool: return ( ([self.protoss_competent_comp(state), self.protoss_fleet(state), self.protoss_static_defense(state)].count(True) >= 2) and self.protoss_very_hard_mission_weapon_armor_level(state) and self.protoss_power_rating(state) >= 6 ) def terran_salvation_requirement(self, state: CollectionState) -> bool: return ( self.terran_beats_protoss_deathball(state) and self.terran_very_hard_mission_weapon_armor_level(state) and self.terran_air_anti_air(state) and state.has_any({item_names.SIEGE_TANK, item_names.LIBERATOR}, self.player) and state.has_any({item_names.PERDITION_TURRET, item_names.DEVASTATOR_TURRET, item_names.PLANETARY_FORTRESS}, self.player) and self.terran_power_rating(state) >= 6 ) def zerg_salvation_requirement(self, state: CollectionState) -> bool: return ( self.zerg_competent_comp(state) and self.zerg_competent_anti_air(state) and state.has(item_names.SPINE_CRAWLER, self.player) and self.zerg_very_hard_mission_weapon_armor_level(state) and ( self.morph_impaler_or_lurker(state) or state.has_all({item_names.INFESTED_LIBERATOR, item_names.INFESTED_LIBERATOR_DEFENDER_MODE}, self.player) or state.has_all({item_names.MUTALISK, item_names.MUTALISK_SEVERING_GLAIVE, item_names.MUTALISK_VICIOUS_GLAIVE}, self.player) ) and ( state.has_all({item_names.INFESTED_LIBERATOR, item_names.INFESTED_LIBERATOR_CLOUD_DISPERSAL}, self.player) or (self.morph_devourer(state) and state.has(item_names.MUTALISK, self.player)) or state.has_all({item_names.MUTALISK, item_names.MUTALISK_SUNDERING_GLAIVE}, self.player) ) and self.zerg_power_rating(state) >= 6 ) def into_the_void_requirement(self, state: CollectionState) -> bool: if not self.protoss_very_hard_mission_weapon_armor_level(state): return False if self.take_over_ai_allies and not ( self.terran_very_hard_mission_weapon_armor_level(state) and self.zerg_very_hard_mission_weapon_armor_level(state) ): return False return self.protoss_competent_comp(state) or ( self.take_over_ai_allies and ( state.has(item_names.BATTLECRUISER, self.player) or (state.has(item_names.ULTRALISK, self.player) and self.protoss_competent_anti_air(state)) ) ) def essence_of_eternity_requirement(self, state: CollectionState) -> bool: if not self.terran_very_hard_mission_weapon_armor_level(state): return False if self.take_over_ai_allies and not ( self.protoss_very_hard_mission_weapon_armor_level(state) and self.zerg_very_hard_mission_weapon_armor_level(state) ): return False defense_score = self.terran_defense_rating(state, False, True) if self.take_over_ai_allies and self.protoss_static_defense(state): defense_score += 2 return ( defense_score >= 12 and (self.terran_competent_anti_air(state) or self.take_over_ai_allies and self.protoss_competent_anti_air(state)) and ( state.has(item_names.BATTLECRUISER, self.player) or ( state.has_any((item_names.BANSHEE, item_names.LIBERATOR), self.player) and state.has_any({item_names.VIKING, item_names.VALKYRIE}, self.player) ) or self.take_over_ai_allies and self.protoss_fleet(state) ) and self.terran_power_rating(state) >= 6 ) def amons_fall_requirement(self, state: CollectionState) -> bool: if not self.zerg_very_hard_mission_weapon_armor_level(state): return False if not self.zerg_competent_anti_air(state): return False if self.zerg_power_rating(state) < 6: return False if self.take_over_ai_allies and not ( self.terran_very_hard_mission_weapon_armor_level(state) and self.protoss_very_hard_mission_weapon_armor_level(state) ): return False if self.take_over_ai_allies: return ( ( state.has_any({item_names.BATTLECRUISER, item_names.CARRIER, item_names.SKYLORD, item_names.TRIREME}, self.player) or ( state.has(item_names.ULTRALISK, self.player) and self.protoss_competent_anti_air(state) and ( state.has_any({item_names.LIBERATOR, item_names.BANSHEE, item_names.VALKYRIE, item_names.VIKING}, self.player) or state.has_all({item_names.WRAITH, item_names.WRAITH_ADVANCED_LASER_TECHNOLOGY}, self.player) or self.protoss_fleet(state) ) and ( self.terran_sustainable_mech_heal(state) or ( self.spear_of_adun_passive_presence == SpearOfAdunPassiveAbilityPresence.option_everywhere and state.has(item_names.RECONSTRUCTION_BEAM, self.player) ) ) ) ) and self.terran_competent_anti_air(state) and self.protoss_deathball(state) and self.zerg_competent_comp(state) ) else: return ( ( state.has_any((item_names.MUTALISK, item_names.CORRUPTOR, item_names.BROOD_QUEEN, item_names.INFESTED_BANSHEE), self.player) or state.has_all((item_names.INFESTED_LIBERATOR, item_names.INFESTED_LIBERATOR_CLOUD_DISPERSAL), self.player) or state.has_all((item_names.SCOURGE, item_names.SCOURGE_RESOURCE_EFFICIENCY), self.player) or self.morph_brood_lord(state) or self.morph_guardian(state) or self.morph_devourer(state) ) or (self.advanced_tactics and self.spread_creep(state, False) and self.zerg_big_monsters(state)) ) and self.zerg_competent_comp(state) def the_escape_stuff_granted(self) -> bool: """ The NCO first mission requires having too much stuff first before actually able to do anything :return: """ return self.grant_story_tech == GrantStoryTech.option_grant or (self.mission_order == MissionOrder.option_vanilla and self.enabled_campaigns == {SC2Campaign.NCO}) def the_escape_first_stage_requirement(self, state: CollectionState) -> bool: return self.the_escape_stuff_granted() or (self.nova_ranged_weapon(state) and (self.nova_full_stealth(state) or self.nova_heal(state))) def the_escape_requirement(self, state: CollectionState) -> bool: return self.the_escape_first_stage_requirement(state) and (self.the_escape_stuff_granted() or self.nova_splash(state)) def terran_able_to_snipe_defiler(self, state: CollectionState) -> bool: return ( state.has(item_names.BANSHEE, self.player) or ( state.has(item_names.NOVA_JUMP_SUIT_MODULE, self.player) and (state.has_any({item_names.NOVA_DOMINATION, item_names.NOVA_C20A_CANISTER_RIFLE, item_names.NOVA_PULSE_GRENADES}, self.player)) ) or (state.has_all({item_names.SIEGE_TANK, item_names.SIEGE_TANK_MAELSTROM_ROUNDS, item_names.SIEGE_TANK_JUMP_JETS}, self.player)) ) def sudden_strike_requirement(self, state: CollectionState) -> bool: return ( self.terran_able_to_snipe_defiler(state) and (self.terran_cliffjumper(state) or state.has(item_names.BANSHEE, self.player)) and self.nova_splash(state) and self.terran_defense_rating(state, True, False) >= 3 and (self.advanced_tactics or state.has(item_names.NOVA_JUMP_SUIT_MODULE, self.player) ) ) def enemy_intelligence_garrisonable_unit(self, state: CollectionState) -> bool: """ Has unit usable as a Garrison in Enemy Intelligence """ return ( state.has_any(( item_names.MARINE, item_names.SON_OF_KORHAL, item_names.REAPER, item_names.MARAUDER, item_names.GHOST, item_names.SPECTRE, item_names.HELLION, item_names.GOLIATH, item_names.WARHOUND, item_names.DIAMONDBACK, item_names.VIKING, item_names.DOMINION_TROOPER, ), self.player) or (self.advanced_tactics and state.has(item_names.ROGUE_FORCES, self.player) and state.count_from_list(( item_names.WAR_PIGS, item_names.HAMMER_SECURITIES, item_names.DEATH_HEADS, item_names.SPARTAN_COMPANY, item_names.HELS_ANGELS, item_names.BRYNHILDS, ), self.player) >= 3 ) ) def enemy_intelligence_cliff_garrison(self, state: CollectionState) -> bool: return ( state.has_any((item_names.REAPER, item_names.VIKING), self.player) or (state.has_any((item_names.MEDIVAC, item_names.HERCULES), self.player) and self.enemy_intelligence_garrisonable_unit(state) ) or state.has_all({item_names.GOLIATH, item_names.GOLIATH_JUMP_JETS}, self.player) or (self.advanced_tactics and state.has_any({item_names.HELS_ANGELS, item_names.BRYNHILDS}, self.player)) ) def enemy_intelligence_first_stage_requirement(self, state: CollectionState) -> bool: return ( self.enemy_intelligence_garrisonable_unit(state) and ( self.terran_competent_comp(state) or (self.terran_common_unit(state) and self.terran_competent_anti_air(state) and state.has(item_names.NOVA_NUKE, self.player)) ) and self.terran_defense_rating(state, True, True) >= 5 ) def enemy_intelligence_second_stage_requirement(self, state: CollectionState) -> bool: return ( self.enemy_intelligence_first_stage_requirement(state) and self.enemy_intelligence_cliff_garrison(state) and ( self.grant_story_tech == GrantStoryTech.option_grant or ( self.nova_any_weapon(state) and (self.nova_full_stealth(state) or (self.nova_heal(state) and self.nova_splash(state) and self.nova_ranged_weapon(state))) ) ) ) def enemy_intelligence_third_stage_requirement(self, state: CollectionState) -> bool: return self.enemy_intelligence_second_stage_requirement(state) and ( self.grant_story_tech == GrantStoryTech.option_grant or (state.has(item_names.NOVA_PROGRESSIVE_STEALTH_SUIT_MODULE, self.player) and self.nova_dash(state)) ) def enemy_intelligence_cliff_garrison_and_nova_mobility(self, state: CollectionState) -> bool: return self.enemy_intelligence_cliff_garrison(state) and ( self.nova_any_nobuild_damage(state) or ( state.has(item_names.NOVA_PROGRESSIVE_STEALTH_SUIT_MODULE, self.player, 2) and state.has_any((item_names.NOVA_FLASHBANG_GRENADES, item_names.NOVA_BLINK), self.player) ) ) def trouble_in_paradise_requirement(self, state: CollectionState) -> bool: return ( self.nova_any_weapon(state) and self.nova_splash(state) and self.terran_beats_protoss_deathball(state) and self.terran_defense_rating(state, True, True) >= 7 and self.terran_power_rating(state) >= 5 ) def night_terrors_requirement(self, state: CollectionState) -> bool: return ( self.terran_common_unit(state) and self.terran_competent_anti_air(state) and ( # These can handle the waves of infested, even volatile ones state.has(item_names.SIEGE_TANK, self.player) or state.has_all({item_names.VIKING, item_names.VIKING_SHREDDER_ROUNDS}, self.player) or state.has_all((item_names.BANSHEE, item_names.BANSHEE_SHOCKWAVE_MISSILE_BATTERY), self.player) or ( ( # Regular infesteds ( state.has_any((item_names.FIREBAT, item_names.REAPER), self.player) or state.has_all({item_names.HELLION, item_names.HELLION_HELLBAT}, self.player) ) and self.terran_bio_heal(state) or (self.advanced_tactics and state.has_any({item_names.PERDITION_TURRET, item_names.PLANETARY_FORTRESS}, self.player)) ) and ( # Volatile infesteds state.has(item_names.LIBERATOR, self.player) or ( self.advanced_tactics and state.has(item_names.VULTURE, self.player) or (state.has(item_names.HERC, self.player) and self.terran_bio_heal(state)) ) ) ) ) and self.terran_army_weapon_armor_upgrade_min_level(state) >= 2 ) def flashpoint_far_requirement(self, state: CollectionState) -> bool: return ( self.terran_competent_comp(state) and self.terran_mobile_detector(state) and self.terran_defense_rating(state, True, False) >= 6 and self.terran_army_weapon_armor_upgrade_min_level(state) >= 2 and self.nova_splash(state) and (self.advanced_tactics or self.terran_competent_ground_to_air(state)) ) def enemy_shadow_tripwires_tool(self, state: CollectionState) -> bool: return state.has_any({item_names.NOVA_FLASHBANG_GRENADES, item_names.NOVA_BLINK, item_names.NOVA_DOMINATION}, self.player) def enemy_shadow_door_unlocks_tool(self, state: CollectionState) -> bool: return state.has_any({item_names.NOVA_DOMINATION, item_names.NOVA_BLINK, item_names.NOVA_JUMP_SUIT_MODULE}, self.player) def enemy_shadow_nova_damage_and_blazefire_unlock(self, state: CollectionState) -> bool: return self.nova_any_nobuild_damage(state) and ( state.has(item_names.NOVA_BLINK, self.player) or state.has_all((item_names.NOVA_HOLO_DECOY, item_names.NOVA_DOMINATION), self.player) ) def enemy_shadow_domination(self, state: CollectionState) -> bool: return self.grant_story_tech == GrantStoryTech.option_grant or ( self.nova_ranged_weapon(state) and ( self.nova_full_stealth(state) or state.has(item_names.NOVA_JUMP_SUIT_MODULE, self.player) or (self.nova_heal(state) and self.nova_splash(state)) ) ) def enemy_shadow_first_stage(self, state: CollectionState) -> bool: return self.enemy_shadow_domination(state) and ( self.grant_story_tech == GrantStoryTech.option_grant or ((self.nova_full_stealth(state) and self.enemy_shadow_tripwires_tool(state)) or (self.nova_heal(state) and self.nova_splash(state))) ) def enemy_shadow_second_stage(self, state: CollectionState) -> bool: return self.enemy_shadow_first_stage(state) and ( self.grant_story_tech == GrantStoryTech.option_grant or (self.nova_splash(state) or self.nova_heal(state) or self.nova_escape_assist(state)) and (self.advanced_tactics or state.has(item_names.NOVA_GHOST_VISOR, self.player)) ) def enemy_shadow_door_controls(self, state: CollectionState) -> bool: return self.enemy_shadow_second_stage(state) and (self.grant_story_tech == GrantStoryTech.option_grant or self.enemy_shadow_door_unlocks_tool(state)) def enemy_shadow_victory(self, state: CollectionState) -> bool: return self.enemy_shadow_door_controls(state) and (self.grant_story_tech == GrantStoryTech.option_grant or (self.nova_heal(state) and self.nova_beat_stone(state))) def dark_skies_requirement(self, state: CollectionState) -> bool: return self.terran_common_unit(state) and self.terran_beats_protoss_deathball(state) and self.terran_defense_rating(state, False, True) >= 8 def end_game_requirement(self, state: CollectionState) -> bool: return ( self.terran_competent_comp(state) and self.terran_mobile_detector(state) and self.nova_any_weapon(state) and self.nova_splash(state) and ( # Xanthos state.has_any((item_names.BATTLECRUISER, item_names.VIKING, item_names.WARHOUND), self.player) or state.has_all((item_names.LIBERATOR, item_names.LIBERATOR_SMART_SERVOS), self.player) or state.has_all((item_names.THOR, item_names.THOR_PROGRESSIVE_HIGH_IMPACT_PAYLOAD), self.player) or ( state.has(item_names.VALKYRIE, self.player) and state.has_any((item_names.VALKYRIE_AFTERBURNERS, item_names.VALKYRIE_SHAPED_HULL), self.player) and state.has_any((item_names.VALKYRIE_FLECHETTE_MISSILES, item_names.VALKYRIE_ENHANCED_CLUSTER_LAUNCHERS), self.player) ) or (state.has(item_names.BANSHEE, self.player) and (self.advanced_tactics or state.has(item_names.BANSHEE_SHAPED_HULL, self.player))) or ( self.advanced_tactics and ( ( state.has_all((item_names.MARINE, item_names.MARINE_PROGRESSIVE_STIMPACK), self.player) and (self.terran_bio_heal(state) or state.count(item_names.MARINE_PROGRESSIVE_STIMPACK, self.player) >= 2) ) or (state.has(item_names.DOMINION_TROOPER, self.player) and self.terran_bio_heal(state)) or state.has_all( (item_names.PREDATOR, item_names.PREDATOR_RESOURCE_EFFICIENCY, item_names.PREDATOR_ADAPTIVE_DEFENSES), self.player ) or state.has_all((item_names.CYCLONE, item_names.CYCLONE_TARGETING_OPTICS), self.player) ) ) ) and ( # The enemy has 3/3 BCs state.has_any( (item_names.GOLIATH, item_names.VIKING, item_names.NOVA_C20A_CANISTER_RIFLE, item_names.NOVA_BLAZEFIRE_GUNBLADE), self.player ) or state.has_all((item_names.THOR, item_names.THOR_PROGRESSIVE_HIGH_IMPACT_PAYLOAD), self.player) or state.has_all((item_names.GHOST, item_names.GHOST_LOCKDOWN), self.player) or state.has_all((item_names.BATTLECRUISER, item_names.BATTLECRUISER_ATX_LASER_BATTERY), self.player) ) and self.terran_army_weapon_armor_upgrade_min_level(state) >= 3 ) def has_terran_units(self, target: int) -> Callable[["CollectionState"], bool]: def _has_terran_units(state: CollectionState) -> bool: return ( state.count_from_list_unique( item_groups.terran_units + item_groups.terran_buildings, self.player ) >= target and ( target < 5 or self.terran_any_anti_air(state) ) and ( # Anything that can hit buildings state.has_any(( # Infantry item_names.MARINE, item_names.FIREBAT, item_names.MARAUDER, item_names.REAPER, item_names.HERC, item_names.DOMINION_TROOPER, item_names.GHOST, item_names.SPECTRE, # Vehicles item_names.HELLION, item_names.VULTURE, item_names.SIEGE_TANK, item_names.WARHOUND, item_names.GOLIATH, item_names.DIAMONDBACK, item_names.THOR, item_names.PREDATOR, item_names.CYCLONE, # Ships item_names.WRAITH, item_names.VIKING, item_names.BANSHEE, item_names.RAVEN, item_names.BATTLECRUISER, # RG item_names.SON_OF_KORHAL, item_names.AEGIS_GUARD, item_names.EMPERORS_SHADOW, item_names.BULWARK_COMPANY, item_names.SHOCK_DIVISION, item_names.BLACKHAMMER, item_names.SKY_FURY, item_names.NIGHT_WOLF, item_names.NIGHT_HAWK, item_names.PRIDE_OF_AUGUSTRGRAD, ), self.player) or state.has_all((item_names.LIBERATOR, item_names.LIBERATOR_RAID_ARTILLERY), self.player) or state.has_all((item_names.EMPERORS_GUARDIAN, item_names.LIBERATOR_RAID_ARTILLERY), self.player) or state.has_all((item_names.VALKYRIE, item_names.VALKYRIE_FLECHETTE_MISSILES), self.player) or state.has_all((item_names.WIDOW_MINE, item_names.WIDOW_MINE_DEMOLITION_PAYLOAD), self.player) or ( state.has_any(( # Mercs with shortest initial cooldown (300s) item_names.WAR_PIGS, item_names.DEATH_HEADS, item_names.HELS_ANGELS, item_names.WINGED_NIGHTMARES, ), self.player) # + 2 upgrades that allow getting faster/earlier mercs and state.count_from_list(( item_names.RAPID_REINFORCEMENT, item_names.PROGRESSIVE_FAST_DELIVERY, item_names.ROGUE_FORCES, # item_names.SIGNAL_BEACON, # Probably doesn't help too much on the first unit ), self.player) >= 2 ) ) ) return _has_terran_units def has_zerg_units(self, target: int) -> Callable[["CollectionState"], bool]: def _has_zerg_units(state: CollectionState) -> bool: num_units = ( state.count_from_list_unique( item_groups.zerg_nonmorph_units + item_groups.zerg_buildings + [item_names.OVERLORD_OVERSEER_ASPECT], self.player ) + self.morph_baneling(state) + self.morph_ravager(state) + self.morph_igniter(state) + self.morph_lurker(state) + self.morph_impaler(state) + self.morph_viper(state) + self.morph_devourer(state) + self.morph_brood_lord(state) + self.morph_guardian(state) + self.morph_tyrannozor(state) ) return ( num_units >= target and ( target < 5 or self.zerg_any_anti_air(state) ) and ( # Anything that can hit buildings state.has_any(( item_names.ZERGLING, item_names.SWARM_QUEEN, item_names.ROACH, item_names.HYDRALISK, item_names.ABERRATION, item_names.SWARM_HOST, item_names.MUTALISK, item_names.ULTRALISK, item_names.PYGALISK, item_names.INFESTED_MARINE, item_names.INFESTED_BUNKER, item_names.INFESTED_DIAMONDBACK, item_names.INFESTED_SIEGE_TANK, item_names.INFESTED_BANSHEE, ), self.player) or state.has_all((item_names.INFESTOR, item_names.INFESTOR_INFESTED_TERRAN), self.player) or self.morph_baneling(state) or self.morph_lurker(state) or self.morph_impaler(state) or self.morph_brood_lord(state) or self.morph_guardian(state) or self.morph_ravager(state) or self.morph_igniter(state) or self.morph_tyrannozor(state) or (self.morph_devourer(state) and state.has(item_names.DEVOURER_PRESCIENT_SPORES, self.player) ) or ( state.has_any(( # Mercs with <= 300s first drop time item_names.DEVOURING_ONES, item_names.HUNTER_KILLERS, item_names.CAUSTIC_HORRORS, item_names.HUNTERLING, ), self.player) # + 2 upgrades that allow getting faster/earlier mercs and state.count_from_list(( item_names.UNRESTRICTED_MUTATION, item_names.EVOLUTIONARY_LEAP, item_names.CELL_DIVISION, item_names.SELF_SUFFICIENT, ), self.player) >= 2 ) ) ) return _has_zerg_units def has_protoss_units(self, target: int) -> Callable[["CollectionState"], bool]: def _has_protoss_units(state: CollectionState) -> bool: return ( state.count_from_list_unique(item_groups.protoss_units + item_groups.protoss_buildings + [item_names.NEXUS_OVERCHARGE], self.player) >= target ) and ( target < 5 or self.protoss_any_anti_air_unit(state) ) and ( # Anything that can hit buildings state.has_any(( # Gateway item_names.ZEALOT, item_names.CENTURION, item_names.SENTINEL, item_names.SUPPLICANT, item_names.STALKER, item_names.INSTIGATOR, item_names.SLAYER, item_names.DRAGOON, item_names.ADEPT, item_names.SENTRY, item_names.ENERGIZER, item_names.AVENGER, item_names.DARK_TEMPLAR, item_names.BLOOD_HUNTER, item_names.HIGH_TEMPLAR, item_names.SIGNIFIER, item_names.ASCENDANT, item_names.DARK_ARCHON, # Robo item_names.IMMORTAL, item_names.ANNIHILATOR, item_names.VANGUARD, item_names.STALWART, item_names.COLOSSUS, item_names.WRATHWALKER, item_names.REAVER, item_names.DISRUPTOR, # Stargate item_names.SKIRMISHER, item_names.SCOUT, item_names.MISTWING, item_names.OPPRESSOR, item_names.PULSAR, item_names.VOID_RAY, item_names.DESTROYER, item_names.DAWNBRINGER, item_names.ARBITER, item_names.ORACLE, item_names.CARRIER, item_names.TRIREME, item_names.SKYLORD, item_names.TEMPEST, item_names.MOTHERSHIP, ), self.player) or state.has_all((item_names.WARP_PRISM, item_names.WARP_PRISM_PHASE_BLASTER), self.player) or state.has_all((item_names.CALADRIUS, item_names.CALADRIUS_CORONA_BEAM), self.player) or state.has_all((item_names.PHOTON_CANNON, item_names.KHALAI_INGENUITY), self.player) or state.has_all((item_names.KHAYDARIN_MONOLITH, item_names.KHALAI_INGENUITY), self.player) ) return _has_protoss_units def has_race_units(self, target: int, race: SC2Race) -> Callable[["CollectionState"], bool]: if target == 0 or race == SC2Race.ANY: return Location.access_rule result = self.unit_count_functions.get((race, target)) if result is not None: return result if race == SC2Race.TERRAN: result = self.has_terran_units(target) if race == SC2Race.ZERG: result = self.has_zerg_units(target) if race == SC2Race.PROTOSS: result = self.has_protoss_units(target) assert result self.unit_count_functions[(race, target)] = result return result def get_basic_units(logic_level: int, race: SC2Race) -> Set[str]: if logic_level > RequiredTactics.option_advanced: return no_logic_basic_units[race] elif logic_level == RequiredTactics.option_advanced: return advanced_basic_units[race] else: return basic_units[race]