SC2: Fix Conviction logic for Grant Story Tech (#5419)

* Fix Conviction logic for Grant Story Tech

- Kinetic Blast and Crushing Grip is available for the mission if story tech is granted

* Review updates
This commit is contained in:
Ziktofel
2025-09-30 18:35:26 +02:00
committed by GitHub
parent 92ff0ddba8
commit 897d5ab089
4 changed files with 70 additions and 28 deletions

View File

@@ -2341,8 +2341,7 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
SC2HOTS_LOC_ID_OFFSET + 200,
LocationType.VICTORY,
lambda state: logic.basic_kerrigan(state)
or kerriganless
or logic.grant_story_tech == GrantStoryTech.option_grant,
or kerriganless,
hard_rule=logic.zerg_any_units_back_in_the_saddle_requirement,
),
make_location_data(
@@ -2351,8 +2350,7 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
SC2HOTS_LOC_ID_OFFSET + 201,
LocationType.EXTRA,
lambda state: logic.basic_kerrigan(state)
or kerriganless
or logic.grant_story_tech == GrantStoryTech.option_grant,
or kerriganless,
hard_rule=logic.zerg_any_units_back_in_the_saddle_requirement,
),
make_location_data(
@@ -2379,8 +2377,7 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
SC2HOTS_LOC_ID_OFFSET + 205,
LocationType.EXTRA,
lambda state: logic.basic_kerrigan(state)
or kerriganless
or logic.grant_story_tech == GrantStoryTech.option_grant,
or kerriganless,
hard_rule=logic.zerg_any_units_back_in_the_saddle_requirement,
),
make_location_data(
@@ -2446,7 +2443,7 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
lambda state: (
logic.zerg_competent_comp(state)
and logic.zerg_competent_anti_air(state)
and (logic.basic_kerrigan(state) or kerriganless)
and (logic.basic_kerrigan(state, False) or kerriganless)
and logic.zerg_defense_rating(state, False, False) >= 3
and logic.zerg_power_rating(state) >= 5
),
@@ -3530,7 +3527,7 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
kerriganless
or (
logic.two_kerrigan_actives(state)
and (logic.basic_kerrigan(state) or logic.grant_story_tech == GrantStoryTech.option_grant)
and logic.basic_kerrigan(state)
and logic.kerrigan_levels(state, 25)
)
),
@@ -3554,7 +3551,7 @@ def get_locations(world: Optional["SC2World"]) -> Tuple[LocationData, ...]:
kerriganless
or (
logic.two_kerrigan_actives(state)
and (logic.basic_kerrigan(state) or logic.grant_story_tech == GrantStoryTech.option_grant)
and logic.basic_kerrigan(state)
and logic.kerrigan_levels(state, 25)
)
),

View File

@@ -1127,8 +1127,10 @@ class SC2Logic:
return levels >= target
def basic_kerrigan(self, state: CollectionState) -> bool:
# One active ability that can be used to defeat enemies directly on Standard
def basic_kerrigan(self, state: CollectionState, story_tech_available=True) -> bool:
if story_tech_available and self.grant_story_tech == GrantStoryTech.option_grant:
return True
# One active ability that can be used to defeat enemies directly
if not state.has_any(
(
item_names.KERRIGAN_LEAPING_STRIKE,
@@ -1149,7 +1151,9 @@ class SC2Logic:
return True
return False
def two_kerrigan_actives(self, state: CollectionState) -> bool:
def two_kerrigan_actives(self, state: CollectionState, story_tech_available=True) -> bool:
if story_tech_available and self.grant_story_tech == GrantStoryTech.option_grant:
return True
count = 0
for i in range(7):
if state.has_any(kerrigan_logic_active_abilities, self.player):
@@ -2396,7 +2400,7 @@ class SC2Logic:
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)
and (self.basic_kerrigan(state, False) or self.zerg_power_rating(state) >= 4)
)
def protoss_hand_of_darkness_requirement(self, state: CollectionState) -> bool:
@@ -2412,7 +2416,7 @@ class SC2Logic:
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)):
if not (self.zerg_power_rating(state) >= 6 or self.basic_kerrigan(state, False)):
return False
if self.take_over_ai_allies:
return (
@@ -2460,20 +2464,22 @@ class SC2Logic:
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,
self.kerrigan_levels(state, 70)
and (
self.grant_story_tech == GrantStoryTech.option_grant
or not self.kerrigan_unit_available
or (
state.has_any(
(
item_names.KERRIGAN_KINETIC_BLAST,
item_names.KERRIGAN_SPAWN_BANELINGS,
item_names.KERRIGAN_LEAPING_STRIKE,
item_names.KERRIGAN_SPAWN_LEVIATHAN,
),
self.player,
)
and self.basic_kerrigan(state)
)
and self.basic_kerrigan(state)
and self.kerrigan_levels(state, 70)
)
)

View File

@@ -2,6 +2,7 @@
Unit tests for world generation
"""
from typing import *
from .test_base import Sc2SetupTestBase
from .. import mission_groups, mission_tables, options, locations, SC2Mission, SC2Campaign, SC2Race, unreleased_items, \

View File

@@ -6,7 +6,9 @@ from .test_base import Sc2SetupTestBase
from .. import get_all_missions, mission_tables, options
from ..item import item_groups, item_tables, item_names
from ..mission_tables import SC2Race, SC2Mission, SC2Campaign, MissionFlag
from ..options import EnabledCampaigns, MasteryLocations
from ..options import EnabledCampaigns, MasteryLocations, MissionOrder, EnableRaceSwapVariants, ShuffleCampaigns, \
ShuffleNoBuild, StarterUnit, RequiredTactics, KerriganPresence, KerriganLevelItemDistribution, GrantStoryTech, \
GrantStoryLevels
class TestSupportedUseCases(Sc2SetupTestBase):
@@ -490,3 +492,39 @@ class TestSupportedUseCases(Sc2SetupTestBase):
self.assertTupleEqual(terran_nonmerc_units, ())
self.assertTupleEqual(zerg_nonmerc_units, ())
def test_all_kerrigan_missions_are_nobuild_and_grant_story_tech_is_on(self) -> None:
# The actual situation the bug got caught
world_options = {
'mission_order': MissionOrder.option_vanilla_shuffled,
'selected_races': [
SC2Race.TERRAN.get_title(),
SC2Race.ZERG.get_title(),
SC2Race.PROTOSS.get_title(),
],
'enabled_campaigns': [
SC2Campaign.WOL.campaign_name,
SC2Campaign.PROPHECY.campaign_name,
SC2Campaign.HOTS.campaign_name,
SC2Campaign.PROLOGUE.campaign_name,
SC2Campaign.LOTV.campaign_name,
SC2Campaign.EPILOGUE.campaign_name,
SC2Campaign.NCO.campaign_name,
],
'enable_race_swap': EnableRaceSwapVariants.option_shuffle_all_non_vanilla, # Causes no build Kerrigan missions to be present, only nobuilds remain
'shuffle_campaigns': ShuffleCampaigns.option_true,
'shuffle_no_build': ShuffleNoBuild.option_true,
'starter_unit': StarterUnit.option_balanced,
'required_tactics': RequiredTactics.option_standard,
'kerrigan_presence': KerriganPresence.option_vanilla,
'kerrigan_levels_per_mission_completed': 0,
'kerrigan_levels_per_mission_completed_cap': -1,
'kerrigan_level_item_sum': 87,
'kerrigan_level_item_distribution': KerriganLevelItemDistribution.option_size_7,
'kerrigan_total_level_cap': -1,
'start_primary_abilities': 0,
'grant_story_tech': GrantStoryTech.option_grant,
'grant_story_levels': GrantStoryLevels.option_additive,
}
self.generate_world(world_options)
# Just check that the world itself generates under those rules and no exception is thrown