| 
									
										
										
										
											2023-03-05 07:31:31 -05:00
										 |  |  | import os | 
					
						
							|  |  |  | import threading | 
					
						
							| 
									
										
										
										
											2023-06-24 19:58:54 -04:00
										 |  |  | from pkgutil import get_data | 
					
						
							| 
									
										
										
										
											2023-03-05 07:31:31 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  | import bsdiff4 | 
					
						
							|  |  |  | import Utils | 
					
						
							| 
									
										
										
										
											2023-07-05 22:39:35 +02:00
										 |  |  | import settings | 
					
						
							|  |  |  | import typing | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | from typing import NamedTuple, Union, Dict, Any | 
					
						
							| 
									
										
										
										
											2023-03-05 07:31:31 -05:00
										 |  |  | from BaseClasses import Item, Location, Region, Entrance, MultiWorld, ItemClassification, Tutorial | 
					
						
							|  |  |  | from .ItemPool import generate_itempool, starting_weapons, dangerous_weapon_locations | 
					
						
							|  |  |  | from .Items import item_table, item_prices, item_game_ids | 
					
						
							|  |  |  | from .Locations import location_table, level_locations, major_locations, shop_locations, all_level_locations, \ | 
					
						
							| 
									
										
										
										
											2024-07-24 08:00:16 -04:00
										 |  |  |     standard_level_locations, shop_price_location_ids, secret_money_ids, location_ids, food_locations, \ | 
					
						
							|  |  |  |     take_any_locations, sword_cave_locations | 
					
						
							| 
									
										
										
										
											2024-01-14 22:47:32 -05:00
										 |  |  | from .Options import TlozOptions | 
					
						
							| 
									
										
										
										
											2023-03-05 07:31:31 -05:00
										 |  |  | from .Rom import TLoZDeltaPatch, get_base_rom_path, first_quest_dungeon_items_early, first_quest_dungeon_items_late | 
					
						
							| 
									
										
										
										
											2023-03-08 05:22:14 -05:00
										 |  |  | from .Rules import set_rules | 
					
						
							| 
									
										
										
										
											2023-03-05 07:31:31 -05:00
										 |  |  | from worlds.AutoWorld import World, WebWorld | 
					
						
							|  |  |  | from worlds.generic.Rules import add_rule | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-05 22:39:35 +02:00
										 |  |  | class TLoZSettings(settings.Group): | 
					
						
							|  |  |  |     class RomFile(settings.UserFilePath): | 
					
						
							|  |  |  |         """File name of the Zelda 1""" | 
					
						
							|  |  |  |         description = "The Legend of Zelda (U) ROM File" | 
					
						
							|  |  |  |         copy_to = "Legend of Zelda, The (U) (PRG0) [!].nes" | 
					
						
							|  |  |  |         md5s = [TLoZDeltaPatch.hash] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     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 .nes file with | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     class DisplayMsgs(settings.Bool): | 
					
						
							|  |  |  |         """Display message inside of Bizhawk""" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     rom_file: RomFile = RomFile(RomFile.copy_to) | 
					
						
							|  |  |  |     rom_start: typing.Union[RomStart, bool] = True | 
					
						
							|  |  |  |     display_msgs: typing.Union[DisplayMsgs, bool] = True | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-05 07:31:31 -05:00
										 |  |  | class TLoZWeb(WebWorld): | 
					
						
							|  |  |  |     theme = "stone" | 
					
						
							|  |  |  |     setup = Tutorial( | 
					
						
							| 
									
										
										
										
											2024-02-20 11:22:32 -05:00
										 |  |  |         "Multiworld Setup Guide", | 
					
						
							| 
									
										
										
										
											2023-03-05 07:31:31 -05:00
										 |  |  |         "A guide to setting up The Legend of Zelda for Archipelago on your computer.", | 
					
						
							|  |  |  |         "English", | 
					
						
							|  |  |  |         "multiworld_en.md", | 
					
						
							|  |  |  |         "multiworld/en", | 
					
						
							|  |  |  |         ["Rosalie and Figment"] | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     tutorials = [setup] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class TLoZWorld(World): | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     The Legend of Zelda needs almost no introduction. Gather the eight fragments of the | 
					
						
							| 
									
										
										
										
											2023-03-30 09:31:16 -04:00
										 |  |  |     Triforce of Wisdom, enter Death Mountain, defeat Ganon, and rescue Princess Zelda. | 
					
						
							| 
									
										
										
										
											2023-03-05 07:31:31 -05:00
										 |  |  |     This randomizer shuffles all the items in the game around, leading to a new adventure | 
					
						
							|  |  |  |     every time. | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2024-01-14 22:47:32 -05:00
										 |  |  |     options_dataclass = TlozOptions | 
					
						
							| 
									
										
										
										
											2024-01-15 14:19:18 -05:00
										 |  |  |     options: TlozOptions | 
					
						
							| 
									
										
										
										
											2023-07-05 22:39:35 +02:00
										 |  |  |     settings: typing.ClassVar[TLoZSettings] | 
					
						
							| 
									
										
										
										
											2023-03-05 07:31:31 -05:00
										 |  |  |     game = "The Legend of Zelda" | 
					
						
							|  |  |  |     topology_present = False | 
					
						
							|  |  |  |     base_id = 7000 | 
					
						
							|  |  |  |     web = TLoZWeb() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     item_name_to_id = {name: data.code for name, data in item_table.items()} | 
					
						
							|  |  |  |     location_name_to_id = location_table | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     item_name_groups = { | 
					
						
							|  |  |  |         'weapons': starting_weapons, | 
					
						
							|  |  |  |         'swords': { | 
					
						
							|  |  |  |             "Sword", "White Sword", "Magical Sword" | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         "candles": { | 
					
						
							|  |  |  |             "Candle", "Red Candle" | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         "arrows": { | 
					
						
							|  |  |  |             "Arrow", "Silver Arrow" | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-24 08:00:16 -04:00
										 |  |  |     location_name_groups = { | 
					
						
							|  |  |  |         "Shops": set(shop_locations), | 
					
						
							|  |  |  |         "Take Any": set(take_any_locations), | 
					
						
							|  |  |  |         "Sword Caves": set(sword_cave_locations), | 
					
						
							|  |  |  |         "Level 1": set(level_locations[0]), | 
					
						
							|  |  |  |         "Level 2": set(level_locations[1]), | 
					
						
							|  |  |  |         "Level 3": set(level_locations[2]), | 
					
						
							|  |  |  |         "Level 4": set(level_locations[3]), | 
					
						
							|  |  |  |         "Level 5": set(level_locations[4]), | 
					
						
							|  |  |  |         "Level 6": set(level_locations[5]), | 
					
						
							|  |  |  |         "Level 7": set(level_locations[6]), | 
					
						
							|  |  |  |         "Level 8": set(level_locations[7]), | 
					
						
							|  |  |  |         "Level 9": set(level_locations[8]) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-05 07:31:31 -05:00
										 |  |  |     for k, v in item_name_to_id.items(): | 
					
						
							|  |  |  |         item_name_to_id[k] = v + base_id | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for k, v in location_name_to_id.items(): | 
					
						
							|  |  |  |         if v is not None: | 
					
						
							|  |  |  |             location_name_to_id[k] = v + base_id | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-09 16:38:42 -04:00
										 |  |  |     def __init__(self, multiworld: MultiWorld, player: int): | 
					
						
							|  |  |  |         super().__init__(multiworld, player) | 
					
						
							| 
									
										
										
										
											2023-03-05 07:31:31 -05:00
										 |  |  |         self.generator_in_use = threading.Event() | 
					
						
							|  |  |  |         self.rom_name_available_event = threading.Event() | 
					
						
							|  |  |  |         self.levels = None | 
					
						
							|  |  |  |         self.filler_items = None | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-14 23:40:48 -04:00
										 |  |  |     @classmethod | 
					
						
							|  |  |  |     def stage_assert_generate(cls, multiworld: MultiWorld): | 
					
						
							|  |  |  |         rom_file = get_base_rom_path() | 
					
						
							|  |  |  |         if not os.path.exists(rom_file): | 
					
						
							|  |  |  |             raise FileNotFoundError(rom_file) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-05 07:31:31 -05:00
										 |  |  |     def create_item(self, name: str): | 
					
						
							|  |  |  |         return TLoZItem(name, item_table[name].classification, self.item_name_to_id[name], self.player) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def create_event(self, event: str): | 
					
						
							|  |  |  |         return TLoZItem(event, ItemClassification.progression, None, self.player) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def create_location(self, name, id, parent, event=False): | 
					
						
							|  |  |  |         return_location = TLoZLocation(self.player, name, id, parent) | 
					
						
							|  |  |  |         return return_location | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def create_regions(self): | 
					
						
							|  |  |  |         menu = Region("Menu", self.player, self.multiworld) | 
					
						
							|  |  |  |         overworld = Region("Overworld", self.player, self.multiworld) | 
					
						
							|  |  |  |         self.levels = [None]  # Yes I'm making a one-indexed array in a zero-indexed language. I hate me too. | 
					
						
							|  |  |  |         for i in range(1, 10): | 
					
						
							|  |  |  |             level = Region(f"Level {i}", self.player, self.multiworld) | 
					
						
							|  |  |  |             self.levels.append(level) | 
					
						
							|  |  |  |             new_entrance = Entrance(self.player, f"Level {i}", overworld) | 
					
						
							|  |  |  |             new_entrance.connect(level) | 
					
						
							|  |  |  |             overworld.exits.append(new_entrance) | 
					
						
							|  |  |  |             self.multiworld.regions.append(level) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for i, level in enumerate(level_locations): | 
					
						
							|  |  |  |             for location in level: | 
					
						
							| 
									
										
										
										
											2024-01-14 22:47:32 -05:00
										 |  |  |                 if self.options.ExpandedPool or "Drop" not in location: | 
					
						
							| 
									
										
										
										
											2023-03-05 07:31:31 -05:00
										 |  |  |                     self.levels[i + 1].locations.append( | 
					
						
							|  |  |  |                         self.create_location(location, self.location_name_to_id[location], self.levels[i + 1])) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for level in range(1, 9): | 
					
						
							|  |  |  |             boss_event = self.create_location(f"Level {level} Boss Status", None, | 
					
						
							|  |  |  |                                               self.multiworld.get_region(f"Level {level}", self.player), | 
					
						
							|  |  |  |                                               True) | 
					
						
							|  |  |  |             boss_event.show_in_spoiler = False | 
					
						
							|  |  |  |             self.levels[level].locations.append(boss_event) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for location in major_locations: | 
					
						
							| 
									
										
										
										
											2024-01-14 22:47:32 -05:00
										 |  |  |             if self.options.ExpandedPool or "Take Any" not in location: | 
					
						
							| 
									
										
										
										
											2023-03-05 07:31:31 -05:00
										 |  |  |                 overworld.locations.append( | 
					
						
							|  |  |  |                     self.create_location(location, self.location_name_to_id[location], overworld)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for location in shop_locations: | 
					
						
							|  |  |  |             overworld.locations.append( | 
					
						
							|  |  |  |                 self.create_location(location, self.location_name_to_id[location], overworld)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         ganon = self.create_location("Ganon", None, self.multiworld.get_region("Level 9", self.player)) | 
					
						
							|  |  |  |         zelda = self.create_location("Zelda", None, self.multiworld.get_region("Level 9", self.player)) | 
					
						
							|  |  |  |         ganon.show_in_spoiler = False | 
					
						
							|  |  |  |         zelda.show_in_spoiler = False | 
					
						
							|  |  |  |         self.levels[9].locations.append(ganon) | 
					
						
							|  |  |  |         self.levels[9].locations.append(zelda) | 
					
						
							|  |  |  |         begin_game = Entrance(self.player, "Begin Game", menu) | 
					
						
							|  |  |  |         menu.exits.append(begin_game) | 
					
						
							|  |  |  |         begin_game.connect(overworld) | 
					
						
							|  |  |  |         self.multiworld.regions.append(menu) | 
					
						
							|  |  |  |         self.multiworld.regions.append(overworld) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-21 10:45:31 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-08 05:22:14 -05:00
										 |  |  |     def create_items(self): | 
					
						
							|  |  |  |         # refer to ItemPool.py | 
					
						
							|  |  |  |         generate_itempool(self) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # refer to Rules.py | 
					
						
							| 
									
										
										
										
											2023-03-05 07:31:31 -05:00
										 |  |  |     set_rules = set_rules | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def generate_basic(self): | 
					
						
							|  |  |  |         ganon = self.multiworld.get_location("Ganon", self.player) | 
					
						
							|  |  |  |         ganon.place_locked_item(self.create_event("Triforce of Power")) | 
					
						
							|  |  |  |         add_rule(ganon, lambda state: state.has("Silver Arrow", self.player) and state.has("Bow", self.player)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self.multiworld.get_location("Zelda", self.player).place_locked_item(self.create_event("Rescued Zelda!")) | 
					
						
							|  |  |  |         add_rule(self.multiworld.get_location("Zelda", self.player), | 
					
						
							| 
									
										
										
										
											2024-03-05 16:55:59 -05:00
										 |  |  |                  lambda state: state.has("Triforce of Power", self.player)) | 
					
						
							| 
									
										
										
										
											2023-03-05 07:31:31 -05:00
										 |  |  |         self.multiworld.completion_condition[self.player] = lambda state: state.has("Rescued Zelda!", self.player) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def apply_base_patch(self, rom): | 
					
						
							|  |  |  |         # The base patch source is on a different repo, so here's the summary of changes: | 
					
						
							|  |  |  |         # Remove Triforce check for recorder, so you can always warp. | 
					
						
							|  |  |  |         # Remove level check for Triforce Fragments (and maps and compasses, but this won't matter) | 
					
						
							|  |  |  |         # Replace some code with a jump to free space | 
					
						
							|  |  |  |         # Check if we're picking up a Triforce Fragment. If so, increment the local count | 
					
						
							|  |  |  |         # In either case, we do the instructions we overwrote with the jump and then return to normal flow | 
					
						
							|  |  |  |         # Remove map/compass check so they're always on | 
					
						
							|  |  |  |         # Removing a bit from the boss roars flags, so we can have more dungeon items. This allows us to | 
					
						
							|  |  |  |         # go past 0x1F items for dungeon items. | 
					
						
							| 
									
										
										
										
											2023-08-10 17:26:25 -04:00
										 |  |  |         base_patch = get_data(__name__, "z1_base_patch.bsdiff4") | 
					
						
							| 
									
										
										
										
											2023-06-24 19:58:54 -04:00
										 |  |  |         rom_data = bsdiff4.patch(rom.read(), base_patch) | 
					
						
							| 
									
										
										
										
											2023-03-05 07:31:31 -05:00
										 |  |  |         rom_data = bytearray(rom_data) | 
					
						
							|  |  |  |         # Set every item to the new nothing value, but keep room flags. Type 2 boss roars should | 
					
						
							|  |  |  |         # become type 1 boss roars, so we at least keep the sound of roaring where it should be. | 
					
						
							|  |  |  |         for i in range(0, 0x7F): | 
					
						
							|  |  |  |             item = rom_data[first_quest_dungeon_items_early + i] | 
					
						
							|  |  |  |             if item & 0b00100000: | 
					
						
							| 
									
										
										
										
											2023-12-28 06:16:38 -05:00
										 |  |  |                 item = item & 0b11011111 | 
					
						
							|  |  |  |                 item = item | 0b01000000 | 
					
						
							|  |  |  |                 rom_data[first_quest_dungeon_items_early + i] = item | 
					
						
							| 
									
										
										
										
											2023-03-05 07:31:31 -05:00
										 |  |  |             if item & 0b00011111 == 0b00000011: # Change all Item 03s to Item 3F, the proper "nothing" | 
					
						
							|  |  |  |                 rom_data[first_quest_dungeon_items_early + i] = item | 0b00111111 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             item = rom_data[first_quest_dungeon_items_late + i] | 
					
						
							|  |  |  |             if item & 0b00100000: | 
					
						
							| 
									
										
										
										
											2023-12-28 06:16:38 -05:00
										 |  |  |                 item = item & 0b11011111 | 
					
						
							|  |  |  |                 item = item | 0b01000000 | 
					
						
							|  |  |  |                 rom_data[first_quest_dungeon_items_late + i] = item | 
					
						
							| 
									
										
										
										
											2023-03-05 07:31:31 -05:00
										 |  |  |             if item & 0b00011111 == 0b00000011: | 
					
						
							|  |  |  |                 rom_data[first_quest_dungeon_items_late + i] = item | 0b00111111 | 
					
						
							|  |  |  |         return rom_data | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def apply_randomizer(self): | 
					
						
							|  |  |  |         with open(get_base_rom_path(), 'rb') as rom: | 
					
						
							|  |  |  |             rom_data = self.apply_base_patch(rom) | 
					
						
							|  |  |  |         # Write each location's new data in | 
					
						
							|  |  |  |         for location in self.multiworld.get_filled_locations(self.player): | 
					
						
							|  |  |  |             # Zelda and Ganon aren't real locations | 
					
						
							|  |  |  |             if location.name == "Ganon" or location.name == "Zelda": | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |             # Neither are boss defeat events | 
					
						
							|  |  |  |             if "Status" in location.name: | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |             item = location.item.name | 
					
						
							|  |  |  |             # Remote items are always going to look like Rupees. | 
					
						
							|  |  |  |             if location.item.player != self.player: | 
					
						
							|  |  |  |                 item = "Rupee" | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |             item_id = item_game_ids[item] | 
					
						
							|  |  |  |             location_id = location_ids[location.name] | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |             # Shop prices need to be set | 
					
						
							|  |  |  |             if location.name in shop_locations: | 
					
						
							|  |  |  |                 if location.name[-5:] == "Right": | 
					
						
							|  |  |  |                     # Final item in stores has bit 6 and 7 set. It's what marks the cave a shop. | 
					
						
							|  |  |  |                     item_id = item_id | 0b11000000 | 
					
						
							|  |  |  |                 price_location = shop_price_location_ids[location.name] | 
					
						
							|  |  |  |                 item_price = item_prices[item] | 
					
						
							|  |  |  |                 if item == "Rupee": | 
					
						
							|  |  |  |                     item_class = location.item.classification | 
					
						
							|  |  |  |                     if item_class == ItemClassification.progression: | 
					
						
							|  |  |  |                         item_price = item_price * 2 | 
					
						
							|  |  |  |                     elif item_class == ItemClassification.useful: | 
					
						
							|  |  |  |                         item_price = item_price // 2 | 
					
						
							|  |  |  |                     elif item_class == ItemClassification.filler: | 
					
						
							|  |  |  |                         item_price = item_price // 2 | 
					
						
							|  |  |  |                     elif item_class == ItemClassification.trap: | 
					
						
							|  |  |  |                         item_price = item_price * 2 | 
					
						
							|  |  |  |                 rom_data[price_location] = item_price | 
					
						
							|  |  |  |             if location.name == "Take Any Item Right": | 
					
						
							|  |  |  |                 # Same story as above: bit 6 is what makes this a Take Any cave | 
					
						
							|  |  |  |                 item_id = item_id | 0b01000000 | 
					
						
							|  |  |  |             rom_data[location_id] = item_id | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         # We shuffle the tiers of rupee caves. Caves that shared a value before still will. | 
					
						
							| 
									
										
										
										
											2024-05-02 20:56:20 -04:00
										 |  |  |         secret_caves = self.random.sample(sorted(secret_money_ids), 3) | 
					
						
							| 
									
										
										
										
											2023-03-05 07:31:31 -05:00
										 |  |  |         secret_cave_money_amounts = [20, 50, 100] | 
					
						
							|  |  |  |         for i, amount in enumerate(secret_cave_money_amounts): | 
					
						
							|  |  |  |             # Giving approximately double the money to keep grinding down | 
					
						
							| 
									
										
										
										
											2024-05-02 20:56:20 -04:00
										 |  |  |             amount = amount * self.random.triangular(1.5, 2.5) | 
					
						
							| 
									
										
										
										
											2023-03-05 07:31:31 -05:00
										 |  |  |             secret_cave_money_amounts[i] = int(amount) | 
					
						
							|  |  |  |         for i, cave in enumerate(secret_caves): | 
					
						
							|  |  |  |             rom_data[secret_money_ids[cave]] = secret_cave_money_amounts[i] | 
					
						
							|  |  |  |         return rom_data | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def generate_output(self, output_directory: str): | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             patched_rom = self.apply_randomizer() | 
					
						
							|  |  |  |             outfilebase = 'AP_' + self.multiworld.seed_name | 
					
						
							|  |  |  |             outfilepname = f'_P{self.player}' | 
					
						
							|  |  |  |             outfilepname += f"_{self.multiworld.get_file_safe_player_name(self.player).replace(' ', '_')}" | 
					
						
							|  |  |  |             outputFilename = os.path.join(output_directory, f'{outfilebase}{outfilepname}.nes') | 
					
						
							|  |  |  |             self.rom_name_text = f'LOZ{Utils.__version__.replace(".", "")[0:3]}_{self.player}_{self.multiworld.seed:11}\0' | 
					
						
							|  |  |  |             self.romName = bytearray(self.rom_name_text, 'utf8')[:0x20] | 
					
						
							|  |  |  |             self.romName.extend([0] * (0x20 - len(self.romName))) | 
					
						
							|  |  |  |             self.rom_name = self.romName | 
					
						
							|  |  |  |             patched_rom[0x10:0x30] = self.romName | 
					
						
							|  |  |  |             self.playerName = bytearray(self.multiworld.player_name[self.player], 'utf8')[:0x20] | 
					
						
							|  |  |  |             self.playerName.extend([0] * (0x20 - len(self.playerName))) | 
					
						
							|  |  |  |             patched_rom[0x30:0x50] = self.playerName | 
					
						
							|  |  |  |             patched_filename = os.path.join(output_directory, outputFilename) | 
					
						
							|  |  |  |             with open(patched_filename, 'wb') as patched_rom_file: | 
					
						
							|  |  |  |                 patched_rom_file.write(patched_rom) | 
					
						
							|  |  |  |             patch = TLoZDeltaPatch(os.path.splitext(outputFilename)[0] + TLoZDeltaPatch.patch_file_ending, | 
					
						
							|  |  |  |                                    player=self.player, | 
					
						
							|  |  |  |                                    player_name=self.multiworld.player_name[self.player], | 
					
						
							|  |  |  |                                    patched_path=outputFilename) | 
					
						
							|  |  |  |             patch.write() | 
					
						
							|  |  |  |             os.unlink(patched_filename) | 
					
						
							|  |  |  |         finally: | 
					
						
							|  |  |  |             self.rom_name_available_event.set() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def modify_multidata(self, multidata: dict): | 
					
						
							|  |  |  |         import base64 | 
					
						
							|  |  |  |         self.rom_name_available_event.wait() | 
					
						
							| 
									
										
										
										
											2023-06-24 19:58:54 -04:00
										 |  |  |         rom_name = getattr(self, "rom_name", None) | 
					
						
							|  |  |  |         if rom_name: | 
					
						
							|  |  |  |             new_name = base64.b64encode(bytes(self.rom_name)).decode() | 
					
						
							|  |  |  |             multidata["connect_names"][new_name] = multidata["connect_names"][self.multiworld.player_name[self.player]] | 
					
						
							| 
									
										
										
										
											2023-03-05 07:31:31 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def get_filler_item_name(self) -> str: | 
					
						
							|  |  |  |         if self.filler_items is None: | 
					
						
							|  |  |  |             self.filler_items = [item for item in item_table if item_table[item].classification == ItemClassification.filler] | 
					
						
							| 
									
										
										
										
											2024-07-24 08:00:16 -04:00
										 |  |  |         return self.random.choice(self.filler_items) | 
					
						
							| 
									
										
										
										
											2023-03-05 07:31:31 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def fill_slot_data(self) -> Dict[str, Any]: | 
					
						
							| 
									
										
										
										
											2024-01-14 22:47:32 -05:00
										 |  |  |         if self.options.ExpandedPool: | 
					
						
							| 
									
										
										
										
											2023-03-05 07:31:31 -05:00
										 |  |  |             take_any_left = self.multiworld.get_location("Take Any Item Left", self.player).item | 
					
						
							|  |  |  |             take_any_middle = self.multiworld.get_location("Take Any Item Middle", self.player).item | 
					
						
							|  |  |  |             take_any_right = self.multiworld.get_location("Take Any Item Right", self.player).item | 
					
						
							|  |  |  |             if take_any_left.player == self.player: | 
					
						
							|  |  |  |                 take_any_left = take_any_left.code | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 take_any_left = -1 | 
					
						
							|  |  |  |             if take_any_middle.player == self.player: | 
					
						
							|  |  |  |                 take_any_middle = take_any_middle.code | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 take_any_middle = -1 | 
					
						
							|  |  |  |             if take_any_right.player == self.player: | 
					
						
							|  |  |  |                 take_any_right = take_any_right.code | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 take_any_right = -1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             slot_data = { | 
					
						
							|  |  |  |                 "TakeAnyLeft": take_any_left, | 
					
						
							|  |  |  |                 "TakeAnyMiddle": take_any_middle, | 
					
						
							|  |  |  |                 "TakeAnyRight": take_any_right | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             slot_data = { | 
					
						
							|  |  |  |                 "TakeAnyLeft": -1, | 
					
						
							|  |  |  |                 "TakeAnyMiddle": -1, | 
					
						
							|  |  |  |                 "TakeAnyRight": -1 | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         return slot_data | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class TLoZItem(Item): | 
					
						
							|  |  |  |     game = 'The Legend of Zelda' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class TLoZLocation(Location): | 
					
						
							| 
									
										
										
										
											2023-06-24 19:58:54 -04:00
										 |  |  |     game = 'The Legend of Zelda' |