mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 20:21:32 -06:00
The Wind Waker: Implement New Game (#4458)
Adds The Legend of Zelda: The Wind Waker as a supported game in Archipelago. The game uses [LagoLunatic's randomizer](https://github.com/LagoLunatic/wwrando) as its base (regarding logic, options, etc.) and builds from there.
This commit is contained in:
121
worlds/tww/randomizers/RequiredBosses.py
Normal file
121
worlds/tww/randomizers/RequiredBosses.py
Normal file
@@ -0,0 +1,121 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from Options import OptionError
|
||||
|
||||
from ..Locations import DUNGEON_NAMES, LOCATION_TABLE, TWWFlag, split_location_name_by_zone
|
||||
from ..Options import TWWOptions
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .. import TWWWorld
|
||||
|
||||
|
||||
class RequiredBossesRandomizer:
|
||||
"""
|
||||
This class handles the randomization of the required bosses in The Wind Waker game based on user options.
|
||||
|
||||
If the option is on, the required bosses must be defeated as part of the unlock condition of Puppet Ganon's door.
|
||||
The quadrants in which the bosses are located are marked on the player's Sea Chart.
|
||||
|
||||
:param world: The Wind Waker game world.
|
||||
"""
|
||||
|
||||
def __init__(self, world: "TWWWorld"):
|
||||
self.world = world
|
||||
self.multiworld = world.multiworld
|
||||
|
||||
self.required_boss_item_locations: list[str] = []
|
||||
self.required_dungeons: set[str] = set()
|
||||
self.required_bosses: list[str] = []
|
||||
self.banned_locations: set[str] = set()
|
||||
self.banned_dungeons: set[str] = set()
|
||||
self.banned_bosses: list[str] = []
|
||||
|
||||
def validate_boss_options(self, options: TWWOptions) -> None:
|
||||
"""
|
||||
Validate the user-defined boss options to ensure logical consistency.
|
||||
|
||||
:param options: The game options set by the user.
|
||||
:raises OptionError: If the boss options are inconsistent.
|
||||
"""
|
||||
if not options.progression_dungeons:
|
||||
raise OptionError("You cannot make bosses required when progression dungeons are disabled.")
|
||||
|
||||
if len(options.included_dungeons.value & options.excluded_dungeons.value) != 0:
|
||||
raise OptionError(
|
||||
"A conflict was found in the lists of required and banned dungeons for required bosses mode."
|
||||
)
|
||||
|
||||
def randomize_required_bosses(self) -> None:
|
||||
"""
|
||||
Randomize the required bosses based on user-defined constraints and options.
|
||||
|
||||
:raises OptionError: If the randomization fails to meet user-defined constraints.
|
||||
"""
|
||||
options = self.world.options
|
||||
|
||||
# Validate constraints on required bosses options.
|
||||
self.validate_boss_options(options)
|
||||
|
||||
# If the user enforces a dungeon location to be priority, consider that when selecting required bosses.
|
||||
dungeon_names = set(DUNGEON_NAMES)
|
||||
required_dungeons = options.included_dungeons.value
|
||||
for location_name in options.priority_locations.value:
|
||||
dungeon_name, _ = split_location_name_by_zone(location_name)
|
||||
if dungeon_name in dungeon_names:
|
||||
required_dungeons.add(dungeon_name)
|
||||
|
||||
# Ensure we aren't prioritizing more dungeon locations than the requested number of required bosses.
|
||||
num_required_bosses = options.num_required_bosses
|
||||
if len(required_dungeons) > num_required_bosses:
|
||||
raise OptionError(
|
||||
"Could not select required bosses to satisfy options set by the user. "
|
||||
"There are more dungeons with priority locations than the desired number of required bosses."
|
||||
)
|
||||
|
||||
# Ensure that after removing excluded dungeons, we still have enough to satisfy user options.
|
||||
num_remaining = num_required_bosses - len(required_dungeons)
|
||||
remaining_dungeon_options = dungeon_names - required_dungeons - options.excluded_dungeons.value
|
||||
if len(remaining_dungeon_options) < num_remaining:
|
||||
raise OptionError(
|
||||
"Could not select required bosses to satisfy options set by the user. "
|
||||
"After removing the excluded dungeons, there are not enough to meet the desired number of required "
|
||||
"bosses."
|
||||
)
|
||||
|
||||
# Finish selecting required bosses.
|
||||
required_dungeons.update(self.world.random.sample(sorted(remaining_dungeon_options), num_remaining))
|
||||
|
||||
# Exclude locations that are not in the dungeon of a required boss.
|
||||
banned_dungeons = dungeon_names - required_dungeons
|
||||
for location_name, location_data in LOCATION_TABLE.items():
|
||||
dungeon_name, _ = split_location_name_by_zone(location_name)
|
||||
if dungeon_name in banned_dungeons and TWWFlag.DUNGEON in location_data.flags:
|
||||
self.banned_locations.add(location_name)
|
||||
elif location_name == "Mailbox - Letter from Orca" and "Forbidden Woods" in banned_dungeons:
|
||||
self.banned_locations.add(location_name)
|
||||
elif location_name == "Mailbox - Letter from Baito" and "Earth Temple" in banned_dungeons:
|
||||
self.banned_locations.add(location_name)
|
||||
elif location_name == "Mailbox - Letter from Aryll" and "Forsaken Fortress" in banned_dungeons:
|
||||
self.banned_locations.add(location_name)
|
||||
elif location_name == "Mailbox - Letter from Tingle" and "Forsaken Fortress" in banned_dungeons:
|
||||
self.banned_locations.add(location_name)
|
||||
for location_name in self.banned_locations:
|
||||
self.world.nonprogress_locations.add(location_name)
|
||||
|
||||
# Record the item location names for required bosses.
|
||||
self.required_boss_item_locations = []
|
||||
self.required_bosses = []
|
||||
self.banned_bosses = []
|
||||
possible_boss_item_locations = [loc for loc, data in LOCATION_TABLE.items() if TWWFlag.BOSS in data.flags]
|
||||
for location_name in possible_boss_item_locations:
|
||||
dungeon_name, specific_location_name = split_location_name_by_zone(location_name)
|
||||
assert specific_location_name.endswith(" Heart Container")
|
||||
boss_name = specific_location_name.removesuffix(" Heart Container")
|
||||
|
||||
if dungeon_name in required_dungeons:
|
||||
self.required_boss_item_locations.append(location_name)
|
||||
self.required_bosses.append(boss_name)
|
||||
else:
|
||||
self.banned_bosses.append(boss_name)
|
||||
self.required_dungeons = required_dungeons
|
||||
self.banned_dungeons = banned_dungeons
|
||||
Reference in New Issue
Block a user