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
3583 lines
170 KiB
Python
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]
|