""" 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)