SC2: any_unit and item parent bugfixes (#5480)

* sc2: Fixing a Reaver item being classified as a scout item

* sc2: any_units now requires any AA in the first 5 units
* Fixing Shoot the Messenger not requiring AA in a hard rule
* Fixing any_unit zerg still allowing unupgraded mercs

* sc2: Fixed an issue where terran was requiring zerg anti-air in any_units
This commit is contained in:
Phaneros
2025-09-30 13:18:42 -07:00
committed by GitHub
parent 6a63de2f0f
commit 76b0197462
3 changed files with 93 additions and 68 deletions

View File

@@ -1860,7 +1860,7 @@ item_table = {
item_names.DARK_TEMPLAR_ARCHON_MERGE: ItemData(417 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_4, 27, SC2Race.PROTOSS, classification=ItemClassification.progression, parent=item_names.DARK_TEMPLAR), item_names.DARK_TEMPLAR_ARCHON_MERGE: ItemData(417 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_4, 27, SC2Race.PROTOSS, classification=ItemClassification.progression, parent=item_names.DARK_TEMPLAR),
item_names.ASCENDANT_ARCHON_MERGE: ItemData(418 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_4, 28, SC2Race.PROTOSS, classification=ItemClassification.progression_skip_balancing, parent=item_names.ASCENDANT), item_names.ASCENDANT_ARCHON_MERGE: ItemData(418 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_4, 28, SC2Race.PROTOSS, classification=ItemClassification.progression_skip_balancing, parent=item_names.ASCENDANT),
item_names.SCOUT_SUPPLY_EFFICIENCY: ItemData(419 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_4, 29, SC2Race.PROTOSS, parent=item_names.SCOUT), item_names.SCOUT_SUPPLY_EFFICIENCY: ItemData(419 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_4, 29, SC2Race.PROTOSS, parent=item_names.SCOUT),
item_names.REAVER_BARGAIN_BIN_PRICES: ItemData(420 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_5, 0, SC2Race.PROTOSS, parent=item_names.SCOUT), item_names.REAVER_BARGAIN_BIN_PRICES: ItemData(420 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_5, 0, SC2Race.PROTOSS, parent=item_names.REAVER),
# War Council # War Council

View File

@@ -2535,6 +2535,7 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
lambda state: ( lambda state: (
logic.zerg_common_unit(state) and logic.zerg_competent_anti_air(state) logic.zerg_common_unit(state) and logic.zerg_competent_anti_air(state)
), ),
hard_rule=logic.zerg_any_anti_air,
), ),
make_location_data( make_location_data(
SC2Mission.SHOOT_THE_MESSENGER.mission_name, SC2Mission.SHOOT_THE_MESSENGER.mission_name,
@@ -2569,6 +2570,7 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
lambda state: ( lambda state: (
logic.zerg_common_unit(state) and logic.zerg_competent_anti_air(state) logic.zerg_common_unit(state) and logic.zerg_competent_anti_air(state)
), ),
hard_rule=logic.zerg_any_anti_air,
), ),
make_location_data( make_location_data(
SC2Mission.SHOOT_THE_MESSENGER.mission_name, SC2Mission.SHOOT_THE_MESSENGER.mission_name,
@@ -2610,6 +2612,7 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
SC2HOTS_LOC_ID_OFFSET + 510, SC2HOTS_LOC_ID_OFFSET + 510,
LocationType.CHALLENGE, LocationType.CHALLENGE,
logic.zerg_competent_comp_competent_aa, logic.zerg_competent_comp_competent_aa,
hard_rule=logic.zerg_any_anti_air,
flags=LocationFlag.BASEBUST, flags=LocationFlag.BASEBUST,
), ),
make_location_data( make_location_data(
@@ -2618,6 +2621,7 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
SC2HOTS_LOC_ID_OFFSET + 511, SC2HOTS_LOC_ID_OFFSET + 511,
LocationType.CHALLENGE, LocationType.CHALLENGE,
logic.zerg_competent_comp_competent_aa, logic.zerg_competent_comp_competent_aa,
hard_rule=logic.zerg_any_anti_air,
flags=LocationFlag.BASEBUST, flags=LocationFlag.BASEBUST,
), ),
make_location_data( make_location_data(
@@ -2626,6 +2630,7 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
SC2HOTS_LOC_ID_OFFSET + 512, SC2HOTS_LOC_ID_OFFSET + 512,
LocationType.CHALLENGE, LocationType.CHALLENGE,
logic.zerg_competent_comp_competent_aa, logic.zerg_competent_comp_competent_aa,
hard_rule=logic.zerg_any_anti_air,
flags=LocationFlag.BASEBUST, flags=LocationFlag.BASEBUST,
), ),
make_location_data( make_location_data(
@@ -10042,6 +10047,7 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
logic.terran_common_unit(state) logic.terran_common_unit(state)
and logic.terran_competent_anti_air(state) and logic.terran_competent_anti_air(state)
), ),
hard_rule=logic.terran_any_anti_air,
), ),
make_location_data( make_location_data(
SC2Mission.SHOOT_THE_MESSENGER_T.mission_name, SC2Mission.SHOOT_THE_MESSENGER_T.mission_name,
@@ -10079,6 +10085,7 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
logic.terran_common_unit(state) logic.terran_common_unit(state)
and logic.terran_competent_anti_air(state) and logic.terran_competent_anti_air(state)
), ),
hard_rule=logic.terran_any_anti_air,
), ),
make_location_data( make_location_data(
SC2Mission.SHOOT_THE_MESSENGER_T.mission_name, SC2Mission.SHOOT_THE_MESSENGER_T.mission_name,
@@ -10122,7 +10129,7 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
SC2_RACESWAP_LOC_ID_OFFSET + 6710, SC2_RACESWAP_LOC_ID_OFFSET + 6710,
LocationType.CHALLENGE, LocationType.CHALLENGE,
lambda state: logic.terran_beats_protoss_deathball(state) lambda state: logic.terran_beats_protoss_deathball(state)
and logic.terran_common_unit(state), and logic.terran_common_unit(state),
flags=LocationFlag.BASEBUST, flags=LocationFlag.BASEBUST,
), ),
make_location_data( make_location_data(
@@ -10131,8 +10138,9 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
SC2_RACESWAP_LOC_ID_OFFSET + 6711, SC2_RACESWAP_LOC_ID_OFFSET + 6711,
LocationType.CHALLENGE, LocationType.CHALLENGE,
lambda state: logic.terran_beats_protoss_deathball(state) lambda state: logic.terran_beats_protoss_deathball(state)
and logic.terran_competent_ground_to_air(state) and logic.terran_competent_ground_to_air(state)
and logic.terran_common_unit(state), and logic.terran_common_unit(state),
hard_rule=logic.terran_any_anti_air,
flags=LocationFlag.BASEBUST, flags=LocationFlag.BASEBUST,
), ),
make_location_data( make_location_data(
@@ -10141,8 +10149,9 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
SC2_RACESWAP_LOC_ID_OFFSET + 6712, SC2_RACESWAP_LOC_ID_OFFSET + 6712,
LocationType.CHALLENGE, LocationType.CHALLENGE,
lambda state: logic.terran_beats_protoss_deathball(state) lambda state: logic.terran_beats_protoss_deathball(state)
and logic.terran_competent_ground_to_air(state) and logic.terran_competent_ground_to_air(state)
and logic.terran_common_unit(state), and logic.terran_common_unit(state),
hard_rule=logic.terran_any_anti_air,
flags=LocationFlag.BASEBUST, flags=LocationFlag.BASEBUST,
), ),
make_location_data( make_location_data(
@@ -10154,6 +10163,7 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
logic.protoss_common_unit(state) logic.protoss_common_unit(state)
and logic.protoss_anti_armor_anti_air(state) and logic.protoss_anti_armor_anti_air(state)
), ),
hard_rule=logic.protoss_any_anti_air_unit,
), ),
make_location_data( make_location_data(
SC2Mission.SHOOT_THE_MESSENGER_P.mission_name, SC2Mission.SHOOT_THE_MESSENGER_P.mission_name,
@@ -10191,6 +10201,7 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
logic.protoss_common_unit(state) logic.protoss_common_unit(state)
and logic.protoss_anti_armor_anti_air(state) and logic.protoss_anti_armor_anti_air(state)
), ),
hard_rule=logic.protoss_any_anti_air_unit,
), ),
make_location_data( make_location_data(
SC2Mission.SHOOT_THE_MESSENGER_P.mission_name, SC2Mission.SHOOT_THE_MESSENGER_P.mission_name,
@@ -10238,6 +10249,7 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
SC2_RACESWAP_LOC_ID_OFFSET + 6810, SC2_RACESWAP_LOC_ID_OFFSET + 6810,
LocationType.CHALLENGE, LocationType.CHALLENGE,
logic.protoss_competent_comp, logic.protoss_competent_comp,
hard_rule=logic.protoss_any_anti_air_unit,
flags=LocationFlag.BASEBUST, flags=LocationFlag.BASEBUST,
), ),
make_location_data( make_location_data(
@@ -10246,6 +10258,7 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
SC2_RACESWAP_LOC_ID_OFFSET + 6811, SC2_RACESWAP_LOC_ID_OFFSET + 6811,
LocationType.CHALLENGE, LocationType.CHALLENGE,
logic.protoss_competent_comp, logic.protoss_competent_comp,
hard_rule=logic.protoss_any_anti_air_unit,
flags=LocationFlag.BASEBUST, flags=LocationFlag.BASEBUST,
), ),
make_location_data( make_location_data(
@@ -10254,6 +10267,7 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
SC2_RACESWAP_LOC_ID_OFFSET + 6812, SC2_RACESWAP_LOC_ID_OFFSET + 6812,
LocationType.CHALLENGE, LocationType.CHALLENGE,
logic.protoss_competent_comp, logic.protoss_competent_comp,
hard_rule=logic.protoss_any_anti_air_unit,
flags=LocationFlag.BASEBUST, flags=LocationFlag.BASEBUST,
), ),
make_location_data( make_location_data(

View File

@@ -3367,65 +3367,74 @@ class SC2Logic:
def has_terran_units(self, target: int) -> Callable[["CollectionState"], bool]: def has_terran_units(self, target: int) -> Callable[["CollectionState"], bool]:
def _has_terran_units(state: 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 ( return (
# Anything that can hit buildings state.count_from_list_unique(
state.has_any(( item_groups.terran_units + item_groups.terran_buildings, self.player
# Infantry ) >= target
item_names.MARINE, and (
item_names.FIREBAT, target < 5
item_names.MARAUDER, or self.terran_any_anti_air(state)
item_names.REAPER, )
item_names.HERC, and (
item_names.DOMINION_TROOPER, # Anything that can hit buildings
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(( state.has_any((
# Mercs with shortest initial cooldown (300s) # Infantry
item_names.WAR_PIGS, item_names.MARINE,
item_names.DEATH_HEADS, item_names.FIREBAT,
item_names.HELS_ANGELS, item_names.MARAUDER,
item_names.WINGED_NIGHTMARES, 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) ), self.player)
# + 2 upgrades that allow getting faster/earlier mercs or state.has_all((item_names.LIBERATOR, item_names.LIBERATOR_RAID_ARTILLERY), self.player)
and state.count_from_list(( or state.has_all((item_names.EMPERORS_GUARDIAN, item_names.LIBERATOR_RAID_ARTILLERY), self.player)
item_names.RAPID_REINFORCEMENT, or state.has_all((item_names.VALKYRIE, item_names.VALKYRIE_FLECHETTE_MISSILES), self.player)
item_names.PROGRESSIVE_FAST_DELIVERY, or state.has_all((item_names.WIDOW_MINE, item_names.WIDOW_MINE_DEMOLITION_PAYLOAD), self.player)
item_names.ROGUE_FORCES, or (
# item_names.SIGNAL_BEACON, # Probably doesn't help too much on the first unit state.has_any((
), self.player) >= 2 # 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
)
) )
) )
@@ -3451,6 +3460,10 @@ class SC2Logic:
) )
return ( return (
num_units >= target num_units >= target
and (
target < 5
or self.zerg_any_anti_air(state)
)
and ( and (
# Anything that can hit buildings # Anything that can hit buildings
state.has_any(( state.has_any((
@@ -3468,11 +3481,6 @@ class SC2Logic:
item_names.INFESTED_DIAMONDBACK, item_names.INFESTED_DIAMONDBACK,
item_names.INFESTED_SIEGE_TANK, item_names.INFESTED_SIEGE_TANK,
item_names.INFESTED_BANSHEE, 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) ), self.player)
or state.has_all((item_names.INFESTOR, item_names.INFESTOR_INFESTED_TERRAN), self.player) or state.has_all((item_names.INFESTOR, item_names.INFESTOR_INFESTED_TERRAN), self.player)
or self.morph_baneling(state) or self.morph_baneling(state)
@@ -3512,6 +3520,9 @@ class SC2Logic:
return ( return (
state.count_from_list_unique(item_groups.protoss_units + item_groups.protoss_buildings + [item_names.NEXUS_OVERCHARGE], self.player) state.count_from_list_unique(item_groups.protoss_units + item_groups.protoss_buildings + [item_names.NEXUS_OVERCHARGE], self.player)
>= target >= target
) and (
target < 5
or self.protoss_any_anti_air_unit(state)
) and ( ) and (
# Anything that can hit buildings # Anything that can hit buildings
state.has_any(( state.has_any((