| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  | import binascii | 
					
						
							| 
									
										
										
										
											2024-06-17 22:48:15 -04:00
										 |  |  | import dataclasses | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  | import os | 
					
						
							| 
									
										
										
										
											2023-11-25 15:07:02 -06:00
										 |  |  | import typing | 
					
						
							| 
									
										
										
										
											2025-03-07 19:24:58 -05:00
										 |  |  | import logging | 
					
						
							| 
									
										
										
										
											2024-12-13 16:49:30 -05:00
										 |  |  | import re | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-25 15:07:02 -06:00
										 |  |  | import settings | 
					
						
							| 
									
										
										
										
											2025-07-26 16:16:00 -04:00
										 |  |  | from BaseClasses import CollectionState, Entrance, Item, ItemClassification, Location, Tutorial | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  | from Fill import fill_restrictive | 
					
						
							|  |  |  | from worlds.AutoWorld import WebWorld, World | 
					
						
							|  |  |  | from .Common import * | 
					
						
							| 
									
										
										
										
											2024-12-13 16:49:30 -05:00
										 |  |  | from . import ItemIconGuessing | 
					
						
							| 
									
										
										
										
											2023-11-25 15:07:02 -06:00
										 |  |  | from .Items import (DungeonItemData, DungeonItemType, ItemName, LinksAwakeningItem, TradeItemData, | 
					
						
							| 
									
										
										
										
											2024-12-05 06:06:52 -05:00
										 |  |  |                     ladxr_item_to_la_item_name, links_awakening_items, links_awakening_items_by_name, | 
					
						
							|  |  |  |                     links_awakening_item_name_groups) | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  | from .LADXR.itempool import ItemPool as LADXRItemPool | 
					
						
							| 
									
										
										
										
											2023-11-25 15:07:02 -06:00
										 |  |  | from .LADXR.locations.constants import CHEST_ITEMS | 
					
						
							|  |  |  | from .LADXR.locations.instrument import Instrument | 
					
						
							| 
									
										
										
										
											2024-06-17 22:48:15 -04:00
										 |  |  | from .LADXR.logic import Logic as LADXRLogic | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  | from .LADXR.settings import Settings as LADXRSettings | 
					
						
							|  |  |  | from .LADXR.worldSetup import WorldSetup as LADXRWorldSetup | 
					
						
							|  |  |  | from .Locations import (LinksAwakeningLocation, LinksAwakeningRegion, | 
					
						
							| 
									
										
										
										
											2024-12-05 06:06:52 -05:00
										 |  |  |                         create_regions_from_ladxr, get_locations_to_id, | 
					
						
							|  |  |  |                         links_awakening_location_name_groups) | 
					
						
							| 
									
										
										
										
											2024-06-19 08:40:10 +02:00
										 |  |  | from .Options import DungeonItemShuffle, ShuffleInstruments, LinksAwakeningOptions, ladx_option_groups | 
					
						
							| 
									
										
										
										
											2025-07-26 16:16:00 -04:00
										 |  |  | from .Rom import LADXProcedurePatch, write_patch_data | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  | DEVELOPER_MODE = False | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-29 01:17:30 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-05 22:39:35 +02:00
										 |  |  | class LinksAwakeningSettings(settings.Group): | 
					
						
							|  |  |  |     class RomFile(settings.UserFilePath): | 
					
						
							|  |  |  |         """File name of the Link's Awakening DX rom""" | 
					
						
							|  |  |  |         copy_to = "Legend of Zelda, The - Link's Awakening DX (USA, Europe) (SGB Enhanced).gbc" | 
					
						
							|  |  |  |         description = "LADX ROM File" | 
					
						
							| 
									
										
										
										
											2025-07-26 16:16:00 -04:00
										 |  |  |         md5s = [LADXProcedurePatch.hash] | 
					
						
							| 
									
										
										
										
											2023-07-05 22:39:35 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-09 06:17:24 -07:00
										 |  |  |     class RomStart(str): | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         Set this to false to never autostart a rom (such as after patching) | 
					
						
							|  |  |  |                     true  for operating system default program | 
					
						
							|  |  |  |         Alternatively, a path to a program to open the .gbc file with | 
					
						
							|  |  |  |         Examples: | 
					
						
							|  |  |  |            Retroarch: | 
					
						
							|  |  |  |         rom_start: "C:/RetroArch-Win64/retroarch.exe -L sameboy" | 
					
						
							|  |  |  |            BizHawk: | 
					
						
							|  |  |  |         rom_start: "C:/BizHawk-2.9-win-x64/EmuHawk.exe --lua=data/lua/connector_ladx_bizhawk.lua" | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     class DisplayMsgs(settings.Bool): | 
					
						
							|  |  |  |         """Display message inside of Bizhawk""" | 
					
						
							| 
									
										
										
										
											2023-07-05 22:39:35 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-26 16:16:00 -04:00
										 |  |  |     class GfxModFile(settings.FilePath): | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         Gfxmod file, get it from upstream: https://github.com/daid/LADXR/tree/master/gfx | 
					
						
							|  |  |  |         Only .bin or .bdiff files | 
					
						
							|  |  |  |         The same directory will be checked for a matching text modification file | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-09 06:17:24 -07:00
										 |  |  |     rom_file: RomFile = RomFile(RomFile.copy_to) | 
					
						
							|  |  |  |     rom_start: typing.Union[RomStart, bool] = True | 
					
						
							| 
									
										
										
										
											2025-07-26 16:16:00 -04:00
										 |  |  |     gfx_mod_file: GfxModFile = GfxModFile() | 
					
						
							| 
									
										
										
										
											2023-07-05 22:39:35 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  | class LinksAwakeningWebWorld(WebWorld): | 
					
						
							|  |  |  |     tutorials = [Tutorial( | 
					
						
							|  |  |  |         "Multiworld Setup Guide", | 
					
						
							|  |  |  |         "A guide to setting up Links Awakening DX for MultiWorld.", | 
					
						
							|  |  |  |         "English", | 
					
						
							|  |  |  |         "setup_en.md", | 
					
						
							|  |  |  |         "setup/en", | 
					
						
							|  |  |  |         ["zig"] | 
					
						
							|  |  |  |     )] | 
					
						
							|  |  |  |     theme = "dirt" | 
					
						
							| 
									
										
										
										
											2024-06-19 08:40:10 +02:00
										 |  |  |     option_groups = ladx_option_groups | 
					
						
							| 
									
										
										
										
											2024-12-05 06:06:52 -05:00
										 |  |  |     options_presets: typing.Dict[str, typing.Dict[str, typing.Any]] = { | 
					
						
							|  |  |  |         "Keysanity": { | 
					
						
							|  |  |  |             "shuffle_nightmare_keys": "any_world", | 
					
						
							|  |  |  |             "shuffle_small_keys": "any_world", | 
					
						
							|  |  |  |             "shuffle_maps": "any_world", | 
					
						
							|  |  |  |             "shuffle_compasses": "any_world", | 
					
						
							|  |  |  |             "shuffle_stone_beaks": "any_world", | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-07-05 22:39:35 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  | class LinksAwakeningWorld(World): | 
					
						
							| 
									
										
										
										
											2023-03-23 13:22:42 -07:00
										 |  |  |     """
 | 
					
						
							|  |  |  |     After a previous adventure, Link is stranded on Koholint Island, full of mystery and familiar faces. | 
					
						
							|  |  |  |     Gather the 8 Instruments of the Sirens to wake the Wind Fish, so that Link can go home! | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2023-05-29 01:17:30 +02:00
										 |  |  |     game = LINKS_AWAKENING  # name of the game/world | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  |     web = LinksAwakeningWebWorld() | 
					
						
							| 
									
										
										
										
											2024-06-17 22:48:15 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |     options_dataclass = LinksAwakeningOptions | 
					
						
							|  |  |  |     options: LinksAwakeningOptions | 
					
						
							| 
									
										
										
										
											2023-07-05 22:39:35 +02:00
										 |  |  |     settings: typing.ClassVar[LinksAwakeningSettings] | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  |     topology_present = True  # show path to required location checks in spoiler | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # ID of first item and location, could be hard-coded but code may be easier | 
					
						
							|  |  |  |     # to read with this as a propery. | 
					
						
							|  |  |  |     base_id = BASE_ID | 
					
						
							|  |  |  |     # Instead of dynamic numbering, IDs could be part of data. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # The following two dicts are required for the generation to know which | 
					
						
							|  |  |  |     # items exist. They could be generated from json or something else. They can | 
					
						
							|  |  |  |     # include events, but don't have to since events will be placed manually. | 
					
						
							|  |  |  |     item_name_to_id = { | 
					
						
							|  |  |  |         item.item_name : BASE_ID + item.item_id for item in links_awakening_items | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     item_name_to_data = links_awakening_items_by_name | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     location_name_to_id = get_locations_to_id() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Items can be grouped using their names to allow easy checking if any item | 
					
						
							|  |  |  |     # from that group has been collected. Group names can also be used for !hint | 
					
						
							| 
									
										
										
										
											2024-12-05 06:06:52 -05:00
										 |  |  |     item_name_groups = links_awakening_item_name_groups | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     location_name_groups = links_awakening_location_name_groups | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  |     prefill_dungeon_items = None | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-17 22:48:15 -04:00
										 |  |  |     ladxr_settings: LADXRSettings | 
					
						
							|  |  |  |     ladxr_logic: LADXRLogic | 
					
						
							|  |  |  |     ladxr_itempool: LADXRItemPool | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     multi_key: bytearray | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-29 01:17:30 +02:00
										 |  |  |     rupees = { | 
					
						
							| 
									
										
										
										
											2023-07-04 10:33:33 -07:00
										 |  |  |         ItemName.RUPEES_20: 20, | 
					
						
							|  |  |  |         ItemName.RUPEES_50: 50, | 
					
						
							| 
									
										
										
										
											2023-05-29 01:17:30 +02:00
										 |  |  |         ItemName.RUPEES_100: 100, | 
					
						
							|  |  |  |         ItemName.RUPEES_200: 200, | 
					
						
							|  |  |  |         ItemName.RUPEES_500: 500, | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  |     def convert_ap_options_to_ladxr_logic(self): | 
					
						
							| 
									
										
										
										
											2024-06-17 22:48:15 -04:00
										 |  |  |         self.ladxr_settings = LADXRSettings(dataclasses.asdict(self.options)) | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-17 22:48:15 -04:00
										 |  |  |         self.ladxr_settings.validate() | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  |         world_setup = LADXRWorldSetup() | 
					
						
							| 
									
										
										
										
											2024-06-17 22:48:15 -04:00
										 |  |  |         world_setup.randomize(self.ladxr_settings, self.random) | 
					
						
							|  |  |  |         self.ladxr_logic = LADXRLogic(configuration_options=self.ladxr_settings, world_setup=world_setup) | 
					
						
							| 
									
										
										
										
											2025-01-15 21:42:19 -05:00
										 |  |  |         self.ladxr_itempool = LADXRItemPool(self.ladxr_logic, self.ladxr_settings, self.random, bool(self.options.stabilize_item_pool)).toDict() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-14 13:52:58 -05:00
										 |  |  |     def generate_early(self) -> None: | 
					
						
							|  |  |  |         self.dungeon_item_types = { | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         for dungeon_item_type in ["maps", "compasses", "small_keys", "nightmare_keys", "stone_beaks", "instruments"]: | 
					
						
							|  |  |  |             option_name = "shuffle_" + dungeon_item_type | 
					
						
							|  |  |  |             option: DungeonItemShuffle = getattr(self.options, option_name) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             self.dungeon_item_types[option.ladxr_item] = option.value | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # The color dungeon does not contain an instrument | 
					
						
							|  |  |  |             num_items = 8 if dungeon_item_type == "instruments" else 9 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # For any and different world, set item rule instead | 
					
						
							|  |  |  |             if option.value == DungeonItemShuffle.option_own_world: | 
					
						
							|  |  |  |                 self.options.local_items.value |= { | 
					
						
							|  |  |  |                     ladxr_item_to_la_item_name[f"{option.ladxr_item}{i}"] for i in range(1, num_items + 1) | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             elif option.value == DungeonItemShuffle.option_different_world: | 
					
						
							|  |  |  |                 self.options.non_local_items.value |= { | 
					
						
							|  |  |  |                     ladxr_item_to_la_item_name[f"{option.ladxr_item}{i}"] for i in range(1, num_items + 1) | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  |     def create_regions(self) -> None: | 
					
						
							|  |  |  |         # Initialize | 
					
						
							|  |  |  |         self.convert_ap_options_to_ladxr_logic() | 
					
						
							|  |  |  |         regions = create_regions_from_ladxr(self.player, self.multiworld, self.ladxr_logic) | 
					
						
							|  |  |  |         self.multiworld.regions += regions | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Connect Menu -> Start | 
					
						
							|  |  |  |         start = None | 
					
						
							|  |  |  |         for region in regions: | 
					
						
							|  |  |  |             if region.name == "Start House": | 
					
						
							|  |  |  |                 start = region | 
					
						
							|  |  |  |                 break | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         assert(start) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-26 16:16:00 -04:00
										 |  |  |         menu_region = LinksAwakeningRegion("Menu", None, "Menu", self.player, self.multiworld)         | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  |         menu_region.exits = [Entrance(self.player, "Start Game", menu_region)] | 
					
						
							|  |  |  |         menu_region.exits[0].connect(start) | 
					
						
							| 
									
										
										
										
											2025-07-26 16:16:00 -04:00
										 |  |  |          | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  |         self.multiworld.regions.append(menu_region) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Place RAFT, other access events | 
					
						
							|  |  |  |         for region in regions: | 
					
						
							|  |  |  |             for loc in region.locations: | 
					
						
							| 
									
										
										
										
											2024-04-14 13:37:48 -05:00
										 |  |  |                 if loc.address is None: | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  |                     loc.place_locked_item(self.create_event(loc.ladxr_item.event)) | 
					
						
							| 
									
										
										
										
											2025-07-26 16:16:00 -04:00
										 |  |  |          | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  |         # Connect Windfish -> Victory | 
					
						
							|  |  |  |         windfish = self.multiworld.get_region("Windfish", self.player) | 
					
						
							|  |  |  |         l = Location(self.player, "Windfish", parent=windfish) | 
					
						
							|  |  |  |         windfish.locations = [l] | 
					
						
							| 
									
										
										
										
											2025-07-26 16:16:00 -04:00
										 |  |  |                  | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  |         l.place_locked_item(self.create_event("An Alarm Clock")) | 
					
						
							| 
									
										
										
										
											2025-07-26 16:16:00 -04:00
										 |  |  |          | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  |         self.multiworld.completion_condition[self.player] = lambda state: state.has("An Alarm Clock", player=self.player) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def create_item(self, item_name: str): | 
					
						
							|  |  |  |         return LinksAwakeningItem(self.item_name_to_data[item_name], self, self.player) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def create_event(self, event: str): | 
					
						
							|  |  |  |         return Item(event, ItemClassification.progression, None, self.player) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-27 20:30:13 -07:00
										 |  |  |     def create_items(self) -> None: | 
					
						
							| 
									
										
										
										
											2025-03-07 19:24:58 -05:00
										 |  |  |         itempool = [] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  |         exclude = [item.name for item in self.multiworld.precollected_items[self.player]] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self.prefill_original_dungeon = [ [], [], [], [], [], [], [], [], [] ] | 
					
						
							|  |  |  |         self.prefill_own_dungeons = [] | 
					
						
							| 
									
										
										
										
											2023-04-27 20:30:13 -07:00
										 |  |  |         self.pre_fill_items = [] | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  |         # option_original_dungeon = 0 | 
					
						
							|  |  |  |         # option_own_dungeons = 1 | 
					
						
							|  |  |  |         # option_own_world = 2 | 
					
						
							|  |  |  |         # option_any_world = 3 | 
					
						
							|  |  |  |         # option_different_world = 4 | 
					
						
							|  |  |  |         # option_delete = 5 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for ladx_item_name, count in self.ladxr_itempool.items(): | 
					
						
							|  |  |  |             # event | 
					
						
							|  |  |  |             if ladx_item_name not in ladxr_item_to_la_item_name: | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             item_name = ladxr_item_to_la_item_name[ladx_item_name] | 
					
						
							|  |  |  |             for _ in range(count): | 
					
						
							|  |  |  |                 if item_name in exclude: | 
					
						
							|  |  |  |                     exclude.remove(item_name)  # this is destructive. create unique list above | 
					
						
							| 
									
										
										
										
											2025-01-15 21:42:19 -05:00
										 |  |  |                     self.multiworld.itempool.append(self.create_item(self.get_filler_item_name())) | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  |                 else: | 
					
						
							|  |  |  |                     item = self.create_item(item_name) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-17 22:48:15 -04:00
										 |  |  |                     if not self.options.tradequest and isinstance(item.item_data, TradeItemData): | 
					
						
							| 
									
										
										
										
											2023-04-20 00:12:53 -07:00
										 |  |  |                         location = self.multiworld.get_location(item.item_data.vanilla_location, self.player) | 
					
						
							|  |  |  |                         location.place_locked_item(item) | 
					
						
							| 
									
										
										
										
											2023-07-13 18:14:04 -07:00
										 |  |  |                         location.show_in_spoiler = False | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  |                         continue | 
					
						
							| 
									
										
										
										
											2023-04-27 20:30:13 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  |                     if isinstance(item.item_data, DungeonItemData): | 
					
						
							| 
									
										
										
										
											2024-03-10 14:48:00 +01:00
										 |  |  |                         item_type = item.item_data.ladxr_id[:-1] | 
					
						
							| 
									
										
										
										
											2025-01-14 13:52:58 -05:00
										 |  |  |                         shuffle_type = self.dungeon_item_types[item_type] | 
					
						
							| 
									
										
										
										
											2024-03-10 14:48:00 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |                         if item.item_data.dungeon_item_type == DungeonItemType.INSTRUMENT and shuffle_type == ShuffleInstruments.option_vanilla: | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  |                             # Find instrument, lock | 
					
						
							|  |  |  |                             # TODO: we should be able to pinpoint the region we want, save a lookup table please | 
					
						
							|  |  |  |                             found = False | 
					
						
							| 
									
										
										
										
											2023-10-29 19:47:37 +01:00
										 |  |  |                             for r in self.multiworld.get_regions(self.player): | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  |                                 if r.dungeon_index != item.item_data.dungeon_index: | 
					
						
							|  |  |  |                                     continue | 
					
						
							|  |  |  |                                 for loc in r.locations: | 
					
						
							|  |  |  |                                     if not isinstance(loc, LinksAwakeningLocation): | 
					
						
							|  |  |  |                                         continue | 
					
						
							|  |  |  |                                     if not isinstance(loc.ladxr_item, Instrument): | 
					
						
							|  |  |  |                                         continue | 
					
						
							|  |  |  |                                     loc.place_locked_item(item) | 
					
						
							|  |  |  |                                     found = True | 
					
						
							|  |  |  |                                     break | 
					
						
							|  |  |  |                                 if found: | 
					
						
							| 
									
										
										
										
											2024-03-10 14:48:00 +01:00
										 |  |  |                                     break | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  |                         else: | 
					
						
							|  |  |  |                             if shuffle_type == DungeonItemShuffle.option_original_dungeon: | 
					
						
							|  |  |  |                                 self.prefill_original_dungeon[item.item_data.dungeon_index - 1].append(item) | 
					
						
							| 
									
										
										
										
											2023-04-27 20:30:13 -07:00
										 |  |  |                                 self.pre_fill_items.append(item) | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  |                             elif shuffle_type == DungeonItemShuffle.option_own_dungeons: | 
					
						
							|  |  |  |                                 self.prefill_own_dungeons.append(item) | 
					
						
							| 
									
										
										
										
											2023-04-27 20:30:13 -07:00
										 |  |  |                                 self.pre_fill_items.append(item) | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  |                             else: | 
					
						
							| 
									
										
										
										
											2025-03-07 19:24:58 -05:00
										 |  |  |                                 itempool.append(item) | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  |                     else: | 
					
						
							| 
									
										
										
										
											2025-03-07 19:24:58 -05:00
										 |  |  |                         itempool.append(item) | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-23 13:23:58 -07:00
										 |  |  |         self.multi_key = self.generate_multi_key() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  |         # Add special case for trendy shop access | 
					
						
							|  |  |  |         trendy_region = self.multiworld.get_region("Trendy Shop", self.player) | 
					
						
							|  |  |  |         event_location = Location(self.player, "Can Play Trendy Game", parent=trendy_region) | 
					
						
							|  |  |  |         trendy_region.locations.insert(0, event_location) | 
					
						
							|  |  |  |         event_location.place_locked_item(self.create_event("Can Play Trendy Game")) | 
					
						
							| 
									
										
										
										
											2025-07-26 16:16:00 -04:00
										 |  |  |         | 
					
						
							|  |  |  |         self.dungeon_locations_by_dungeon = [[], [], [], [], [], [], [], [], []]      | 
					
						
							| 
									
										
										
										
											2023-10-29 19:47:37 +01:00
										 |  |  |         for r in self.multiworld.get_regions(self.player): | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  |             # Set aside dungeon locations | 
					
						
							|  |  |  |             if r.dungeon_index: | 
					
						
							| 
									
										
										
										
											2023-04-27 20:30:13 -07:00
										 |  |  |                 self.dungeon_locations_by_dungeon[r.dungeon_index - 1] += r.locations | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  |                 for location in r.locations: | 
					
						
							| 
									
										
										
										
											2023-04-27 20:30:13 -07:00
										 |  |  |                     # Don't place dungeon items on pit button chest, to reduce chance of the filler blowing up | 
					
						
							|  |  |  |                     # TODO: no need for this if small key shuffle | 
					
						
							|  |  |  |                     if location.name == "Pit Button Chest (Tail Cave)" or location.item: | 
					
						
							|  |  |  |                         self.dungeon_locations_by_dungeon[r.dungeon_index - 1].remove(location) | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  |                     # Properly fill locations within dungeon | 
					
						
							|  |  |  |                     location.dungeon = r.dungeon_index | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-07 19:24:58 -05:00
										 |  |  |         if self.options.tarins_gift != "any_item": | 
					
						
							|  |  |  |             self.force_start_item(itempool) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self.multiworld.itempool += itempool | 
					
						
							| 
									
										
										
										
											2024-03-02 21:28:26 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-07 19:24:58 -05:00
										 |  |  |     def force_start_item(self, itempool): | 
					
						
							| 
									
										
										
										
											2023-07-04 10:33:33 -07:00
										 |  |  |         start_loc = self.multiworld.get_location("Tarin's Gift (Mabe Village)", self.player) | 
					
						
							|  |  |  |         if not start_loc.item: | 
					
						
							| 
									
										
										
										
											2025-03-07 19:24:58 -05:00
										 |  |  |             """
 | 
					
						
							|  |  |  |             Find an item that forces progression or a bush breaker for the player, depending on settings. | 
					
						
							|  |  |  |             """
 | 
					
						
							|  |  |  |             def is_possible_start_item(item): | 
					
						
							|  |  |  |                 return item.advancement and item.name not in self.options.non_local_items | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             def opens_new_regions(item): | 
					
						
							|  |  |  |                 collection_state = base_collection_state.copy() | 
					
						
							| 
									
										
										
										
											2025-03-25 17:30:25 -04:00
										 |  |  |                 collection_state.collect(item, prevent_sweep=True) | 
					
						
							|  |  |  |                 collection_state.sweep_for_advancements(self.get_locations()) | 
					
						
							| 
									
										
										
										
											2025-03-07 19:24:58 -05:00
										 |  |  |                 return len(collection_state.reachable_regions[self.player]) > reachable_count | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             start_items = [item for item in itempool if is_possible_start_item(item)] | 
					
						
							|  |  |  |             self.random.shuffle(start_items) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if self.options.tarins_gift == "bush_breaker": | 
					
						
							|  |  |  |                 start_item = next((item for item in start_items if item.name in links_awakening_item_name_groups["Bush Breakers"]), None) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             else:  # local_progression | 
					
						
							|  |  |  |                 entrance_mapping = self.ladxr_logic.world_setup.entrance_mapping | 
					
						
							|  |  |  |                 # Tail key opens a region but not a location if d1 entrance is not mapped to d1 or d4 | 
					
						
							|  |  |  |                 # exclude it in these cases to avoid fill errors | 
					
						
							|  |  |  |                 if entrance_mapping['d1'] not in ['d1', 'd4']: | 
					
						
							|  |  |  |                     start_items = [item for item in start_items if item.name != 'Tail Key'] | 
					
						
							|  |  |  |                 # Exclude shovel unless starting in Mabe Village | 
					
						
							|  |  |  |                 if entrance_mapping['start_house'] not in ['start_house', 'shop']: | 
					
						
							|  |  |  |                     start_items = [item for item in start_items if item.name != 'Shovel'] | 
					
						
							|  |  |  |                 base_collection_state = CollectionState(self.multiworld) | 
					
						
							| 
									
										
										
										
											2025-03-25 17:30:25 -04:00
										 |  |  |                 base_collection_state.sweep_for_advancements(self.get_locations()) | 
					
						
							| 
									
										
										
										
											2025-03-07 19:24:58 -05:00
										 |  |  |                 reachable_count = len(base_collection_state.reachable_regions[self.player]) | 
					
						
							|  |  |  |                 start_item = next((item for item in start_items if opens_new_regions(item)), None) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if start_item: | 
					
						
							| 
									
										
										
										
											2025-06-16 13:49:30 +02:00
										 |  |  |                 # Make sure we're removing the same copy of the item that we're placing | 
					
						
							|  |  |  |                 # (.remove checks __eq__, which could be a different copy, so we find the first index and use .pop) | 
					
						
							|  |  |  |                 start_item = itempool.pop(itempool.index(start_item)) | 
					
						
							| 
									
										
										
										
											2023-07-04 10:33:33 -07:00
										 |  |  |                 start_loc.place_locked_item(start_item) | 
					
						
							| 
									
										
										
										
											2025-03-07 19:24:58 -05:00
										 |  |  |             else: | 
					
						
							|  |  |  |                 logging.getLogger("Link's Awakening Logger").warning(f"No {self.options.tarins_gift.current_option_name} available for Tarin's Gift.") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-04 10:33:33 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-27 20:30:13 -07:00
										 |  |  |     def get_pre_fill_items(self): | 
					
						
							|  |  |  |         return self.pre_fill_items | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def pre_fill(self) -> None: | 
					
						
							|  |  |  |         allowed_locations_by_item = {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Set up filter rules | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # set containing the list of all possible dungeon locations for the player | 
					
						
							|  |  |  |         all_dungeon_locs = set() | 
					
						
							| 
									
										
										
										
											2025-07-26 16:16:00 -04:00
										 |  |  |          | 
					
						
							| 
									
										
										
										
											2023-04-27 20:30:13 -07:00
										 |  |  |         # Do dungeon specific things | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  |         for dungeon_index in range(0, 9): | 
					
						
							| 
									
										
										
										
											2023-04-27 20:30:13 -07:00
										 |  |  |             # set up allow-list for dungeon specific items | 
					
						
							| 
									
										
										
										
											2023-07-04 10:33:33 -07:00
										 |  |  |             locs = set(loc for loc in self.dungeon_locations_by_dungeon[dungeon_index] if not loc.item) | 
					
						
							| 
									
										
										
										
											2023-04-27 20:30:13 -07:00
										 |  |  |             for item in self.prefill_original_dungeon[dungeon_index]: | 
					
						
							|  |  |  |                 allowed_locations_by_item[item] = locs | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # ...and gather the list of all dungeon locations | 
					
						
							|  |  |  |             all_dungeon_locs |= locs | 
					
						
							|  |  |  |             # ...also set the rules for the dungeon | 
					
						
							|  |  |  |             for location in locs: | 
					
						
							|  |  |  |                 orig_rule = location.item_rule | 
					
						
							| 
									
										
										
										
											2025-07-26 16:16:00 -04:00
										 |  |  |                 # If an item is about to be placed on a dungeon location, it can go there iff  | 
					
						
							| 
									
										
										
										
											2023-04-27 20:30:13 -07:00
										 |  |  |                 # 1. it fits the general rules for that location (probably 'return True' for most places) | 
					
						
							|  |  |  |                 # 2. Either | 
					
						
							|  |  |  |                 #    2a. it's not a restricted dungeon item | 
					
						
							|  |  |  |                 #    2b. it's a restricted dungeon item and this location is specified as allowed | 
					
						
							|  |  |  |                 location.item_rule = lambda item, location=location, orig_rule=orig_rule: \ | 
					
						
							|  |  |  |                     (item not in allowed_locations_by_item or location in allowed_locations_by_item[item]) and orig_rule(item) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Now set up the allow-list for any-dungeon items | 
					
						
							|  |  |  |         for item in self.prefill_own_dungeons: | 
					
						
							|  |  |  |             # They of course get to go in any spot | 
					
						
							|  |  |  |             allowed_locations_by_item[item] = all_dungeon_locs | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Get the list of locations and shuffle | 
					
						
							| 
									
										
										
										
											2023-07-04 10:33:33 -07:00
										 |  |  |         all_dungeon_locs_to_fill = sorted(all_dungeon_locs) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-17 22:48:15 -04:00
										 |  |  |         self.random.shuffle(all_dungeon_locs_to_fill) | 
					
						
							| 
									
										
										
										
											2023-04-27 20:30:13 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |         # Get the list of items and sort by priority | 
					
						
							|  |  |  |         def priority(item): | 
					
						
							|  |  |  |             # 0 - Nightmare dungeon-specific | 
					
						
							|  |  |  |             # 1 - Key dungeon-specific | 
					
						
							|  |  |  |             # 2 - Other dungeon-specific | 
					
						
							|  |  |  |             # 3 - Nightmare any local dungeon | 
					
						
							|  |  |  |             # 4 - Key any local dungeon | 
					
						
							|  |  |  |             # 5 - Other any local dungeon | 
					
						
							|  |  |  |             i = 2 | 
					
						
							|  |  |  |             if "Nightmare" in item.name: | 
					
						
							|  |  |  |                 i = 0 | 
					
						
							|  |  |  |             elif "Key" in item.name: | 
					
						
							|  |  |  |                 i = 1 | 
					
						
							|  |  |  |             if allowed_locations_by_item[item] is all_dungeon_locs: | 
					
						
							|  |  |  |                 i += 3 | 
					
						
							|  |  |  |             return i | 
					
						
							| 
									
										
										
										
											2025-02-01 22:03:49 +01:00
										 |  |  |         all_dungeon_items_to_fill = self.get_pre_fill_items() | 
					
						
							| 
									
										
										
										
											2023-04-27 20:30:13 -07:00
										 |  |  |         all_dungeon_items_to_fill.sort(key=priority) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Set up state | 
					
						
							| 
									
										
										
										
											2025-02-01 22:03:49 +01:00
										 |  |  |         partial_all_state = CollectionState(self.multiworld) | 
					
						
							|  |  |  |         # Collect every item from the item pool and every pre-fill item like MultiWorld.get_all_state, except not our own pre-fill items. | 
					
						
							|  |  |  |         for item in self.multiworld.itempool: | 
					
						
							|  |  |  |             partial_all_state.collect(item, prevent_sweep=True) | 
					
						
							|  |  |  |         for player in self.multiworld.player_ids: | 
					
						
							|  |  |  |             if player == self.player: | 
					
						
							|  |  |  |                 # Don't collect the items we're about to place. | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             subworld = self.multiworld.worlds[player] | 
					
						
							|  |  |  |             for item in subworld.get_pre_fill_items(): | 
					
						
							|  |  |  |                 partial_all_state.collect(item, prevent_sweep=True) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Sweep to pick up already placed items that are reachable with everything but the dungeon items. | 
					
						
							|  |  |  |         partial_all_state.sweep_for_advancements() | 
					
						
							| 
									
										
										
										
											2025-03-07 19:24:58 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-01 22:03:49 +01:00
										 |  |  |         fill_restrictive(self.multiworld, partial_all_state, all_dungeon_locs_to_fill, all_dungeon_items_to_fill, lock=True, single_player_placement=True, allow_partial=False) | 
					
						
							| 
									
										
										
										
											2025-07-26 16:16:00 -04:00
										 |  |  |          | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  |     name_cache = {} | 
					
						
							|  |  |  |     # Tries to associate an icon from another game with an icon we have | 
					
						
							| 
									
										
										
										
											2024-12-13 16:49:30 -05:00
										 |  |  |     def guess_icon_for_other_world(self, foreign_item): | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  |         if not self.name_cache: | 
					
						
							|  |  |  |             for item in ladxr_item_to_la_item_name.keys(): | 
					
						
							|  |  |  |                 self.name_cache[item] = item | 
					
						
							|  |  |  |                 splits = item.split("_") | 
					
						
							|  |  |  |                 for word in item.split("_"): | 
					
						
							| 
									
										
										
										
											2024-12-13 16:49:30 -05:00
										 |  |  |                     if word not in ItemIconGuessing.BLOCKED_ASSOCIATIONS and not word.isnumeric(): | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  |                         self.name_cache[word] = item | 
					
						
							| 
									
										
										
										
											2024-12-13 16:49:30 -05:00
										 |  |  |             for name in ItemIconGuessing.SYNONYMS.values(): | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  |                 assert name in self.name_cache, name | 
					
						
							|  |  |  |                 assert name in CHEST_ITEMS, name | 
					
						
							| 
									
										
										
										
											2024-12-13 16:49:30 -05:00
										 |  |  |             self.name_cache.update(ItemIconGuessing.SYNONYMS) | 
					
						
							|  |  |  |             pluralizations = {k + "S": v for k, v in self.name_cache.items()} | 
					
						
							|  |  |  |             self.name_cache = pluralizations | self.name_cache | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         uppered = foreign_item.name.upper() | 
					
						
							|  |  |  |         foreign_game = self.multiworld.game[foreign_item.player] | 
					
						
							|  |  |  |         phrases = ItemIconGuessing.PHRASES.copy() | 
					
						
							|  |  |  |         if foreign_game in ItemIconGuessing.GAME_SPECIFIC_PHRASES: | 
					
						
							|  |  |  |             phrases.update(ItemIconGuessing.GAME_SPECIFIC_PHRASES[foreign_game]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for phrase, icon in phrases.items(): | 
					
						
							| 
									
										
											  
											
												LADX: Add more specific "item icon guessing" support for some games (#4706)
* DKC3, PKMN R/B/Em, M&L specific item matches
* MLSS Bean types are now discrete
* Add Doom 1/2 items
* Add Doom 1/2 items, actually
* Add Inscryption items
* Add more SA2B items, Minecraft
* Add VVVVVV
* Add misc items, comma fixes
* Hat in Time items
* Misc changes
* Expand TODO
* Add more OoT items, Pokemon consumables
* KH2
* KH1, adjust KH2 items
* Formatting fixes
* more item changes, fix kh1 name
* Fix KH1 name
* Add Full Heal to MEDICINE graphics
* Final comma fixes before PR
* Add Full Restore as Medicine
* Move some names to generic, drink fixes, double-quotes consistency fix
* moved ROCK SMASH match to PHRASES dict
* Removed some redundant name checks, remove Old Amber check from Emerald
* Added "PASS" generic check as "LETTER" sprite
* Removed TODO
* Corrected KH1 name for real this time
* Icon assignment now uppers freogin item string during comparison
* Doom skull keys are now NIGHTMARE_KEY, added QUILL as generic for FEATHER
* KH2 armor is Blunic, accessories are Ribbons
* KH1 accessories/armor are Blunic
* "ROCK SMASH" is now "BOMB"
* Removed extra space
											
										 
											2025-03-17 11:50:57 -04:00
										 |  |  |             if phrase.upper() in uppered: | 
					
						
							| 
									
										
										
										
											2024-12-13 16:49:30 -05:00
										 |  |  |                 return icon | 
					
						
							|  |  |  |         # pattern for breaking down camelCase, also separates out digits | 
					
						
							|  |  |  |         pattern = re.compile(r"(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|(?<=[a-zA-Z])(?=\d)") | 
					
						
							|  |  |  |         possibles = pattern.sub(' ', foreign_item.name).upper() | 
					
						
							|  |  |  |         for ch in "[]()_": | 
					
						
							|  |  |  |             possibles = possibles.replace(ch, " ") | 
					
						
							|  |  |  |         possibles = possibles.split() | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  |         for name in possibles: | 
					
						
							|  |  |  |             if name in self.name_cache: | 
					
						
							|  |  |  |                 return self.name_cache[name] | 
					
						
							| 
									
										
										
										
											2025-07-26 16:16:00 -04:00
										 |  |  |          | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  |         return "TRADING_ITEM_LETTER" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def generate_output(self, output_directory: str): | 
					
						
							|  |  |  |         # copy items back to locations | 
					
						
							|  |  |  |         for r in self.multiworld.get_regions(self.player): | 
					
						
							|  |  |  |             for loc in r.locations: | 
					
						
							|  |  |  |                 if isinstance(loc, LinksAwakeningLocation): | 
					
						
							|  |  |  |                     assert(loc.item) | 
					
						
							| 
									
										
										
										
											2025-07-26 16:16:00 -04:00
										 |  |  |                          | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  |                     # If we're a links awakening item, just use the item | 
					
						
							|  |  |  |                     if isinstance(loc.item, LinksAwakeningItem): | 
					
						
							|  |  |  |                         loc.ladxr_item.item = loc.item.item_data.ladxr_id | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-04 10:33:33 -07:00
										 |  |  |                     # If the item name contains "sword", use a sword icon, etc | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  |                     # Otherwise, use a cute letter as the icon | 
					
						
							| 
									
										
										
										
											2024-12-13 16:49:30 -05:00
										 |  |  |                     elif self.options.foreign_item_icons == 'guess_by_name': | 
					
						
							|  |  |  |                         loc.ladxr_item.item = self.guess_icon_for_other_world(loc.item) | 
					
						
							| 
									
										
										
										
											2025-01-16 21:59:19 -05:00
										 |  |  |                         loc.ladxr_item.setCustomItemName(loc.item.name) | 
					
						
							| 
									
										
										
										
											2024-12-13 16:49:30 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  |                     else: | 
					
						
							| 
									
										
										
										
											2024-12-13 16:49:30 -05:00
										 |  |  |                         if loc.item.advancement: | 
					
						
							|  |  |  |                             loc.ladxr_item.item = 'PIECE_OF_POWER' | 
					
						
							|  |  |  |                         else: | 
					
						
							|  |  |  |                             loc.ladxr_item.item = 'GUARDIAN_ACORN' | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  |                         loc.ladxr_item.custom_item_name = loc.item.name | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     if loc.item: | 
					
						
							|  |  |  |                         loc.ladxr_item.item_owner = loc.item.player | 
					
						
							|  |  |  |                     else: | 
					
						
							|  |  |  |                         loc.ladxr_item.item_owner = self.player | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     # Kind of kludge, make it possible for the location to differentiate between local and remote items | 
					
						
							|  |  |  |                     loc.ladxr_item.location_owner = self.player | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-26 16:16:00 -04:00
										 |  |  |          | 
					
						
							|  |  |  |         patch = LADXProcedurePatch(player=self.player, player_name=self.player_name) | 
					
						
							|  |  |  |         write_patch_data(self, patch) | 
					
						
							|  |  |  |         out_path = os.path.join(output_directory, f"{self.multiworld.get_out_file_name_base(self.player)}" | 
					
						
							|  |  |  |                                                   f"{patch.patch_file_ending}") | 
					
						
							| 
									
										
										
										
											2023-04-14 22:47:45 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-26 16:16:00 -04:00
										 |  |  |         patch.write(out_path) | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def generate_multi_key(self): | 
					
						
							| 
									
										
										
										
											2024-06-17 22:48:15 -04:00
										 |  |  |         return bytearray(self.random.getrandbits(8) for _ in range(10)) + self.player.to_bytes(2, 'big') | 
					
						
							| 
									
										
										
										
											2023-03-21 01:26:03 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def modify_multidata(self, multidata: dict): | 
					
						
							| 
									
										
										
										
											2024-06-17 22:48:15 -04:00
										 |  |  |         multidata["connect_names"][binascii.hexlify(self.multi_key).decode()] = multidata["connect_names"][self.player_name] | 
					
						
							| 
									
										
										
										
											2023-05-29 01:17:30 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def collect(self, state, item: Item) -> bool: | 
					
						
							|  |  |  |         change = super().collect(state, item) | 
					
						
							| 
									
										
										
										
											2023-11-25 15:07:02 -06:00
										 |  |  |         if change and item.name in self.rupees: | 
					
						
							|  |  |  |             state.prog_items[self.player]["RUPEES"] += self.rupees[item.name] | 
					
						
							| 
									
										
										
										
											2023-05-29 01:17:30 +02:00
										 |  |  |         return change | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def remove(self, state, item: Item) -> bool: | 
					
						
							|  |  |  |         change = super().remove(state, item) | 
					
						
							| 
									
										
										
										
											2023-11-25 15:07:02 -06:00
										 |  |  |         if change and item.name in self.rupees: | 
					
						
							|  |  |  |             state.prog_items[self.player]["RUPEES"] -= self.rupees[item.name] | 
					
						
							| 
									
										
										
										
											2023-05-29 01:17:30 +02:00
										 |  |  |         return change | 
					
						
							| 
									
										
										
										
											2024-09-17 23:56:40 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-15 21:42:19 -05:00
										 |  |  |     # Same fill choices and weights used in LADXR.itempool.__randomizeRupees | 
					
						
							|  |  |  |     filler_choices = ("Bomb", "Single Arrow", "10 Arrows", "Magic Powder", "Medicine") | 
					
						
							|  |  |  |     filler_weights = ( 10,     5,              10,          10,             1) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-20 10:18:09 -04:00
										 |  |  |     def get_filler_item_name(self) -> str: | 
					
						
							| 
									
										
										
										
											2025-01-15 21:42:19 -05:00
										 |  |  |         if self.options.stabilize_item_pool: | 
					
						
							|  |  |  |             return "Nothing" | 
					
						
							|  |  |  |         return self.random.choices(self.filler_choices, self.filler_weights)[0] | 
					
						
							| 
									
										
										
										
											2024-09-20 10:18:09 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-17 23:56:40 +02:00
										 |  |  |     def fill_slot_data(self): | 
					
						
							|  |  |  |         slot_data = {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if not self.multiworld.is_race: | 
					
						
							|  |  |  |             # all of these option are NOT used by the LADX- or Text-Client. | 
					
						
							|  |  |  |             # they are used by Magpie tracker (https://github.com/kbranch/Magpie/wiki/Autotracker-API) | 
					
						
							|  |  |  |             # for convenient auto-tracking of the generated settings and adjusting the tracker accordingly | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             slot_options = ["instrument_count"] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             slot_options_display_name = [ | 
					
						
							| 
									
										
										
										
											2024-12-19 21:19:00 -05:00
										 |  |  |                 "goal", | 
					
						
							|  |  |  |                 "logic", | 
					
						
							|  |  |  |                 "tradequest", | 
					
						
							|  |  |  |                 "rooster", | 
					
						
							|  |  |  |                 "experimental_dungeon_shuffle", | 
					
						
							|  |  |  |                 "experimental_entrance_shuffle", | 
					
						
							|  |  |  |                 "trendy_game", | 
					
						
							|  |  |  |                 "gfxmod", | 
					
						
							|  |  |  |                 "shuffle_nightmare_keys", | 
					
						
							|  |  |  |                 "shuffle_small_keys", | 
					
						
							|  |  |  |                 "shuffle_maps", | 
					
						
							|  |  |  |                 "shuffle_compasses", | 
					
						
							|  |  |  |                 "shuffle_stone_beaks", | 
					
						
							|  |  |  |                 "shuffle_instruments", | 
					
						
							|  |  |  |                 "nag_messages", | 
					
						
							|  |  |  |                 "hard_mode", | 
					
						
							| 
									
										
										
										
											2024-12-20 07:55:32 -05:00
										 |  |  |                 "overworld", | 
					
						
							| 
									
										
										
										
											2024-09-17 23:56:40 +02:00
										 |  |  |             ] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # use the default behaviour to grab options | 
					
						
							|  |  |  |             slot_data = self.options.as_dict(*slot_options) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # for options which should not get the internal int value but the display name use the extra handling | 
					
						
							|  |  |  |             slot_data.update({ | 
					
						
							|  |  |  |                 option: value.current_key | 
					
						
							|  |  |  |                 for option, value in dataclasses.asdict(self.options).items() if option in slot_options_display_name | 
					
						
							|  |  |  |             }) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-05 12:39:31 -04:00
										 |  |  |             slot_data.update({"entrance_mapping": self.ladxr_logic.world_setup.entrance_mapping}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-17 23:56:40 +02:00
										 |  |  |         return slot_data |