SC2: Content update (#5312)
Feature highlights: - Adds many content to the SC2 game - Allows custom mission order - Adds race-swapped missions for build missions (except Epilogue and NCO) - Allows War Council Nerfs (Protoss units can get pre - War Council State, alternative units get another custom nerf to match the power level of base units) - Revamps Predator's upgrade tree (never was considered strategically important) - Adds some units and upgrades - Locked and excluded items can specify quantity - Key mode (if opt-in, missions require keys to be unlocked on top of their regular regular requirements - Victory caches - Victory locations can grant multiple items to the multiworld instead of one - The generator is more resilient for generator failures as it validates logic for item excludes - Fixes the following issues: - https://github.com/ArchipelagoMW/Archipelago/issues/3531 - https://github.com/ArchipelagoMW/Archipelago/issues/3548
This commit is contained in:
@@ -1,41 +0,0 @@
|
||||
import unittest
|
||||
from .test_base import Sc2TestBase
|
||||
from .. import Regions
|
||||
from .. import Options, MissionTables
|
||||
|
||||
class TestGridsizes(unittest.TestCase):
|
||||
def test_grid_sizes_meet_specs(self):
|
||||
self.assertTupleEqual((1, 2, 0), Regions.get_grid_dimensions(2))
|
||||
self.assertTupleEqual((1, 3, 0), Regions.get_grid_dimensions(3))
|
||||
self.assertTupleEqual((2, 2, 0), Regions.get_grid_dimensions(4))
|
||||
self.assertTupleEqual((2, 3, 1), Regions.get_grid_dimensions(5))
|
||||
self.assertTupleEqual((2, 4, 1), Regions.get_grid_dimensions(7))
|
||||
self.assertTupleEqual((2, 4, 0), Regions.get_grid_dimensions(8))
|
||||
self.assertTupleEqual((3, 3, 0), Regions.get_grid_dimensions(9))
|
||||
self.assertTupleEqual((2, 5, 0), Regions.get_grid_dimensions(10))
|
||||
self.assertTupleEqual((3, 4, 1), Regions.get_grid_dimensions(11))
|
||||
self.assertTupleEqual((3, 4, 0), Regions.get_grid_dimensions(12))
|
||||
self.assertTupleEqual((3, 5, 0), Regions.get_grid_dimensions(15))
|
||||
self.assertTupleEqual((4, 4, 0), Regions.get_grid_dimensions(16))
|
||||
self.assertTupleEqual((4, 6, 0), Regions.get_grid_dimensions(24))
|
||||
self.assertTupleEqual((5, 5, 0), Regions.get_grid_dimensions(25))
|
||||
self.assertTupleEqual((5, 6, 1), Regions.get_grid_dimensions(29))
|
||||
self.assertTupleEqual((5, 7, 2), Regions.get_grid_dimensions(33))
|
||||
|
||||
|
||||
class TestGridGeneration(Sc2TestBase):
|
||||
options = {
|
||||
"mission_order": Options.MissionOrder.option_grid,
|
||||
"excluded_missions": [MissionTables.SC2Mission.ZERO_HOUR.mission_name,],
|
||||
"enable_hots_missions": False,
|
||||
"enable_prophecy_missions": True,
|
||||
"enable_lotv_prologue_missions": False,
|
||||
"enable_lotv_missions": False,
|
||||
"enable_epilogue_missions": False,
|
||||
"enable_nco_missions": False
|
||||
}
|
||||
|
||||
def test_size_matches_exclusions(self):
|
||||
self.assertNotIn(MissionTables.SC2Mission.ZERO_HOUR.mission_name, self.multiworld.regions)
|
||||
# WoL has 29 missions. -1 for Zero Hour being excluded, +1 for the automatically-added menu location
|
||||
self.assertEqual(len(self.multiworld.regions), 29)
|
||||
@@ -1,11 +1,52 @@
|
||||
from typing import *
|
||||
import unittest
|
||||
import random
|
||||
from argparse import Namespace
|
||||
from BaseClasses import MultiWorld, CollectionState, PlandoOptions
|
||||
from Generate import get_seed_name
|
||||
from worlds import AutoWorld
|
||||
from test.general import gen_steps, call_all
|
||||
|
||||
from test.TestBase import WorldTestBase
|
||||
from test.bases import WorldTestBase
|
||||
from .. import SC2World
|
||||
from .. import Client
|
||||
from .. import client
|
||||
|
||||
class Sc2TestBase(WorldTestBase):
|
||||
game = Client.SC2Context.game
|
||||
game = client.SC2Context.game
|
||||
world: SC2World
|
||||
player: ClassVar[int] = 1
|
||||
skip_long_tests: bool = True
|
||||
|
||||
|
||||
class Sc2SetupTestBase(unittest.TestCase):
|
||||
"""
|
||||
A custom sc2-specific test base class that provides an explicit function to generate the world from options.
|
||||
This allows potentially generating multiple worlds in one test case, useful for tracking down a rare / sporadic
|
||||
crash.
|
||||
"""
|
||||
seed: Optional[int] = None
|
||||
game = SC2World.game
|
||||
player = 1
|
||||
def generate_world(self, options: Dict[str, Any]) -> None:
|
||||
self.multiworld = MultiWorld(1)
|
||||
self.multiworld.game[self.player] = self.game
|
||||
self.multiworld.player_name = {self.player: "Tester"}
|
||||
self.multiworld.set_seed(self.seed)
|
||||
random.seed(self.multiworld.seed)
|
||||
self.multiworld.seed_name = get_seed_name(random) # only called to get same RNG progression as Generate.py
|
||||
args = Namespace()
|
||||
for name, option in AutoWorld.AutoWorldRegister.world_types[self.game].options_dataclass.type_hints.items():
|
||||
new_option = option.from_any(options.get(name, option.default))
|
||||
new_option.verify(SC2World, "Tester", PlandoOptions.items|PlandoOptions.connections|PlandoOptions.texts|PlandoOptions.bosses)
|
||||
setattr(args, name, {
|
||||
1: new_option
|
||||
})
|
||||
self.multiworld.set_options(args)
|
||||
self.world: SC2World = cast(SC2World, self.multiworld.worlds[self.player])
|
||||
self.multiworld.state = CollectionState(self.multiworld)
|
||||
try:
|
||||
for step in gen_steps:
|
||||
call_all(self.multiworld, step)
|
||||
except Exception as ex:
|
||||
ex.add_note(f"Seed: {self.multiworld.seed}")
|
||||
raise
|
||||
|
||||
216
worlds/sc2/test/test_custom_mission_orders.py
Normal file
216
worlds/sc2/test/test_custom_mission_orders.py
Normal file
@@ -0,0 +1,216 @@
|
||||
"""
|
||||
Unit tests for custom mission orders
|
||||
"""
|
||||
|
||||
from .test_base import Sc2SetupTestBase
|
||||
from .. import MissionFlag
|
||||
from ..item import item_tables, item_names
|
||||
from BaseClasses import ItemClassification
|
||||
|
||||
class TestCustomMissionOrders(Sc2SetupTestBase):
|
||||
def test_mini_wol_generates(self):
|
||||
world_options = {
|
||||
'mission_order': 'custom',
|
||||
'custom_mission_order': {
|
||||
'Mini Wings of Liberty': {
|
||||
'global': {
|
||||
'type': 'column',
|
||||
'mission_pool': [
|
||||
'terran missions',
|
||||
'^ wol missions'
|
||||
]
|
||||
},
|
||||
'Mar Sara': {
|
||||
'size': 1
|
||||
},
|
||||
'Colonist': {
|
||||
'size': 2,
|
||||
'entry_rules': [{
|
||||
'scope': '../Mar Sara'
|
||||
}]
|
||||
},
|
||||
'Artifact': {
|
||||
'size': 3,
|
||||
'entry_rules': [{
|
||||
'scope': '../Mar Sara'
|
||||
}],
|
||||
'missions': [
|
||||
{
|
||||
'index': 1,
|
||||
'entry_rules': [{
|
||||
'scope': 'Mini Wings of Liberty',
|
||||
'amount': 4
|
||||
}]
|
||||
},
|
||||
{
|
||||
'index': 2,
|
||||
'entry_rules': [{
|
||||
'scope': 'Mini Wings of Liberty',
|
||||
'amount': 8
|
||||
}]
|
||||
}
|
||||
]
|
||||
},
|
||||
'Prophecy': {
|
||||
'size': 2,
|
||||
'entry_rules': [{
|
||||
'scope': '../Artifact/1'
|
||||
}],
|
||||
'mission_pool': [
|
||||
'protoss missions',
|
||||
'^ prophecy missions'
|
||||
]
|
||||
},
|
||||
'Covert': {
|
||||
'size': 2,
|
||||
'entry_rules': [{
|
||||
'scope': 'Mini Wings of Liberty',
|
||||
'amount': 2
|
||||
}]
|
||||
},
|
||||
'Rebellion': {
|
||||
'size': 2,
|
||||
'entry_rules': [{
|
||||
'scope': 'Mini Wings of Liberty',
|
||||
'amount': 3
|
||||
}]
|
||||
},
|
||||
'Char': {
|
||||
'size': 3,
|
||||
'entry_rules': [{
|
||||
'scope': '../Artifact/2'
|
||||
}],
|
||||
'missions': [
|
||||
{
|
||||
'index': 0,
|
||||
'next': [2]
|
||||
},
|
||||
{
|
||||
'index': 1,
|
||||
'entrance': True
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.generate_world(world_options)
|
||||
flags = self.world.custom_mission_order.get_used_flags()
|
||||
self.assertEqual(flags[MissionFlag.Terran], 13)
|
||||
self.assertEqual(flags[MissionFlag.Protoss], 2)
|
||||
self.assertEqual(flags.get(MissionFlag.Zerg, 0), 0)
|
||||
sc2_regions = set(self.multiworld.regions.region_cache[self.player]) - {"Menu"}
|
||||
self.assertEqual(len(self.world.custom_mission_order.get_used_missions()), len(sc2_regions))
|
||||
|
||||
def test_locked_and_necessary_item_appears_once(self):
|
||||
# This is a filler upgrade with a parent
|
||||
test_item = item_names.MARINE_OPTIMIZED_LOGISTICS
|
||||
world_options = {
|
||||
'mission_order': 'custom',
|
||||
'locked_items': { test_item: 1 },
|
||||
'custom_mission_order': {
|
||||
'test': {
|
||||
'type': 'column',
|
||||
'size': 5, # Give the generator some space to place the key
|
||||
'max_difficulty': 'easy',
|
||||
'missions': [{
|
||||
'index': 4,
|
||||
'entry_rules': [{
|
||||
'items': { test_item: 1 }
|
||||
}]
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.assertNotEqual(item_tables.item_table[test_item].classification, ItemClassification.progression, f"Test item {test_item} won't change classification")
|
||||
|
||||
self.generate_world(world_options)
|
||||
test_items_in_pool = [item for item in self.multiworld.itempool if item.name == test_item]
|
||||
test_items_in_pool += [item for item in self.multiworld.precollected_items[self.player] if item.name == test_item]
|
||||
self.assertEqual(len(test_items_in_pool), 1)
|
||||
self.assertEqual(test_items_in_pool[0].classification, ItemClassification.progression)
|
||||
|
||||
def test_start_inventory_and_necessary_item_appears_once(self):
|
||||
# This is a filler upgrade with a parent
|
||||
test_item = item_names.ZERGLING_METABOLIC_BOOST
|
||||
world_options = {
|
||||
'mission_order': 'custom',
|
||||
'start_inventory': { test_item: 1 },
|
||||
'custom_mission_order': {
|
||||
'test': {
|
||||
'type': 'column',
|
||||
'size': 5, # Give the generator some space to place the key
|
||||
'max_difficulty': 'easy',
|
||||
'missions': [{
|
||||
'index': 4,
|
||||
'entry_rules': [{
|
||||
'items': { test_item: 1 }
|
||||
}]
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.generate_world(world_options)
|
||||
test_items_in_pool = [item for item in self.multiworld.itempool if item.name == test_item]
|
||||
self.assertEqual(len(test_items_in_pool), 0)
|
||||
test_items_in_start_inventory = [item for item in self.multiworld.precollected_items[self.player] if item.name == test_item]
|
||||
self.assertEqual(len(test_items_in_start_inventory), 1)
|
||||
|
||||
def test_start_inventory_and_locked_and_necessary_item_appears_once(self):
|
||||
# This is a filler upgrade with a parent
|
||||
test_item = item_names.ZERGLING_METABOLIC_BOOST
|
||||
world_options = {
|
||||
'mission_order': 'custom',
|
||||
'start_inventory': { test_item: 1 },
|
||||
'locked_items': { test_item: 1 },
|
||||
'custom_mission_order': {
|
||||
'test': {
|
||||
'type': 'column',
|
||||
'size': 5, # Give the generator some space to place the key
|
||||
'max_difficulty': 'easy',
|
||||
'missions': [{
|
||||
'index': 4,
|
||||
'entry_rules': [{
|
||||
'items': { test_item: 1 }
|
||||
}]
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.generate_world(world_options)
|
||||
test_items_in_pool = [item for item in self.multiworld.itempool if item.name == test_item]
|
||||
self.assertEqual(len(test_items_in_pool), 0)
|
||||
test_items_in_start_inventory = [item for item in self.multiworld.precollected_items[self.player] if item.name == test_item]
|
||||
self.assertEqual(len(test_items_in_start_inventory), 1)
|
||||
|
||||
def test_key_item_rule_creates_correct_item_amount(self):
|
||||
# This is an item that normally only exists once
|
||||
test_item = item_names.ZERGLING
|
||||
test_amount = 3
|
||||
world_options = {
|
||||
'mission_order': 'custom',
|
||||
'locked_items': { test_item: 1 }, # Make sure it is generated as normal
|
||||
'custom_mission_order': {
|
||||
'test': {
|
||||
'type': 'column',
|
||||
'size': 12, # Give the generator some space to place the keys
|
||||
'max_difficulty': 'easy',
|
||||
'mission_pool': ['zerg missions'], # Make sure the item isn't excluded by race selection
|
||||
'missions': [{
|
||||
'index': 10,
|
||||
'entry_rules': [{
|
||||
'items': { test_item: test_amount } # Require more than the usual item amount
|
||||
}]
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.generate_world(world_options)
|
||||
test_items_in_pool = [item for item in self.multiworld.itempool if item.name == test_item]
|
||||
test_items_in_start_inventory = [item for item in self.multiworld.precollected_items[self.player] if item.name == test_item]
|
||||
self.assertEqual(len(test_items_in_pool + test_items_in_start_inventory), test_amount)
|
||||
1228
worlds/sc2/test/test_generation.py
Normal file
1228
worlds/sc2/test/test_generation.py
Normal file
File diff suppressed because it is too large
Load Diff
88
worlds/sc2/test/test_item_filtering.py
Normal file
88
worlds/sc2/test/test_item_filtering.py
Normal file
@@ -0,0 +1,88 @@
|
||||
"""
|
||||
Unit tests for item filtering like pool_filter.py
|
||||
"""
|
||||
|
||||
from .test_base import Sc2SetupTestBase
|
||||
from ..item import item_groups, item_names
|
||||
from .. import options
|
||||
from ..mission_tables import SC2Race
|
||||
|
||||
class ItemFilterTests(Sc2SetupTestBase):
|
||||
def test_excluding_all_barracks_units_excludes_infantry_upgrades(self) -> None:
|
||||
world_options = {
|
||||
'excluded_items': {
|
||||
item_groups.ItemGroupNames.BARRACKS_UNITS: 0
|
||||
},
|
||||
'required_tactics': 'standard',
|
||||
'min_number_of_upgrades': 1,
|
||||
'selected_races': {
|
||||
SC2Race.TERRAN.get_title()
|
||||
},
|
||||
'mission_order': 'grid',
|
||||
}
|
||||
self.generate_world(world_options)
|
||||
self.assertTrue(self.multiworld.itempool)
|
||||
races = {mission.race for mission in self.world.custom_mission_order.get_used_missions()}
|
||||
self.assertIn(SC2Race.TERRAN, races)
|
||||
self.assertNotIn(SC2Race.ZERG, races)
|
||||
self.assertNotIn(SC2Race.PROTOSS, races)
|
||||
itempool = [item.name for item in self.multiworld.itempool]
|
||||
self.assertNotIn(item_names.MARINE, itempool)
|
||||
self.assertNotIn(item_names.MARAUDER, itempool)
|
||||
|
||||
self.assertNotIn(item_names.PROGRESSIVE_TERRAN_INFANTRY_WEAPON, itempool)
|
||||
self.assertNotIn(item_names.PROGRESSIVE_TERRAN_INFANTRY_ARMOR, itempool)
|
||||
self.assertNotIn(item_names.PROGRESSIVE_TERRAN_INFANTRY_UPGRADE, itempool)
|
||||
|
||||
def test_excluding_one_item_of_multi_parent_doesnt_filter_children(self) -> None:
|
||||
world_options = {
|
||||
'locked_items': {
|
||||
item_names.SENTINEL: 1,
|
||||
item_names.CENTURION: 1,
|
||||
},
|
||||
'excluded_items': {
|
||||
item_names.ZEALOT: 1,
|
||||
# Exclude more items to make space
|
||||
item_names.WRATHWALKER: 1,
|
||||
item_names.ENERGIZER: 1,
|
||||
item_names.AVENGER: 1,
|
||||
item_names.ARBITER: 1,
|
||||
item_names.VOID_RAY: 1,
|
||||
item_names.PULSAR: 1,
|
||||
item_names.DESTROYER: 1,
|
||||
item_names.DAWNBRINGER: 1,
|
||||
},
|
||||
'min_number_of_upgrades': 2,
|
||||
'required_tactics': 'standard',
|
||||
'selected_races': {
|
||||
SC2Race.PROTOSS.get_title()
|
||||
},
|
||||
'mission_order': 'grid',
|
||||
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
|
||||
}
|
||||
self.generate_world(world_options)
|
||||
self.assertTrue(self.multiworld.itempool)
|
||||
itempool = [item.name for item in self.multiworld.itempool]
|
||||
self.assertIn(item_names.ZEALOT_SENTINEL_CENTURION_SHIELD_CAPACITY, itempool)
|
||||
self.assertIn(item_names.ZEALOT_SENTINEL_CENTURION_LEG_ENHANCEMENTS, itempool)
|
||||
|
||||
def test_excluding_all_items_in_multiparent_excludes_child_items(self) -> None:
|
||||
world_options = {
|
||||
'excluded_items': {
|
||||
item_names.ZEALOT: 1,
|
||||
item_names.SENTINEL: 1,
|
||||
item_names.CENTURION: 1,
|
||||
},
|
||||
'min_number_of_upgrades': 2,
|
||||
'required_tactics': 'standard',
|
||||
'selected_races': {
|
||||
SC2Race.PROTOSS.get_title()
|
||||
},
|
||||
'mission_order': 'grid',
|
||||
}
|
||||
self.generate_world(world_options)
|
||||
self.assertTrue(self.multiworld.itempool)
|
||||
itempool = [item.name for item in self.multiworld.itempool]
|
||||
self.assertNotIn(item_names.ZEALOT_SENTINEL_CENTURION_SHIELD_CAPACITY, itempool)
|
||||
self.assertNotIn(item_names.ZEALOT_SENTINEL_CENTURION_LEG_ENHANCEMENTS, itempool)
|
||||
|
||||
18
worlds/sc2/test/test_itemdescriptions.py
Normal file
18
worlds/sc2/test/test_itemdescriptions.py
Normal file
@@ -0,0 +1,18 @@
|
||||
import unittest
|
||||
|
||||
from ..item import item_descriptions, item_tables
|
||||
|
||||
|
||||
class TestItemDescriptions(unittest.TestCase):
|
||||
def test_all_items_have_description(self) -> None:
|
||||
for item_name in item_tables.item_table:
|
||||
self.assertIn(item_name, item_descriptions.item_descriptions)
|
||||
|
||||
def test_all_descriptions_refer_to_item_and_end_in_dot(self) -> None:
|
||||
for item_name, item_desc in item_descriptions.item_descriptions.items():
|
||||
self.assertIn(item_name, item_tables.item_table)
|
||||
self.assertEqual(item_desc.strip()[-1], '.', msg=f"{item_name}'s item description does not end in a '.': '{item_desc}'")
|
||||
|
||||
def test_item_descriptions_follow_single_space_after_period_style(self) -> None:
|
||||
for item_name, item_desc in item_descriptions.item_descriptions.items():
|
||||
self.assertNotIn('. ', item_desc, f"Double-space after period in description for {item_name}")
|
||||
32
worlds/sc2/test/test_itemgroups.py
Normal file
32
worlds/sc2/test/test_itemgroups.py
Normal file
@@ -0,0 +1,32 @@
|
||||
"""
|
||||
Unit tests for item_groups.py
|
||||
"""
|
||||
|
||||
import unittest
|
||||
from ..item import item_groups, item_tables
|
||||
|
||||
|
||||
class ItemGroupsUnitTests(unittest.TestCase):
|
||||
def test_all_production_structure_groups_capture_all_units(self) -> None:
|
||||
self.assertCountEqual(
|
||||
item_groups.terran_units,
|
||||
item_groups.barracks_units + item_groups.factory_units + item_groups.starport_units + item_groups.terran_mercenaries
|
||||
)
|
||||
self.assertCountEqual(
|
||||
item_groups.protoss_units,
|
||||
item_groups.gateway_units + item_groups.robo_units + item_groups.stargate_units
|
||||
)
|
||||
|
||||
def test_terran_original_progressive_group_fully_contained_in_wol_upgrades(self) -> None:
|
||||
for item_name in item_groups.terran_original_progressive_upgrades:
|
||||
self.assertIn(item_tables.item_table[item_name].type, (
|
||||
item_tables.TerranItemType.Progressive, item_tables.TerranItemType.Progressive_2), f"{item_name} is not progressive")
|
||||
self.assertIn(item_name, item_groups.wol_upgrades)
|
||||
|
||||
def test_all_items_in_stimpack_group_are_stimpacks(self) -> None:
|
||||
for item_name in item_groups.terran_stimpacks:
|
||||
self.assertIn("Stimpack", item_name)
|
||||
|
||||
def test_all_item_group_names_have_a_group_defined(self) -> None:
|
||||
for display_name in item_groups.ItemGroupNames.get_all_group_names():
|
||||
self.assertIn(display_name, item_groups.item_name_groups)
|
||||
170
worlds/sc2/test/test_items.py
Normal file
170
worlds/sc2/test/test_items.py
Normal file
@@ -0,0 +1,170 @@
|
||||
import unittest
|
||||
from typing import List, Set
|
||||
|
||||
from ..item import item_tables
|
||||
|
||||
|
||||
class TestItems(unittest.TestCase):
|
||||
def test_grouped_upgrades_number(self) -> None:
|
||||
"""
|
||||
Tests if grouped upgrades have set number correctly
|
||||
"""
|
||||
bundled_items = item_tables.upgrade_bundles.keys()
|
||||
bundled_item_data = [item_tables.get_full_item_list()[item_name] for item_name in bundled_items]
|
||||
bundled_item_numbers = [item_data.number for item_data in bundled_item_data]
|
||||
|
||||
check_numbers = [number == -1 for number in bundled_item_numbers]
|
||||
|
||||
self.assertNotIn(False, check_numbers)
|
||||
|
||||
def test_non_grouped_upgrades_number(self) -> None:
|
||||
"""
|
||||
Checks if non-grouped upgrades number is set correctly thus can be sent into the game.
|
||||
"""
|
||||
check_modulo = 4
|
||||
bundled_items = item_tables.upgrade_bundles.keys()
|
||||
non_bundled_upgrades = [
|
||||
item_name for item_name in item_tables.get_full_item_list().keys()
|
||||
if (item_name not in bundled_items
|
||||
and item_tables.get_full_item_list()[item_name].type in item_tables.upgrade_item_types)
|
||||
]
|
||||
non_bundled_upgrade_data = [item_tables.get_full_item_list()[item_name] for item_name in non_bundled_upgrades]
|
||||
non_bundled_upgrade_numbers = [item_data.number for item_data in non_bundled_upgrade_data]
|
||||
|
||||
check_numbers = [number % check_modulo == 0 for number in non_bundled_upgrade_numbers]
|
||||
|
||||
self.assertNotIn(False, check_numbers)
|
||||
|
||||
def test_bundles_contain_only_basic_elements(self) -> None:
|
||||
"""
|
||||
Checks if there are no bundles within bundles.
|
||||
"""
|
||||
bundled_items = item_tables.upgrade_bundles.keys()
|
||||
bundle_elements: List[str] = [item_name for values in item_tables.upgrade_bundles.values() for item_name in values]
|
||||
|
||||
for element in bundle_elements:
|
||||
self.assertNotIn(element, bundled_items)
|
||||
|
||||
def test_weapon_armor_level(self) -> None:
|
||||
"""
|
||||
Checks if Weapon/Armor upgrade level is correctly set to all Weapon/Armor upgrade items.
|
||||
"""
|
||||
weapon_armor_upgrades = [item for item in item_tables.get_full_item_list() if item_tables.get_item_table()[item].type in item_tables.upgrade_item_types]
|
||||
|
||||
for weapon_armor_upgrade in weapon_armor_upgrades:
|
||||
self.assertEqual(item_tables.get_full_item_list()[weapon_armor_upgrade].quantity, item_tables.WEAPON_ARMOR_UPGRADE_MAX_LEVEL)
|
||||
|
||||
def test_item_ids_distinct(self) -> None:
|
||||
"""
|
||||
Verifies if there are no duplicates of item ID.
|
||||
"""
|
||||
item_ids: Set[int] = {item_tables.get_full_item_list()[item_name].code for item_name in item_tables.get_full_item_list()}
|
||||
|
||||
self.assertEqual(len(item_ids), len(item_tables.get_full_item_list()))
|
||||
|
||||
def test_number_distinct_in_item_type(self) -> None:
|
||||
"""
|
||||
Tests if each item is distinct for sending into the mod.
|
||||
"""
|
||||
item_types: List[item_tables.ItemTypeEnum] = [
|
||||
*[item.value for item in item_tables.TerranItemType],
|
||||
*[item.value for item in item_tables.ZergItemType],
|
||||
*[item.value for item in item_tables.ProtossItemType],
|
||||
*[item.value for item in item_tables.FactionlessItemType]
|
||||
]
|
||||
|
||||
self.assertGreater(len(item_types), 0)
|
||||
|
||||
for item_type in item_types:
|
||||
item_names: List[str] = [
|
||||
item_name for item_name in item_tables.get_full_item_list()
|
||||
if item_tables.get_full_item_list()[item_name].number >= 0 # Negative numbers have special meaning
|
||||
and item_tables.get_full_item_list()[item_name].type == item_type
|
||||
]
|
||||
item_numbers: Set[int] = {item_tables.get_full_item_list()[item_name] for item_name in item_names}
|
||||
|
||||
self.assertEqual(len(item_names), len(item_numbers))
|
||||
|
||||
def test_progressive_has_quantity(self) -> None:
|
||||
"""
|
||||
:return:
|
||||
"""
|
||||
progressive_groups: List[item_tables.ItemTypeEnum] = [
|
||||
item_tables.TerranItemType.Progressive,
|
||||
item_tables.TerranItemType.Progressive_2,
|
||||
item_tables.ProtossItemType.Progressive,
|
||||
item_tables.ZergItemType.Progressive
|
||||
]
|
||||
|
||||
quantities: List[int] = [
|
||||
item_tables.get_full_item_list()[item].quantity for item in item_tables.get_full_item_list()
|
||||
if item_tables.get_full_item_list()[item].type in progressive_groups
|
||||
]
|
||||
|
||||
self.assertNotIn(1, quantities)
|
||||
|
||||
def test_non_progressive_quantity(self) -> None:
|
||||
"""
|
||||
Check if non-progressive items have quantity at most 1.
|
||||
"""
|
||||
non_progressive_single_entity_groups: List[item_tables.ItemTypeEnum] = [
|
||||
# Terran
|
||||
item_tables.TerranItemType.Unit,
|
||||
item_tables.TerranItemType.Unit_2,
|
||||
item_tables.TerranItemType.Mercenary,
|
||||
item_tables.TerranItemType.Armory_1,
|
||||
item_tables.TerranItemType.Armory_2,
|
||||
item_tables.TerranItemType.Armory_3,
|
||||
item_tables.TerranItemType.Armory_4,
|
||||
item_tables.TerranItemType.Armory_5,
|
||||
item_tables.TerranItemType.Armory_6,
|
||||
item_tables.TerranItemType.Armory_7,
|
||||
item_tables.TerranItemType.Building,
|
||||
item_tables.TerranItemType.Laboratory,
|
||||
item_tables.TerranItemType.Nova_Gear,
|
||||
# Zerg
|
||||
item_tables.ZergItemType.Unit,
|
||||
item_tables.ZergItemType.Mercenary,
|
||||
item_tables.ZergItemType.Morph,
|
||||
item_tables.ZergItemType.Strain,
|
||||
item_tables.ZergItemType.Mutation_1,
|
||||
item_tables.ZergItemType.Mutation_2,
|
||||
item_tables.ZergItemType.Mutation_3,
|
||||
item_tables.ZergItemType.Evolution_Pit,
|
||||
item_tables.ZergItemType.Ability,
|
||||
# Protoss
|
||||
item_tables.ProtossItemType.Unit,
|
||||
item_tables.ProtossItemType.Unit_2,
|
||||
item_tables.ProtossItemType.Building,
|
||||
item_tables.ProtossItemType.Forge_1,
|
||||
item_tables.ProtossItemType.Forge_2,
|
||||
item_tables.ProtossItemType.Forge_3,
|
||||
item_tables.ProtossItemType.Forge_4,
|
||||
item_tables.ProtossItemType.Solarite_Core,
|
||||
item_tables.ProtossItemType.Spear_Of_Adun
|
||||
]
|
||||
|
||||
quantities: List[int] = [
|
||||
item_tables.get_full_item_list()[item].quantity for item in item_tables.get_full_item_list()
|
||||
if item_tables.get_full_item_list()[item].type in non_progressive_single_entity_groups
|
||||
]
|
||||
|
||||
for quantity in quantities:
|
||||
self.assertLessEqual(quantity, 1)
|
||||
|
||||
def test_item_number_less_than_30(self) -> None:
|
||||
"""
|
||||
Checks if all item numbers are within bounds supported by game mod.
|
||||
"""
|
||||
not_checked_item_types: List[item_tables.ItemTypeEnum] = [
|
||||
item_tables.ZergItemType.Level
|
||||
]
|
||||
items_to_check: List[str] = [
|
||||
item for item in item_tables.get_full_item_list()
|
||||
if item_tables.get_full_item_list()[item].type not in not_checked_item_types
|
||||
]
|
||||
|
||||
for item in items_to_check:
|
||||
item_number = item_tables.get_full_item_list()[item].number
|
||||
self.assertLess(item_number, 30)
|
||||
|
||||
37
worlds/sc2/test/test_location_groups.py
Normal file
37
worlds/sc2/test/test_location_groups.py
Normal file
@@ -0,0 +1,37 @@
|
||||
import unittest
|
||||
from .. import location_groups
|
||||
from ..mission_tables import SC2Mission, MissionFlag
|
||||
|
||||
|
||||
class TestLocationGroups(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
cls.location_groups = location_groups.get_location_groups()
|
||||
|
||||
def test_location_categories_have_a_group(self) -> None:
|
||||
self.assertIn('Victory', self.location_groups)
|
||||
self.assertIn(f'{SC2Mission.LIBERATION_DAY.mission_name}: Victory', self.location_groups['Victory'])
|
||||
self.assertIn(f'{SC2Mission.IN_UTTER_DARKNESS.mission_name}: Defeat', self.location_groups['Victory'])
|
||||
self.assertIn('Vanilla', self.location_groups)
|
||||
self.assertIn(f'{SC2Mission.WELCOME_TO_THE_JUNGLE.mission_name}: Close Relic', self.location_groups['Vanilla'])
|
||||
self.assertIn('Extra', self.location_groups)
|
||||
self.assertIn(f'{SC2Mission.SMASH_AND_GRAB.mission_name}: First Forcefield Area Busted', self.location_groups['Extra'])
|
||||
self.assertIn('Challenge', self.location_groups)
|
||||
self.assertIn(f'{SC2Mission.ZERO_HOUR.mission_name}: First Hatchery', self.location_groups['Challenge'])
|
||||
self.assertIn('Mastery', self.location_groups)
|
||||
self.assertIn(f'{SC2Mission.WELCOME_TO_THE_JUNGLE.mission_name}: Protoss Cleared', self.location_groups['Mastery'])
|
||||
|
||||
def test_missions_have_a_group(self) -> None:
|
||||
self.assertIn(SC2Mission.LIBERATION_DAY.mission_name, self.location_groups)
|
||||
self.assertIn(f'{SC2Mission.LIBERATION_DAY.mission_name}: Victory', self.location_groups[SC2Mission.LIBERATION_DAY.mission_name])
|
||||
self.assertIn(f'{SC2Mission.LIBERATION_DAY.mission_name}: Special Delivery', self.location_groups[SC2Mission.LIBERATION_DAY.mission_name])
|
||||
|
||||
def test_race_swapped_locations_share_a_group(self) -> None:
|
||||
self.assertIn(MissionFlag.HasRaceSwap, SC2Mission.ZERO_HOUR.flags)
|
||||
ZERO_HOUR = 'Zero Hour'
|
||||
self.assertNotEqual(ZERO_HOUR, SC2Mission.ZERO_HOUR.mission_name)
|
||||
self.assertIn(ZERO_HOUR, self.location_groups)
|
||||
self.assertIn(f'{ZERO_HOUR}: Victory', self.location_groups)
|
||||
self.assertIn(f'{SC2Mission.ZERO_HOUR.mission_name}: Victory', self.location_groups[f'{ZERO_HOUR}: Victory'])
|
||||
self.assertIn(f'{SC2Mission.ZERO_HOUR_P.mission_name}: Victory', self.location_groups[f'{ZERO_HOUR}: Victory'])
|
||||
self.assertIn(f'{SC2Mission.ZERO_HOUR_Z.mission_name}: Victory', self.location_groups[f'{ZERO_HOUR}: Victory'])
|
||||
9
worlds/sc2/test/test_mission_groups.py
Normal file
9
worlds/sc2/test/test_mission_groups.py
Normal file
@@ -0,0 +1,9 @@
|
||||
import unittest
|
||||
from .. import mission_groups
|
||||
|
||||
|
||||
class TestMissionGroups(unittest.TestCase):
|
||||
def test_all_mission_groups_are_defined_and_nonempty(self) -> None:
|
||||
for mission_group_name in mission_groups.MissionGroupNames.get_all_group_names():
|
||||
self.assertIn(mission_group_name, mission_groups.mission_groups)
|
||||
self.assertTrue(mission_groups.mission_groups[mission_group_name])
|
||||
@@ -1,7 +1,19 @@
|
||||
import unittest
|
||||
from .test_base import Sc2TestBase
|
||||
from .. import Options, MissionTables
|
||||
from typing import Dict
|
||||
|
||||
from .. import options
|
||||
from ..item import item_parents
|
||||
|
||||
|
||||
class TestOptions(unittest.TestCase):
|
||||
def test_campaign_size_option_max_matches_number_of_missions(self):
|
||||
self.assertEqual(Options.MaximumCampaignSize.range_end, len(MissionTables.SC2Mission))
|
||||
|
||||
def test_unit_max_upgrades_matching_items(self) -> None:
|
||||
upgrade_group_to_count: Dict[str, int] = {}
|
||||
for parent_id, child_list in item_parents.parent_id_to_children.items():
|
||||
main_parent = item_parents.parent_present[parent_id].constraint_group
|
||||
if main_parent is None:
|
||||
continue
|
||||
upgrade_group_to_count.setdefault(main_parent, 0)
|
||||
upgrade_group_to_count[main_parent] += len(child_list)
|
||||
|
||||
self.assertEqual(options.MAX_UPGRADES_OPTION, max(upgrade_group_to_count.values()))
|
||||
|
||||
40
worlds/sc2/test/test_regions.py
Normal file
40
worlds/sc2/test/test_regions.py
Normal file
@@ -0,0 +1,40 @@
|
||||
import unittest
|
||||
from .test_base import Sc2TestBase
|
||||
from .. import mission_tables, SC2Campaign
|
||||
from .. import options
|
||||
from ..mission_order.layout_types import Grid
|
||||
|
||||
class TestGridsizes(unittest.TestCase):
|
||||
def test_grid_sizes_meet_specs(self):
|
||||
self.assertTupleEqual((1, 2, 0), Grid.get_grid_dimensions(2))
|
||||
self.assertTupleEqual((1, 3, 0), Grid.get_grid_dimensions(3))
|
||||
self.assertTupleEqual((2, 2, 0), Grid.get_grid_dimensions(4))
|
||||
self.assertTupleEqual((2, 3, 1), Grid.get_grid_dimensions(5))
|
||||
self.assertTupleEqual((2, 4, 1), Grid.get_grid_dimensions(7))
|
||||
self.assertTupleEqual((2, 4, 0), Grid.get_grid_dimensions(8))
|
||||
self.assertTupleEqual((3, 3, 0), Grid.get_grid_dimensions(9))
|
||||
self.assertTupleEqual((2, 5, 0), Grid.get_grid_dimensions(10))
|
||||
self.assertTupleEqual((3, 4, 1), Grid.get_grid_dimensions(11))
|
||||
self.assertTupleEqual((3, 4, 0), Grid.get_grid_dimensions(12))
|
||||
self.assertTupleEqual((3, 5, 0), Grid.get_grid_dimensions(15))
|
||||
self.assertTupleEqual((4, 4, 0), Grid.get_grid_dimensions(16))
|
||||
self.assertTupleEqual((4, 6, 0), Grid.get_grid_dimensions(24))
|
||||
self.assertTupleEqual((5, 5, 0), Grid.get_grid_dimensions(25))
|
||||
self.assertTupleEqual((5, 6, 1), Grid.get_grid_dimensions(29))
|
||||
self.assertTupleEqual((5, 7, 2), Grid.get_grid_dimensions(33))
|
||||
|
||||
|
||||
class TestGridGeneration(Sc2TestBase):
|
||||
options = {
|
||||
"mission_order": options.MissionOrder.option_grid,
|
||||
"excluded_missions": [mission_tables.SC2Mission.ZERO_HOUR.mission_name,],
|
||||
"enabled_campaigns": {
|
||||
SC2Campaign.WOL.campaign_name,
|
||||
SC2Campaign.PROPHECY.campaign_name,
|
||||
}
|
||||
}
|
||||
|
||||
def test_size_matches_exclusions(self):
|
||||
self.assertNotIn(mission_tables.SC2Mission.ZERO_HOUR.mission_name, self.multiworld.regions)
|
||||
# WoL has 29 missions. -1 for Zero Hour being excluded, +1 for the automatically-added menu location
|
||||
self.assertEqual(len(self.multiworld.regions), 29)
|
||||
186
worlds/sc2/test/test_rules.py
Normal file
186
worlds/sc2/test/test_rules.py
Normal file
@@ -0,0 +1,186 @@
|
||||
import itertools
|
||||
from dataclasses import fields
|
||||
from random import Random
|
||||
import unittest
|
||||
from typing import List, Set, Iterable
|
||||
|
||||
from BaseClasses import ItemClassification, MultiWorld
|
||||
import Options as CoreOptions
|
||||
from .. import options, locations
|
||||
from ..item import item_tables
|
||||
from ..rules import SC2Logic
|
||||
from ..mission_tables import SC2Race, MissionFlag, lookup_name_to_mission
|
||||
|
||||
|
||||
class TestInventory:
|
||||
"""
|
||||
Runs checks against inventory with validation if all target items are progression and returns a random result
|
||||
"""
|
||||
def __init__(self) -> None:
|
||||
self.random: Random = Random()
|
||||
self.progression_types: Set[ItemClassification] = {ItemClassification.progression, ItemClassification.progression_skip_balancing}
|
||||
|
||||
def is_item_progression(self, item: str) -> bool:
|
||||
return item_tables.item_table[item].classification in self.progression_types
|
||||
|
||||
def random_boolean(self):
|
||||
return self.random.choice([True, False])
|
||||
|
||||
def has(self, item: str, player: int, count: int = 1):
|
||||
if not self.is_item_progression(item):
|
||||
raise AssertionError("Logic item {} is not a progression item".format(item))
|
||||
return self.random_boolean()
|
||||
|
||||
def has_any(self, items: Set[str], player: int):
|
||||
non_progression_items = [item for item in items if not self.is_item_progression(item)]
|
||||
if len(non_progression_items) > 0:
|
||||
raise AssertionError("Logic items {} are not progression items".format(non_progression_items))
|
||||
return self.random_boolean()
|
||||
|
||||
def has_all(self, items: Set[str], player: int):
|
||||
return self.has_any(items, player)
|
||||
|
||||
def has_group(self, item_group: str, player: int, count: int = 1):
|
||||
return self.random_boolean()
|
||||
|
||||
def count_group(self, item_name_group: str, player: int) -> int:
|
||||
return self.random.randrange(0, 20)
|
||||
|
||||
def count(self, item: str, player: int) -> int:
|
||||
if not self.is_item_progression(item):
|
||||
raise AssertionError("Item {} is not a progression item".format(item))
|
||||
random_value: int = self.random.randrange(0, 5)
|
||||
if random_value == 4: # 0-3 has a higher chance due to logic rules
|
||||
return self.random.randrange(4, 100)
|
||||
else:
|
||||
return random_value
|
||||
|
||||
def count_from_list(self, items: Iterable[str], player: int) -> int:
|
||||
return sum(self.count(item_name, player) for item_name in items)
|
||||
|
||||
def count_from_list_unique(self, items: Iterable[str], player: int) -> int:
|
||||
return sum(self.count(item_name, player) for item_name in items)
|
||||
|
||||
|
||||
class TestWorld:
|
||||
"""
|
||||
Mock world to simulate different player options for logic rules
|
||||
"""
|
||||
def __init__(self) -> None:
|
||||
defaults = dict()
|
||||
for field in fields(options.Starcraft2Options):
|
||||
field_class = field.type
|
||||
option_name = field.name
|
||||
if isinstance(field_class, str):
|
||||
if field_class in globals():
|
||||
field_class = globals()[field_class]
|
||||
else:
|
||||
field_class = CoreOptions.__dict__[field.type]
|
||||
defaults[option_name] = field_class(options.get_option_value(None, option_name))
|
||||
self.options: options.Starcraft2Options = options.Starcraft2Options(**defaults)
|
||||
|
||||
self.options.mission_order.value = options.MissionOrder.option_vanilla_shuffled
|
||||
|
||||
self.player = 1
|
||||
self.multiworld = MultiWorld(1)
|
||||
|
||||
|
||||
class TestRules(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
self.required_tactics_values: List[int] = [
|
||||
options.RequiredTactics.option_standard, options.RequiredTactics.option_advanced
|
||||
]
|
||||
self.all_in_map_values: List[int] = [
|
||||
options.AllInMap.option_ground, options.AllInMap.option_air
|
||||
]
|
||||
self.take_over_ai_allies_values: List[int] = [
|
||||
options.TakeOverAIAllies.option_true, options.TakeOverAIAllies.option_false
|
||||
]
|
||||
self.kerrigan_presence_values: List[int] = [
|
||||
options.KerriganPresence.option_vanilla, options.KerriganPresence.option_not_present
|
||||
]
|
||||
self.NUM_TEST_RUNS = 100
|
||||
|
||||
@staticmethod
|
||||
def _get_world(
|
||||
required_tactics: int = options.RequiredTactics.default,
|
||||
all_in_map: int = options.AllInMap.default,
|
||||
take_over_ai_allies: int = options.TakeOverAIAllies.default,
|
||||
kerrigan_presence: int = options.KerriganPresence.default,
|
||||
# setting this to everywhere catches one extra logic check for Amon's Fall without missing any
|
||||
spear_of_adun_passive_presence: int = options.SpearOfAdunPassiveAbilityPresence.option_everywhere,
|
||||
) -> TestWorld:
|
||||
test_world = TestWorld()
|
||||
test_world.options.required_tactics.value = required_tactics
|
||||
test_world.options.all_in_map.value = all_in_map
|
||||
test_world.options.take_over_ai_allies.value = take_over_ai_allies
|
||||
test_world.options.kerrigan_presence.value = kerrigan_presence
|
||||
test_world.options.spear_of_adun_passive_ability_presence.value = spear_of_adun_passive_presence
|
||||
test_world.logic = SC2Logic(test_world) # type: ignore
|
||||
return test_world
|
||||
|
||||
def test_items_in_rules_are_progression(self):
|
||||
test_inventory = TestInventory()
|
||||
for option in self.required_tactics_values:
|
||||
test_world = self._get_world(required_tactics=option)
|
||||
location_data = locations.get_locations(test_world)
|
||||
for location in location_data:
|
||||
for _ in range(self.NUM_TEST_RUNS):
|
||||
location.rule(test_inventory)
|
||||
|
||||
def test_items_in_all_in_are_progression(self):
|
||||
test_inventory = TestInventory()
|
||||
for test_options in itertools.product(self.required_tactics_values, self.all_in_map_values):
|
||||
test_world = self._get_world(required_tactics=test_options[0], all_in_map=test_options[1])
|
||||
for location in locations.get_locations(test_world):
|
||||
if 'All-In' not in location.region:
|
||||
continue
|
||||
for _ in range(self.NUM_TEST_RUNS):
|
||||
location.rule(test_inventory)
|
||||
|
||||
def test_items_in_kerriganless_missions_are_progression(self):
|
||||
test_inventory = TestInventory()
|
||||
for test_options in itertools.product(self.required_tactics_values, self.kerrigan_presence_values):
|
||||
test_world = self._get_world(required_tactics=test_options[0], kerrigan_presence=test_options[1])
|
||||
for location in locations.get_locations(test_world):
|
||||
mission = lookup_name_to_mission[location.region]
|
||||
if MissionFlag.Kerrigan not in mission.flags:
|
||||
continue
|
||||
for _ in range(self.NUM_TEST_RUNS):
|
||||
location.rule(test_inventory)
|
||||
|
||||
def test_items_in_ai_takeover_missions_are_progression(self):
|
||||
test_inventory = TestInventory()
|
||||
for test_options in itertools.product(self.required_tactics_values, self.take_over_ai_allies_values):
|
||||
test_world = self._get_world(required_tactics=test_options[0], take_over_ai_allies=test_options[1])
|
||||
for location in locations.get_locations(test_world):
|
||||
mission = lookup_name_to_mission[location.region]
|
||||
if MissionFlag.AiAlly not in mission.flags:
|
||||
continue
|
||||
for _ in range(self.NUM_TEST_RUNS):
|
||||
location.rule(test_inventory)
|
||||
|
||||
def test_items_in_hard_rules_are_progression(self):
|
||||
test_inventory = TestInventory()
|
||||
test_world = TestWorld()
|
||||
test_world.options.required_tactics.value = options.RequiredTactics.option_any_units
|
||||
test_world.logic = SC2Logic(test_world)
|
||||
location_data = locations.get_locations(test_world)
|
||||
for location in location_data:
|
||||
if location.hard_rule is not None:
|
||||
for _ in range(10):
|
||||
location.hard_rule(test_inventory)
|
||||
|
||||
def test_items_in_any_units_rules_are_progression(self):
|
||||
test_inventory = TestInventory()
|
||||
test_world = TestWorld()
|
||||
test_world.options.required_tactics.value = options.RequiredTactics.option_any_units
|
||||
logic = SC2Logic(test_world)
|
||||
test_world.logic = logic
|
||||
for race in (SC2Race.TERRAN, SC2Race.PROTOSS, SC2Race.ZERG):
|
||||
for target in range(1, 5):
|
||||
rule = logic.has_race_units(target, race)
|
||||
for _ in range(10):
|
||||
rule(test_inventory)
|
||||
|
||||
|
||||
492
worlds/sc2/test/test_usecases.py
Normal file
492
worlds/sc2/test/test_usecases.py
Normal file
@@ -0,0 +1,492 @@
|
||||
"""
|
||||
Unit tests for yaml usecases we want to support
|
||||
"""
|
||||
|
||||
from .test_base import Sc2SetupTestBase
|
||||
from .. import get_all_missions, mission_tables, options
|
||||
from ..item import item_groups, item_tables, item_names
|
||||
from ..mission_tables import SC2Race, SC2Mission, SC2Campaign, MissionFlag
|
||||
from ..options import EnabledCampaigns, MasteryLocations
|
||||
|
||||
|
||||
class TestSupportedUseCases(Sc2SetupTestBase):
|
||||
def test_vanilla_all_campaigns_generates(self) -> None:
|
||||
world_options = {
|
||||
'mission_order': options.MissionOrder.option_vanilla,
|
||||
'enabled_campaigns': EnabledCampaigns.valid_keys,
|
||||
}
|
||||
|
||||
self.generate_world(world_options)
|
||||
world_regions = [region.name for region in self.multiworld.regions if region.name != "Menu"]
|
||||
|
||||
self.assertEqual(len(world_regions), 83, "Unexpected number of missions for vanilla mission order")
|
||||
|
||||
def test_terran_with_nco_units_only_generates(self):
|
||||
world_options = {
|
||||
'enabled_campaigns': {
|
||||
SC2Campaign.WOL.campaign_name,
|
||||
SC2Campaign.NCO.campaign_name
|
||||
},
|
||||
'excluded_items': {
|
||||
item_groups.ItemGroupNames.TERRAN_UNITS: 0,
|
||||
},
|
||||
'unexcluded_items': {
|
||||
item_groups.ItemGroupNames.NCO_UNITS: 0,
|
||||
},
|
||||
'max_number_of_upgrades': 2,
|
||||
}
|
||||
|
||||
self.generate_world(world_options)
|
||||
self.assertTrue(self.multiworld.itempool)
|
||||
world_item_names = [item.name for item in self.multiworld.itempool]
|
||||
|
||||
self.assertIn(item_names.MARINE, world_item_names)
|
||||
self.assertIn(item_names.RAVEN, world_item_names)
|
||||
self.assertIn(item_names.LIBERATOR, world_item_names)
|
||||
self.assertIn(item_names.BATTLECRUISER, world_item_names)
|
||||
self.assertNotIn(item_names.DIAMONDBACK, world_item_names)
|
||||
self.assertNotIn(item_names.DIAMONDBACK_BURST_CAPACITORS, world_item_names)
|
||||
self.assertNotIn(item_names.VIKING, world_item_names)
|
||||
|
||||
def test_nco_with_nobuilds_excluded_generates(self):
|
||||
world_options = {
|
||||
'enabled_campaigns': {
|
||||
SC2Campaign.NCO.campaign_name
|
||||
},
|
||||
'shuffle_no_build': options.ShuffleNoBuild.option_false,
|
||||
'mission_order': options.MissionOrder.option_mini_campaign,
|
||||
}
|
||||
|
||||
self.generate_world(world_options)
|
||||
self.assertTrue(self.multiworld.itempool)
|
||||
missions = get_all_missions(self.world.custom_mission_order)
|
||||
|
||||
self.assertNotIn(mission_tables.SC2Mission.THE_ESCAPE, missions)
|
||||
self.assertNotIn(mission_tables.SC2Mission.IN_THE_ENEMY_S_SHADOW, missions)
|
||||
for mission in missions:
|
||||
self.assertEqual(mission_tables.SC2Campaign.NCO, mission.campaign)
|
||||
|
||||
def test_terran_with_nco_upgrades_units_only_generates(self):
|
||||
world_options = {
|
||||
'enabled_campaigns': {
|
||||
SC2Campaign.WOL.campaign_name,
|
||||
SC2Campaign.NCO.campaign_name
|
||||
},
|
||||
'mission_order': options.MissionOrder.option_vanilla_shuffled,
|
||||
'excluded_items': {
|
||||
item_groups.ItemGroupNames.TERRAN_ITEMS: 0,
|
||||
},
|
||||
'unexcluded_items': {
|
||||
item_groups.ItemGroupNames.NCO_MAX_PROGRESSIVE_ITEMS: 0,
|
||||
item_groups.ItemGroupNames.NCO_MIN_PROGRESSIVE_ITEMS: 1,
|
||||
},
|
||||
'excluded_missions': [
|
||||
# These missions have trouble fulfilling Terran Power Rating under these terms
|
||||
SC2Mission.SUPERNOVA.mission_name,
|
||||
SC2Mission.WELCOME_TO_THE_JUNGLE.mission_name,
|
||||
SC2Mission.TROUBLE_IN_PARADISE.mission_name,
|
||||
],
|
||||
'mastery_locations': MasteryLocations.option_disabled,
|
||||
}
|
||||
|
||||
self.generate_world(world_options)
|
||||
world_item_names = [item.name for item in self.multiworld.itempool + self.multiworld.precollected_items[1]]
|
||||
self.assertTrue(world_item_names)
|
||||
missions = get_all_missions(self.world.custom_mission_order)
|
||||
|
||||
for mission in missions:
|
||||
self.assertIn(mission_tables.MissionFlag.Terran, mission.flags)
|
||||
self.assertIn(item_names.MARINE, world_item_names)
|
||||
self.assertIn(item_names.MARAUDER, world_item_names)
|
||||
self.assertIn(item_names.BUNKER, world_item_names)
|
||||
self.assertIn(item_names.BANSHEE, world_item_names)
|
||||
self.assertIn(item_names.BATTLECRUISER_ATX_LASER_BATTERY, world_item_names)
|
||||
self.assertIn(item_names.NOVA_C20A_CANISTER_RIFLE, world_item_names)
|
||||
self.assertGreaterEqual(world_item_names.count(item_names.BANSHEE_PROGRESSIVE_CROSS_SPECTRUM_DAMPENERS), 2)
|
||||
self.assertGreaterEqual(world_item_names.count(item_names.PROGRESSIVE_TERRAN_SHIP_WEAPON), 3)
|
||||
self.assertNotIn(item_names.MEDIC, world_item_names)
|
||||
self.assertNotIn(item_names.PSI_DISRUPTER, world_item_names)
|
||||
self.assertNotIn(item_names.BATTLECRUISER_PROGRESSIVE_MISSILE_PODS, world_item_names)
|
||||
self.assertNotIn(item_names.HELLION_INFERNAL_PLATING, world_item_names)
|
||||
self.assertNotIn(item_names.CELLULAR_REACTOR, world_item_names)
|
||||
self.assertNotIn(item_names.TECH_REACTOR, world_item_names)
|
||||
|
||||
def test_nco_and_2_wol_missions_only_can_generate_with_vanilla_items_only(self) -> None:
|
||||
world_options = {
|
||||
'enabled_campaigns': {
|
||||
SC2Campaign.WOL.campaign_name,
|
||||
SC2Campaign.NCO.campaign_name
|
||||
},
|
||||
'excluded_missions': [
|
||||
mission.mission_name for mission in mission_tables.SC2Mission
|
||||
if mission.campaign == mission_tables.SC2Campaign.WOL
|
||||
and mission.mission_name not in (mission_tables.SC2Mission.LIBERATION_DAY.mission_name, mission_tables.SC2Mission.THE_OUTLAWS.mission_name)
|
||||
],
|
||||
'mission_order': options.MissionOrder.option_grid,
|
||||
'maximum_campaign_size': options.MaximumCampaignSize.range_end,
|
||||
'mastery_locations': options.MasteryLocations.option_disabled,
|
||||
'vanilla_items_only': True,
|
||||
}
|
||||
|
||||
self.generate_world(world_options)
|
||||
world_item_names = [item.name for item in self.multiworld.itempool]
|
||||
|
||||
self.assertTrue(item_names)
|
||||
self.assertNotIn(item_names.LIBERATOR, world_item_names)
|
||||
self.assertNotIn(item_names.MARAUDER_PROGRESSIVE_STIMPACK, world_item_names)
|
||||
self.assertNotIn(item_names.HELLION_HELLBAT, world_item_names)
|
||||
self.assertNotIn(item_names.BATTLECRUISER_CLOAK, world_item_names)
|
||||
|
||||
def test_free_protoss_only_generates(self) -> None:
|
||||
world_options = {
|
||||
'enabled_campaigns': {
|
||||
SC2Campaign.PROPHECY.campaign_name,
|
||||
SC2Campaign.PROLOGUE.campaign_name
|
||||
},
|
||||
# todo(mm): Currently, these settings don't generate on grid because there are not enough EASY missions
|
||||
'mission_order': options.MissionOrder.option_vanilla_shuffled,
|
||||
'maximum_campaign_size': options.MaximumCampaignSize.range_end,
|
||||
'accessibility': 'locations',
|
||||
}
|
||||
|
||||
self.generate_world(world_options)
|
||||
world_item_names = [item.name for item in self.multiworld.itempool]
|
||||
self.assertTrue(world_item_names)
|
||||
missions = get_all_missions(self.world.custom_mission_order)
|
||||
|
||||
self.assertEqual(len(missions), 7, "Wrong number of missions in free protoss seed")
|
||||
for mission in missions:
|
||||
self.assertIn(mission.campaign, (mission_tables.SC2Campaign.PROLOGUE, mission_tables.SC2Campaign.PROPHECY))
|
||||
for item_name in world_item_names:
|
||||
self.assertIn(item_tables.item_table[item_name].race, (mission_tables.SC2Race.ANY, mission_tables.SC2Race.PROTOSS))
|
||||
|
||||
def test_resource_filler_items_may_be_put_in_start_inventory(self) -> None:
|
||||
NUM_RESOURCE_ITEMS = 10
|
||||
world_options = {
|
||||
'start_inventory': {
|
||||
item_names.STARTING_MINERALS: NUM_RESOURCE_ITEMS,
|
||||
item_names.STARTING_VESPENE: NUM_RESOURCE_ITEMS,
|
||||
item_names.STARTING_SUPPLY: NUM_RESOURCE_ITEMS,
|
||||
},
|
||||
}
|
||||
|
||||
self.generate_world(world_options)
|
||||
start_item_names = [item.name for item in self.multiworld.precollected_items[self.player]]
|
||||
|
||||
self.assertEqual(start_item_names.count(item_names.STARTING_MINERALS), NUM_RESOURCE_ITEMS, "Wrong number of starting minerals in starting inventory")
|
||||
self.assertEqual(start_item_names.count(item_names.STARTING_VESPENE), NUM_RESOURCE_ITEMS, "Wrong number of starting vespene in starting inventory")
|
||||
self.assertEqual(start_item_names.count(item_names.STARTING_SUPPLY), NUM_RESOURCE_ITEMS, "Wrong number of starting supply in starting inventory")
|
||||
|
||||
def test_excluding_protoss_excludes_campaigns_and_items(self) -> None:
|
||||
world_options = {
|
||||
'selected_races': {
|
||||
SC2Race.TERRAN.get_title(),
|
||||
SC2Race.ZERG.get_title(),
|
||||
},
|
||||
'enabled_campaigns': options.EnabledCampaigns.valid_keys,
|
||||
'mission_order': options.MissionOrder.option_grid,
|
||||
}
|
||||
|
||||
self.generate_world(world_options)
|
||||
world_item_names = [item.name for item in self.multiworld.itempool]
|
||||
world_regions = [region.name for region in self.multiworld.regions]
|
||||
world_regions.remove('Menu')
|
||||
|
||||
for item_name in world_item_names:
|
||||
self.assertNotEqual(item_tables.item_table[item_name].race, mission_tables.SC2Race.PROTOSS, f"{item_name} is a PROTOSS item!")
|
||||
for region in world_regions:
|
||||
self.assertNotIn(mission_tables.lookup_name_to_mission[region].campaign,
|
||||
(mission_tables.SC2Campaign.LOTV, mission_tables.SC2Campaign.PROPHECY, mission_tables.SC2Campaign.PROLOGUE),
|
||||
f"{region} is a PROTOSS mission!")
|
||||
|
||||
def test_excluding_terran_excludes_campaigns_and_items(self) -> None:
|
||||
world_options = {
|
||||
'selected_races': {
|
||||
SC2Race.ZERG.get_title(),
|
||||
SC2Race.PROTOSS.get_title(),
|
||||
},
|
||||
'enabled_campaigns': EnabledCampaigns.valid_keys,
|
||||
'mission_order': options.MissionOrder.option_grid,
|
||||
}
|
||||
|
||||
self.generate_world(world_options)
|
||||
world_item_names = [item.name for item in self.multiworld.itempool]
|
||||
world_regions = [region.name for region in self.multiworld.regions]
|
||||
world_regions.remove('Menu')
|
||||
|
||||
for item_name in world_item_names:
|
||||
self.assertNotEqual(item_tables.item_table[item_name].race, mission_tables.SC2Race.TERRAN,
|
||||
f"{item_name} is a TERRAN item!")
|
||||
for region in world_regions:
|
||||
self.assertNotIn(mission_tables.lookup_name_to_mission[region].campaign,
|
||||
(mission_tables.SC2Campaign.WOL, mission_tables.SC2Campaign.NCO),
|
||||
f"{region} is a TERRAN mission!")
|
||||
|
||||
def test_excluding_zerg_excludes_campaigns_and_items(self) -> None:
|
||||
world_options = {
|
||||
'selected_races': {
|
||||
SC2Race.TERRAN.get_title(),
|
||||
SC2Race.PROTOSS.get_title(),
|
||||
},
|
||||
'enabled_campaigns': EnabledCampaigns.valid_keys,
|
||||
'mission_order': options.MissionOrder.option_grid,
|
||||
'excluded_missions': [
|
||||
SC2Mission.THE_INFINITE_CYCLE.mission_name
|
||||
]
|
||||
}
|
||||
|
||||
self.generate_world(world_options)
|
||||
world_item_names = [item.name for item in self.multiworld.itempool]
|
||||
world_regions = [region.name for region in self.multiworld.regions]
|
||||
world_regions.remove('Menu')
|
||||
|
||||
for item_name in world_item_names:
|
||||
self.assertNotEqual(item_tables.item_table[item_name].race, mission_tables.SC2Race.ZERG,
|
||||
f"{item_name} is a ZERG item!")
|
||||
# have to manually exclude the only non-zerg HotS mission...
|
||||
for region in filter(lambda region: region != "With Friends Like These", world_regions):
|
||||
self.assertNotIn(mission_tables.lookup_name_to_mission[region].campaign,
|
||||
([mission_tables.SC2Campaign.HOTS]),
|
||||
f"{region} is a ZERG mission!")
|
||||
|
||||
def test_excluding_faction_on_vanilla_order_excludes_epilogue(self) -> None:
|
||||
world_options = {
|
||||
'selected_races': {
|
||||
SC2Race.TERRAN.get_title(),
|
||||
SC2Race.PROTOSS.get_title(),
|
||||
},
|
||||
'enabled_campaigns': EnabledCampaigns.valid_keys,
|
||||
'mission_order': options.MissionOrder.option_vanilla,
|
||||
}
|
||||
|
||||
self.generate_world(world_options)
|
||||
world_regions = [region.name for region in self.multiworld.regions]
|
||||
world_regions.remove('Menu')
|
||||
|
||||
for region in world_regions:
|
||||
self.assertNotIn(mission_tables.lookup_name_to_mission[region].campaign,
|
||||
([mission_tables.SC2Campaign.EPILOGUE]),
|
||||
f"{region} is an epilogue mission!")
|
||||
|
||||
def test_race_swap_pick_one_has_correct_length_and_includes_swaps(self) -> None:
|
||||
world_options = {
|
||||
'selected_races': options.SelectRaces.valid_keys,
|
||||
'enable_race_swap': options.EnableRaceSwapVariants.option_pick_one,
|
||||
'enabled_campaigns': {
|
||||
SC2Campaign.WOL.campaign_name,
|
||||
},
|
||||
'mission_order': options.MissionOrder.option_grid,
|
||||
'excluded_missions': [mission_tables.SC2Mission.ZERO_HOUR.mission_name],
|
||||
}
|
||||
|
||||
self.generate_world(world_options)
|
||||
world_regions = [region.name for region in self.multiworld.regions]
|
||||
world_regions.remove('Menu')
|
||||
NUM_WOL_MISSIONS = len([mission for mission in SC2Mission if mission.campaign == SC2Campaign.WOL and MissionFlag.RaceSwap not in mission.flags])
|
||||
races = set(mission_tables.lookup_name_to_mission[mission].race for mission in world_regions)
|
||||
|
||||
self.assertEqual(len(world_regions), NUM_WOL_MISSIONS)
|
||||
self.assertTrue(SC2Race.ZERG in races or SC2Race.PROTOSS in races)
|
||||
|
||||
def test_start_inventory_upgrade_level_includes_only_correct_bundle(self) -> None:
|
||||
world_options = {
|
||||
'start_inventory': {
|
||||
item_groups.ItemGroupNames.TERRAN_GENERIC_UPGRADES: 1,
|
||||
},
|
||||
'locked_items': {
|
||||
# One unit of each class to guarantee upgrades are available
|
||||
item_names.MARINE: 1,
|
||||
item_names.VULTURE: 1,
|
||||
item_names.BANSHEE: 1,
|
||||
},
|
||||
'generic_upgrade_items': options.GenericUpgradeItems.option_bundle_unit_class,
|
||||
'selected_races': {
|
||||
SC2Race.TERRAN.get_title(),
|
||||
},
|
||||
'enable_race_swap': options.EnableRaceSwapVariants.option_disabled,
|
||||
'enabled_campaigns': {
|
||||
SC2Campaign.WOL.campaign_name,
|
||||
},
|
||||
'mission_order': options.MissionOrder.option_grid,
|
||||
}
|
||||
self.generate_world(world_options)
|
||||
self.assertTrue(self.multiworld.itempool)
|
||||
world_item_names = [item.name for item in self.multiworld.itempool]
|
||||
start_inventory = [item.name for item in self.multiworld.precollected_items[self.player]]
|
||||
|
||||
# Start inventory
|
||||
self.assertIn(item_names.PROGRESSIVE_TERRAN_INFANTRY_UPGRADE, start_inventory)
|
||||
self.assertIn(item_names.PROGRESSIVE_TERRAN_VEHICLE_UPGRADE, start_inventory)
|
||||
self.assertIn(item_names.PROGRESSIVE_TERRAN_SHIP_UPGRADE, start_inventory)
|
||||
self.assertNotIn(item_names.PROGRESSIVE_TERRAN_INFANTRY_WEAPON, start_inventory)
|
||||
self.assertNotIn(item_names.PROGRESSIVE_TERRAN_INFANTRY_ARMOR, start_inventory)
|
||||
self.assertNotIn(item_names.PROGRESSIVE_TERRAN_VEHICLE_WEAPON, start_inventory)
|
||||
self.assertNotIn(item_names.PROGRESSIVE_TERRAN_VEHICLE_ARMOR, start_inventory)
|
||||
self.assertNotIn(item_names.PROGRESSIVE_TERRAN_SHIP_WEAPON, start_inventory)
|
||||
self.assertNotIn(item_names.PROGRESSIVE_TERRAN_SHIP_ARMOR, start_inventory)
|
||||
self.assertNotIn(item_names.PROGRESSIVE_TERRAN_ARMOR_UPGRADE, start_inventory)
|
||||
|
||||
# Additional items in pool -- standard tactics will require additional levels
|
||||
self.assertIn(item_names.PROGRESSIVE_TERRAN_INFANTRY_UPGRADE, world_item_names)
|
||||
self.assertIn(item_names.PROGRESSIVE_TERRAN_VEHICLE_UPGRADE, world_item_names)
|
||||
self.assertIn(item_names.PROGRESSIVE_TERRAN_SHIP_UPGRADE, world_item_names)
|
||||
self.assertNotIn(item_names.PROGRESSIVE_TERRAN_INFANTRY_WEAPON, world_item_names)
|
||||
self.assertNotIn(item_names.PROGRESSIVE_TERRAN_INFANTRY_ARMOR, world_item_names)
|
||||
self.assertNotIn(item_names.PROGRESSIVE_TERRAN_VEHICLE_WEAPON, world_item_names)
|
||||
self.assertNotIn(item_names.PROGRESSIVE_TERRAN_VEHICLE_ARMOR, world_item_names)
|
||||
self.assertNotIn(item_names.PROGRESSIVE_TERRAN_SHIP_WEAPON, world_item_names)
|
||||
self.assertNotIn(item_names.PROGRESSIVE_TERRAN_SHIP_ARMOR, world_item_names)
|
||||
self.assertNotIn(item_names.PROGRESSIVE_TERRAN_ARMOR_UPGRADE, world_item_names)
|
||||
|
||||
def test_kerrigan_max_active_abilities(self):
|
||||
target_number: int = 8
|
||||
world_options = {
|
||||
'mission_order': options.MissionOrder.option_grid,
|
||||
'maximum_campaign_size': options.MaximumCampaignSize.range_end,
|
||||
'selected_races': {
|
||||
SC2Race.ZERG.get_title(),
|
||||
},
|
||||
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
|
||||
'kerrigan_max_active_abilities': target_number,
|
||||
}
|
||||
|
||||
self.generate_world(world_options)
|
||||
world_item_names = [item.name for item in self.multiworld.itempool]
|
||||
kerrigan_actives = [item_name for item_name in world_item_names if item_name in item_groups.kerrigan_active_abilities]
|
||||
|
||||
self.assertLessEqual(len(kerrigan_actives), target_number)
|
||||
|
||||
def test_kerrigan_max_passive_abilities(self):
|
||||
target_number: int = 3
|
||||
world_options = {
|
||||
'mission_order': options.MissionOrder.option_grid,
|
||||
'maximum_campaign_size': options.MaximumCampaignSize.range_end,
|
||||
'selected_races': {
|
||||
SC2Race.ZERG.get_title(),
|
||||
},
|
||||
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
|
||||
'kerrigan_max_passive_abilities': target_number,
|
||||
}
|
||||
|
||||
self.generate_world(world_options)
|
||||
world_item_names = [item.name for item in self.multiworld.itempool]
|
||||
kerrigan_passives = [item_name for item_name in world_item_names if item_name in item_groups.kerrigan_passives]
|
||||
|
||||
self.assertLessEqual(len(kerrigan_passives), target_number)
|
||||
|
||||
def test_spear_of_adun_max_active_abilities(self):
|
||||
target_number: int = 8
|
||||
world_options = {
|
||||
'mission_order': options.MissionOrder.option_grid,
|
||||
'maximum_campaign_size': options.MaximumCampaignSize.range_end,
|
||||
'selected_races': {
|
||||
SC2Race.PROTOSS.get_title(),
|
||||
},
|
||||
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
|
||||
'spear_of_adun_max_active_abilities': target_number,
|
||||
}
|
||||
|
||||
self.generate_world(world_options)
|
||||
world_item_names = [item.name for item in self.multiworld.itempool]
|
||||
spear_of_adun_actives = [item_name for item_name in world_item_names if item_name in item_tables.spear_of_adun_calldowns]
|
||||
|
||||
self.assertLessEqual(len(spear_of_adun_actives), target_number)
|
||||
|
||||
|
||||
def test_spear_of_adun_max_autocasts(self):
|
||||
target_number: int = 2
|
||||
world_options = {
|
||||
'mission_order': options.MissionOrder.option_grid,
|
||||
'maximum_campaign_size': options.MaximumCampaignSize.range_end,
|
||||
'selected_races': {
|
||||
SC2Race.PROTOSS.get_title(),
|
||||
},
|
||||
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
|
||||
'spear_of_adun_max_passive_abilities': target_number,
|
||||
}
|
||||
|
||||
self.generate_world(world_options)
|
||||
world_item_names = [item.name for item in self.multiworld.itempool]
|
||||
spear_of_adun_autocasts = [item_name for item_name in world_item_names if item_name in item_tables.spear_of_adun_castable_passives]
|
||||
|
||||
self.assertLessEqual(len(spear_of_adun_autocasts), target_number)
|
||||
|
||||
|
||||
def test_nova_max_weapons(self):
|
||||
target_number: int = 3
|
||||
world_options = {
|
||||
'mission_order': options.MissionOrder.option_grid,
|
||||
'maximum_campaign_size': options.MaximumCampaignSize.range_end,
|
||||
'selected_races': {
|
||||
SC2Race.TERRAN.get_title(),
|
||||
},
|
||||
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
|
||||
'nova_max_weapons': target_number,
|
||||
}
|
||||
|
||||
self.generate_world(world_options)
|
||||
world_item_names = [item.name for item in self.multiworld.itempool]
|
||||
nova_weapons = [item_name for item_name in world_item_names if item_name in item_groups.nova_weapons]
|
||||
|
||||
self.assertLessEqual(len(nova_weapons), target_number)
|
||||
|
||||
|
||||
def test_nova_max_gadgets(self):
|
||||
target_number: int = 3
|
||||
world_options = {
|
||||
'mission_order': options.MissionOrder.option_grid,
|
||||
'maximum_campaign_size': options.MaximumCampaignSize.range_end,
|
||||
'selected_races': {
|
||||
SC2Race.TERRAN.get_title(),
|
||||
},
|
||||
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
|
||||
'nova_max_gadgets': target_number,
|
||||
}
|
||||
|
||||
self.generate_world(world_options)
|
||||
world_item_names = [item.name for item in self.multiworld.itempool]
|
||||
nova_gadgets = [item_name for item_name in world_item_names if item_name in item_groups.nova_gadgets]
|
||||
|
||||
self.assertLessEqual(len(nova_gadgets), target_number)
|
||||
|
||||
def test_mercs_only(self) -> None:
|
||||
world_options = {
|
||||
'selected_races': [
|
||||
SC2Race.TERRAN.get_title(),
|
||||
SC2Race.ZERG.get_title(),
|
||||
],
|
||||
'required_tactics': options.RequiredTactics.option_any_units,
|
||||
'excluded_items': {
|
||||
item_groups.ItemGroupNames.TERRAN_UNITS: 0,
|
||||
item_groups.ItemGroupNames.ZERG_UNITS: 0,
|
||||
},
|
||||
'unexcluded_items': {
|
||||
item_groups.ItemGroupNames.TERRAN_MERCENARIES: 0,
|
||||
item_groups.ItemGroupNames.ZERG_MERCENARIES: 0,
|
||||
},
|
||||
'start_inventory': {
|
||||
item_names.PROGRESSIVE_FAST_DELIVERY: 1,
|
||||
item_names.ROGUE_FORCES: 1,
|
||||
item_names.UNRESTRICTED_MUTATION: 1,
|
||||
item_names.EVOLUTIONARY_LEAP: 1,
|
||||
},
|
||||
'mission_order': options.MissionOrder.option_grid,
|
||||
'excluded_missions': [
|
||||
SC2Mission.ENEMY_WITHIN.mission_name, # Requires a unit for Niadra to build
|
||||
],
|
||||
}
|
||||
self.generate_world(world_options)
|
||||
world_item_names = [item.name for item in self.multiworld.itempool]
|
||||
terran_nonmerc_units = tuple(
|
||||
item_name
|
||||
for item_name in world_item_names
|
||||
if item_name in item_groups.terran_units and item_name not in item_groups.terran_mercenaries
|
||||
)
|
||||
zerg_nonmerc_units = tuple(
|
||||
item_name
|
||||
for item_name in world_item_names
|
||||
if item_name in item_groups.zerg_units and item_name not in item_groups.zerg_mercenaries
|
||||
)
|
||||
|
||||
self.assertTupleEqual(terran_nonmerc_units, ())
|
||||
self.assertTupleEqual(zerg_nonmerc_units, ())
|
||||
Reference in New Issue
Block a user