599 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			599 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| 
								 | 
							
								import os
							 | 
						||
| 
								 | 
							
								import zipfile
							 | 
						||
| 
								 | 
							
								from base64 import b64encode
							 | 
						||
| 
								 | 
							
								from collections.abc import Mapping
							 | 
						||
| 
								 | 
							
								from typing import Any, ClassVar
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import yaml
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								from BaseClasses import Item
							 | 
						||
| 
								 | 
							
								from BaseClasses import ItemClassification as IC
							 | 
						||
| 
								 | 
							
								from BaseClasses import MultiWorld, Region, Tutorial
							 | 
						||
| 
								 | 
							
								from Options import Toggle
							 | 
						||
| 
								 | 
							
								from worlds.AutoWorld import WebWorld, World
							 | 
						||
| 
								 | 
							
								from worlds.Files import APContainer, AutoPatchRegister
							 | 
						||
| 
								 | 
							
								from worlds.generic.Rules import add_item_rule
							 | 
						||
| 
								 | 
							
								from worlds.LauncherComponents import Component, SuffixIdentifier, Type, components, icon_paths, launch_subprocess
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								from .Items import ISLAND_NUMBER_TO_CHART_NAME, ITEM_TABLE, TWWItem, item_name_groups
							 | 
						||
| 
								 | 
							
								from .Locations import LOCATION_TABLE, TWWFlag, TWWLocation
							 | 
						||
| 
								 | 
							
								from .Options import TWWOptions, tww_option_groups
							 | 
						||
| 
								 | 
							
								from .Presets import tww_options_presets
							 | 
						||
| 
								 | 
							
								from .randomizers.Charts import ISLAND_NUMBER_TO_NAME, ChartRandomizer
							 | 
						||
| 
								 | 
							
								from .randomizers.Dungeons import Dungeon, create_dungeons
							 | 
						||
| 
								 | 
							
								from .randomizers.Entrances import ALL_EXITS, BOSS_EXIT_TO_DUNGEON, MINIBOSS_EXIT_TO_DUNGEON, EntranceRandomizer
							 | 
						||
| 
								 | 
							
								from .randomizers.ItemPool import generate_itempool
							 | 
						||
| 
								 | 
							
								from .randomizers.RequiredBosses import RequiredBossesRandomizer
							 | 
						||
| 
								 | 
							
								from .Rules import set_rules
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								VERSION: tuple[int, int, int] = (3, 0, 0)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def run_client() -> None:
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    Launch the The Wind Waker client.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    print("Running The Wind Waker Client")
							 | 
						||
| 
								 | 
							
								    from .TWWClient import main
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    launch_subprocess(main, name="TheWindWakerClient")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								components.append(
							 | 
						||
| 
								 | 
							
								    Component(
							 | 
						||
| 
								 | 
							
								        "The Wind Waker Client",
							 | 
						||
| 
								 | 
							
								        func=run_client,
							 | 
						||
| 
								 | 
							
								        component_type=Type.CLIENT,
							 | 
						||
| 
								 | 
							
								        file_identifier=SuffixIdentifier(".aptww"),
							 | 
						||
| 
								 | 
							
								        icon="The Wind Waker",
							 | 
						||
| 
								 | 
							
								    )
							 | 
						||
| 
								 | 
							
								)
							 | 
						||
| 
								 | 
							
								icon_paths["The Wind Waker"] = "ap:worlds.tww/assets/icon.png"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class TWWContainer(APContainer, metaclass=AutoPatchRegister):
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    This class defines the container file for The Wind Waker.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    game: str = "The Wind Waker"
							 | 
						||
| 
								 | 
							
								    patch_file_ending: str = ".aptww"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __init__(self, *args: Any, **kwargs: Any) -> None:
							 | 
						||
| 
								 | 
							
								        if "data" in kwargs:
							 | 
						||
| 
								 | 
							
								            self.data = kwargs["data"]
							 | 
						||
| 
								 | 
							
								            del kwargs["data"]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        super().__init__(*args, **kwargs)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def write_contents(self, opened_zipfile: zipfile.ZipFile) -> None:
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        Write the contents of the container file.
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        super().write_contents(opened_zipfile)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Record the data for the game under the key `plando`.
							 | 
						||
| 
								 | 
							
								        opened_zipfile.writestr("plando", b64encode(bytes(yaml.safe_dump(self.data, sort_keys=False), "utf-8")))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class TWWWeb(WebWorld):
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    This class handles the web interface for The Wind Waker.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    The web interface includes the setup guide and the options page for generating YAMLs.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    tutorials = [
							 | 
						||
| 
								 | 
							
								        Tutorial(
							 | 
						||
| 
								 | 
							
								            "Multiworld Setup Guide",
							 | 
						||
| 
								 | 
							
								            "A guide to setting up the Archipelago The Wind Waker software on your computer.",
							 | 
						||
| 
								 | 
							
								            "English",
							 | 
						||
| 
								 | 
							
								            "setup_en.md",
							 | 
						||
| 
								 | 
							
								            "setup/en",
							 | 
						||
| 
								 | 
							
								            ["tanjo3", "Lunix"],
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								    ]
							 | 
						||
| 
								 | 
							
								    theme = "ocean"
							 | 
						||
| 
								 | 
							
								    options_presets = tww_options_presets
							 | 
						||
| 
								 | 
							
								    option_groups = tww_option_groups
							 | 
						||
| 
								 | 
							
								    rich_text_options_doc = True
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class TWWWorld(World):
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    Legend has it that whenever evil has appeared, a hero named Link has arisen to defeat it. The legend continues on
							 | 
						||
| 
								 | 
							
								    the surface of a vast and mysterious sea as Link sets sail in his most epic, awe-inspiring adventure yet. Aided by a
							 | 
						||
| 
								 | 
							
								    magical conductor's baton called the Wind Waker, he will face unimaginable monsters, explore puzzling dungeons, and
							 | 
						||
| 
								 | 
							
								    meet a cast of unforgettable characters as he searches for his kidnapped sister.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    options_dataclass = TWWOptions
							 | 
						||
| 
								 | 
							
								    options: TWWOptions
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    game: ClassVar[str] = "The Wind Waker"
							 | 
						||
| 
								 | 
							
								    topology_present: bool = True
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    item_name_to_id: ClassVar[dict[str, int]] = {
							 | 
						||
| 
								 | 
							
								        name: TWWItem.get_apid(data.code) for name, data in ITEM_TABLE.items() if data.code is not None
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    location_name_to_id: ClassVar[dict[str, int]] = {
							 | 
						||
| 
								 | 
							
								        name: TWWLocation.get_apid(data.code) for name, data in LOCATION_TABLE.items() if data.code is not None
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    item_name_groups: ClassVar[dict[str, set[str]]] = item_name_groups
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    required_client_version: tuple[int, int, int] = (0, 5, 1)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    web: ClassVar[TWWWeb] = TWWWeb()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    origin_region_name: str = "The Great Sea"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    create_items = generate_itempool
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    logic_rematch_bosses_skipped: bool
							 | 
						||
| 
								 | 
							
								    logic_in_swordless_mode: bool
							 | 
						||
| 
								 | 
							
								    logic_in_required_bosses_mode: bool
							 | 
						||
| 
								 | 
							
								    logic_obscure_1: bool
							 | 
						||
| 
								 | 
							
								    logic_obscure_2: bool
							 | 
						||
| 
								 | 
							
								    logic_obscure_3: bool
							 | 
						||
| 
								 | 
							
								    logic_precise_1: bool
							 | 
						||
| 
								 | 
							
								    logic_precise_2: bool
							 | 
						||
| 
								 | 
							
								    logic_precise_3: bool
							 | 
						||
| 
								 | 
							
								    logic_tuner_logic_enabled: bool
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __init__(self, *args, **kwargs):
							 | 
						||
| 
								 | 
							
								        super().__init__(*args, **kwargs)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        self.progress_locations: set[str] = set()
							 | 
						||
| 
								 | 
							
								        self.nonprogress_locations: set[str] = set()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        self.dungeon_local_item_names: set[str] = set()
							 | 
						||
| 
								 | 
							
								        self.dungeon_specific_item_names: set[str] = set()
							 | 
						||
| 
								 | 
							
								        self.dungeons: dict[str, Dungeon] = {}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        self.item_classification_overrides: dict[str, IC] = {}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        self.useful_pool: list[str] = []
							 | 
						||
| 
								 | 
							
								        self.filler_pool: list[str] = []
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        self.charts = ChartRandomizer(self)
							 | 
						||
| 
								 | 
							
								        self.entrances = EntranceRandomizer(self)
							 | 
						||
| 
								 | 
							
								        self.boss_reqs = RequiredBossesRandomizer(self)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _determine_item_classification_overrides(self) -> None:
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        Determine item classification overrides. The classification of an item may be affected by which options are
							 | 
						||
| 
								 | 
							
								        enabled or disabled.
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        options = self.options
							 | 
						||
| 
								 | 
							
								        item_classification_overrides = self.item_classification_overrides
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Override certain items to be filler depending on user options.
							 | 
						||
| 
								 | 
							
								        # TODO: Calculate filler items dynamically
							 | 
						||
| 
								 | 
							
								        override_as_filler = []
							 | 
						||
| 
								 | 
							
								        if not options.progression_dungeons:
							 | 
						||
| 
								 | 
							
								            override_as_filler.extend(item_name_groups["Small Keys"] | item_name_groups["Big Keys"])
							 | 
						||
| 
								 | 
							
								            override_as_filler.extend(("Command Melody", "Earth God's Lyric", "Wind God's Aria"))
							 | 
						||
| 
								 | 
							
								        if not options.progression_short_sidequests:
							 | 
						||
| 
								 | 
							
								            override_as_filler.extend(("Maggie's Letter", "Moblin's Letter"))
							 | 
						||
| 
								 | 
							
								        if not (options.progression_short_sidequests or options.progression_long_sidequests):
							 | 
						||
| 
								 | 
							
								            override_as_filler.append("Progressive Picto Box")
							 | 
						||
| 
								 | 
							
								        if not options.progression_spoils_trading:
							 | 
						||
| 
								 | 
							
								            override_as_filler.append("Spoils Bag")
							 | 
						||
| 
								 | 
							
								        if not options.progression_triforce_charts:
							 | 
						||
| 
								 | 
							
								            override_as_filler.extend(item_name_groups["Triforce Charts"])
							 | 
						||
| 
								 | 
							
								        if not options.progression_treasure_charts:
							 | 
						||
| 
								 | 
							
								            override_as_filler.extend(item_name_groups["Treasure Charts"])
							 | 
						||
| 
								 | 
							
								        if not options.progression_misc:
							 | 
						||
| 
								 | 
							
								            override_as_filler.extend(item_name_groups["Tingle Statues"])
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        for item_name in override_as_filler:
							 | 
						||
| 
								 | 
							
								            item_classification_overrides[item_name] = IC.filler
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Override certain items to be useful depending on user options.
							 | 
						||
| 
								 | 
							
								        # TODO: Calculate useful items dynamically
							 | 
						||
| 
								 | 
							
								        override_as_useful = []
							 | 
						||
| 
								 | 
							
								        if not options.progression_big_octos_gunboats:
							 | 
						||
| 
								 | 
							
								            override_as_useful.append("Quiver Capacity Upgrade")
							 | 
						||
| 
								 | 
							
								        if options.sword_mode in ("swords_optional", "swordless"):
							 | 
						||
| 
								 | 
							
								            override_as_useful.append("Progressive Sword")
							 | 
						||
| 
								 | 
							
								        if not options.enable_tuner_logic:
							 | 
						||
| 
								 | 
							
								            override_as_useful.append("Tingle Tuner")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        for item_name in override_as_useful:
							 | 
						||
| 
								 | 
							
								            item_classification_overrides[item_name] = IC.useful
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _determine_progress_and_nonprogress_locations(self) -> tuple[set[str], set[str]]:
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        Determine which locations are progress and nonprogress in the world based on the player's options.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        :return: A tuple of two sets, the first containing the names of the progress locations and the second containing
							 | 
						||
| 
								 | 
							
								        the names of the nonprogress locations.
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        def add_flag(option: Toggle, flag: TWWFlag) -> TWWFlag:
							 | 
						||
| 
								 | 
							
								            return flag if option else TWWFlag.ALWAYS
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        options = self.options
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        enabled_flags = TWWFlag.ALWAYS
							 | 
						||
| 
								 | 
							
								        enabled_flags |= add_flag(options.progression_dungeons, TWWFlag.DUNGEON | TWWFlag.BOSS)
							 | 
						||
| 
								 | 
							
								        enabled_flags |= add_flag(options.progression_tingle_chests, TWWFlag.TNGL_CT)
							 | 
						||
| 
								 | 
							
								        enabled_flags |= add_flag(options.progression_dungeon_secrets, TWWFlag.DG_SCRT)
							 | 
						||
| 
								 | 
							
								        enabled_flags |= add_flag(options.progression_puzzle_secret_caves, TWWFlag.PZL_CVE)
							 | 
						||
| 
								 | 
							
								        enabled_flags |= add_flag(options.progression_combat_secret_caves, TWWFlag.CBT_CVE)
							 | 
						||
| 
								 | 
							
								        enabled_flags |= add_flag(options.progression_savage_labyrinth, TWWFlag.SAVAGE)
							 | 
						||
| 
								 | 
							
								        enabled_flags |= add_flag(options.progression_great_fairies, TWWFlag.GRT_FRY)
							 | 
						||
| 
								 | 
							
								        enabled_flags |= add_flag(options.progression_short_sidequests, TWWFlag.SHRT_SQ)
							 | 
						||
| 
								 | 
							
								        enabled_flags |= add_flag(options.progression_long_sidequests, TWWFlag.LONG_SQ)
							 | 
						||
| 
								 | 
							
								        enabled_flags |= add_flag(options.progression_spoils_trading, TWWFlag.SPOILS)
							 | 
						||
| 
								 | 
							
								        enabled_flags |= add_flag(options.progression_minigames, TWWFlag.MINIGME)
							 | 
						||
| 
								 | 
							
								        enabled_flags |= add_flag(options.progression_battlesquid, TWWFlag.SPLOOSH)
							 | 
						||
| 
								 | 
							
								        enabled_flags |= add_flag(options.progression_free_gifts, TWWFlag.FREE_GF)
							 | 
						||
| 
								 | 
							
								        enabled_flags |= add_flag(options.progression_mail, TWWFlag.MAILBOX)
							 | 
						||
| 
								 | 
							
								        enabled_flags |= add_flag(options.progression_platforms_rafts, TWWFlag.PLTFRMS)
							 | 
						||
| 
								 | 
							
								        enabled_flags |= add_flag(options.progression_submarines, TWWFlag.SUBMRIN)
							 | 
						||
| 
								 | 
							
								        enabled_flags |= add_flag(options.progression_eye_reef_chests, TWWFlag.EYE_RFS)
							 | 
						||
| 
								 | 
							
								        enabled_flags |= add_flag(options.progression_big_octos_gunboats, TWWFlag.BG_OCTO)
							 | 
						||
| 
								 | 
							
								        enabled_flags |= add_flag(options.progression_expensive_purchases, TWWFlag.XPENSVE)
							 | 
						||
| 
								 | 
							
								        enabled_flags |= add_flag(options.progression_island_puzzles, TWWFlag.ISLND_P)
							 | 
						||
| 
								 | 
							
								        enabled_flags |= add_flag(options.progression_misc, TWWFlag.MISCELL)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        progress_locations: set[str] = set()
							 | 
						||
| 
								 | 
							
								        nonprogress_locations: set[str] = set()
							 | 
						||
| 
								 | 
							
								        for location, data in LOCATION_TABLE.items():
							 | 
						||
| 
								 | 
							
								            if data.flags & enabled_flags == data.flags:
							 | 
						||
| 
								 | 
							
								                progress_locations.add(location)
							 | 
						||
| 
								 | 
							
								            else:
							 | 
						||
| 
								 | 
							
								                nonprogress_locations.add(location)
							 | 
						||
| 
								 | 
							
								        assert progress_locations.isdisjoint(nonprogress_locations)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return progress_locations, nonprogress_locations
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @staticmethod
							 | 
						||
| 
								 | 
							
								    def _get_classification_name(classification: IC) -> str:
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        Return a string representation of the item's highest-order classification.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        :param classification: The item's classification.
							 | 
						||
| 
								 | 
							
								        :return: A string representation of the item's highest classification. The order of classification is
							 | 
						||
| 
								 | 
							
								        progression > trap > useful > filler.
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if IC.progression in classification:
							 | 
						||
| 
								 | 
							
								            return "progression"
							 | 
						||
| 
								 | 
							
								        elif IC.trap in classification:
							 | 
						||
| 
								 | 
							
								            return "trap"
							 | 
						||
| 
								 | 
							
								        elif IC.useful in classification:
							 | 
						||
| 
								 | 
							
								            return "useful"
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            return "filler"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def generate_early(self) -> None:
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        Run before any general steps of the MultiWorld other than options.
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        options = self.options
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Only randomize secret cave inner entrances if both puzzle secret caves and combat secret caves are enabled.
							 | 
						||
| 
								 | 
							
								        if not (options.progression_puzzle_secret_caves and options.progression_combat_secret_caves):
							 | 
						||
| 
								 | 
							
								            options.randomize_secret_cave_inner_entrances.value = False
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Determine which locations are progression and which are not from options.
							 | 
						||
| 
								 | 
							
								        self.progress_locations, self.nonprogress_locations = self._determine_progress_and_nonprogress_locations()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        for dungeon_item in ["randomize_smallkeys", "randomize_bigkeys", "randomize_mapcompass"]:
							 | 
						||
| 
								 | 
							
								            option = getattr(options, dungeon_item)
							 | 
						||
| 
								 | 
							
								            if option == "local":
							 | 
						||
| 
								 | 
							
								                options.local_items.value |= self.item_name_groups[option.item_name_group]
							 | 
						||
| 
								 | 
							
								            elif option.in_dungeon:
							 | 
						||
| 
								 | 
							
								                self.dungeon_local_item_names |= self.item_name_groups[option.item_name_group]
							 | 
						||
| 
								 | 
							
								                if option == "dungeon":
							 | 
						||
| 
								 | 
							
								                    self.dungeon_specific_item_names |= self.item_name_groups[option.item_name_group]
							 | 
						||
| 
								 | 
							
								                else:
							 | 
						||
| 
								 | 
							
								                    options.local_items.value |= self.dungeon_local_item_names
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Resolve logic options and set them onto the world instance for faster lookup in logic rules.
							 | 
						||
| 
								 | 
							
								        self.logic_rematch_bosses_skipped = bool(options.skip_rematch_bosses.value)
							 | 
						||
| 
								 | 
							
								        self.logic_in_swordless_mode = options.sword_mode in ("swords_optional", "swordless")
							 | 
						||
| 
								 | 
							
								        self.logic_in_required_bosses_mode = bool(options.required_bosses.value)
							 | 
						||
| 
								 | 
							
								        self.logic_obscure_3 = options.logic_obscurity == "very_hard"
							 | 
						||
| 
								 | 
							
								        self.logic_obscure_2 = self.logic_obscure_3 or options.logic_obscurity == "hard"
							 | 
						||
| 
								 | 
							
								        self.logic_obscure_1 = self.logic_obscure_2 or options.logic_obscurity == "normal"
							 | 
						||
| 
								 | 
							
								        self.logic_precise_3 = options.logic_precision == "very_hard"
							 | 
						||
| 
								 | 
							
								        self.logic_precise_2 = self.logic_precise_3 or options.logic_precision == "hard"
							 | 
						||
| 
								 | 
							
								        self.logic_precise_1 = self.logic_precise_2 or options.logic_precision == "normal"
							 | 
						||
| 
								 | 
							
								        self.logic_tuner_logic_enabled = bool(options.enable_tuner_logic.value)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Determine any item classification overrides based on user options.
							 | 
						||
| 
								 | 
							
								        self._determine_item_classification_overrides()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def create_regions(self) -> None:
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        Create and connect regions for the The Wind Waker world.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        This method first randomizes the charts and picks the required bosses if these options are enabled.
							 | 
						||
| 
								 | 
							
								        It then loops through all the world's progress locations and creates the locations, assigning dungeon locations
							 | 
						||
| 
								 | 
							
								        to their respective dungeons.
							 | 
						||
| 
								 | 
							
								        Finally, the flags for sunken treasure locations are updated as appropriate, and the entrances are randomized
							 | 
						||
| 
								 | 
							
								        if that option is enabled.
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        multiworld = self.multiworld
							 | 
						||
| 
								 | 
							
								        player = self.player
							 | 
						||
| 
								 | 
							
								        options = self.options
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # "The Great Sea" region contains all locations that are not in a randomizable region.
							 | 
						||
| 
								 | 
							
								        great_sea_region = Region("The Great Sea", player, multiworld)
							 | 
						||
| 
								 | 
							
								        multiworld.regions.append(great_sea_region)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Add all randomizable regions.
							 | 
						||
| 
								 | 
							
								        for _exit in ALL_EXITS:
							 | 
						||
| 
								 | 
							
								            multiworld.regions.append(Region(_exit.unique_name, player, multiworld))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Set up sunken treasure locations, randomizing the charts if necessary.
							 | 
						||
| 
								 | 
							
								        self.charts.setup_progress_sunken_treasure_locations()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Select required bosses.
							 | 
						||
| 
								 | 
							
								        if options.required_bosses:
							 | 
						||
| 
								 | 
							
								            self.boss_reqs.randomize_required_bosses()
							 | 
						||
| 
								 | 
							
								            self.progress_locations -= self.boss_reqs.banned_locations
							 | 
						||
| 
								 | 
							
								            self.nonprogress_locations |= self.boss_reqs.banned_locations
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Create the dungeon classes.
							 | 
						||
| 
								 | 
							
								        create_dungeons(self)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Assign each location to their region.
							 | 
						||
| 
								 | 
							
								        # Progress locations are sorted for deterministic results.
							 | 
						||
| 
								 | 
							
								        for location_name in sorted(self.progress_locations):
							 | 
						||
| 
								 | 
							
								            data = LOCATION_TABLE[location_name]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            region = self.get_region(data.region)
							 | 
						||
| 
								 | 
							
								            location = TWWLocation(player, location_name, region, data)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            # Additionally, assign dungeon locations to the appropriate dungeon.
							 | 
						||
| 
								 | 
							
								            if region.name in self.dungeons:
							 | 
						||
| 
								 | 
							
								                location.dungeon = self.dungeons[region.name]
							 | 
						||
| 
								 | 
							
								            elif region.name in MINIBOSS_EXIT_TO_DUNGEON and not options.randomize_miniboss_entrances:
							 | 
						||
| 
								 | 
							
								                location.dungeon = self.dungeons[MINIBOSS_EXIT_TO_DUNGEON[region.name]]
							 | 
						||
| 
								 | 
							
								            elif region.name in BOSS_EXIT_TO_DUNGEON and not options.randomize_boss_entrances:
							 | 
						||
| 
								 | 
							
								                location.dungeon = self.dungeons[BOSS_EXIT_TO_DUNGEON[region.name]]
							 | 
						||
| 
								 | 
							
								            elif location.name in [
							 | 
						||
| 
								 | 
							
								                "Forsaken Fortress - Phantom Ganon",
							 | 
						||
| 
								 | 
							
								                "Forsaken Fortress - Chest Outside Upper Jail Cell",
							 | 
						||
| 
								 | 
							
								                "Forsaken Fortress - Chest Inside Lower Jail Cell",
							 | 
						||
| 
								 | 
							
								                "Forsaken Fortress - Chest Guarded By Bokoblin",
							 | 
						||
| 
								 | 
							
								                "Forsaken Fortress - Chest on Bed",
							 | 
						||
| 
								 | 
							
								            ]:
							 | 
						||
| 
								 | 
							
								                location.dungeon = self.dungeons["Forsaken Fortress"]
							 | 
						||
| 
								 | 
							
								            region.locations.append(location)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Correct the flags of the sunken treasure locations if the charts are randomized.
							 | 
						||
| 
								 | 
							
								        self.charts.update_chart_location_flags()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Connect the regions in the multiworld. Randomize entrances to exits if the option is set.
							 | 
						||
| 
								 | 
							
								        self.entrances.randomize_entrances()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def set_rules(self) -> None:
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        Set access and item rules on locations.
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        # Set the access rules for all progression locations.
							 | 
						||
| 
								 | 
							
								        set_rules(self)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Ban the Bait Bag slot from having bait.
							 | 
						||
| 
								 | 
							
								        # Beedle's shop does not work correctly if the same item is in multiple slots in the same shop.
							 | 
						||
| 
								 | 
							
								        if "The Great Sea - Beedle's Shop Ship - 20 Rupee Item" in self.progress_locations:
							 | 
						||
| 
								 | 
							
								            beedle_20 = self.get_location("The Great Sea - Beedle's Shop Ship - 20 Rupee Item")
							 | 
						||
| 
								 | 
							
								            add_item_rule(beedle_20, lambda item: item.name not in ["All-Purpose Bait", "Hyoi Pear"])
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # For the same reason, the same item should not appear more than once on the Rock Spire Isle shop ship.
							 | 
						||
| 
								 | 
							
								        # All non-TWW items use the same item (Father's Letter), so at most one non-TWW item can appear in the shop.
							 | 
						||
| 
								 | 
							
								        # The rest must be (unique, but not necessarily local) TWW items.
							 | 
						||
| 
								 | 
							
								        locations = [f"Rock Spire Isle - Beedle's Special Shop Ship - {v} Rupee Item" for v in [500, 950, 900]]
							 | 
						||
| 
								 | 
							
								        if all(loc in self.progress_locations for loc in locations):
							 | 
						||
| 
								 | 
							
								            rock_spire_shop_ship_locations = [self.get_location(location_name) for location_name in locations]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            for i in range(len(rock_spire_shop_ship_locations)):
							 | 
						||
| 
								 | 
							
								                curr_loc = rock_spire_shop_ship_locations[i]
							 | 
						||
| 
								 | 
							
								                other_locs = rock_spire_shop_ship_locations[:i] + rock_spire_shop_ship_locations[i + 1:]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                add_item_rule(
							 | 
						||
| 
								 | 
							
								                    curr_loc,
							 | 
						||
| 
								 | 
							
								                    lambda item, locations=other_locs: (
							 | 
						||
| 
								 | 
							
								                        item.game == "The Wind Waker"
							 | 
						||
| 
								 | 
							
								                        and all(location.item is None or item.name != location.item.name for location in locations)
							 | 
						||
| 
								 | 
							
								                    )
							 | 
						||
| 
								 | 
							
								                    or (
							 | 
						||
| 
								 | 
							
								                        item.game != "The Wind Waker"
							 | 
						||
| 
								 | 
							
								                        and all(
							 | 
						||
| 
								 | 
							
								                            location.item is None or location.item.game == "The Wind Waker" for location in locations
							 | 
						||
| 
								 | 
							
								                        )
							 | 
						||
| 
								 | 
							
								                    ),
							 | 
						||
| 
								 | 
							
								                )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @classmethod
							 | 
						||
| 
								 | 
							
								    def stage_set_rules(cls, multiworld: MultiWorld) -> None:
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        Class method used to modify the rules for The Wind Waker dungeon locations.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        :param multiworld: The MultiWorld.
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        from .randomizers.Dungeons import modify_dungeon_location_rules
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Set additional rules on dungeon locations as necessary.
							 | 
						||
| 
								 | 
							
								        modify_dungeon_location_rules(multiworld)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @classmethod
							 | 
						||
| 
								 | 
							
								    def stage_pre_fill(cls, multiworld: MultiWorld) -> None:
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        Class method used to correctly place dungeon items for The Wind Waker worlds.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        :param multiworld: The MultiWorld.
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        from .randomizers.Dungeons import fill_dungeons_restrictive
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        fill_dungeons_restrictive(multiworld)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def generate_output(self, output_directory: str) -> None:
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        Create the output APTWW file that is used to randomize the ISO.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        :param output_directory: The output directory for the APTWW file.
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        multiworld = self.multiworld
							 | 
						||
| 
								 | 
							
								        player = self.player
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Determine the current arrangement for charts.
							 | 
						||
| 
								 | 
							
								        # Create a list where the original island number is the index, and the value is the new island number.
							 | 
						||
| 
								 | 
							
								        # Without randomized charts, this array would be just an ordered list of the numbers 1 to 49.
							 | 
						||
| 
								 | 
							
								        # With randomized charts, the new island number is where the chart for the original island now leads.
							 | 
						||
| 
								 | 
							
								        chart_name_to_island_number = {
							 | 
						||
| 
								 | 
							
								            chart_name: island_number for island_number, chart_name in self.charts.island_number_to_chart_name.items()
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        charts_mapping: list[int] = []
							 | 
						||
| 
								 | 
							
								        for i in range(1, 49 + 1):
							 | 
						||
| 
								 | 
							
								            original_chart_name = ISLAND_NUMBER_TO_CHART_NAME[i]
							 | 
						||
| 
								 | 
							
								            new_island_number = chart_name_to_island_number[original_chart_name]
							 | 
						||
| 
								 | 
							
								            charts_mapping.append(new_island_number)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Output seed name and slot number to seed RNG in randomizer client.
							 | 
						||
| 
								 | 
							
								        output_data = {
							 | 
						||
| 
								 | 
							
								            "Version": list(VERSION),
							 | 
						||
| 
								 | 
							
								            "Seed": multiworld.seed_name,
							 | 
						||
| 
								 | 
							
								            "Slot": player,
							 | 
						||
| 
								 | 
							
								            "Name": self.player_name,
							 | 
						||
| 
								 | 
							
								            "Options": self.options.as_dict(*self.options_dataclass.type_hints),
							 | 
						||
| 
								 | 
							
								            "Required Bosses": self.boss_reqs.required_boss_item_locations,
							 | 
						||
| 
								 | 
							
								            "Locations": {},
							 | 
						||
| 
								 | 
							
								            "Entrances": {},
							 | 
						||
| 
								 | 
							
								            "Charts": charts_mapping,
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Output which item has been placed at each location.
							 | 
						||
| 
								 | 
							
								        output_locations = output_data["Locations"]
							 | 
						||
| 
								 | 
							
								        locations = multiworld.get_locations(player)
							 | 
						||
| 
								 | 
							
								        for location in locations:
							 | 
						||
| 
								 | 
							
								            if location.name != "Defeat Ganondorf":
							 | 
						||
| 
								 | 
							
								                if location.item:
							 | 
						||
| 
								 | 
							
								                    item_info = {
							 | 
						||
| 
								 | 
							
								                        "player": location.item.player,
							 | 
						||
| 
								 | 
							
								                        "name": location.item.name,
							 | 
						||
| 
								 | 
							
								                        "game": location.item.game,
							 | 
						||
| 
								 | 
							
								                        "classification": self._get_classification_name(location.item.classification),
							 | 
						||
| 
								 | 
							
								                    }
							 | 
						||
| 
								 | 
							
								                else:
							 | 
						||
| 
								 | 
							
								                    item_info = {"name": "Nothing", "game": "The Wind Waker", "classification": "filler"}
							 | 
						||
| 
								 | 
							
								                output_locations[location.name] = item_info
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Output the mapping of entrances to exits.
							 | 
						||
| 
								 | 
							
								        output_entrances = output_data["Entrances"]
							 | 
						||
| 
								 | 
							
								        for zone_entrance, zone_exit in self.entrances.done_entrances_to_exits.items():
							 | 
						||
| 
								 | 
							
								            output_entrances[zone_entrance.entrance_name] = zone_exit.unique_name
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Output the plando details to file.
							 | 
						||
| 
								 | 
							
								        aptww = TWWContainer(
							 | 
						||
| 
								 | 
							
								            path=os.path.join(
							 | 
						||
| 
								 | 
							
								                output_directory, f"{multiworld.get_out_file_name_base(player)}{TWWContainer.patch_file_ending}"
							 | 
						||
| 
								 | 
							
								            ),
							 | 
						||
| 
								 | 
							
								            player=player,
							 | 
						||
| 
								 | 
							
								            player_name=self.player_name,
							 | 
						||
| 
								 | 
							
								            data=output_data,
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								        aptww.write()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def extend_hint_information(self, hint_data: dict[int, dict[int, str]]) -> None:
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        Fill in additional information text into locations, displayed when hinted.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        :param hint_data: A dictionary of mapping a player ID to a dictionary mapping location IDs to the extra hint
							 | 
						||
| 
								 | 
							
								        information text. This dictionary should be modified as a side-effect of this method.
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        # Create a mapping of island names to numbers for sunken treasure hints.
							 | 
						||
| 
								 | 
							
								        island_name_to_number = {v: k for k, v in ISLAND_NUMBER_TO_NAME.items()}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        hint_data[self.player] = {}
							 | 
						||
| 
								 | 
							
								        for location in self.multiworld.get_locations(self.player):
							 | 
						||
| 
								 | 
							
								            if location.address is not None and location.item is not None:
							 | 
						||
| 
								 | 
							
								                # Regardless of ER settings, always hint at the outermost entrance for every "interior" location.
							 | 
						||
| 
								 | 
							
								                zone_exit = self.entrances.get_zone_exit_for_item_location(location.name)
							 | 
						||
| 
								 | 
							
								                if zone_exit is not None:
							 | 
						||
| 
								 | 
							
								                    outermost_entrance = self.entrances.get_outermost_entrance_for_exit(zone_exit)
							 | 
						||
| 
								 | 
							
								                    assert outermost_entrance is not None and outermost_entrance.island_name is not None
							 | 
						||
| 
								 | 
							
								                    hint_data[self.player][location.address] = outermost_entrance.island_name
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                # Hint at which chart leads to the sunken treasure for these locations.
							 | 
						||
| 
								 | 
							
								                if location.name.endswith(" - Sunken Treasure"):
							 | 
						||
| 
								 | 
							
								                    island_name = location.name.removesuffix(" - Sunken Treasure")
							 | 
						||
| 
								 | 
							
								                    island_number = island_name_to_number[island_name]
							 | 
						||
| 
								 | 
							
								                    chart_name = self.charts.island_number_to_chart_name[island_number]
							 | 
						||
| 
								 | 
							
								                    hint_data[self.player][location.address] = chart_name
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def create_item(self, name: str) -> TWWItem:
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        Create an item for this world type and player.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        :param name: The name of the item to create.
							 | 
						||
| 
								 | 
							
								        :raises KeyError: If an invalid item name is provided.
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        if name in ITEM_TABLE:
							 | 
						||
| 
								 | 
							
								            return TWWItem(name, self.player, ITEM_TABLE[name], self.item_classification_overrides.get(name))
							 | 
						||
| 
								 | 
							
								        raise KeyError(f"Invalid item name: {name}")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def get_filler_item_name(self, strict: bool = True) -> str:
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        This method is called when the item pool needs to be filled with additional items to match the location count.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        :param strict: Whether the item should be one strictly classified as filler. Defaults to `True`.
							 | 
						||
| 
								 | 
							
								        :return: The name of a filler item from this world.
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        # If there are still useful items to place, place those first.
							 | 
						||
| 
								 | 
							
								        if not strict and len(self.useful_pool) > 0:
							 | 
						||
| 
								 | 
							
								            return self.useful_pool.pop()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # If there are still vanilla filler items to place, place those first.
							 | 
						||
| 
								 | 
							
								        if len(self.filler_pool) > 0:
							 | 
						||
| 
								 | 
							
								            return self.filler_pool.pop()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Use the same weights for filler items used in the base randomizer.
							 | 
						||
| 
								 | 
							
								        filler_consumables = ["Yellow Rupee", "Red Rupee", "Purple Rupee", "Joy Pendant"]
							 | 
						||
| 
								 | 
							
								        filler_weights = [3, 7, 10, 3]
							 | 
						||
| 
								 | 
							
								        if not strict:
							 | 
						||
| 
								 | 
							
								            filler_consumables.append("Orange Rupee")
							 | 
						||
| 
								 | 
							
								            filler_weights.append(15)
							 | 
						||
| 
								 | 
							
								        return self.multiworld.random.choices(filler_consumables, weights=filler_weights, k=1)[0]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def get_pre_fill_items(self) -> list[Item]:
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        Return items that need to be collected when creating a fresh `all_state` but don't exist in the multiworld's
							 | 
						||
| 
								 | 
							
								        item pool.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        :return: A list of pre-fill items.
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        res = []
							 | 
						||
| 
								 | 
							
								        if self.dungeon_local_item_names:
							 | 
						||
| 
								 | 
							
								            for dungeon in self.dungeons.values():
							 | 
						||
| 
								 | 
							
								                for item in dungeon.all_items:
							 | 
						||
| 
								 | 
							
								                    if item.name in self.dungeon_local_item_names:
							 | 
						||
| 
								 | 
							
								                        res.append(item)
							 | 
						||
| 
								 | 
							
								        return res
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def fill_slot_data(self) -> Mapping[str, Any]:
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        Return the `slot_data` field that will be in the `Connected` network package.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        This is a way the generator can give custom data to the client.
							 | 
						||
| 
								 | 
							
								        The client will receive this as JSON in the `Connected` response.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        :return: A dictionary to be sent to the client when it connects to the server.
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        slot_data = self.options.as_dict(*self.options_dataclass.type_hints)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Add entrances to `slot_data`. This is the same data that is written to the .aptww file.
							 | 
						||
| 
								 | 
							
								        entrances = {
							 | 
						||
| 
								 | 
							
								            zone_entrance.entrance_name: zone_exit.unique_name
							 | 
						||
| 
								 | 
							
								            for zone_entrance, zone_exit in self.entrances.done_entrances_to_exits.items()
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        slot_data["entrances"] = entrances
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return slot_data
							 |