Files
Grinch-AP/worlds/sc2/rules.py
Ziktofel 5f1835c546 SC2: Content update (#5312)
Feature highlights:
- Adds many content to the SC2 game
- Allows custom mission order
- Adds race-swapped missions for build missions (except Epilogue and NCO)
- Allows War Council Nerfs (Protoss units can get pre - War Council State, alternative units get another custom nerf to match the power level of base units)
- Revamps Predator's upgrade tree (never was considered strategically important)
- Adds some units and upgrades
- Locked and excluded items can specify quantity
- Key mode (if opt-in, missions require keys to be unlocked on top of their regular regular requirements
- Victory caches - Victory locations can grant multiple items to the multiworld instead of one 
- The generator is more resilient for generator failures as it validates logic for item excludes
- Fixes the following issues:
  - https://github.com/ArchipelagoMW/Archipelago/issues/3531 
  - https://github.com/ArchipelagoMW/Archipelago/issues/3548
2025-09-02 17:40:58 +02:00

3583 lines
170 KiB
Python

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
class SC2Logic:
def __init__(self, world: Optional["SC2World"]):
# 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):
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):
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):
"""
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) -> 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 >= 2 and infantry_armor >= 1 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 >= 1 and ship_armor >= 1:
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 >= 1 and vehicle_armor >= 1:
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 = min(count, self.zerg_melee_weapon_armor_upgrade_min_level(state))
if self.has_zerg_ranged_unit:
count = min(count, self.zerg_ranged_weapon_armor_upgrade_min_level(state))
if self.has_zerg_air_unit:
count = min(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 min(
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 min(
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 min(
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):
"""
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 = min(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 = min(levels, self.kerrigan_total_level_cap)
return levels >= target
def basic_kerrigan(self, state: CollectionState) -> bool:
# One active ability that can be used to defeat enemies directly on Standard
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) -> bool:
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):
"""
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):
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) -> int:
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) -> int:
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):
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):
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):
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):
"""
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):
"""
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):
"""
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) 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)):
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.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.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)
and self.kerrigan_levels(state, 70)
)
)
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 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 (
# 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 (
# 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,
# Mercs with <= 300s first drop time
item_names.DEVOURING_ONES,
item_names.HUNTER_KILLERS,
item_names.CAUSTIC_HORRORS,
item_names.HUNTERLING,
), 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 (
# 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]