SC2: New Mission Order options for shorter campaigns and smaller item pools

This commit is contained in:
Magnemania
2022-10-04 12:57:42 -04:00
parent 2b360519a0
commit 646ecc7679
4 changed files with 191 additions and 177 deletions

View File

@@ -38,22 +38,29 @@ class AllInMap(Choice):
class MissionOrder(Choice):
"""Determines the order the missions are played in.
Vanilla: Keeps the standard mission order and branching from the WoL Campaign.
Vanilla Shuffled: Keeps same branching paths from the WoL Campaign but randomizes the order of missions within."""
Vanilla Shuffled: Keeps same branching paths from the WoL Campaign but randomizes the order of missions within.
Mini Shuffle: Halves the number of missions in the campaign and randomizes the missions available.
Gauntlet: A linear series of 7 random missions to complete the campaign.
"""
display_name = "Mission Order"
option_vanilla = 0
option_vanilla_shuffled = 1
option_mini_shuffle = 2
option_gauntlet = 3
class ShuffleProtoss(DefaultOnToggle):
"""Determines if the 3 protoss missions are included in the shuffle if Vanilla Shuffled is enabled. If this is
not the 3 protoss missions will stay in their vanilla order in the mission order making them optional to complete
the game."""
"""Determines if the 3 protoss missions are included in the shuffle if Vanilla mission order is not enabled.
On Vanilla Shuffled, the 3 protoss missions will be in their normal position on the Prophecy chain if not shuffled.
On Mini Shuffle or Gauntlet, the 3 protoss missions will not appear and Protoss units are removed from the pool if not shuffled.
"""
display_name = "Shuffle Protoss Missions"
class RelegateNoBuildMissions(DefaultOnToggle):
"""If enabled, all no build missions besides the needed first one will be placed at the end of optional routes so
that none of them become required to complete the game. Only takes effect if mission order is not set to vanilla."""
"""Determines if the 5 no-build missions are included in the shuffle if Vanilla mission order is not enabled.
On Vanilla Shuffled, one no-build mission will be placed as the first mission and the rest will be placed at the end of optional routes.
On Mini Shuffle or Gauntlet, the 5 no-build missions will not appear."""
display_name = "Relegate No-Build Missions"

View File

@@ -1,6 +1,8 @@
from BaseClasses import MultiWorld, Item, ItemClassification
from typing import Callable
from BaseClasses import MultiWorld, ItemClassification, Item, Location
from .Items import item_table
from .MissionTables import no_build_regions_list, easy_regions_list, medium_regions_list, hard_regions_list, vanilla_mission_req_table, starting_mission_locations
from .MissionTables import no_build_regions_list, easy_regions_list, medium_regions_list, hard_regions_list,\
mission_orders, starting_mission_locations, MissionInfo
from .Options import get_option_value
from .LogicMixin import SC2WoLLogic
@@ -22,32 +24,39 @@ MIN_UNITS_PER_STRUCTURE = [
0 # Gauntlet
]
PROTOSS_REGIONS = ["A Sinister Turn", "Echoes of the Future", "In Utter Darkness"]
PROTOSS_REGIONS = {"A Sinister Turn", "Echoes of the Future", "In Utter Darkness"}
UPGRADES = [
"Progressive Infantry Weapon", "Progressive Infantry Armor",
"Progressive Vehicle Weapon", "Progressive Vehicle Armor",
"Progressive Ship Weapon", "Progressive Ship Armor"
ALWAYS_USEFUL_ARMORY = [
"Combat Shield (Marine)", "Stabilizer Medpacks (Medic)" # Needed for no-build logic
]
def filter_missions(world: MultiWorld, player: int) -> set[str]:
def filter_missions(world: MultiWorld, player: int) -> dict[str, list[str]]:
"""
Returns a semi-randomly pruned set of missions based on yaml configuration
Returns a semi-randomly pruned tuple of no-build, easy, medium, and hard mission sets
"""
missions = set(vanilla_mission_req_table.keys())
mission_order = get_option_value(world, player, "mission_order")
mission_order_type = get_option_value(world, player, "mission_order")
shuffle_protoss = get_option_value(world, player, "shuffle_protoss")
relegate_no_build = get_option_value(world, player, "relegate_no_build")
# Vanilla and Vanilla Shuffled use the entire mission pool
if mission_order not in (2, 3):
return missions
if mission_order_type in (0, 1):
return {
'no_build': no_build_regions_list[:],
'easy': easy_regions_list[:],
'medium': medium_regions_list[:],
'hard': hard_regions_list[:]
}
mission_count = 0
for mission in mission_orders[mission_order_type]:
if mission is None:
continue
if mission.type == 'all_in': # All-In is placed separately
continue
mission_count += 1
if mission_order == 2:
mission_count = 15
else:
mission_count = 7
mission_sets = [
set(no_build_regions_list),
set(easy_regions_list),
@@ -56,12 +65,10 @@ def filter_missions(world: MultiWorld, player: int) -> set[str]:
]
# Omitting Protoss missions if not shuffling protoss
if not shuffle_protoss:
missions.difference_update(PROTOSS_REGIONS)
for mission_set in mission_sets:
mission_set.difference_update(PROTOSS_REGIONS)
# Omitting No Build missions if relegating no-build
if relegate_no_build:
missions.difference_update(no_build_regions_list)
# The build missions in starting_mission_locations become the new "no build missions"
mission_sets[0] = set(starting_mission_locations.keys())
mission_sets[0].difference_update(no_build_regions_list)
@@ -70,124 +77,136 @@ def filter_missions(world: MultiWorld, player: int) -> set[str]:
mission_set.difference_update(mission_sets[0])
# Removing random missions from each difficulty set in a cycle
set_cycle = 0
while len(missions) > mission_count:
mission_pools = [list(mission_set) for mission_set in mission_sets]
current_count = sum(len(mission_pool) for mission_pool in mission_pools)
if current_count < mission_count:
raise Exception('Not enough missions available to fill the campaign on current settings.')
while current_count > mission_count:
if set_cycle == 4:
set_cycle = 0
# Must contain at least one mission per set
if len(mission_sets[set_cycle]) == 1:
continue
removed_mission = world.random.choice(mission_sets[set_cycle])
mission_sets[set_cycle].remove(removed_mission)
missions.remove(removed_mission)
mission_pool = mission_pools[set_cycle]
set_cycle += 1
return missions
if len(mission_pool) == 1:
continue
mission_pool.remove(world.random.choice(mission_pool))
current_count -= 1
return {
'no_build': mission_pools[0],
'easy': mission_pools[1],
'medium': mission_pools[2],
'hard': mission_pools[3]
}
class ValidInventory(SC2WoLLogic):
class ValidInventory:
def has(self, item: str, player: int = 0):
def has(self, item: str, player: int):
return item in self.logical_inventory
def has_any(self, items: set[str], player: int = 0):
return any([item in self.logical_inventory for item in items])
def has_any(self, items: set[str], player: int):
return any(item in self.logical_inventory for item in items)
# Necessary for Piercing the Shroud
def _sc2wol_has_mm_upgrade(self, world: MultiWorld, player: int):
return self.has_any({"Combat Shield (Marine)", "Stabilizer Medpacks (Medic)"}, player)
def has_all(self, items: set[str], player: int):
return all(item in self.logical_inventory for item in items)
# Necessary for Maw of the Void
def _sc2wol_survives_rip_field(self, world: MultiWorld, player: int):
return self.has("Battlecruiser", player) or \
self._sc2wol_has_air(world, player) and \
self._sc2wol_has_competent_anti_air(world, player) and \
self.has("Science Vessel", player)
def has_units_per_structure(self, min_units_per_structure) -> bool:
return len(BARRACKS_UNITS.intersection(self.logical_inventory)) > min_units_per_structure and \
len(FACTORY_UNITS.intersection(self.logical_inventory)) > min_units_per_structure and \
len(STARPORT_UNITS.intersection(self.logical_inventory)) > min_units_per_structure
def _sc2wol_has_units_per_structure(self, world: MultiWorld, player: int):
return len(BARRACKS_UNITS.intersection(self.logical_inventory)) > self.min_units_per_structure and \
len(FACTORY_UNITS.intersection(self.logical_inventory)) > self.min_units_per_structure and \
len(STARPORT_UNITS.intersection(self.logical_inventory)) > self.min_units_per_structure
def generate_reduced_inventory(self, number_of_locations: int):
inventory = set(self.inventory)
def generate_reduced_inventory(self, inventory_size: int, mission_requirements: list[Callable]) -> list[Item]:
inventory = list(self.item_pool)
locked_items = list(self.locked_items)
self.logical_inventory = set(self.progression_items)
while len(inventory) + len(locked_items) > number_of_locations:
self.logical_inventory = {item.name for item in self.progression_items.union(self.locked_items)}
requirements = mission_requirements
mission_order_type = get_option_value(self.world, self.player, "mission_order")
min_units_per_structure = MIN_UNITS_PER_STRUCTURE[mission_order_type]
if min_units_per_structure > 0:
requirements.append(lambda state: state.has_units_per_structure(min_units_per_structure))
while len(inventory) + len(locked_items) > inventory_size:
if len(inventory) == 0:
raise Exception('Reduced item pool generation failed.')
# Select random item from removable items
item = self.world.random.choice(inventory)
inventory.remove(item)
# Only run logic checks when removing logic items
if item in self.logical_inventory:
self.logical_inventory.remove(item)
if not all(self.requirements):
# If item cannot be removed, move it to locked items
self.logical_inventory.add(item)
locked_items.add(item)
else:
if item.name in self.logical_inventory:
self.logical_inventory.remove(item.name)
if all(requirement(self) for requirement in requirements):
# If item can be removed and is a unit, remove armory upgrades
if item in UPGRADABLE_ITEMS:
inventory = [inv_item for inv_item in inventory if not inv_item.endswith('(' + item + ')')]
return list(inventory) + locked_items
# Some armory upgrades are kept regardless, as they remain logically relevant
if item.name in UPGRADABLE_ITEMS:
inventory = [
inv_item for inv_item in inventory
if inv_item.name in ALWAYS_USEFUL_ARMORY or not inv_item.name.endswith('(' + item.name + ')')
]
else:
# If item cannot be removed, move it to locked items
self.logical_inventory.add(item.name)
locked_items.append(item)
return inventory + locked_items
def __init__(self, world: MultiWorld, player: int, locked_items: list[str]):
def _read_logic(self):
self._sc2wol_has_common_unit = lambda world, player: SC2WoLLogic._sc2wol_has_common_unit(self, world, player)
self._sc2wol_has_air = lambda world, player: SC2WoLLogic._sc2wol_has_air(self, world, player)
self._sc2wol_has_air_anti_air = lambda world, player: SC2WoLLogic._sc2wol_has_air_anti_air(self, world, player)
self._sc2wol_has_competent_anti_air = lambda world, player: SC2WoLLogic._sc2wol_has_competent_anti_air(self, world, player)
self._sc2wol_has_anti_air = lambda world, player: SC2WoLLogic._sc2wol_has_anti_air(self, world, player)
self._sc2wol_has_heavy_defense = lambda world, player: SC2WoLLogic._sc2wol_has_heavy_defense(self, world, player)
self._sc2wol_has_competent_comp = lambda world, player: SC2WoLLogic._sc2wol_has_competent_comp(self, world, player)
self._sc2wol_has_train_killers = lambda world, player: SC2WoLLogic._sc2wol_has_train_killers(self, world, player)
self._sc2wol_able_to_rescue = lambda world, player: SC2WoLLogic._sc2wol_able_to_rescue(self, world, player)
self._sc2wol_beats_protoss_deathball = lambda world, player: SC2WoLLogic._sc2wol_beats_protoss_deathball(self, world, player)
self._sc2wol_survives_rip_field = lambda world, player: SC2WoLLogic._sc2wol_survives_rip_field(self, world, player)
self._sc2wol_has_protoss_common_units = lambda world, player: SC2WoLLogic._sc2wol_has_protoss_common_units(self, world, player)
self._sc2wol_has_protoss_medium_units = lambda world, player: SC2WoLLogic._sc2wol_has_protoss_medium_units(self, world, player)
self._sc2wol_has_mm_upgrade = lambda world, player: SC2WoLLogic._sc2wol_has_mm_upgrade(self, world, player)
self._sc2wol_has_bunker_unit = lambda world, player: SC2WoLLogic._sc2wol_has_bunker_unit(self, world, player)
def __init__(self, world: MultiWorld, player: int,
item_pool: list[Item], existing_items: list[Item], locked_items: list[Item],
has_protoss: bool):
self.world = world
mission_order = get_option_value(world, player, "mission_order")
self.min_units_per_structure = MIN_UNITS_PER_STRUCTURE[mission_order]
self.locked_items = locked_items
self.inventory = set(item_table.keys())
protoss_items = set()
# Scanning item table
self.player = player
self.logical_inventory = set()
self.progression_items = set()
for item_name, item in item_table.items():
self.locked_items = locked_items[:]
self.existing_items = existing_items
self._read_logic()
# Initial filter of item pool
self.item_pool = []
item_quantities: dict[str, int] = dict()
for item in item_pool:
item_info = item_table[item.name]
if item.classification == ItemClassification.progression:
self.progression_items.add(item_name)
elif item.type in ("Minerals", "Vespene", "Supply"):
self.inventory.remove(item_name)
if item.type == "Protoss":
protoss_items.add(item_name)
self.requirements = [
self._sc2wol_has_common_unit,
self._sc2wol_has_air,
self._sc2wol_has_competent_anti_air,
self._sc2wol_has_heavy_defense,
self._sc2wol_has_competent_comp,
self._sc2wol_has_train_killers,
self._sc2wol_able_to_rescue,
self._sc2wol_beats_protoss_deathball,
self._sc2wol_survives_rip_field
]
# Only include units per structure requirement if more than 0
if self.min_units_per_structure > 0:
self.requirements.append(self._sc2wol_has_units_per_structure)
# Only include Marine/Medic upgrade requirements if no-build missions are present
if mission_order in (1, 2) or get_option_value(world, player, "relegate_no_build"):
self.requirements.append(self._has_mm_upgrade)
# Only include protoss requirements if protoss missions are present
if mission_order in (1, 2) or get_option_value(world, player, "shuffle_protoss"):
self.requirements += [
self._sc2wol_has_protoss_common_units,
self._sc2wol_has_protoss_medium_units
]
else:
# Remove protoss items if protoss missions are not present
self.locked_items = [item for item in self.locked_items if item not in protoss_items]
self.inventory = [item for item in self.inventory if item not in protoss_items]
# 2 tiers of each upgrade are guaranteed
self.locked_items += 2 * UPGRADES
self.inventory.difference_update(locked_items)
# 1 tier of each upgrade can be potentially removed
self.inventory += UPGRADES
self.progression_items.add(item)
if item_info.type == 'Upgrade':
# All Upgrades are locked except for the final tier
if item.name not in item_quantities:
item_quantities[item.name] = 0
item_quantities[item.name] += 1
if item_quantities[item.name] < item_info.quantity:
self.locked_items.append(item)
else:
self.item_pool.append(item)
elif item_info.type == 'Goal':
locked_items.append(item)
elif item_info.type != 'Protoss' or has_protoss:
self.item_pool.append(item)
def filter_items(world: MultiWorld, player: int, regions: set[str], locked_items: list[str] = []) -> list[str]:
def filter_items(world: MultiWorld, player: int, mission_req_table: dict[str, MissionInfo], location_cache: list[Location],
item_pool: list[Item], existing_items: list[Item], locked_items: list[Item]) -> list[Item]:
"""
Returns a semi-randomly pruned set of items based on number of available locations.
The returned inventory must be capable of logically accessing every location in the world.
"""
valid_inventory = ValidInventory(world, player, locked_items)
number_of_locations = sum([
info.extra_locations for name, info in vanilla_mission_req_table.items() if name in regions
])
return valid_inventory.generate_reduced_inventory(number_of_locations)
open_locations = [location for location in location_cache if location.item is None]
inventory_size = len(open_locations)
has_protoss = bool(PROTOSS_REGIONS.intersection(mission_req_table.keys()))
mission_requirements = [location.access_rule for location in location_cache]
valid_inventory = ValidInventory(world, player, item_pool, existing_items, locked_items, has_protoss)
valid_items = valid_inventory.generate_reduced_inventory(inventory_size, mission_requirements)
return valid_items

View File

@@ -2,55 +2,34 @@ from typing import List, Set, Dict, Tuple, Optional, Callable
from BaseClasses import MultiWorld, Region, Entrance, Location, RegionType
from .Locations import LocationData
from .Options import get_option_value
from .MissionTables import MissionInfo, vanilla_shuffle_order, vanilla_mission_req_table, \
no_build_regions_list, easy_regions_list, medium_regions_list, hard_regions_list
from .MissionTables import MissionInfo, mission_orders, vanilla_mission_req_table
from .PoolFilter import filter_missions
import random
def create_regions(world: MultiWorld, player: int, locations: Tuple[LocationData, ...], location_cache: List[Location]):
locations_per_region = get_locations_per_region(locations)
regions = [
create_region(world, player, locations_per_region, location_cache, "Menu"),
create_region(world, player, locations_per_region, location_cache, "Liberation Day"),
create_region(world, player, locations_per_region, location_cache, "The Outlaws"),
create_region(world, player, locations_per_region, location_cache, "Zero Hour"),
create_region(world, player, locations_per_region, location_cache, "Evacuation"),
create_region(world, player, locations_per_region, location_cache, "Outbreak"),
create_region(world, player, locations_per_region, location_cache, "Safe Haven"),
create_region(world, player, locations_per_region, location_cache, "Haven's Fall"),
create_region(world, player, locations_per_region, location_cache, "Smash and Grab"),
create_region(world, player, locations_per_region, location_cache, "The Dig"),
create_region(world, player, locations_per_region, location_cache, "The Moebius Factor"),
create_region(world, player, locations_per_region, location_cache, "Supernova"),
create_region(world, player, locations_per_region, location_cache, "Maw of the Void"),
create_region(world, player, locations_per_region, location_cache, "Devil's Playground"),
create_region(world, player, locations_per_region, location_cache, "Welcome to the Jungle"),
create_region(world, player, locations_per_region, location_cache, "Breakout"),
create_region(world, player, locations_per_region, location_cache, "Ghost of a Chance"),
create_region(world, player, locations_per_region, location_cache, "The Great Train Robbery"),
create_region(world, player, locations_per_region, location_cache, "Cutthroat"),
create_region(world, player, locations_per_region, location_cache, "Engine of Destruction"),
create_region(world, player, locations_per_region, location_cache, "Media Blitz"),
create_region(world, player, locations_per_region, location_cache, "Piercing the Shroud"),
create_region(world, player, locations_per_region, location_cache, "Whispers of Doom"),
create_region(world, player, locations_per_region, location_cache, "A Sinister Turn"),
create_region(world, player, locations_per_region, location_cache, "Echoes of the Future"),
create_region(world, player, locations_per_region, location_cache, "In Utter Darkness"),
create_region(world, player, locations_per_region, location_cache, "Gates of Hell"),
create_region(world, player, locations_per_region, location_cache, "Belly of the Beast"),
create_region(world, player, locations_per_region, location_cache, "Shatter the Sky"),
create_region(world, player, locations_per_region, location_cache, "All-In")
]
mission_order_type = get_option_value(world, player, "mission_order")
mission_order = mission_orders[mission_order_type]
mission_pools = filter_missions(world, player)
used_regions = [mission for mission_pool in mission_pools.values() for mission in mission_pool]
used_regions += ['All-In']
regions = [create_region(world, player, locations_per_region, location_cache, "Menu")]
for region_name in used_regions:
regions.append(create_region(world, player, locations_per_region, location_cache, region_name))
if __debug__:
throwIfAnyLocationIsNotAssignedToARegion(regions, locations_per_region.keys())
if mission_order_type in (0, 1):
throwIfAnyLocationIsNotAssignedToARegion(regions, locations_per_region.keys())
world.regions += regions
names: Dict[str, int] = {}
if get_option_value(world, player, "mission_order") == 0:
if mission_order_type == 0:
connect(world, player, names, 'Menu', 'Liberation Day'),
connect(world, player, names, 'Liberation Day', 'The Outlaws',
lambda state: state.has("Beat Liberation Day", player)),
@@ -121,24 +100,22 @@ def create_regions(world: MultiWorld, player: int, locations: Tuple[LocationData
return vanilla_mission_req_table
elif get_option_value(world, player, "mission_order") == 1:
else:
missions = []
no_build_pool = no_build_regions_list[:]
easy_pool = easy_regions_list[:]
medium_pool = medium_regions_list[:]
hard_pool = hard_regions_list[:]
# Initial fill out of mission list and marking all-in mission
for mission in vanilla_shuffle_order:
if mission.type == "all_in":
for mission in mission_order:
if mission is None:
missions.append(None)
elif mission.type == "all_in":
missions.append("All-In")
elif get_option_value(world, player, "relegate_no_build") and mission.relegate:
missions.append("no_build")
else:
missions.append(mission.type)
# Place Protoss Missions if we are not using ShuffleProtoss
if get_option_value(world, player, "shuffle_protoss") == 0:
# Place Protoss Missions if we are not using ShuffleProtoss and are in Vanilla Shuffled
if get_option_value(world, player, "shuffle_protoss") == 0 and mission_order_type == 1:
missions[22] = "A Sinister Turn"
medium_pool.remove("A Sinister Turn")
missions[23] = "Echoes of the Future"
@@ -153,6 +130,8 @@ def create_regions(world: MultiWorld, player: int, locations: Tuple[LocationData
# Search through missions to find slots needed to fill
for i in range(len(missions)):
if missions[i] is None:
continue
if missions[i] == "no_build":
no_build_slots.append(i)
elif missions[i] == "easy":
@@ -163,28 +142,28 @@ def create_regions(world: MultiWorld, player: int, locations: Tuple[LocationData
hard_slots.append(i)
# Add no_build missions to the pool and fill in no_build slots
missions_to_add = no_build_pool
missions_to_add = mission_pools['no_build']
for slot in no_build_slots:
filler = random.randint(0, len(missions_to_add)-1)
missions[slot] = missions_to_add.pop(filler)
# Add easy missions into pool and fill in easy slots
missions_to_add = missions_to_add + easy_pool
missions_to_add = missions_to_add + mission_pools['easy']
for slot in easy_slots:
filler = random.randint(0, len(missions_to_add) - 1)
missions[slot] = missions_to_add.pop(filler)
# Add medium missions into pool and fill in medium slots
missions_to_add = missions_to_add + medium_pool
missions_to_add = missions_to_add + mission_pools['medium']
for slot in medium_slots:
filler = random.randint(0, len(missions_to_add) - 1)
missions[slot] = missions_to_add.pop(filler)
# Add hard missions into pool and fill in hard slots
missions_to_add = missions_to_add + hard_pool
missions_to_add = missions_to_add + mission_pools['hard']
for slot in hard_slots:
filler = random.randint(0, len(missions_to_add) - 1)
@@ -195,7 +174,7 @@ def create_regions(world: MultiWorld, player: int, locations: Tuple[LocationData
mission_req_table = {}
for i in range(len(missions)):
connections = []
for connection in vanilla_shuffle_order[i].connect_to:
for connection in mission_order[i].connect_to:
if connection == -1:
connect(world, player, names, "Menu", missions[i])
else:
@@ -203,14 +182,14 @@ def create_regions(world: MultiWorld, player: int, locations: Tuple[LocationData
(lambda name, missions_req: (lambda state: state.has(f"Beat {name}", player) and
state._sc2wol_cleared_missions(world, player,
missions_req)))
(missions[connection], vanilla_shuffle_order[i].number))
(missions[connection], mission_order[i].number))
connections.append(connection + 1)
mission_req_table.update({missions[i]: MissionInfo(
vanilla_mission_req_table[missions[i]].id, vanilla_mission_req_table[missions[i]].extra_locations,
connections, vanilla_shuffle_order[i].category, number=vanilla_shuffle_order[i].number,
completion_critical=vanilla_shuffle_order[i].completion_critical,
or_requirements=vanilla_shuffle_order[i].or_requirements)})
connections, mission_order[i].category, number=mission_order[i].number,
completion_critical=mission_order[i].completion_critical,
or_requirements=mission_order[i].or_requirements)})
return mission_req_table

View File

@@ -9,6 +9,8 @@ from .Locations import get_locations
from .Regions import create_regions
from .Options import sc2wol_options, get_option_value
from .LogicMixin import SC2WoLLogic
from .PoolFilter import filter_missions, filter_items
from .MissionTables import starting_mission_locations, MissionInfo
class Starcraft2WoLWebWorld(WebWorld):
@@ -64,9 +66,9 @@ class SC2WoLWorld(World):
def generate_basic(self):
excluded_items = get_excluded_items(self, self.world, self.player)
assign_starter_items(self.world, self.player, excluded_items, self.locked_locations)
starter_items = assign_starter_items(self.world, self.player, excluded_items, self.locked_locations)
pool = get_item_pool(self.world, self.player, excluded_items)
pool = get_item_pool(self.world, self.player, self.mission_req_table, starter_items, excluded_items, self.location_cache)
fill_item_pool_with_dummy_items(self, self.world, self.player, self.locked_locations, self.location_cache, pool)
@@ -123,7 +125,7 @@ def get_excluded_items(self: SC2WoLWorld, world: MultiWorld, player: int) -> Set
return excluded_items
def assign_starter_items(world: MultiWorld, player: int, excluded_items: Set[str], locked_locations: List[str]):
def assign_starter_items(world: MultiWorld, player: int, excluded_items: Set[str], locked_locations: List[str]) -> List[Item]:
non_local_items = world.non_local_items[player].value
local_basic_unit = tuple(item for item in basic_unit if item not in non_local_items)
@@ -138,12 +140,11 @@ def assign_starter_items(world: MultiWorld, player: int, excluded_items: Set[str
else:
first_location = first_location + ": Victory"
assign_starter_item(world, player, excluded_items, locked_locations, first_location,
local_basic_unit)
return [assign_starter_item(world, player, excluded_items, locked_locations, first_location, local_basic_unit)]
def assign_starter_item(world: MultiWorld, player: int, excluded_items: Set[str], locked_locations: List[str],
location: str, item_list: Tuple[str, ...]):
location: str, item_list: Tuple[str, ...]) -> Item:
item_name = world.random.choice(item_list)
@@ -155,8 +156,11 @@ def assign_starter_item(world: MultiWorld, player: int, excluded_items: Set[str]
locked_locations.append(location)
return item
def get_item_pool(world: MultiWorld, player: int, excluded_items: Set[str]) -> List[Item]:
def get_item_pool(world: MultiWorld, player: int, mission_req_table: dict[str, MissionInfo],
starter_items: List[str], excluded_items: Set[str], location_cache: List[Location]) -> List[Item]:
pool: List[Item] = []
for name, data in item_table.items():
@@ -165,7 +169,12 @@ def get_item_pool(world: MultiWorld, player: int, excluded_items: Set[str]) -> L
item = create_item_with_correct_settings(world, player, name)
pool.append(item)
return pool
existing_items = starter_items + [item.name for item in world.precollected_items[player]]
# For the future: goal items like Artifact Shards go here
locked_items = []
filtered_pool = filter_items(world, player, mission_req_table, location_cache, pool, existing_items, locked_items)
return filtered_pool
def fill_item_pool_with_dummy_items(self: SC2WoLWorld, world: MultiWorld, player: int, locked_locations: List[str],