forked from mirror/Archipelago
SC2: New Mission Order options for shorter campaigns and smaller item pools
This commit is contained in:
@@ -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"
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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],
|
||||
|
||||
Reference in New Issue
Block a user