Skip to content

Commit

Permalink
SC2: Adding top-down race selection
Browse files Browse the repository at this point in the history
SC2: Basic race-swap mission logic

sc2: removing mission variants for now
  • Loading branch information
EnvyDragon committed May 26, 2024
1 parent 284237e commit f9ba43d
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 16 deletions.
9 changes: 5 additions & 4 deletions worlds/sc2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from .options import (get_option_value, LocationInclusion, KerriganLevelItemDistribution,
KerriganPresence, KerriganPrimalStatus, kerrigan_unit_available, StarterUnit, SpearOfAdunPresence,
get_enabled_campaigns, SpearOfAdunAutonomouslyCastAbilityPresence, Starcraft2Options,
GrantStoryTech, GenericUpgradeResearch,
GrantStoryTech, GenericUpgradeResearch, get_enabled_races
)
from .pool_filter import filter_items
from .mission_tables import (
Expand Down Expand Up @@ -246,6 +246,7 @@ def resolve_count(count: Optional[int], max_count: int) -> int:
def flag_excludes_by_faction_presence(world: SC2World, item_list: List[FilterItem]) -> None:
"""Excludes items based on if their faction has a mission present where they can be used"""
missions = get_all_missions(world.mission_req_table)
races = get_enabled_races(world)
if world.options.take_over_ai_allies.value:
terran_missions = [mission for mission in missions if (MissionFlag.Terran|MissionFlag.AiTerranAlly) & mission.flags]
zerg_missions = [mission for mission in missions if (MissionFlag.Zerg|MissionFlag.AiZergAlly) & mission.flags]
Expand All @@ -264,13 +265,13 @@ def flag_excludes_by_faction_presence(world: SC2World, item_list: List[FilterIte

for item in item_list:
# Catch-all for all of a faction's items
if (not terran_missions and item.data.race == SC2Race.TERRAN):
if (not terran_missions or SC2Race.TERRAN not in races) and item.data.race == SC2Race.TERRAN:
item.flags |= ItemFilterFlags.Excluded
continue
if (not zerg_missions and item.data.race == SC2Race.ZERG):
if (not zerg_missions or SC2Race.ZERG not in races) and item.data.race == SC2Race.ZERG:
item.flags |= ItemFilterFlags.Excluded
continue
if (not protoss_missions and item.data.race == SC2Race.PROTOSS):
if (not protoss_missions or SC2Race.PROTOSS not in races) and item.data.race == SC2Race.PROTOSS:
if item.name not in item_groups.soa_items:
item.flags |= ItemFilterFlags.Excluded
continue
Expand Down
3 changes: 2 additions & 1 deletion worlds/sc2/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1013,6 +1013,7 @@ def kerrigan_primal(ctx: SC2Context, kerrigan_level: int) -> bool:
return get_full_item_list()[item_names.KERRIGAN_PRIMAL_FORM].code in codes
return False


async def starcraft_launch(ctx: SC2Context, mission_id: int):
sc2_logger.info(f"Launching {lookup_id_to_mission[mission_id].mission_name}. If game does not launch check log file for errors.")

Expand Down Expand Up @@ -1084,7 +1085,7 @@ async def on_step(self, iteration: int):
self.ctx.mission_order,
1 if self.ctx.nova_covert_ops_only else 0,
self.ctx.grant_story_levels,
self.ctx.enable_morphling
self.ctx.enable_morphling,
))
await self.chat_send("?GiveResources {} {} {}".format(
start_items[SC2Race.ANY][0],
Expand Down
4 changes: 2 additions & 2 deletions worlds/sc2/mission_tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -486,5 +486,5 @@ def get_campaign_potential_goal_missions(campaign: SC2Campaign) -> List[SC2Missi
return missions


def get_no_build_missions() -> List[SC2Mission]:
return [mission for mission in SC2Mission if MissionFlag.NoBuild in mission.flags]
def get_missions_with_flags(flags: MissionFlag) -> List[SC2Mission]:
return [mission for mission in SC2Mission if flags & mission.flags]
63 changes: 57 additions & 6 deletions worlds/sc2/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
PerGameCommonOptions, Option, VerifyKeys)
from Utils import get_fuzzy_results
from BaseClasses import PlandoOptions
from .mission_tables import SC2Campaign, SC2Mission, lookup_name_to_mission, MissionPools, get_no_build_missions, \
campaign_mission_table
from .mission_tables import SC2Campaign, SC2Mission, lookup_name_to_mission, MissionPools, get_missions_with_flags, \
campaign_mission_table, SC2Race, MissionFlag
from .mission_orders import vanilla_shuffle_order, mini_campaign_order
from .mission_groups import mission_groups, MissionGroupNames

Expand Down Expand Up @@ -55,6 +55,23 @@ def __len__(self) -> int:
return self.value.__len__()


class SelectRaces(Choice):
"""
Pick which factions' missions and items can be shuffled into the world.
"""
# bit 0: terran, bit 1: zerg, bit 2: protoss. all disabled means plando only
display_name = "Select Playable Races"
option_all = 7
option_terran = 1
option_zerg = 2
option_protoss = 4
option_terran_and_zerg = 3
option_terran_and_protoss = 5
option_zerg_and_protoss = 6
option_plando = 0
default = option_all


class GameDifficulty(Choice):
"""
The difficulty of the campaign, affects enemy AI, starting units, and game speed.
Expand Down Expand Up @@ -874,6 +891,7 @@ class StartingSupplyPerItem(Range):

@dataclass
class Starcraft2Options(PerGameCommonOptions):
selected_races: SelectRaces
game_difficulty: GameDifficulty
game_speed: GameSpeed
disable_forced_camera: DisableForcedCamera
Expand Down Expand Up @@ -944,6 +962,23 @@ def get_option_value(world: 'SC2World', name: str) -> Union[int, FrozenSet]:
return player_option.value


def get_enabled_races(world: 'SC2World') -> Set[SC2Race]:
selection = get_option_value(world, 'selected_races')
if selection == SelectRaces.option_all:
return set(SC2Race)
enabled = set()
# if bit 0, enable terran
if selection & 1:
enabled.add(SC2Race.TERRAN)
# if bit 1, enable zerg
if selection & (1 << 1):
enabled.add(SC2Race.ZERG)
# if bit 2, enable protoss
if selection & (1 << 2):
enabled.add(SC2Race.PROTOSS)
return enabled


def get_enabled_campaigns(world: 'SC2World') -> Set[SC2Campaign]:
enabled_campaigns = set()
if get_option_value(world, "enable_wol_missions"):
Expand All @@ -960,7 +995,7 @@ def get_enabled_campaigns(world: 'SC2World') -> Set[SC2Campaign]:
enabled_campaigns.add(SC2Campaign.EPILOGUE)
if get_option_value(world, "enable_nco_missions"):
enabled_campaigns.add(SC2Campaign.NCO)
return enabled_campaigns
return set([campaign for campaign in enabled_campaigns if campaign.race in get_enabled_races(world)])


def get_disabled_campaigns(world: 'SC2World') -> Set[SC2Campaign]:
Expand All @@ -971,11 +1006,27 @@ def get_disabled_campaigns(world: 'SC2World') -> Set[SC2Campaign]:
return disabled_campaigns


def get_disabled_flags(world: 'SC2World') -> MissionFlag:
excluded = 0
races = get_enabled_races(world)
# filter out missions based on disabled races
if SC2Race.TERRAN not in races:
excluded |= MissionFlag.Terran
if SC2Race.ZERG not in races:
excluded |= MissionFlag.Zerg
if SC2Race.PROTOSS not in races:
excluded |= MissionFlag.Protoss
# filter out no-build missions
if not get_option_value(world, "shuffle_no_build"):
excluded |= MissionFlag.NoBuild
return MissionFlag(excluded)


def get_excluded_missions(world: 'SC2World') -> Set[SC2Mission]:
mission_order_type = world.options.mission_order.value
excluded_mission_names = world.options.excluded_missions.value
shuffle_no_build = world.options.shuffle_no_build.value
disabled_campaigns = get_disabled_campaigns(world)
disabled_flags = get_disabled_flags(world)

excluded_missions: Set[SC2Mission] = set([lookup_name_to_mission[name] for name in excluded_mission_names])

Expand All @@ -991,8 +1042,8 @@ def get_excluded_missions(world: 'SC2World') -> Set[SC2Mission]:
mission.pool == MissionPools.VERY_HARD and mission.campaign != SC2Campaign.EPILOGUE]
)
# Omitting No-Build missions if not shuffling no-build
if not shuffle_no_build:
excluded_missions = excluded_missions.union(get_no_build_missions())
if disabled_flags:
excluded_missions = excluded_missions.union(get_missions_with_flags(disabled_flags))
# Omitting missions not in enabled campaigns
for campaign in disabled_campaigns:
excluded_missions = excluded_missions.union(campaign_mission_table[campaign])
Expand Down
9 changes: 6 additions & 3 deletions worlds/sc2/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -340,9 +340,12 @@ def zerg_competent_anti_air(self, state: CollectionState) -> bool:
or (self.advanced_tactics and state.has(item_names.INFESTOR, self.player))

def zerg_basic_anti_air(self, state: CollectionState) -> bool:
return self.zerg_competent_anti_air(state) or self.kerrigan_unit_available in kerrigan_unit_available or \
state.has_any({item_names.SWARM_QUEEN, item_names.SCOURGE}, self.player) or (self.advanced_tactics and state.has(item_names.SPORE_CRAWLER, self.player))

return self.zerg_basic_kerriganless_anti_air(state) or self.kerrigan_unit_available in kerrigan_unit_available

def zerg_basic_kerriganless_anti_air(self, state: CollectionState) -> bool:
return self.zerg_competent_anti_air(state) or state.has_any({item_names.SWARM_QUEEN, item_names.SCOURGE}, self.player) \
or (self.advanced_tactics and state.has(item_names.SPORE_CRAWLER, 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)
Expand Down
21 changes: 21 additions & 0 deletions worlds/sc2/test/test_usecases.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,3 +154,24 @@ def test_resource_filler_items_may_be_put_in_start_inventory(self) -> None:
self.assertEqual(start_item_names.count(item_names.STARTING_VESPENE), NUM_RESOURCE_ITEMS, "Wrong number of starting vespene in starting inventory")
self.assertEqual(start_item_names.count(item_names.STARTING_SUPPLY), NUM_RESOURCE_ITEMS, "Wrong number of starting supply in starting inventory")

def test_excluding_race_excludes_campaigns_and_items(self) -> None:
# default options except vanilla shuffled and no terran; should generate without terran campaigns or items (or Epilogue, due to absent WoL)
world_options = {
'selected_races': options.SelectRaces.option_zerg_and_protoss,
'enable_wol_missions': True,
'enable_nco_missions': True,
'enable_prophecy_missions': True,
'enable_hots_missions': True,
'enable_lotv_prologue_missions': True,
'enable_lotv_missions': True,
'enable_epilogue_missions': True,
'mission_order': options.MissionOrder.option_vanilla_shuffled,
}
self.generate_world(world_options)
world_item_names = [item.name for item in self.multiworld.itempool]
world_regions = [region.name for region in self.multiworld.regions]
world_regions.remove('Menu')
for item_name in world_item_names:
self.assertNotEqual(items.item_table[item_name].race, mission_tables.SC2Race.TERRAN)
for region in world_regions:
self.assertNotIn(mission_tables.lookup_name_to_mission[region].campaign, (mission_tables.SC2Campaign.WOL, mission_tables.SC2Campaign.NCO, mission_tables.SC2Campaign.EPILOGUE))

0 comments on commit f9ba43d

Please sign in to comment.