SC2 WoL - Mod, Item and Location update (#2113)

Migrates SC2 WoL world to the new mod with new items and locations. The new mod has a different architecture making it more future proof (with planned adding of other campaigns). Also gets rid of several old bugs

Adds new short game formats intended for sync games (Tiny Grid, Mini Gauntlet). The final mission isn't decided by campaign length anymore but it's configurable instead. Allow excluding missions for Vanilla Shuffled, corrected some documentation.

NOTE: This is a squashed commit with Salz' HotS excluded (not ready for the release and I plan multi-campaign instead)

---------

Co-authored-by: Matthew <matthew.marinets@gmail.com>
This commit is contained in:
Ziktofel
2023-09-15 02:22:10 +02:00
committed by GitHub
parent 47cf3e06c0
commit 648d682add
68 changed files with 2856 additions and 1468 deletions

View File

@@ -3,11 +3,11 @@ import typing
from typing import List, Set, Tuple, Dict
from BaseClasses import Item, MultiWorld, Location, Tutorial, ItemClassification
from worlds.AutoWorld import WebWorld, World
from .Items import StarcraftWoLItem, item_table, filler_items, item_name_groups, get_full_item_list, \
get_basic_units
from .Locations import get_locations
from .Items import StarcraftWoLItem, filler_items, item_name_groups, get_item_table, get_full_item_list, \
get_basic_units, ItemData, upgrade_included_names, progressive_if_nco
from .Locations import get_locations, LocationType
from .Regions import create_regions
from .Options import sc2wol_options, get_option_value
from .Options import sc2wol_options, get_option_value, LocationInclusion
from .LogicMixin import SC2WoLLogic
from .PoolFilter import filter_missions, filter_items, get_item_upgrades
from .MissionTables import starting_mission_locations, MissionInfo
@@ -36,7 +36,7 @@ class SC2WoLWorld(World):
web = Starcraft2WoLWebWorld()
data_version = 4
item_name_to_id = {name: data.code for name, data in item_table.items()}
item_name_to_id = {name: data.code for name, data in get_full_item_list().items()}
location_name_to_id = {location.name: location.code for location in get_locations(None, None)}
option_definitions = sc2wol_options
@@ -69,6 +69,8 @@ class SC2WoLWorld(World):
starter_items = assign_starter_items(self.multiworld, self.player, excluded_items, self.locked_locations)
filter_locations(self.multiworld, self.player, self.locked_locations, self.location_cache)
pool = get_item_pool(self.multiworld, self.player, self.mission_req_table, starter_items, excluded_items, self.location_cache)
fill_item_pool_with_dummy_items(self, self.multiworld, self.player, self.locked_locations, self.location_cache, pool)
@@ -109,16 +111,6 @@ def setup_events(player: int, locked_locations: typing.List[str], location_cache
def get_excluded_items(multiworld: MultiWorld, player: int) -> Set[str]:
excluded_items: Set[str] = set()
if get_option_value(multiworld, player, "upgrade_bonus") == 1:
excluded_items.add("Ultra-Capacitors")
else:
excluded_items.add("Vanadium Plating")
if get_option_value(multiworld, player, "bunker_upgrade") == 1:
excluded_items.add("Shrike Turret")
else:
excluded_items.add("Fortified Bunker")
for item in multiworld.precollected_items[player]:
excluded_items.add(item.name)
@@ -167,7 +159,7 @@ def assign_starter_item(multiworld: MultiWorld, player: int, excluded_items: Set
def get_item_pool(multiworld: MultiWorld, player: int, mission_req_table: Dict[str, MissionInfo],
starter_items: List[str], excluded_items: Set[str], location_cache: List[Location]) -> List[Item]:
starter_items: List[Item], excluded_items: Set[str], location_cache: List[Location]) -> List[Item]:
pool: List[Item] = []
# For the future: goal items like Artifact Shards go here
@@ -176,17 +168,43 @@ def get_item_pool(multiworld: MultiWorld, player: int, mission_req_table: Dict[s
# YAML items
yaml_locked_items = get_option_value(multiworld, player, 'locked_items')
for name, data in item_table.items():
if name not in excluded_items:
for _ in range(data.quantity):
item = create_item_with_correct_settings(player, name)
if name in yaml_locked_items:
locked_items.append(item)
else:
pool.append(item)
# Adjust generic upgrade availability based on options
include_upgrades = get_option_value(multiworld, player, 'generic_upgrade_missions') == 0
upgrade_items = get_option_value(multiworld, player, 'generic_upgrade_items')
# Include items from outside Wings of Liberty
item_sets = {'wol'}
if get_option_value(multiworld, player, 'nco_items'):
item_sets.add('nco')
if get_option_value(multiworld, player, 'bw_items'):
item_sets.add('bw')
if get_option_value(multiworld, player, 'ext_items'):
item_sets.add('ext')
def allowed_quantity(name: str, data: ItemData) -> int:
if name in excluded_items \
or data.type == "Upgrade" and (not include_upgrades or name not in upgrade_included_names[upgrade_items]) \
or not data.origin.intersection(item_sets):
return 0
elif name in progressive_if_nco and 'nco' not in item_sets:
return 1
else:
return data.quantity
for name, data in get_item_table(multiworld, player).items():
for i in range(allowed_quantity(name, data)):
item = create_item_with_correct_settings(player, name)
if name in yaml_locked_items:
locked_items.append(item)
else:
pool.append(item)
existing_items = starter_items + [item for item in multiworld.precollected_items[player]]
existing_names = [item.name for item in existing_items]
# Check the parent item integrity, exclude items
pool[:] = [item for item in pool if pool_contains_parent(item, pool + locked_items + existing_items)]
# Removing upgrades for excluded items
for item_name in excluded_items:
if item_name in existing_names:
@@ -207,8 +225,100 @@ def fill_item_pool_with_dummy_items(self: SC2WoLWorld, multiworld: MultiWorld, p
def create_item_with_correct_settings(player: int, name: str) -> Item:
data = item_table[name]
data = get_full_item_list()[name]
item = Item(name, data.classification, data.code, player)
return item
def pool_contains_parent(item: Item, pool: [Item]):
item_data = get_full_item_list().get(item.name)
if item_data.parent_item is None:
# The item has not associated parent, the item is valid
return True
parent_item = item_data.parent_item
# Check if the pool contains the parent item
return parent_item in [pool_item.name for pool_item in pool]
def filter_locations(multiworld: MultiWorld, player, locked_locations: List[str], location_cache: List[Location]):
"""
Filters the locations in the world using a trash or Nothing item
:param multiworld:
:param player:
:param locked_locations:
:param location_cache:
:return:
"""
open_locations = [location for location in location_cache if location.item is None]
plando_locations = get_plando_locations(multiworld, player)
mission_progress_locations = get_option_value(multiworld, player, "mission_progress_locations")
bonus_locations = get_option_value(multiworld, player, "bonus_locations")
challenge_locations = get_option_value(multiworld, player, "challenge_locations")
optional_boss_locations = get_option_value(multiworld, player, "optional_boss_locations")
location_data = get_locations(multiworld, player)
for location in open_locations:
# Go through the locations that aren't locked yet (early unit, etc)
if location.name not in plando_locations:
# The location is not plando'd
sc2_location = [sc2_location for sc2_location in location_data if sc2_location.name == location.name][0]
location_type = sc2_location.type
if location_type == LocationType.MISSION_PROGRESS \
and mission_progress_locations != LocationInclusion.option_enabled:
item_name = get_exclusion_item(multiworld, mission_progress_locations)
place_exclusion_item(item_name, location, locked_locations, player)
if location_type == LocationType.BONUS \
and bonus_locations != LocationInclusion.option_enabled:
item_name = get_exclusion_item(multiworld, bonus_locations)
place_exclusion_item(item_name, location, locked_locations, player)
if location_type == LocationType.CHALLENGE \
and challenge_locations != LocationInclusion.option_enabled:
item_name = get_exclusion_item(multiworld, challenge_locations)
place_exclusion_item(item_name, location, locked_locations, player)
if location_type == LocationType.OPTIONAL_BOSS \
and optional_boss_locations != LocationInclusion.option_enabled:
item_name = get_exclusion_item(multiworld, optional_boss_locations)
place_exclusion_item(item_name, location, locked_locations, player)
def place_exclusion_item(item_name, location, locked_locations, player):
item = create_item_with_correct_settings(player, item_name)
location.place_locked_item(item)
locked_locations.append(location.name)
def get_exclusion_item(multiworld: MultiWorld, option) -> str:
"""
Gets the exclusion item according to settings (trash/nothing)
:param multiworld:
:param option:
:return: Item used for location exclusion
"""
if option == LocationInclusion.option_nothing:
return "Nothing"
elif option == LocationInclusion.option_trash:
index = multiworld.random.randint(0, len(filler_items) - 1)
return filler_items[index]
raise Exception(f"Unsupported option type: {option}")
def get_plando_locations(multiworld: MultiWorld, player) -> List[str]:
"""
:param multiworld:
:param player:
:return: A list of locations affected by a plando in a world
"""
plando_locations = []
for plando_setting in multiworld.plando_items[player]:
plando_locations += plando_setting.get("locations", [])
plando_setting_location = plando_setting.get("location", None)
if plando_setting_location is not None:
plando_locations.append(plando_setting_location)
return plando_locations