Files
Grinch-AP/worlds/sc2/test/test_generation.py
Phaneros 49f2d30587 Sc2: [performance] change default options (#5424)
* sc2: Changing default campaign options to something more performative and desirable for new players

* sc2: Fixing broken test that was missed in roundup

* SC2: Update tests for new defaults

* SC2: Fix incomplete test

* sc2: Updating description for enabled campaigns to mention which are free to play

* sc2: PR comments; Updating additional unit tests that were affected by a default change

* sc2: Adding a comment to the Enabled Campaigns option to list all the valid campaign names

* sc2: Adding quotes wrapping sample values in enabled_campaigns comment to aid copy-pasting

---------

Co-authored-by: Salzkorn <salzkitty@gmail.com>
2025-09-30 18:36:41 +02:00

1303 lines
64 KiB
Python

"""
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, \
RequiredTactics
from ..item import item_groups, item_tables, item_names
from .. import get_all_missions, get_random_first_mission
from ..options import EnabledCampaigns, NovaGhostOfAChanceVariant, MissionOrder, ExcludeOverpoweredItems, \
VanillaItemsOnly, MaximumCampaignSize
class TestItemFiltering(Sc2SetupTestBase):
def test_explicit_locks_excludes_interact_and_set_flags(self):
world_options = {
**self.ALL_CAMPAIGNS,
'locked_items': {
item_names.MARINE: 0,
item_names.MARAUDER: 0,
item_names.MEDIVAC: 1,
item_names.FIREBAT: 1,
item_names.ZEALOT: 0,
item_names.PROGRESSIVE_REGENERATIVE_BIO_STEEL: 2,
},
'excluded_items': {
item_names.MARINE: 0,
item_names.MARAUDER: 0,
item_names.MEDIVAC: 0,
item_names.FIREBAT: 1,
item_names.ZERGLING: 0,
item_names.PROGRESSIVE_REGENERATIVE_BIO_STEEL: 2,
}
}
self.generate_world(world_options)
self.assertTrue(self.multiworld.itempool)
itempool = [item.name for item in self.multiworld.itempool]
self.assertIn(item_names.MARINE, itempool)
self.assertIn(item_names.MARAUDER, itempool)
self.assertIn(item_names.MEDIVAC, itempool)
self.assertIn(item_names.FIREBAT, itempool)
self.assertIn(item_names.ZEALOT, itempool)
self.assertNotIn(item_names.ZERGLING, itempool)
regen_biosteel_items = [x for x in itempool if x == item_names.PROGRESSIVE_REGENERATIVE_BIO_STEEL]
self.assertEqual(len(regen_biosteel_items), 2)
def test_unexcludes_cancel_out_excludes(self):
world_options = {
'grant_story_tech': options.GrantStoryTech.option_grant,
'excluded_items': {
item_groups.ItemGroupNames.NOVA_EQUIPMENT: 15,
item_names.MARINE_PROGRESSIVE_STIMPACK: 1,
item_names.MARAUDER_PROGRESSIVE_STIMPACK: 2,
item_names.MARINE: 0,
item_names.MARAUDER: 0,
item_names.REAPER: 1,
item_names.DIAMONDBACK: 0,
item_names.HELLION: 1,
# Additional excludes to increase the likelihood that unexcluded items actually appear
item_groups.ItemGroupNames.STARPORT_UNITS: 0,
item_names.WARHOUND: 0,
item_names.VULTURE: 0,
item_names.WIDOW_MINE: 0,
item_names.THOR: 0,
item_names.GHOST: 0,
item_names.SPECTRE: 0,
item_groups.ItemGroupNames.MENGSK_UNITS: 0,
item_groups.ItemGroupNames.TERRAN_VETERANCY_UNITS: 0,
},
'unexcluded_items': {
item_names.NOVA_PLASMA_RIFLE: 1, # Necessary to pass logic
item_names.NOVA_PULSE_GRENADES: 0, # Necessary to pass logic
item_names.NOVA_JUMP_SUIT_MODULE: 0, # Necessary to pass logic
item_groups.ItemGroupNames.BARRACKS_UNITS: 0,
item_names.NOVA_PROGRESSIVE_STEALTH_SUIT_MODULE: 1,
item_names.HELLION: 1,
item_names.MARINE_PROGRESSIVE_STIMPACK: 1,
item_names.MARAUDER_PROGRESSIVE_STIMPACK: 0,
# Additional unexcludes for logic
item_names.MEDIVAC: 0,
item_names.BATTLECRUISER: 0,
item_names.SCIENCE_VESSEL: 0,
},
# Terran-only
'enabled_campaigns': {
SC2Campaign.WOL.campaign_name,
SC2Campaign.NCO.campaign_name
},
}
self.generate_world(world_options)
self.assertTrue(self.multiworld.itempool)
itempool = [item.name for item in self.multiworld.itempool]
self.assertIn(item_names.MARINE, itempool)
self.assertIn(item_names.MARAUDER, itempool)
self.assertIn(item_names.REAPER, itempool)
self.assertEqual(itempool.count(item_names.NOVA_PROGRESSIVE_STEALTH_SUIT_MODULE), 1, "Stealth suit occurred the wrong number of times")
self.assertIn(item_names.HELLION, itempool)
self.assertEqual(itempool.count(item_names.MARINE_PROGRESSIVE_STIMPACK), 2, f"Marine stimpacks weren't unexcluded (seed {self.multiworld.seed})")
self.assertEqual(itempool.count(item_names.MARAUDER_PROGRESSIVE_STIMPACK), 2, f"Marauder stimpacks weren't unexcluded (seed {self.multiworld.seed})")
self.assertNotIn(item_names.DIAMONDBACK, itempool)
self.assertNotIn(item_names.NOVA_BLAZEFIRE_GUNBLADE, itempool)
self.assertNotIn(item_names.NOVA_ENERGY_SUIT_MODULE, itempool)
def test_excluding_groups_excludes_all_items_in_group(self):
world_options = {
'excluded_items': [
item_groups.ItemGroupNames.BARRACKS_UNITS.lower(),
]
}
self.generate_world(world_options)
itempool = [item.name for item in self.multiworld.itempool]
self.assertIn(item_names.MARINE, self.world.options.excluded_items)
for item_name in item_groups.barracks_units:
self.assertNotIn(item_name, itempool)
def test_excluding_mission_groups_excludes_all_missions_in_group(self):
world_options = {
**self.ZERG_CAMPAIGNS,
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
'excluded_missions': [
mission_groups.MissionGroupNames.HOTS_ZERUS_MISSIONS,
],
'mission_order': options.MissionOrder.option_grid,
}
self.generate_world(world_options)
missions = get_all_missions(self.world.custom_mission_order)
self.assertTrue(missions)
self.assertNotIn(mission_tables.SC2Mission.WAKING_THE_ANCIENT, missions)
self.assertNotIn(mission_tables.SC2Mission.THE_CRUCIBLE, missions)
self.assertNotIn(mission_tables.SC2Mission.SUPREME, missions)
def test_excluding_campaigns_excludes_campaign_specific_items(self) -> None:
world_options = {
'enabled_campaigns': {
SC2Campaign.WOL.campaign_name
},
}
self.generate_world(world_options)
self.assertTrue(self.multiworld.itempool)
world_items = [(item.name, item_tables.item_table[item.name]) for item in self.multiworld.itempool]
for item_name, item_data in world_items:
self.assertNotIn(item_data.type, item_tables.ProtossItemType)
self.assertNotIn(item_data.type, item_tables.ZergItemType)
self.assertNotEqual(item_data.type, item_tables.TerranItemType.Nova_Gear)
self.assertNotEqual(item_name, item_names.NOVA_PROGRESSIVE_STEALTH_SUIT_MODULE)
def test_starter_unit_populates_start_inventory(self):
world_options = {
'enabled_campaigns': {
SC2Campaign.WOL.campaign_name,
},
'shuffle_no_build': options.ShuffleNoBuild.option_false,
'mission_order': options.MissionOrder.option_grid,
'starter_unit': options.StarterUnit.option_any_starter_unit,
}
self.generate_world(world_options)
self.assertTrue(self.multiworld.itempool)
self.assertTrue(self.multiworld.precollected_items[self.player])
def test_excluding_all_terran_missions_excludes_all_terran_items(self) -> None:
world_options = {
**self.ALL_CAMPAIGNS,
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
'mission_order': options.MissionOrder.option_grid,
'maximum_campaign_size': options.MaximumCampaignSize.range_end,
'excluded_missions': [
mission.mission_name for mission in mission_tables.SC2Mission
if mission_tables.MissionFlag.Terran in mission.flags
],
}
self.generate_world(world_options)
self.assertTrue(self.multiworld.itempool)
world_items = [(item.name, item_tables.item_table[item.name]) for item in self.multiworld.itempool]
for item_name, item_data in world_items:
self.assertNotIn(item_data.type, item_tables.TerranItemType, f"Item '{item_name}' included when all terran missions are excluded")
def test_excluding_all_terran_build_missions_excludes_all_terran_units(self) -> None:
world_options = {
**self.ALL_CAMPAIGNS,
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
'mission_order': options.MissionOrder.option_grid,
'maximum_campaign_size': options.MaximumCampaignSize.range_end,
'excluded_missions': [
mission.mission_name for mission in mission_tables.SC2Mission
if mission_tables.MissionFlag.Terran in mission.flags
and mission_tables.MissionFlag.NoBuild not in mission.flags
],
}
self.generate_world(world_options)
self.assertTrue(self.multiworld.itempool)
world_items = [(item.name, item_tables.item_table[item.name]) for item in self.multiworld.itempool]
for item_name, item_data in world_items:
self.assertNotEqual(item_data.type, item_tables.TerranItemType.Unit, f"Item '{item_name}' included when all terran build missions are excluded")
self.assertNotEqual(item_data.type, item_tables.TerranItemType.Mercenary, f"Item '{item_name}' included when all terran build missions are excluded")
self.assertNotEqual(item_data.type, item_tables.TerranItemType.Building, f"Item '{item_name}' included when all terran build missions are excluded")
def test_excluding_all_zerg_and_kerrigan_missions_excludes_all_zerg_items(self) -> None:
world_options = {
**self.ALL_CAMPAIGNS,
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
'mission_order': options.MissionOrder.option_grid,
'maximum_campaign_size': options.MaximumCampaignSize.range_end,
'excluded_missions': [
mission.mission_name for mission in mission_tables.SC2Mission
if (mission_tables.MissionFlag.Kerrigan | mission_tables.MissionFlag.Zerg) & mission.flags
],
}
self.generate_world(world_options)
self.assertTrue(self.multiworld.itempool)
world_items = [(item.name, item_tables.item_table[item.name]) for item in self.multiworld.itempool]
for item_name, item_data in world_items:
self.assertNotIn(item_data.type, item_tables.ZergItemType, f"Item '{item_name}' included when all zerg missions are excluded")
def test_excluding_all_zerg_build_missions_excludes_zerg_units(self) -> None:
world_options = {
**self.ALL_CAMPAIGNS,
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
'mission_order': options.MissionOrder.option_grid,
'maximum_campaign_size': options.MaximumCampaignSize.range_end,
'excluded_missions': [
*[mission.mission_name
for mission in mission_tables.SC2Mission
if mission_tables.MissionFlag.Zerg in mission.flags
and mission_tables.MissionFlag.NoBuild not in mission.flags],
mission_tables.SC2Mission.ENEMY_WITHIN.mission_name,
],
}
self.generate_world(world_options)
self.assertTrue(self.multiworld.itempool)
world_items = [(item.name, item_tables.item_table[item.name]) for item in self.multiworld.itempool]
for item_name, item_data in world_items:
self.assertNotEqual(item_data.type, item_tables.ZergItemType.Unit, f"Item '{item_name}' included when all zerg build missions are excluded")
self.assertNotEqual(item_data.type, item_tables.ZergItemType.Mercenary, f"Item '{item_name}' included when all zerg build missions are excluded")
def test_excluding_all_protoss_missions_excludes_all_protoss_items(self) -> None:
world_options = {
**self.ALL_CAMPAIGNS,
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
'mission_order': options.MissionOrder.option_grid,
'maximum_campaign_size': options.MaximumCampaignSize.range_end,
'accessibility': 'locations',
'excluded_missions': [
*[mission.mission_name
for mission in mission_tables.SC2Mission
if mission_tables.MissionFlag.Protoss in mission.flags],
],
}
self.generate_world(world_options)
self.assertTrue(self.multiworld.itempool)
world_items = [(item.name, item_tables.item_table[item.name]) for item in self.multiworld.itempool]
for item_name, item_data in world_items:
self.assertNotIn(item_data.type, item_tables.ProtossItemType, f"Item '{item_name}' included when all protoss missions are excluded")
def test_excluding_all_protoss_build_missions_excludes_protoss_units(self) -> None:
world_options = {
**self.ALL_CAMPAIGNS,
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
'mission_order': options.MissionOrder.option_grid,
'maximum_campaign_size': options.MaximumCampaignSize.range_end,
'accessibility': 'locations',
'excluded_missions': [
*[mission.mission_name
for mission in mission_tables.SC2Mission
if mission.race == mission_tables.SC2Race.PROTOSS
and mission_tables.MissionFlag.NoBuild not in mission.flags],
mission_tables.SC2Mission.TEMPLAR_S_RETURN.mission_name,
],
}
self.generate_world(world_options)
self.assertTrue(self.multiworld.itempool)
world_items = [(item.name, item_tables.item_table[item.name]) for item in self.multiworld.itempool]
for item_name, item_data in world_items:
self.assertNotEqual(item_data.type, item_tables.ProtossItemType.Unit, f"Item '{item_name}' included when all protoss build missions are excluded")
self.assertNotEqual(item_data.type, item_tables.ProtossItemType.Unit_2, f"Item '{item_name}' included when all protoss build missions are excluded")
self.assertNotEqual(item_data.type, item_tables.ProtossItemType.Building, f"Item '{item_name}' included when all protoss build missions are excluded")
def test_vanilla_items_only_excludes_terran_progressives(self) -> None:
world_options = {
'enabled_campaigns': {
SC2Campaign.WOL.campaign_name,
SC2Campaign.NCO.campaign_name
},
'mission_order': options.MissionOrder.option_grid,
'maximum_campaign_size': options.MaximumCampaignSize.range_end,
'accessibility': 'locations',
'vanilla_items_only': True,
}
self.generate_world(world_options)
world_items = [(item.name, item_tables.item_table[item.name]) for item in self.multiworld.itempool]
self.assertTrue(world_items)
occurrences: Dict[str, int] = {}
for item_name, _ in world_items:
if item_name in item_groups.terran_progressive_items:
if item_name in item_groups.nova_equipment:
# The option imposes no contraint on Nova equipment
continue
occurrences.setdefault(item_name, 0)
occurrences[item_name] += 1
self.assertLessEqual(occurrences[item_name], 1, f"'{item_name}' unexpectedly appeared multiple times in the pool")
def test_vanilla_items_only_includes_only_nova_equipment_and_vanilla_and_filler_items(self) -> None:
world_options = {
**self.ALL_CAMPAIGNS,
'mission_order': options.MissionOrder.option_grid,
'maximum_campaign_size': options.MaximumCampaignSize.range_end,
# Avoid options that lock non-vanilla items for logic
'spear_of_adun_presence': options.SpearOfAdunPresence.option_protoss,
'required_tactics': options.RequiredTactics.option_advanced,
'mastery_locations': options.MasteryLocations.option_disabled,
'accessibility': 'locations',
'vanilla_items_only': True,
# Move the unit nerf items from the start inventory to the pool,
# else this option could push non-vanilla items past this test
'war_council_nerfs': True,
}
self.generate_world(world_options)
world_items = [(item.name, item_tables.item_table[item.name]) for item in self.multiworld.itempool]
self.assertTrue(world_items)
self.assertNotIn(item_names.DESTROYER_REFORGED_BLOODSHARD_CORE, world_items)
for item_name, item_data in world_items:
if item_data.quantity == 0:
continue
self.assertIn(item_name, item_groups.vanilla_items + item_groups.nova_equipment)
def test_evil_awoken_with_vanilla_items_only_generates(self) -> None:
world_options = {
'enabled_campaigns': {
SC2Campaign.PROLOGUE.campaign_name,
SC2Campaign.LOTV.campaign_name
},
'mission_order': options.MissionOrder.option_grid,
'maximum_campaign_size': options.MaximumCampaignSize.range_end,
'accessibility': 'locations',
'vanilla_items_only': True,
}
self.generate_world(world_options)
itempool = [item.name for item in self.multiworld.itempool]
self.assertTrue(itempool)
self.assertTrue(self.world.get_region(mission_tables.SC2Mission.EVIL_AWOKEN.mission_name))
def test_enemy_within_and_no_zerg_build_missions_generates(self) -> None:
world_options = {
# including WoL to allow for valid goal missions
'enabled_campaigns': {
SC2Campaign.WOL.campaign_name,
SC2Campaign.HOTS.campaign_name
},
'excluded_missions': [
mission.mission_name for mission in mission_tables.SC2Mission
if mission_tables.MissionFlag.Zerg in mission.flags
and mission_tables.MissionFlag.NoBuild not in mission.flags
],
'mission_order': options.MissionOrder.option_grid,
'maximum_campaign_size': options.MaximumCampaignSize.range_end,
'accessibility': 'locations',
'vanilla_items_only': True,
}
self.generate_world(world_options)
itempool = [item.name for item in self.multiworld.itempool]
self.assertTrue(itempool)
self.assertTrue(self.world.get_region(mission_tables.SC2Mission.ENEMY_WITHIN.mission_name))
self.assertNotIn(item_names.ULTRALISK, itempool)
self.assertNotIn(item_names.SWARM_QUEEN, itempool)
self.assertNotIn(item_names.MUTALISK, itempool)
self.assertNotIn(item_names.CORRUPTOR, itempool)
self.assertNotIn(item_names.SCOURGE, itempool)
def test_soa_items_are_included_in_wol_when_presence_set_to_everywhere(self) -> None:
world_options = {
'enabled_campaigns': {
SC2Campaign.WOL.campaign_name,
},
'spear_of_adun_presence': options.SpearOfAdunPresence.option_everywhere,
'mission_order': options.MissionOrder.option_grid,
'maximum_campaign_size': options.MaximumCampaignSize.range_end,
'accessibility': 'locations',
# Ensure enough locations to fit all wanted items
'generic_upgrade_missions': 1,
'victory_cache': 5,
'excluded_items': {item_groups.ItemGroupNames.BARRACKS_UNITS: 0},
}
self.generate_world(world_options)
itempool = [item.name for item in self.multiworld.itempool]
self.assertTrue(itempool)
soa_items_in_pool = [item_name for item_name in itempool if item_tables.item_table[item_name].type == item_tables.ProtossItemType.Spear_Of_Adun]
self.assertGreater(len(soa_items_in_pool), 5)
def test_lotv_only_doesnt_include_kerrigan_items_with_grant_story_tech(self) -> None:
world_options = {
'enabled_campaigns': {
SC2Campaign.LOTV.campaign_name,
},
'mission_order': options.MissionOrder.option_grid,
'maximum_campaign_size': options.MaximumCampaignSize.range_end,
'accessibility': 'locations',
'grant_story_tech': options.GrantStoryTech.option_grant,
}
self.generate_world(world_options)
missions = get_all_missions(self.world.custom_mission_order)
self.assertIn(mission_tables.SC2Mission.TEMPLE_OF_UNIFICATION, missions)
itempool = [item.name for item in self.multiworld.itempool]
self.assertTrue(itempool)
kerrigan_items_in_pool = set(item_groups.kerrigan_abilities).intersection(itempool)
self.assertFalse(kerrigan_items_in_pool)
kerrigan_passives_in_pool = set(item_groups.kerrigan_passives).intersection(itempool)
self.assertFalse(kerrigan_passives_in_pool)
def test_excluding_zerg_units_with_morphling_enabled_doesnt_exclude_aspects(self) -> None:
world_options = {
'enabled_campaigns': {
SC2Campaign.HOTS.campaign_name,
},
'required_tactics': options.RequiredTactics.option_no_logic,
'enable_morphling': options.EnableMorphling.option_true,
'excluded_items': [
item_groups.ItemGroupNames.ZERG_UNITS.lower()
],
'unexcluded_items': [
item_groups.ItemGroupNames.ZERG_MORPHS.lower()
]
}
self.generate_world(world_options)
itempool = [item.name for item in self.multiworld.itempool]
self.assertTrue(itempool)
aspects_in_pool = list(set(itempool).intersection(set(item_groups.zerg_morphs)))
self.assertTrue(aspects_in_pool)
units_in_pool = list(set(itempool).intersection(set(item_groups.zerg_units))
.difference(set(item_groups.zerg_morphs)))
self.assertFalse(units_in_pool)
def test_excluding_zerg_units_with_morphling_disabled_should_exclude_aspects(self) -> None:
world_options = {
'enabled_campaigns': {
SC2Campaign.HOTS.campaign_name,
},
'required_tactics': options.RequiredTactics.option_no_logic,
'enable_morphling': options.EnableMorphling.option_false,
'excluded_items': [
item_groups.ItemGroupNames.ZERG_UNITS.lower()
],
'unexcluded_items': [
item_groups.ItemGroupNames.ZERG_MORPHS.lower()
]
}
self.generate_world(world_options)
itempool = [item.name for item in self.multiworld.itempool]
self.assertTrue(itempool)
aspects_in_pool = list(set(itempool).intersection(set(item_groups.zerg_morphs)))
if item_names.OVERLORD_OVERSEER_ASPECT in aspects_in_pool:
# Overseer morphs from Overlord, that's available always
aspects_in_pool.remove(item_names.OVERLORD_OVERSEER_ASPECT)
self.assertFalse(aspects_in_pool)
units_in_pool = list(set(itempool).intersection(set(item_groups.zerg_units))
.difference(set(item_groups.zerg_morphs)))
self.assertFalse(units_in_pool)
def test_deprecated_orbital_command_not_present(self) -> None:
"""
Orbital command got replaced. The item is still there for backwards compatibility.
It shouldn't be generated.
"""
world_options = {}
self.generate_world(world_options)
itempool = [item.name for item in self.multiworld.itempool]
self.assertTrue(itempool)
self.assertNotIn(item_names.PROGRESSIVE_ORBITAL_COMMAND, itempool)
def test_planetary_orbital_module_not_present_without_cc_spells(self) -> None:
world_options = {
"excluded_items": [
item_names.COMMAND_CENTER_MULE,
item_names.COMMAND_CENTER_SCANNER_SWEEP,
item_names.COMMAND_CENTER_EXTRA_SUPPLIES
],
"locked_items": [
item_names.PLANETARY_FORTRESS
]
}
self.generate_world(world_options)
itempool = [item.name for item in self.multiworld.itempool]
self.assertTrue(itempool)
self.assertIn(item_names.PLANETARY_FORTRESS, itempool)
self.assertNotIn(item_names.PLANETARY_FORTRESS_ORBITAL_MODULE, itempool)
def test_disabling_unit_nerfs_start_inventories_war_council_upgrades(self) -> None:
world_options = {
'enabled_campaigns': {
SC2Campaign.PROPHECY.campaign_name,
SC2Campaign.PROLOGUE.campaign_name,
SC2Campaign.LOTV.campaign_name
},
'mission_order': options.MissionOrder.option_grid,
'war_council_nerfs': options.WarCouncilNerfs.option_false,
}
self.generate_world(world_options)
itempool = [item.name for item in self.multiworld.itempool]
war_council_item_names = set(item_groups.item_name_groups[item_groups.ItemGroupNames.WAR_COUNCIL])
present_war_council_items = war_council_item_names.intersection(itempool)
starting_inventory = [item.name for item in self.multiworld.precollected_items[self.player]]
starting_war_council_items = war_council_item_names.intersection(starting_inventory)
self.assertTrue(itempool)
self.assertFalse(present_war_council_items, f'Found war council upgrades when war_council_nerfs is false: {present_war_council_items}')
self.assertEqual(war_council_item_names, starting_war_council_items)
def test_disabling_speedrun_locations_removes_them_from_the_pool(self) -> None:
world_options = {
'enabled_campaigns': {
SC2Campaign.HOTS.campaign_name,
},
'mission_order': options.MissionOrder.option_grid,
'speedrun_locations': options.SpeedrunLocations.option_disabled,
'preventative_locations': options.PreventativeLocations.option_filler,
}
self.generate_world(world_options)
world_regions = list(self.multiworld.regions)
world_location_names = [location.name for region in world_regions for location in region.locations]
all_location_names = [location_data.name for location_data in locations.DEFAULT_LOCATION_LIST]
speedrun_location_name = f"{mission_tables.SC2Mission.LAB_RAT.mission_name}: Win In Under 10 Minutes"
self.assertIn(speedrun_location_name, all_location_names)
self.assertNotIn(speedrun_location_name, world_location_names)
def test_nco_and_wol_picks_correct_starting_mission(self):
world_options = {
'mission_order': MissionOrder.option_vanilla,
'enabled_campaigns': {
SC2Campaign.WOL.campaign_name,
SC2Campaign.NCO.campaign_name
},
}
self.generate_world(world_options)
self.assertEqual(get_random_first_mission(self.world, self.world.custom_mission_order), mission_tables.SC2Mission.LIBERATION_DAY)
def test_excluding_mission_short_name_excludes_all_variants_of_mission(self):
world_options = {
'excluded_missions': [
mission_tables.SC2Mission.ZERO_HOUR.mission_name.split(" (")[0]
],
'mission_order': options.MissionOrder.option_grid,
'selected_races': options.SelectedRaces.valid_keys,
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
'enabled_campaigns': {
SC2Campaign.WOL.campaign_name,
},
}
self.generate_world(world_options)
missions = get_all_missions(self.world.custom_mission_order)
self.assertTrue(missions)
self.assertNotIn(mission_tables.SC2Mission.ZERO_HOUR, missions)
self.assertNotIn(mission_tables.SC2Mission.ZERO_HOUR_Z, missions)
self.assertNotIn(mission_tables.SC2Mission.ZERO_HOUR_P, missions)
def test_excluding_mission_variant_excludes_just_that_variant(self):
world_options = {
'excluded_missions': [
mission_tables.SC2Mission.ZERO_HOUR.mission_name
],
'mission_order': options.MissionOrder.option_grid,
'selected_races': options.SelectedRaces.valid_keys,
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
'enabled_campaigns': {
SC2Campaign.WOL.campaign_name,
},
}
self.generate_world(world_options)
missions = get_all_missions(self.world.custom_mission_order)
self.assertTrue(missions)
self.assertNotIn(mission_tables.SC2Mission.ZERO_HOUR, missions)
self.assertIn(mission_tables.SC2Mission.ZERO_HOUR_Z, missions)
self.assertIn(mission_tables.SC2Mission.ZERO_HOUR_P, missions)
def test_weapon_armor_upgrades(self):
world_options = {
# Vanilla WoL with all missions
'mission_order': options.MissionOrder.option_vanilla,
'starter_unit': options.StarterUnit.option_off,
'enabled_campaigns': {
SC2Campaign.WOL.campaign_name,
},
'start_inventory': {
item_names.GOLIATH: 1 # Don't fail with early item placement
},
'generic_upgrade_items': options.GenericUpgradeItems.option_individual_items,
# Disable locations in order to cause item culling
'vanilla_locations': options.VanillaLocations.option_disabled,
'extra_locations': options.ExtraLocations.option_disabled,
'challenge_locations': options.ChallengeLocations.option_disabled,
'mastery_locations': options.MasteryLocations.option_disabled,
'speedrun_locations': options.SpeedrunLocations.option_disabled,
'preventative_locations': options.PreventativeLocations.option_disabled,
}
self.generate_world(world_options)
starting_inventory = [item.name for item in self.multiworld.precollected_items[self.player]]
itempool = [item.name for item in self.multiworld.itempool]
world_items = starting_inventory + itempool
vehicle_weapon_items = [x for x in world_items if x == item_names.PROGRESSIVE_TERRAN_VEHICLE_WEAPON]
other_bundle_items = [
x for x in world_items if x in (
item_names.PROGRESSIVE_TERRAN_WEAPON_ARMOR_UPGRADE,
item_names.PROGRESSIVE_TERRAN_WEAPON_UPGRADE,
item_names.PROGRESSIVE_TERRAN_VEHICLE_UPGRADE,
)
]
# Under standard tactics you need to place L3 upgrades for available unit classes
self.assertGreaterEqual(len(vehicle_weapon_items), 3)
self.assertEqual(len(other_bundle_items), 0)
def test_weapon_armor_upgrades_with_bundles(self):
world_options = {
# Vanilla WoL with all missions
'mission_order': options.MissionOrder.option_vanilla,
'starter_unit': options.StarterUnit.option_off,
'enabled_campaigns': {
SC2Campaign.WOL.campaign_name,
},
'start_inventory': {
item_names.GOLIATH: 1 # Don't fail with early item placement
},
'generic_upgrade_items': options.GenericUpgradeItems.option_bundle_unit_class,
# Disable locations in order to cause item culling
'vanilla_locations': options.VanillaLocations.option_disabled,
'extra_locations': options.ExtraLocations.option_disabled,
'challenge_locations': options.ChallengeLocations.option_disabled,
'mastery_locations': options.MasteryLocations.option_disabled,
'speedrun_locations': options.SpeedrunLocations.option_disabled,
'preventative_locations': options.PreventativeLocations.option_disabled,
}
self.generate_world(world_options)
starting_inventory = [item.name for item in self.multiworld.precollected_items[self.player]]
itempool = [item.name for item in self.multiworld.itempool]
world_items = starting_inventory + itempool
vehicle_upgrade_items = [x for x in world_items if x == item_names.PROGRESSIVE_TERRAN_VEHICLE_UPGRADE]
other_bundle_items = [
x for x in world_items if x in (
item_names.PROGRESSIVE_TERRAN_WEAPON_ARMOR_UPGRADE,
item_names.PROGRESSIVE_TERRAN_WEAPON_UPGRADE,
item_names.PROGRESSIVE_TERRAN_VEHICLE_WEAPON,
)
]
# Under standard tactics you need to place L3 upgrades for available unit classes
self.assertGreaterEqual(len(vehicle_upgrade_items), 3)
self.assertEqual(len(other_bundle_items), 0)
def test_weapon_armor_upgrades_all_in_air(self):
world_options = {
# Vanilla WoL with all missions
'mission_order': options.MissionOrder.option_vanilla,
'starter_unit': options.StarterUnit.option_off,
'enabled_campaigns': {
SC2Campaign.WOL.campaign_name,
},
'all_in_map': options.AllInMap.option_air, # All-in air forces an air unit
'start_inventory': {
item_names.GOLIATH: 1 # Don't fail with early item placement
},
'generic_upgrade_items': options.GenericUpgradeItems.option_individual_items,
# Disable locations in order to cause item culling
'vanilla_locations': options.VanillaLocations.option_disabled,
'extra_locations': options.ExtraLocations.option_disabled,
'challenge_locations': options.ChallengeLocations.option_disabled,
'mastery_locations': options.MasteryLocations.option_disabled,
'speedrun_locations': options.SpeedrunLocations.option_disabled,
'preventative_locations': options.PreventativeLocations.option_disabled,
}
self.generate_world(world_options)
starting_inventory = [item.name for item in self.multiworld.precollected_items[self.player]]
itempool = [item.name for item in self.multiworld.itempool]
world_items = starting_inventory + itempool
vehicle_weapon_items = [x for x in world_items if x == item_names.PROGRESSIVE_TERRAN_VEHICLE_WEAPON]
ship_weapon_items = [x for x in world_items if x == item_names.PROGRESSIVE_TERRAN_SHIP_WEAPON]
# Under standard tactics you need to place L3 upgrades for available unit classes
self.assertGreaterEqual(len(vehicle_weapon_items), 3)
self.assertGreaterEqual(len(ship_weapon_items), 3)
def test_weapon_armor_upgrades_generic_upgrade_missions(self):
"""
Tests the case when there aren't enough missions in order to get required weapon/armor upgrades
for logic requirements.
:return:
"""
world_options = {
# Vanilla WoL with all missions
'mission_order': options.MissionOrder.option_vanilla,
'required_tactics': options.RequiredTactics.option_standard,
'starter_unit': options.StarterUnit.option_off,
'enabled_campaigns': {
SC2Campaign.WOL.campaign_name,
},
'all_in_map': options.AllInMap.option_air, # All-in air forces an air unit
'start_inventory': {
item_names.GOLIATH: 1 # Don't fail with early item placement
},
'generic_upgrade_items': options.GenericUpgradeItems.option_individual_items,
'generic_upgrade_missions': 100, # Fallback happens by putting weapon/armor upgrades into starting inventory
}
self.generate_world(world_options)
starting_inventory = [item.name for item in self.multiworld.precollected_items[self.player]]
upgrade_items = [x for x in starting_inventory if x == item_names.PROGRESSIVE_TERRAN_WEAPON_ARMOR_UPGRADE]
# Under standard tactics you need to place L3 upgrades for available unit classes
self.assertEqual(len(upgrade_items), 3)
def test_weapon_armor_upgrades_generic_upgrade_missions_no_logic(self):
"""
Tests the case when there aren't enough missions in order to get required weapon/armor upgrades
for logic requirements.
Except the case above it's No Logic, thus the fallback won't take place.
:return:
"""
world_options = {
# Vanilla WoL with all missions
'mission_order': options.MissionOrder.option_vanilla,
'required_tactics': options.RequiredTactics.option_no_logic,
'starter_unit': options.StarterUnit.option_off,
'enabled_campaigns': {
SC2Campaign.WOL.campaign_name,
},
'all_in_map': options.AllInMap.option_air, # All-in air forces an air unit
'start_inventory': {
item_names.GOLIATH: 1 # Don't fail with early item placement
},
'generic_upgrade_items': options.GenericUpgradeItems.option_individual_items,
'generic_upgrade_missions': 100, # Fallback happens by putting weapon/armor upgrades into starting inventory
}
self.generate_world(world_options)
starting_inventory = [item.name for item in self.multiworld.precollected_items[self.player]]
upgrade_items = [x for x in starting_inventory if x == item_names.PROGRESSIVE_TERRAN_WEAPON_ARMOR_UPGRADE]
# No logic won't take the fallback to trigger
self.assertEqual(len(upgrade_items), 0)
def test_weapon_armor_upgrades_generic_upgrade_missions_no_countermeasure_needed(self):
world_options = {
# Vanilla WoL with all missions
'mission_order': options.MissionOrder.option_vanilla,
'required_tactics': options.RequiredTactics.option_standard,
'starter_unit': options.StarterUnit.option_off,
'enabled_campaigns': {
SC2Campaign.WOL.campaign_name,
},
'all_in_map': options.AllInMap.option_air, # All-in air forces an air unit
'start_inventory': {
item_names.GOLIATH: 1 # Don't fail with early item placement
},
'generic_upgrade_items': options.GenericUpgradeItems.option_individual_items,
'generic_upgrade_missions': 1, # Weapon / Armor upgrades should be available almost instantly
}
self.generate_world(world_options)
starting_inventory = [item.name for item in self.multiworld.precollected_items[self.player]]
upgrade_items = [x for x in starting_inventory if x == item_names.PROGRESSIVE_TERRAN_WEAPON_ARMOR_UPGRADE]
# No additional starting inventory item placement is needed
self.assertEqual(len(upgrade_items), 0)
def test_kerrigan_levels_per_mission_triggering_pre_fill(self):
world_options = {
**self.ALL_CAMPAIGNS,
'mission_order': options.MissionOrder.option_custom,
'custom_mission_order': {
'campaign': {
'goal': True,
'layout': {
'type': 'column',
'size': 3,
'missions': [
{
'index': 0,
'mission_pool': [SC2Mission.LIBERATION_DAY.mission_name]
},
{
'index': 1,
'mission_pool': [SC2Mission.THE_INFINITE_CYCLE.mission_name]
},
{
'index': 2,
'mission_pool': [SC2Mission.THE_RECKONING.mission_name]
},
]
}
}
},
'required_tactics': options.RequiredTactics.option_standard,
'starter_unit': options.StarterUnit.option_off,
'generic_upgrade_items': options.GenericUpgradeItems.option_individual_items,
'grant_story_levels': options.GrantStoryLevels.option_disabled,
'kerrigan_levels_per_mission_completed': 1,
'kerrigan_level_item_distribution': options.KerriganLevelItemDistribution.option_size_2,
}
self.generate_world(world_options)
starting_inventory = [item.name for item in self.multiworld.precollected_items[self.player]]
kerrigan_1_stacks = [x for x in starting_inventory if x == item_names.KERRIGAN_LEVELS_1]
self.assertGreater(len(kerrigan_1_stacks), 0)
def test_kerrigan_levels_per_mission_and_generic_upgrades_both_triggering_pre_fill(self):
world_options = {
**self.ALL_CAMPAIGNS,
'mission_order': options.MissionOrder.option_custom,
'custom_mission_order': {
'campaign': {
'goal': True,
'layout': {
'type': 'column',
'size': 3,
'missions': [
{
'index': 0,
'mission_pool': [SC2Mission.LIBERATION_DAY.mission_name]
},
{
'index': 1,
'mission_pool': [SC2Mission.THE_INFINITE_CYCLE.mission_name]
},
{
'index': 2,
'mission_pool': [SC2Mission.THE_RECKONING.mission_name]
},
]
}
}
},
'required_tactics': options.RequiredTactics.option_standard,
'starter_unit': options.StarterUnit.option_off,
'generic_upgrade_items': options.GenericUpgradeItems.option_individual_items,
'grant_story_levels': options.GrantStoryLevels.option_disabled,
'kerrigan_levels_per_mission_completed': 1,
'kerrigan_level_item_distribution': options.KerriganLevelItemDistribution.option_size_2,
'generic_upgrade_missions': 100, # Weapon / Armor upgrades
}
self.generate_world(world_options)
starting_inventory = [item.name for item in self.multiworld.precollected_items[self.player]]
itempool = [item.name for item in self.multiworld.itempool]
kerrigan_1_stacks = [x for x in starting_inventory if x == item_names.KERRIGAN_LEVELS_1]
upgrade_items = [x for x in starting_inventory if x == item_names.PROGRESSIVE_ZERG_WEAPON_ARMOR_UPGRADE]
self.assertGreater(len(kerrigan_1_stacks), 0) # Kerrigan levels were added
self.assertEqual(len(upgrade_items), 3) # W/A upgrades were added
self.assertNotIn(item_names.KERRIGAN_LEVELS_70, itempool)
self.assertNotIn(item_names.KERRIGAN_LEVELS_70, starting_inventory)
def test_locking_required_items(self):
world_options = {
**self.ALL_CAMPAIGNS,
'mission_order': options.MissionOrder.option_custom,
'custom_mission_order': {
'campaign': {
'goal': True,
'layout': {
'type': 'column',
'size': 2,
'missions': [
{
'index': 0,
'mission_pool': [SC2Mission.LIBERATION_DAY.mission_name]
},
{
'index': 1,
'mission_pool': [SC2Mission.SUPREME.mission_name]
},
]
}
}
},
'grant_story_levels': options.GrantStoryLevels.option_additive,
'excluded_items': [
item_names.KERRIGAN_LEAPING_STRIKE,
item_names.KERRIGAN_MEND,
]
}
self.generate_world(world_options)
itempool = [item.name for item in self.multiworld.itempool]
# These items will be in the pool despite exclusions
self.assertIn(item_names.KERRIGAN_LEAPING_STRIKE, itempool)
self.assertIn(item_names.KERRIGAN_MEND, itempool)
def test_fully_balanced_mission_races(self):
"""
Tests whether fully balanced mission race balancing actually is fully balanced.
"""
campaign_size = 57
self.assertEqual(campaign_size % 3, 0, "Chosen test size cannot be perfectly balanced")
world_options = {
# Reasonably large grid with enough missions to balance races
'mission_order': options.MissionOrder.option_grid,
'maximum_campaign_size': campaign_size,
'enabled_campaigns': EnabledCampaigns.valid_keys,
'selected_races': options.SelectedRaces.valid_keys,
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
'mission_race_balancing': options.EnableMissionRaceBalancing.option_fully_balanced,
}
self.generate_world(world_options)
world_regions = [region.name for region in self.multiworld.regions]
world_regions.remove('Menu')
missions = [mission_tables.lookup_name_to_mission[region] for region in world_regions]
race_flags = [mission_tables.MissionFlag.Terran, mission_tables.MissionFlag.Zerg, mission_tables.MissionFlag.Protoss]
race_counts = { flag: sum(flag in mission.flags for mission in missions) for flag in race_flags }
self.assertEqual(race_counts[mission_tables.MissionFlag.Terran], race_counts[mission_tables.MissionFlag.Zerg])
self.assertEqual(race_counts[mission_tables.MissionFlag.Zerg], race_counts[mission_tables.MissionFlag.Protoss])
def test_setting_filter_weight_to_zero_excludes_that_item(self) -> None:
world_options = {
'filler_items_distribution': {
item_names.STARTING_MINERALS: 0,
item_names.STARTING_VESPENE: 1,
item_names.STARTING_SUPPLY: 0,
item_names.MAX_SUPPLY: 0,
item_names.REDUCED_MAX_SUPPLY: 0,
item_names.SHIELD_REGENERATION: 0,
item_names.BUILDING_CONSTRUCTION_SPEED: 0,
},
# Exclude many items to get filler to generate
'excluded_items': {
item_groups.ItemGroupNames.TERRAN_VETERANCY_UNITS: 0,
},
'max_number_of_upgrades': 2,
'mission_order': options.MissionOrder.option_grid,
**self.ALL_CAMPAIGNS,
'selected_races': {
SC2Race.TERRAN.get_title(),
},
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
}
self.generate_world(world_options)
itempool = [item.name for item in self.multiworld.itempool]
self.assertNotIn(item_names.STARTING_MINERALS, itempool)
self.assertNotIn(item_names.STARTING_SUPPLY, itempool)
self.assertNotIn(item_names.MAX_SUPPLY, itempool)
self.assertNotIn(item_names.REDUCED_MAX_SUPPLY, itempool)
self.assertNotIn(item_names.SHIELD_REGENERATION, itempool)
self.assertNotIn(item_names.BUILDING_CONSTRUCTION_SPEED, itempool)
self.assertIn(item_names.STARTING_VESPENE, itempool)
def test_shields_filler_doesnt_appear_if_no_protoss_missions_appear(self) -> None:
world_options = {
'filler_items_distribution': {
item_names.STARTING_MINERALS: 1,
item_names.STARTING_VESPENE: 0,
item_names.STARTING_SUPPLY: 0,
item_names.MAX_SUPPLY: 0,
item_names.REDUCED_MAX_SUPPLY: 1,
item_names.SHIELD_REGENERATION: 1,
item_names.BUILDING_CONSTRUCTION_SPEED: 0,
},
# Exclude many items to get filler to generate
'excluded_items': {
item_groups.ItemGroupNames.TERRAN_VETERANCY_UNITS: 0,
item_groups.ItemGroupNames.ZERG_MORPHS: 0,
},
'max_number_of_upgrades': 2,
'mission_order': options.MissionOrder.option_grid,
**self.ALL_CAMPAIGNS,
'selected_races': {
SC2Race.TERRAN.get_title(),
SC2Race.ZERG.get_title(),
},
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
}
self.generate_world(world_options)
itempool = [item.name for item in self.multiworld.itempool]
self.assertNotIn(item_names.SHIELD_REGENERATION, itempool)
self.assertNotIn(item_names.STARTING_VESPENE, itempool)
self.assertNotIn(item_names.STARTING_SUPPLY, itempool)
self.assertNotIn(item_names.MAX_SUPPLY, itempool)
self.assertNotIn(item_names.BUILDING_CONSTRUCTION_SPEED, itempool)
self.assertIn(item_names.STARTING_MINERALS, itempool)
self.assertIn(item_names.REDUCED_MAX_SUPPLY, itempool)
def test_weapon_armor_upgrade_items_capped_by_max_upgrade_level(self) -> None:
MAX_LEVEL = 3
world_options = {
'locked_items': {
item_groups.ItemGroupNames.TERRAN_GENERIC_UPGRADES: MAX_LEVEL,
item_groups.ItemGroupNames.ZERG_GENERIC_UPGRADES: MAX_LEVEL,
item_groups.ItemGroupNames.PROTOSS_GENERIC_UPGRADES: MAX_LEVEL + 1,
},
'max_upgrade_level': MAX_LEVEL,
'mission_order': options.MissionOrder.option_grid,
**self.ALL_CAMPAIGNS,
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
'generic_upgrade_items': options.GenericUpgradeItems.option_bundle_weapon_and_armor
}
self.generate_world(world_options)
itempool = [item.name for item in self.multiworld.itempool]
upgrade_item_counts: Dict[str, int] = {}
for item_name in itempool:
if item_tables.item_table[item_name].type in (
item_tables.TerranItemType.Upgrade,
item_tables.ZergItemType.Upgrade,
item_tables.ProtossItemType.Upgrade,
):
upgrade_item_counts[item_name] = upgrade_item_counts.get(item_name, 0) + 1
expected_result = {
item_names.PROGRESSIVE_TERRAN_ARMOR_UPGRADE: MAX_LEVEL,
item_names.PROGRESSIVE_TERRAN_WEAPON_UPGRADE: MAX_LEVEL,
item_names.PROGRESSIVE_ZERG_ARMOR_UPGRADE: MAX_LEVEL,
item_names.PROGRESSIVE_ZERG_WEAPON_UPGRADE: MAX_LEVEL,
item_names.PROGRESSIVE_PROTOSS_ARMOR_UPGRADE: MAX_LEVEL + 1,
item_names.PROGRESSIVE_PROTOSS_WEAPON_UPGRADE: MAX_LEVEL + 1,
}
self.assertDictEqual(expected_result, upgrade_item_counts)
def test_ghost_of_a_chance_generates_without_nco(self) -> None:
world_options = {
**self.TERRAN_CAMPAIGNS,
'mission_order': MissionOrder.option_custom,
'nova_ghost_of_a_chance_variant': NovaGhostOfAChanceVariant.option_auto,
'custom_mission_order': {
'test': {
'type': 'column',
'size': 1,
'mission_pool': [
SC2Mission.GHOST_OF_A_CHANCE.mission_name
]
}
}
}
self.generate_world(world_options)
itempool = [item.name for item in self.multiworld.itempool]
self.assertNotIn(item_names.NOVA_C20A_CANISTER_RIFLE, itempool)
self.assertNotIn(item_names.NOVA_DOMINATION, itempool)
def test_ghost_of_a_chance_generates_using_nco_nova(self) -> None:
world_options = {
**self.TERRAN_CAMPAIGNS,
'mission_order': MissionOrder.option_custom,
'nova_ghost_of_a_chance_variant': NovaGhostOfAChanceVariant.option_nco,
'custom_mission_order': {
'test': {
'type': 'column',
'size': 2,
'mission_pool': [
SC2Mission.LIBERATION_DAY.mission_name, # Starter mission
SC2Mission.GHOST_OF_A_CHANCE.mission_name,
]
}
}
}
self.generate_world(world_options)
itempool = [item.name for item in self.multiworld.itempool]
self.assertGreater(len({item_names.NOVA_C20A_CANISTER_RIFLE, item_names.NOVA_DOMINATION}.intersection(itempool)), 0)
def test_ghost_of_a_chance_generates_with_nco(self) -> None:
world_options = {
**self.TERRAN_CAMPAIGNS,
'mission_order': MissionOrder.option_custom,
'nova_ghost_of_a_chance_variant': NovaGhostOfAChanceVariant.option_auto,
'custom_mission_order': {
'test': {
'type': 'column',
'size': 3,
'mission_pool': [
SC2Mission.LIBERATION_DAY.mission_name, # Starter mission
SC2Mission.GHOST_OF_A_CHANCE.mission_name,
SC2Mission.FLASHPOINT.mission_name, # A NCO mission
]
}
}
}
self.generate_world(world_options)
itempool = [item.name for item in self.multiworld.itempool]
self.assertGreater(len({item_names.NOVA_C20A_CANISTER_RIFLE, item_names.NOVA_DOMINATION}.intersection(itempool)), 0)
def test_exclude_overpowered_items(self) -> None:
world_options = {
**self.ALL_CAMPAIGNS,
'mission_order': MissionOrder.option_grid,
'maximum_campaign_size': MaximumCampaignSize.range_end,
'exclude_overpowered_items': ExcludeOverpoweredItems.option_true,
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
'selected_races': [SC2Race.TERRAN.get_title()],
}
self.generate_world(world_options)
itempool = [item.name for item in self.multiworld.itempool]
regen_biosteel_items = [item for item in itempool if item == item_names.PROGRESSIVE_REGENERATIVE_BIO_STEEL]
atx_laser_battery_items = [item for item in itempool if item == item_names.BATTLECRUISER_ATX_LASER_BATTERY]
self.assertEqual(len(regen_biosteel_items), 2) # Progressive, only top level is excluded
self.assertEqual(len(atx_laser_battery_items), 0) # Non-progressive
def test_exclude_overpowered_items_not_excluded(self) -> None:
world_options = {
**self.ALL_CAMPAIGNS,
'mission_order': MissionOrder.option_grid,
'maximum_campaign_size': MaximumCampaignSize.range_end,
'exclude_overpowered_items': ExcludeOverpoweredItems.option_false,
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
'selected_races': [SC2Race.TERRAN.get_title()],
}
self.generate_world(world_options)
itempool = [item.name for item in self.multiworld.itempool]
regen_biosteel_items = [item for item in itempool if item == item_names.PROGRESSIVE_REGENERATIVE_BIO_STEEL]
atx_laser_battery_items = [item for item in itempool if item == item_names.BATTLECRUISER_ATX_LASER_BATTERY]
self.assertEqual(len(regen_biosteel_items), 3)
self.assertEqual(len(atx_laser_battery_items), 1)
def test_exclude_overpowered_items_vanilla_only(self) -> None:
world_options = {
**self.ALL_CAMPAIGNS,
'mission_order': MissionOrder.option_grid,
'maximum_campaign_size': MaximumCampaignSize.range_end,
'exclude_overpowered_items': ExcludeOverpoweredItems.option_true,
'vanilla_items_only': VanillaItemsOnly.option_true,
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
'selected_races': [SC2Race.TERRAN.get_title()],
}
self.generate_world(world_options)
itempool = [item.name for item in self.multiworld.itempool]
# Regen biosteel is in both of the lists
regen_biosteel_items = [item for item in itempool if item == item_names.PROGRESSIVE_REGENERATIVE_BIO_STEEL]
self.assertEqual(len(regen_biosteel_items), 1) # One stack shall remain
def test_exclude_locked_overpowered_items(self) -> None:
locked_item = item_names.BATTLECRUISER_ATX_LASER_BATTERY
world_options = {
**self.ALL_CAMPAIGNS,
'mission_order': MissionOrder.option_grid,
'maximum_campaign_size': MaximumCampaignSize.range_end,
'exclude_overpowered_items': ExcludeOverpoweredItems.option_true,
'locked_items': [locked_item],
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
'selected_races': [SC2Race.TERRAN.get_title()],
}
self.generate_world(world_options)
itempool = [item.name for item in self.multiworld.itempool]
atx_laser_battery_items = [item for item in itempool if item == locked_item]
self.assertEqual(len(atx_laser_battery_items), 1) # Locked, remains
def test_unreleased_item_quantity(self) -> None:
"""
Checks if all unreleased items are marked properly not to generate
"""
world_options = {
**self.ALL_CAMPAIGNS,
'mission_order': MissionOrder.option_grid,
'maximum_campaign_size': MaximumCampaignSize.range_end,
'exclude_overpowered_items': ExcludeOverpoweredItems.option_false,
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
}
self.generate_world(world_options)
itempool = [item.name for item in self.multiworld.itempool]
items_to_check: List[str] = unreleased_items
for item in items_to_check:
self.assertNotIn(item, itempool)
def test_unreleased_item_quantity_locked(self) -> None:
"""
Checks if all unreleased items are marked properly not to generate
Locking overrides this behavior - if they're locked, they must appear
"""
world_options = {
**self.ALL_CAMPAIGNS,
'mission_order': MissionOrder.option_grid,
'maximum_campaign_size': MaximumCampaignSize.range_end,
'exclude_overpowered_items': ExcludeOverpoweredItems.option_false,
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
'locked_items': {item_name: 0 for item_name in unreleased_items},
}
self.generate_world(world_options)
itempool = [item.name for item in self.multiworld.itempool]
items_to_check: List[str] = unreleased_items
for item in items_to_check:
self.assertIn(item, itempool)
def test_merc_excluded_excludes_merc_upgrades(self) -> None:
world_options = {
**self.ALL_CAMPAIGNS,
'mission_order': MissionOrder.option_grid,
'maximum_campaign_size': MaximumCampaignSize.range_end,
'excluded_items': [item_name for item_name in item_groups.terran_mercenaries],
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
'selected_races': [SC2Race.TERRAN.get_title()],
}
self.generate_world(world_options)
itempool = [item.name for item in self.multiworld.itempool]
self.assertNotIn(item_names.ROGUE_FORCES, itempool)
def test_unexcluded_items_applies_over_op_items(self) -> None:
world_options = {
**self.ALL_CAMPAIGNS,
'mission_order': MissionOrder.option_grid,
'maximum_campaign_size': MaximumCampaignSize.range_end,
'exclude_overpowered_items': ExcludeOverpoweredItems.option_true,
'unexcluded_items': [item_names.SOA_TIME_STOP],
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
}
self.generate_world(world_options)
itempool = [item.name for item in self.multiworld.itempool]
self.assertNotIn(
item_groups.overpowered_items[0],
itempool,
f"OP item {item_groups.overpowered_items[0]} in the item pool when exclude_overpowered_items was true"
)
self.assertIn(
item_names.SOA_TIME_STOP,
itempool,
f"{item_names.SOA_TIME_STOP} was not unexcluded by unexcluded_items when exclude_overpowered_items was true"
)
def test_exclude_overpowered_items_and_not_allow_unit_nerfs(self) -> None:
world_options = {
**self.ALL_CAMPAIGNS,
'mission_order': MissionOrder.option_grid,
'maximum_campaign_size': MaximumCampaignSize.range_end,
'exclude_overpowered_items': ExcludeOverpoweredItems.option_true,
'war_council_nerfs': options.WarCouncilNerfs.option_false,
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
'selected_races': [SC2Race.PROTOSS.get_title()],
}
self.generate_world(world_options)
starting_inventory = [item.name for item in self.multiworld.precollected_items[self.player]]
# A unit nerf happens due to excluding OP items
self.assertNotIn(item_names.MOTHERSHIP_INTEGRATED_POWER, starting_inventory)
def test_terran_nobuild_sections_get_marine_medic_upgrades_with_units_excluded(self) -> None:
world_options = {
'mission_order': MissionOrder.option_grid,
'maximum_campaign_size': MaximumCampaignSize.range_end,
'enabled_campaigns': {
SC2Campaign.WOL.campaign_name
},
'excluded_items': [item_names.MARINE, item_names.MEDIC],
'shuffle_no_build': False,
'required_tactics': RequiredTactics.option_standard
}
mm_logic_upgrades = {
item_names.MARINE_COMBAT_SHIELD, item_names.MARINE_MAGRAIL_MUNITIONS,
item_names.MARINE_LASER_TARGETING_SYSTEM,
item_names.MARINE_PROGRESSIVE_STIMPACK, item_names.MEDIC_STABILIZER_MEDPACKS
}
self.generate_world(world_options)
itempool = [item.name for item in self.multiworld.itempool]
missions = self.multiworld.worlds[self.player].custom_mission_order.get_used_missions()
# These missions are rolled
self.assertIn(SC2Mission.THE_DIG, missions)
self.assertIn(SC2Mission.ENGINE_OF_DESTRUCTION, missions)
# This is not rolled
self.assertNotIn(SC2Mission.PIERCING_OF_THE_SHROUD, missions)
self.assertNotIn(SC2Mission.BELLY_OF_THE_BEAST, missions)
# These items are excluded and shouldn't appear
self.assertNotIn(item_names.MARINE, itempool)
self.assertNotIn(item_names.MEDIC, itempool)
# An upgrade is requested by logic for The Dig and Engine of Destruction
self.assertGreaterEqual(len(set(itempool).intersection(mm_logic_upgrades)), 1)