diff --git a/.gitignore b/.gitignore index bee1283a..b36abdb5 100644 --- a/.gitignore +++ b/.gitignore @@ -36,6 +36,6 @@ resources/user/* get-pip.py venv -test +test_games/ data/sprites/official/selan.1.zspr *.zspr diff --git a/Adjuster.py b/Adjuster.py index a6e964a8..9e003b3f 100755 --- a/Adjuster.py +++ b/Adjuster.py @@ -37,6 +37,7 @@ def main(): parser.add_argument('--uw_palettes', default='default', choices=['default', 'random', 'blackout']) parser.add_argument('--reduce_flashing', help='Reduce some in-game flashing.', action='store_true') parser.add_argument('--shuffle_sfx', help='Shuffles sound sfx', action='store_true') + parser.add_argument('--msu_resume', help='Enable MSU resume', action='store_true') parser.add_argument('--sprite', help='''\ Path to a sprite sheet to use for Link. Needs to be in binary format and have a length of 0x7000 (28672) bytes, diff --git a/AdjusterMain.py b/AdjusterMain.py index 7d7e2f6e..c6f19680 100644 --- a/AdjusterMain.py +++ b/AdjusterMain.py @@ -2,8 +2,15 @@ import os import time import logging +try: + import bps.apply + import bps.io +except ImportError: + raise Exception('Could not load BPS module') + from Utils import output_path from Rom import LocalRom, apply_rom_settings +from source.tools.BPS import bps_read_vlv def adjust(args): @@ -25,7 +32,8 @@ def adjust(args): args.sprite = None apply_rom_settings(rom, args.heartbeep, args.heartcolor, args.quickswap, args.fastmenu, args.disablemusic, - args.sprite, args.ow_palettes, args.uw_palettes, args.reduce_flashing, args.shuffle_sfx) + args.sprite, args.ow_palettes, args.uw_palettes, args.reduce_flashing, args.shuffle_sfx, + args.msu_resume) output_path.cached_path = args.outputpath rom.write_to_file(output_path('%s.sfc' % outfilebase)) @@ -34,3 +42,38 @@ def adjust(args): logger.debug('Total Time: %s', time.process_time() - start) return args + + +def patch(args): + start = time.process_time() + logger = logging.getLogger('') + logger.info('Patching ROM.') + + outfile_base = os.path.basename(args.patch)[:-4] + + rom = LocalRom(args.baserom, False) + if os.path.isfile(args.baserom): + rom.verify_base_rom() + orig_buffer = rom.buffer.copy() + with open(args.patch, 'rb') as stream: + stream.seek(4) # skip BPS1 + bps_read_vlv(stream) # skip source size + target_length = bps_read_vlv(stream) + rom.buffer.extend(bytearray([0x00] * (target_length - len(rom.buffer)))) + stream.seek(0) + bps.apply.apply_to_bytearrays(bps.io.read_bps(stream), orig_buffer, rom.buffer) + + if not hasattr(args, "sprite"): + args.sprite = None + + apply_rom_settings(rom, args.heartbeep, args.heartcolor, args.quickswap, args.fastmenu, args.disablemusic, + args.sprite, args.ow_palettes, args.uw_palettes, args.reduce_flashing, args.shuffle_sfx, + args.msu_resume) + + output_path.cached_path = args.outputpath + rom.write_to_file(output_path('%s.sfc' % outfile_base)) + + logger.info('Done. Enjoy.') + logger.debug('Total Time: %s', time.process_time() - start) + + return args diff --git a/BaseClasses.py b/BaseClasses.py index 4ea4bf3d..12ef4ee3 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -16,6 +16,8 @@ from EntranceShuffle import door_addresses, indirect_connections from Utils import int16_as_bytes from Tables import normal_offset_table, spiral_offset_table, multiply_lookup, divisor_lookup from RoomData import Room +from source.dungeon.RoomObject import RoomObject + class World(object): @@ -85,6 +87,7 @@ class World(object): self._portal_cache = {} self.sanc_portal = {} self.fish = BabelFish() + self.pot_contents = {} for player in range(1, players + 1): # If World State is Retro, set to Open and set Retro flag @@ -116,6 +119,7 @@ class World(object): set_player_attr('compassshuffle', False) set_player_attr('keyshuffle', False) set_player_attr('bigkeyshuffle', False) + set_player_attr('restrict_boss_items', 'none') set_player_attr('bombbag', False) set_player_attr('difficulty_requirements', None) set_player_attr('boss_shuffle', 'none') @@ -135,9 +139,11 @@ class World(object): set_player_attr('potshuffle', False) set_player_attr('pot_contents', None) set_player_attr('pseudoboots', False) + set_player_attr('collection_rate', False) + set_player_attr('colorizepots', False) + set_player_attr('pot_pool', {}) set_player_attr('shopsanity', False) - set_player_attr('keydropshuffle', False) set_player_attr('mixed_travel', 'prevent') set_player_attr('standardize_palettes', 'standardize') set_player_attr('force_fix', {'gt': False, 'sw': False, 'pod': False, 'tr': False}) @@ -215,6 +221,11 @@ class World(object): return r_location raise RuntimeError('No such location %s for player %d' % (location, player)) + def get_location_unsafe(self, location, player): + if (location, player) in self._location_cache: + return self._location_cache[(location, player)] + return None + def get_dungeon(self, dungeonname, player): if isinstance(dungeonname, Dungeon): return dungeonname @@ -327,7 +338,7 @@ class World(object): elif item.name.startswith('Bottle'): if ret.bottle_count(item.player) < self.difficulty_requirements[item.player].progressive_bottle_limit: ret.prog_items[item.name, item.player] += 1 - elif item.advancement or item.smallkey or item.bigkey: + elif item.advancement or item.smallkey or item.bigkey or item.compass or item.map: ret.prog_items[item.name, item.player] += 1 for item in self.itempool: @@ -342,6 +353,8 @@ class World(object): key_list += [dungeon.big_key.name] if len(dungeon.small_keys) > 0: key_list += [x.name for x in dungeon.small_keys] + # map/compass may be required now + key_list += [x.name for x in dungeon.dungeon_items] from Items import ItemFactory for item in ItemFactory(key_list, p): soft_collect(item) @@ -372,6 +385,8 @@ class World(object): location.item = item item.location = location item.world = self + if location.player != item.player and location.type == LocationType.Pot: + self.pot_contents[location.player].multiworld_count += 1 if collect: self.state.collect(item, location.event, location) @@ -852,7 +867,7 @@ class CollectionState(object): reduced = Counter() for item, cnt in self.prog_items.items(): item_name, item_player = item - if item_player == player and self.check_if_progressive(item_name): + if item_player == player and self.check_if_progressive(item_name, player): if item_name.startswith('Bottle'): # I think magic requirements can require multiple bottles bottle_count += cnt elif item_name in ['Boss Heart Container', 'Sanctuary Heart Container', 'Piece of Heart']: @@ -868,8 +883,7 @@ class CollectionState(object): reduced[('Heart Container', player)] = 1 return frozenset(reduced.items()) - @staticmethod - def check_if_progressive(item_name): + def check_if_progressive(self, item_name, player): return (item_name in ['Bow', 'Progressive Bow', 'Progressive Bow (Alt)', 'Book of Mudora', 'Hammer', 'Hookshot', 'Magic Mirror', 'Ocarina', 'Pegasus Boots', 'Power Glove', 'Cape', 'Mushroom', 'Shovel', @@ -881,7 +895,8 @@ class CollectionState(object): 'Mirror Shield', 'Progressive Shield', 'Bug Catching Net', 'Cane of Byrna', 'Boss Heart Container', 'Sanctuary Heart Container', 'Piece of Heart', 'Magic Upgrade (1/2)', 'Magic Upgrade (1/4)'] - or item_name.startswith(('Bottle', 'Small Key', 'Big Key'))) + or item_name.startswith(('Bottle', 'Small Key', 'Big Key')) + or (self.world.restrict_boss_items[player] != 'none' and item_name.startswith(('Map', 'Compass')))) def can_reach(self, spot, resolution_hint=None, player=None): try: @@ -1078,16 +1093,16 @@ class CollectionState(object): return self.has('Bow', player) and (self.can_buy_unlimited('Single Arrow', player) or self.has('Single Arrow', player)) return self.has('Bow', player) - def can_get_good_bee(self, player): - cave = self.world.get_region('Good Bee Cave', player) - return ( - self.can_use_bombs(player) and - self.has_bottle(player) and - self.has('Bug Catching Net', player) and - (self.has_Boots(player) or (self.has_sword(player) and self.has('Quake', player))) and - cave.can_reach(self) and - self.is_not_bunny(cave, player) - ) + # def can_get_good_bee(self, player): + # cave = self.world.get_region('Good Bee Cave', player) + # return ( + # self.can_use_bombs(player) and + # self.has_bottle(player) and + # self.has('Bug Catching Net', player) and + # (self.has_Boots(player) or (self.has_sword(player) and self.has('Quake', player))) and + # cave.can_reach(self) and + # self.is_not_bunny(cave, player) + # ) def has_sword(self, player): return self.has('Fighter Sword', player) or self.has('Master Sword', player) or self.has('Tempered Sword', player) or self.has('Golden Sword', player) @@ -1459,6 +1474,10 @@ class Dungeon(object): return self.world.get_name_string_for_object(self) if self.world else f'{self.name} (Player {self.player})' +class FillError(RuntimeError): + pass + + @unique class DoorType(Enum): Normal = 1 @@ -1850,6 +1869,8 @@ class Sector(object): self.destination_entrance = False self.equations = None self.item_logic = set() + self.chest_location_set = set() + def region_set(self): if self.r_name_set is None: @@ -1958,6 +1979,12 @@ class Portal(object): self.dependent = None self.deadEnd = False self.light_world = False + self.chosen = False + + def find_portal_entrance(self): + p_region = self.door.entrance.connected_region + return next((x for x in p_region.entrances + if x.parent_region.type in [RegionType.LightWorld, RegionType.DarkWorld]), None) def change_boss_exit(self, exit_idx): self.default = False @@ -2092,15 +2119,25 @@ class Location(object): self.recursion_count = 0 self.staleness_count = 0 self.locked = False + self.real = not crystal self.always_allow = lambda item, state: False self.access_rule = lambda state: True self.item_rule = lambda item: True self.player = player self.skip = False + self.type = LocationType.Normal if not crystal else LocationType.Prize + self.pot = None def can_fill(self, state, item, check_access=True): + if not self.valid_multiworld(state, item): + return False return self.always_allow(state, item) or (self.parent_region.can_fill(item) and self.item_rule(item) and (not check_access or self.can_reach(state))) + def valid_multiworld(self, state, item): + if self.type == LocationType.Pot and self.player != item.player: + return state.world.pot_contents[self.player].multiworld_count < 256 + return True + def can_reach(self, state): return self.parent_region.can_reach(state) and self.access_rule(state) @@ -2136,6 +2173,15 @@ class Location(object): return hash((self.name, self.player)) +class LocationType(FastEnum): + Normal = 0 + Prize = 1 + Logical = 2 + Shop = 3 + Pot = 4 + Drop = 5 + + class Item(object): def __init__(self, name='', advancement=False, priority=False, type=None, code=None, price=999, pedestal_hint=None, @@ -2187,6 +2233,12 @@ class Item(object): item_dungeon = 'Hyrule Castle' return item_dungeon + def is_inside_dungeon_item(self, world): + return ((self.smallkey and not world.keyshuffle[self.player]) + or (self.bigkey and not world.bigkeyshuffle[self.player]) + or (self.compass and not world.compassshuffle[self.player]) + or (self.map and not world.mapshuffle[self.player])) + def __str__(self): return str(self.__unicode__()) @@ -2307,6 +2359,62 @@ class Spoiler(object): else: self.doorTypes[(doorNames, player)] = OrderedDict([('player', player), ('doorNames', doorNames), ('type', type)]) + def parse_meta(self): + from Main import __version__ as ERVersion + + self.startinventory = list(map(str, self.world.precollected_items)) + self.metadata = {'version': ERVersion, + 'logic': self.world.logic, + 'mode': self.world.mode, + 'retro': self.world.retro, + 'bombbag': self.world.bombbag, + 'weapons': self.world.swords, + 'goal': self.world.goal, + 'shuffle': self.world.shuffle, + 'shuffleganon': self.world.shuffle_ganon, + 'shufflelinks': self.world.shufflelinks, + 'overworld_map': self.world.overworld_map, + 'door_shuffle': self.world.doorShuffle, + 'intensity': self.world.intensity, + 'dungeon_counters': self.world.dungeon_counters, + 'item_pool': self.world.difficulty, + 'item_functionality': self.world.difficulty_adjustments, + 'gt_crystals': self.world.crystals_needed_for_gt, + 'ganon_crystals': self.world.crystals_needed_for_ganon, + 'open_pyramid': self.world.open_pyramid, + 'accessibility': self.world.accessibility, + 'restricted_boss_items': self.world.restrict_boss_items, + 'hints': self.world.hints, + 'mapshuffle': self.world.mapshuffle, + 'compassshuffle': self.world.compassshuffle, + 'keyshuffle': self.world.keyshuffle, + 'bigkeyshuffle': self.world.bigkeyshuffle, + 'boss_shuffle': self.world.boss_shuffle, + 'enemy_shuffle': self.world.enemy_shuffle, + 'enemy_health': self.world.enemy_health, + 'enemy_damage': self.world.enemy_damage, + 'players': self.world.players, + 'teams': self.world.teams, + 'experimental': self.world.experimental, + 'dropshuffle': self.world.dropshuffle, + 'pottery': self.world.pottery, + 'potshuffle': self.world.potshuffle, + 'shopsanity': self.world.shopsanity, + 'pseudoboots': self.world.pseudoboots, + 'triforcegoal': self.world.treasure_hunt_count, + 'triforcepool': self.world.treasure_hunt_total, + 'code': {p: Settings.make_code(self.world, p) for p in range(1, self.world.players + 1)} + } + + for p in range(1, self.world.players + 1): + from ItemList import set_default_triforce + if self.world.custom and p in self.world.customitemarray: + self.metadata['triforcegoal'][p], self.metadata['triforcepool'][p] = set_default_triforce(self.metadata['goal'][p], self.world.customitemarray[p]["triforcepiecesgoal"], self.world.customitemarray[p]["triforcepieces"]) + else: + custom_goal = self.world.treasure_hunt_count[p] if isinstance(self.world.treasure_hunt_count, dict) else self.world.treasure_hunt_count + custom_total = self.world.treasure_hunt_total[p] if isinstance(self.world.treasure_hunt_total, dict) else self.world.treasure_hunt_total + self.metadata['triforcegoal'][p], self.metadata['triforcepool'][p] = set_default_triforce(self.metadata['goal'][p], custom_goal, custom_total) + def parse_data(self): self.medallions = OrderedDict() if self.world.players == 1: @@ -2326,8 +2434,6 @@ class Spoiler(object): self.bottles[f'Waterfall Bottle ({self.world.get_player_names(player)})'] = self.world.bottle_refills[player][0] self.bottles[f'Pyramid Bottle ({self.world.get_player_names(player)})'] = self.world.bottle_refills[player][1] - self.startinventory = list(map(str, self.world.precollected_items)) - self.locations = OrderedDict() listed_locations = set() @@ -2344,11 +2450,11 @@ class Spoiler(object): listed_locations.update(cave_locations) for dungeon in self.world.dungeons: - dungeon_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.dungeon == dungeon and not loc.forced_item] + dungeon_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.dungeon == dungeon and not loc.forced_item and not loc.skip] self.locations[str(dungeon)] = OrderedDict([(location.gen_name(), str(location.item) if location.item is not None else 'Nothing') for location in dungeon_locations]) listed_locations.update(dungeon_locations) - other_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations] + other_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and not loc.skip] if other_locations: self.locations['Other Locations'] = OrderedDict([(location.gen_name(), str(location.item) if location.item is not None else 'Nothing') for location in other_locations]) listed_locations.update(other_locations) @@ -2398,44 +2504,6 @@ class Spoiler(object): for portal in self.world.dungeon_portals[player]: self.set_lobby(portal.name, portal.door.name, player) - from Main import __version__ as ERVersion - self.metadata = {'version': ERVersion, - 'logic': self.world.logic, - 'mode': self.world.mode, - 'retro': self.world.retro, - 'bombbag': self.world.bombbag, - 'weapons': self.world.swords, - 'goal': self.world.goal, - 'shuffle': self.world.shuffle, - 'shufflelinks': self.world.shufflelinks, - 'door_shuffle': self.world.doorShuffle, - 'intensity': self.world.intensity, - 'item_pool': self.world.difficulty, - 'item_functionality': self.world.difficulty_adjustments, - 'gt_crystals': self.world.crystals_needed_for_gt, - 'ganon_crystals': self.world.crystals_needed_for_ganon, - 'open_pyramid': self.world.open_pyramid, - 'accessibility': self.world.accessibility, - 'hints': self.world.hints, - 'mapshuffle': self.world.mapshuffle, - 'compassshuffle': self.world.compassshuffle, - 'keyshuffle': self.world.keyshuffle, - 'bigkeyshuffle': self.world.bigkeyshuffle, - 'boss_shuffle': self.world.boss_shuffle, - 'enemy_shuffle': self.world.enemy_shuffle, - 'enemy_health': self.world.enemy_health, - 'enemy_damage': self.world.enemy_damage, - 'potshuffle': self.world.potshuffle, - 'players': self.world.players, - 'teams': self.world.teams, - 'experimental': self.world.experimental, - 'keydropshuffle': self.world.keydropshuffle, - 'shopsanity': self.world.shopsanity, - 'triforcegoal': self.world.treasure_hunt_count, - 'triforcepool': self.world.treasure_hunt_total, - 'code': {p: Settings.make_code(self.world, p) for p in range(1, self.world.players + 1)} - } - def to_json(self): self.parse_data() out = OrderedDict() @@ -2458,40 +2526,62 @@ class Spoiler(object): return json.dumps(out) - def to_file(self, filename): - self.parse_data() + def mystery_meta_to_file(self, filename): + self.parse_meta() with open(filename, 'w') as outfile: - outfile.write('ALttP Entrance Randomizer Version %s - Seed: %s\n\n' % (self.metadata['version'], self.world.seed)) + outfile.write(f'ALttP Dungeon Randomizer Version {self.metadata["version"]}\n\n') + for player in range(1, self.world.players + 1): + if self.world.players > 1: + outfile.write('\nPlayer %d: %s\n' % (player, self.world.get_player_names(player))) + outfile.write('Logic: %s\n' % self.metadata['logic'][player]) + + def meta_to_file(self, filename): + def yn(flag): + return 'Yes' if flag else 'No' + + line_width = 35 + self.parse_meta() + with open(filename, 'w') as outfile: + outfile.write('ALttP Dungeon Randomizer Version %s - Seed: %s\n\n' % (self.metadata['version'], self.world.seed)) outfile.write('Filling Algorithm: %s\n' % self.world.algorithm) outfile.write('Players: %d\n' % self.world.players) outfile.write('Teams: %d\n' % self.world.teams) for player in range(1, self.world.players + 1): if self.world.players > 1: outfile.write('\nPlayer %d: %s\n' % (player, self.world.get_player_names(player))) - if len(self.hashes) > 0: - for team in range(self.world.teams): - outfile.write('%s%s\n' % (f"Hash - {self.world.player_names[player][team]} (Team {team+1}): " if self.world.teams > 1 else 'Hash: ', self.hashes[player, team])) outfile.write(f'Settings Code: {self.metadata["code"][player]}\n') outfile.write('Logic: %s\n' % self.metadata['logic'][player]) outfile.write('Mode: %s\n' % self.metadata['mode'][player]) outfile.write('Retro: %s\n' % ('Yes' if self.metadata['retro'][player] else 'No')) outfile.write('Swords: %s\n' % self.metadata['weapons'][player]) outfile.write('Goal: %s\n' % self.metadata['goal'][player]) - if self.metadata['goal'][player] == 'triforcehunt': + if self.metadata['goal'][player] in ['triforcehunt', 'trinity']: outfile.write('Triforce Pieces Required: %s\n' % self.metadata['triforcegoal'][player]) outfile.write('Triforce Pieces Total: %s\n' % self.metadata['triforcepool'][player]) + outfile.write('Crystals required for GT: %s\n' % (str(self.world.crystals_gt_orig[player]))) + outfile.write('Crystals required for Ganon: %s\n' % (str(self.world.crystals_ganon_orig[player]))) + outfile.write('Accessibility: %s\n' % self.metadata['accessibility'][player]) + outfile.write(f"Restricted Boss Items: {self.metadata['restricted_boss_items'][player]}\n") outfile.write('Difficulty: %s\n' % self.metadata['item_pool'][player]) outfile.write('Item Functionality: %s\n' % self.metadata['item_functionality'][player]) + outfile.write(f"Shopsanity: {yn(self.metadata['shopsanity'][player])}\n") + outfile.write(f"Bombbag: {yn(self.metadata['bombbag'][player])}\n") + outfile.write(f"Pseudoboots: {yn(self.metadata['pseudoboots'][player])}\n") outfile.write('Entrance Shuffle: %s\n' % self.metadata['shuffle'][player]) - outfile.write(f"Link's House Shuffled: {'Yes' if self.metadata['shufflelinks'][player] else 'No'}\n") + if self.metadata['shuffle'][player] != 'vanilla': + outfile.write(f"Link's House Shuffled: {yn(self.metadata['shufflelinks'])}\n") + outfile.write(f"GT/Ganon Shuffled: {yn(self.metadata['shuffleganon'])}\n") + outfile.write(f"Overworld Map: {self.metadata['overworld_map'][player]}\n") + if self.metadata['goal'][player] != 'trinity': + outfile.write('Pyramid hole pre-opened: %s\n' % ('Yes' if self.metadata['open_pyramid'][player] else 'No')) outfile.write('Door Shuffle: %s\n' % self.metadata['door_shuffle'][player]) - outfile.write('Intensity: %s\n' % self.metadata['intensity'][player]) - addition = ' (Random)' if self.world.crystals_gt_orig[player] == 'random' else '' - outfile.write('Crystals required for GT: %s\n' % (str(self.metadata['gt_crystals'][player]) + addition)) - addition = ' (Random)' if self.world.crystals_ganon_orig[player] == 'random' else '' - outfile.write('Crystals required for Ganon: %s\n' % (str(self.metadata['ganon_crystals'][player]) + addition)) - outfile.write('Pyramid hole pre-opened: %s\n' % ('Yes' if self.metadata['open_pyramid'][player] else 'No')) - outfile.write('Accessibility: %s\n' % self.metadata['accessibility'][player]) + if self.metadata['door_shuffle'][player] != 'vanilla': + outfile.write(f"Intensity: {self.metadata['intensity'][player]}\n") + outfile.write(f"Experimental: {yn(self.metadata['experimental'][player])}\n") + outfile.write(f"Dungeon Counters: {self.metadata['dungeon_counters'][player]}\n") + outfile.write(f"Drop Shuffle: {yn(self.metadata['dropshuffle'][player])}\n") + outfile.write(f"Pottery Mode: {self.metadata['pottery'][player]}\n") + outfile.write(f"Pot Shuffle (Legacy): {yn(self.metadata['potshuffle'][player])}\n") outfile.write('Map shuffle: %s\n' % ('Yes' if self.metadata['mapshuffle'][player] else 'No')) outfile.write('Compass shuffle: %s\n' % ('Yes' if self.metadata['compassshuffle'][player] else 'No')) outfile.write('Small Key shuffle: %s\n' % ('Yes' if self.metadata['keyshuffle'][player] else 'No')) @@ -2500,12 +2590,63 @@ class Spoiler(object): outfile.write('Enemy shuffle: %s\n' % self.metadata['enemy_shuffle'][player]) outfile.write('Enemy health: %s\n' % self.metadata['enemy_health'][player]) outfile.write('Enemy damage: %s\n' % self.metadata['enemy_damage'][player]) - outfile.write('Pot shuffle: %s\n' % ('Yes' if self.metadata['potshuffle'][player] else 'No')) - outfile.write('Hints: %s\n' % ('Yes' if self.metadata['hints'][player] else 'No')) - outfile.write('Experimental: %s\n' % ('Yes' if self.metadata['experimental'][player] else 'No')) - outfile.write('Key Drops shuffled: %s\n' % ('Yes' if self.metadata['keydropshuffle'][player] else 'No')) - outfile.write(f"Shopsanity: {'Yes' if self.metadata['shopsanity'][player] else 'No'}\n") - outfile.write('Bombbag: %s\n' % ('Yes' if self.metadata['bombbag'][player] else 'No')) + outfile.write(f"Hints: {yn(self.metadata['hints'][player])}\n") + + if self.startinventory: + outfile.write('Starting Inventory:'.ljust(line_width)) + outfile.write('\n'.ljust(line_width+1).join(self.startinventory) + '\n') + + def hashes_to_file(self, filename): + with open(filename, 'r') as infile: + contents = infile.readlines() + + def insert(lines, i, value): + lines.insert(i, value) + i += 1 + return i + + idx = 2 + if self.world.players > 1: + idx = insert(contents, idx, 'Hashes:') + for player in range(1, self.world.players + 1): + if self.world.players > 1: + idx = insert(contents, idx, f'\nPlayer {player}: {self.world.get_player_names(player)}\n') + if len(self.hashes) > 0: + for team in range(self.world.teams): + player_name = self.world.player_names[player][team] + label = f"Hash - {player_name} (Team {team+1}): " if self.world.teams > 1 else 'Hash: ' + idx = insert(contents, idx, f'{label}{self.hashes[player, team]}\n') + if self.world.players > 1: + insert(contents, idx, '\n') # return value ignored here, if you want to add more lines + + with open(filename, "w") as f: + contents = "".join(contents) + f.write(contents) + + def to_file(self, filename): + self.parse_data() + with open(filename, 'a') as outfile: + line_width = 35 + + outfile.write('\nRequirements:\n\n') + for dungeon, medallion in self.medallions.items(): + outfile.write(f'{dungeon}: {medallion} Medallion\n') + for player in range(1, self.world.players + 1): + player_name = '' if self.world.players == 1 else str(' (Player ' + str(player) + ')') + if self.world.crystals_gt_orig[player] == 'random': + outfile.write(str('Crystals Required for GT' + player_name + ':').ljust(line_width) + '%s\n' % (str(self.metadata['gt_crystals'][player]))) + if self.world.crystals_ganon_orig[player] == 'random': + outfile.write(str('Crystals Required for Ganon' + player_name + ':').ljust(line_width) + '%s\n' % (str(self.metadata['ganon_crystals'][player]))) + + outfile.write('\n\nBottle Refills:\n\n') + for fairy, bottle in self.bottles.items(): + outfile.write(f'{fairy}: {bottle}\n') + + if self.entrances: + # entrances: To/From overworld; Checking w/ & w/out "Exit" and translating accordingly + outfile.write('\nEntrances:\n\n') + outfile.write('\n'.join(['%s%s %s %s' % (f'{self.world.get_player_names(entry["player"])}: ' if self.world.players > 1 else '', self.world.fish.translate("meta","entrances",entry['entrance']), '<=>' if entry['direction'] == 'both' else '<=' if entry['direction'] == 'exit' else '=>', self.world.fish.translate("meta","entrances",entry['exit'])) for entry in self.entrances.values()])) + if self.doors: outfile.write('\n\nDoors:\n\n') outfile.write('\n'.join( @@ -2526,19 +2667,6 @@ class Spoiler(object): # doorTypes: Small Key, Bombable, Bonkable outfile.write('\n\nDoor Types:\n\n') outfile.write('\n'.join(['%s%s %s' % ('Player {0}: '.format(entry['player']) if self.world.players > 1 else '', self.world.fish.translate("meta","doors",entry['doorNames']), self.world.fish.translate("meta","doorTypes",entry['type'])) for entry in self.doorTypes.values()])) - if self.entrances: - # entrances: To/From overworld; Checking w/ & w/out "Exit" and translating accordingly - outfile.write('\n\nEntrances:\n\n') - outfile.write('\n'.join(['%s%s %s %s' % (f'{self.world.get_player_names(entry["player"])}: ' if self.world.players > 1 else '', self.world.fish.translate("meta","entrances",entry['entrance']), '<=>' if entry['direction'] == 'both' else '<=' if entry['direction'] == 'exit' else '=>', self.world.fish.translate("meta","entrances",entry['exit'])) for entry in self.entrances.values()])) - outfile.write('\n\nMedallions:\n') - for dungeon, medallion in self.medallions.items(): - outfile.write(f'\n{dungeon}: {medallion} Medallion') - outfile.write('\n\nBottle Refills:\n') - for fairy, bottle in self.bottles.items(): - outfile.write(f'\n{fairy}: {bottle}') - if self.startinventory: - outfile.write('\n\nStarting Inventory:\n\n') - outfile.write('\n'.join(self.startinventory)) # locations: Change up location names; in the instance of a location with multiple sections, it'll try to translate the room name # items: Item names @@ -2556,6 +2684,8 @@ class Spoiler(object): outfile.write(f'\n\nBosses ({self.world.get_player_names(player)}):\n\n') outfile.write('\n'.join([f'{x}: {y}' for x, y in bossmap.items() if y not in ['Agahnim', 'Agahnim 2', 'Ganon']])) + def playthrough_to_file(self, filename): + with open(filename, 'a') as outfile: # locations: Change up location names; in the instance of a location with multiple sections, it'll try to translate the room name # items: Item names outfile.write('\n\nPlaythrough:\n\n') @@ -2646,15 +2776,45 @@ class PotFlags(FastEnum): Normal = 0x0 NoSwitch = 0x1 # A switch should never go here SwitchLogicChange = 0x2 # A switch can go here, but requires a logic change + Block = 0x4 # This is actually a block + LowerRegion = 0x8 # This is a pot in the lower region class Pot(object): - def __init__(self, x, y, item, room, flags = PotFlags.Normal): + def __init__(self, x, y, item, room, flags=PotFlags.Normal, obj=None): self.x = x self.y = y self.item = item self.room = room self.flags = flags + self.indicator = None # 0x80 for standing item, 0xC0 multiworld item + self.standing_item_code = None # standing item code if nay + self.obj_ref = obj + self.location = None # location back ref + + def copy(self): + obj_ref = RoomObject(self.obj_ref.address, self.obj_ref.data) if self.obj_ref else None + return Pot(self.x, self.y, self.item, self.room, self.flags, obj_ref) + + def pot_data(self): + high_byte = self.y + if self.flags & PotFlags.LowerRegion: + high_byte |= 0x20 + if self.indicator: + high_byte |= self.indicator + item = self.item if not self.indicator else self.standing_item_code + return [self.x, high_byte, item] + + def get_region(self, world, player): + if world.mode[player] == 'inverted' and self.room == 'Links House': + return world.get_region('Inverted Links House', 1) + return world.get_region(self.room, 1) + + def __eq__(self, other): + return self.x == other.x and self.y == other.y and self.room == other.room + + def __hash__(self): + return hash((self.x, self.y, self.room)) # byte 0: DDDE EEEE (DR, ER) @@ -2668,28 +2828,51 @@ world_mode = {"open": 0, "standard": 1, "inverted": 2} sword_mode = {"random": 0, "assured": 1, "swordless": 2, "vanilla": 3} # byte 2: GGGD DFFH (goal, diff, item_func, hints) -goal_mode = {"ganon": 0, "pedestal": 1, "dungeons": 2, "triforcehunt": 3, "crystals": 4} +goal_mode = {'ganon': 0, 'pedestal': 1, 'dungeons': 2, 'triforcehunt': 3, 'crystals': 4, 'trinity': 5} diff_mode = {"normal": 0, "hard": 1, "expert": 2} func_mode = {"normal": 0, "hard": 1, "expert": 2} -# byte 3: SKMM PIII (shop, keydrop, mixed, palettes, intensity) +# byte 3: S?MM PIII (shop, unused, mixed, palettes, intensity) +# keydrop now has it's own byte mixed_travel_mode = {"prevent": 0, "allow": 1, "force": 2} # intensity is 3 bits (reserves 4-7 levels) -# byte 4: CCCC CTTX (crystals gt, ctr2, experimental) +# new byte 4: ?DDD PPPP (unused, drop, pottery) +# dropshuffle reserves 2 bits, pottery needs 2 but reserves 2 for future modes) +pottery_mode = {'none': 0, 'keys': 2, 'lottery': 3, 'dungeon': 4, 'cave': 5, 'cavekeys': 6, 'reduced': 7, + 'clustered': 8, 'nonempty': 9} + +# byte 5: CCCC CTTX (crystals gt, ctr2, experimental) counter_mode = {"default": 0, "off": 1, "on": 2, "pickup": 3} -# byte 5: CCCC CPAA (crystals ganon, pyramid, access +# byte 6: CCCC CPAA (crystals ganon, pyramid, access access_mode = {"items": 0, "locations": 1, "none": 2} -# byte 6: BSMC BBEE (big, small, maps, compass, bosses, enemies) -boss_mode = {"none": 0, "simple": 1, "full": 2, "random": 3, "chaos": 3} -enemy_mode = {"none": 0, "shuffled": 1, "random": 2, "chaos": 2, "legacy": 3} +# byte 7: BSMC ??EE (big, small, maps, compass, bosses, enemies) +enemy_mode = {"none": 0, "shuffled": 1, "chaos": 2, "random": 2, "legacy": 3} -# byte 7: HHHD DPBS (enemy_health, enemy_dmg, potshuffle, bomb logic, shuffle links) +# byte 8: HHHD DPBS (enemy_health, enemy_dmg, potshuffle, bomb logic, shuffle links) +# potshuffle decprecated, now unused e_health = {"default": 0, "easy": 1, "normal": 2, "hard": 3, "expert": 4} e_dmg = {"default": 0, "shuffled": 1, "random": 2} +# byte 9: RRAA ABBB (restrict boss mode, algorithm, boss shuffle) +rb_mode = {"none": 0, "mapcompass": 1, "dungeon": 2} +# algorithm: +algo_mode = {"balanced": 0, "equitable": 1, "vanilla_fill": 2, "dungeon_only": 3, "district": 4, 'major_only': 5} +boss_mode = {"none": 0, "simple": 1, "full": 2, "chaos": 3, 'random': 3, 'unique': 4} + + +# byte 10: settings_version + +# additions +# psuedoboots does not effect code +# sfx_shuffle and other adjust items does not effect settings code + +# Bump this when making changes that are not backwards compatible (nearly all of them) +settings_version = 0 + + class Settings(object): @staticmethod @@ -2703,10 +2886,12 @@ class Settings(object): (goal_mode[w.goal[p]] << 5) | (diff_mode[w.difficulty[p]] << 3) | (func_mode[w.difficulty_adjustments[p]] << 1) | (1 if w.hints[p] else 0), - (0x80 if w.shopsanity[p] else 0) | (0x40 if w.keydropshuffle[p] else 0) - | (mixed_travel_mode[w.mixed_travel[p]] << 4) | (0x8 if w.standardize_palettes[p] == "original" else 0) + (0x80 if w.shopsanity[p] else 0) | (mixed_travel_mode[w.mixed_travel[p]] << 4) + | (0x8 if w.standardize_palettes[p] == "original" else 0) | (0 if w.intensity[p] == "random" else w.intensity[p]), + (0x10 if w.dropshuffle[p] else 0) | (pottery_mode[w.pottery[p]]), + ((8 if w.crystals_gt_orig[p] == "random" else int(w.crystals_gt_orig[p])) << 3) | (counter_mode[w.dungeon_counters[p]] << 1) | (1 if w.experimental[p] else 0), @@ -2715,16 +2900,25 @@ class Settings(object): (0x80 if w.bigkeyshuffle[p] else 0) | (0x40 if w.keyshuffle[p] else 0) | (0x20 if w.mapshuffle[p] else 0) | (0x10 if w.compassshuffle[p] else 0) - | (boss_mode[w.boss_shuffle[p]] << 2) | (enemy_mode[w.enemy_shuffle[p]]), + | (enemy_mode[w.enemy_shuffle[p]]), (e_health[w.enemy_health[p]] << 5) | (e_dmg[w.enemy_damage[p]] << 3) | (0x4 if w.potshuffle[p] else 0) - | (0x2 if w.bombbag[p] else 0) | (1 if w.shufflelinks[p] else 0)]) + | (0x2 if w.bombbag[p] else 0) | (1 if w.shufflelinks[p] else 0), + + (rb_mode[w.restrict_boss_items[p]] << 6) | (algo_mode[w.algorithm] << 3) | (boss_mode[w.boss_shuffle[p]]), + + settings_version]) return base64.b64encode(code, "+-".encode()).decode() @staticmethod def adjust_args_from_code(code, player, args): settings, p = base64.b64decode(code.encode(), "+-".encode()), player + if len(settings) < 11: + raise Exception('Provided code is incompatible with this version') + if settings[10] != settings_version: + raise Exception('Provided code is incompatible with this version') + def r(d): return {y: x for x, y in d.items()} @@ -2736,34 +2930,45 @@ class Settings(object): args.difficulty[p] = r(diff_mode)[(settings[2] & 0x18) >> 3] args.item_functionality[p] = r(func_mode)[(settings[2] & 0x6) >> 1] args.goal[p] = r(goal_mode)[(settings[2] & 0xE0) >> 5] - args.accessibility[p] = r(access_mode)[settings[5] & 0x3] + args.accessibility[p] = r(access_mode)[settings[6] & 0x3] args.retro[p] = True if settings[1] & 0x01 else False args.hints[p] = True if settings[2] & 0x01 else False - args.retro[p] = True if settings[1] & 0x01 else False args.shopsanity[p] = True if settings[3] & 0x80 else False - args.keydropshuffle[p] = True if settings[3] & 0x40 else False + # args.keydropshuffle[p] = True if settings[3] & 0x40 else False args.mixed_travel[p] = r(mixed_travel_mode)[(settings[3] & 0x30) >> 4] args.standardize_palettes[p] = "original" if settings[3] & 0x8 else "standardize" intensity = settings[3] & 0x7 args.intensity[p] = "random" if intensity == 0 else intensity - args.dungeon_counters[p] = r(counter_mode)[(settings[4] & 0x6) >> 1] - cgt = (settings[4] & 0xf8) >> 3 + + # args.shuffleswitches[p] = True if settings[4] & 0x80 else False + args.dropshuffle[p] = True if settings[4] & 0x10 else False + args.pottery[p] = r(pottery_mode)[settings[4] & 0x0F] + + args.dungeon_counters[p] = r(counter_mode)[(settings[5] & 0x6) >> 1] + cgt = (settings[5] & 0xf8) >> 3 args.crystals_gt[p] = "random" if cgt == 8 else cgt - args.experimental[p] = True if settings[4] & 0x1 else False - cgan = (settings[5] & 0xf8) >> 3 + args.experimental[p] = True if settings[5] & 0x1 else False + + cgan = (settings[6] & 0xf8) >> 3 args.crystals_ganon[p] = "random" if cgan == 8 else cgan - args.openpyramid[p] = True if settings[5] & 0x4 else False - args.bigkeyshuffle[p] = True if settings[6] & 0x80 else False - args.keyshuffle[p] = True if settings[6] & 0x40 else False - args.mapshuffle[p] = True if settings[6] & 0x20 else False - args.compassshuffle[p] = True if settings[6] & 0x10 else False - args.shufflebosses[p] = r(boss_mode)[(settings[6] & 0xc) >> 2] - args.shuffleenemies[p] = r(enemy_mode)[settings[6] & 0x3] - args.enemy_health[p] = r(e_health)[(settings[7] & 0xE0) >> 5] - args.enemy_damage[p] = r(e_dmg)[(settings[7] & 0x18) >> 3] - args.shufflepots[p] = True if settings[7] & 0x4 else False - args.bombbag[p] = True if settings[7] & 0x2 else False - args.shufflelinks[p] = True if settings[7] & 0x1 else False + args.openpyramid[p] = True if settings[6] & 0x4 else False + + args.bigkeyshuffle[p] = True if settings[7] & 0x80 else False + args.keyshuffle[p] = True if settings[7] & 0x40 else False + args.mapshuffle[p] = True if settings[7] & 0x20 else False + args.compassshuffle[p] = True if settings[7] & 0x10 else False + # args.shufflebosses[p] = r(boss_mode)[(settings[7] & 0xc) >> 2] + args.shuffleenemies[p] = r(enemy_mode)[settings[7] & 0x3] + + args.enemy_health[p] = r(e_health)[(settings[8] & 0xE0) >> 5] + args.enemy_damage[p] = r(e_dmg)[(settings[8] & 0x18) >> 3] + args.shufflepots[p] = True if settings[8] & 0x4 else False + args.bombbag[p] = True if settings[8] & 0x2 else False + args.shufflelinks[p] = True if settings[8] & 0x1 else False + if len(settings) > 9: + args.restrict_boss_items[p] = r(rb_mode)[(settings[9] & 0xC0) >> 6] + args.algorithm = r(algo_mode)[(settings[9] & 0x38) >> 3] + args.shufflebosses[p] = r(boss_mode)[(settings[9] & 0x07)] class KeyRuleType(FastEnum): diff --git a/Bosses.py b/Bosses.py index d577c66d..d84df921 100644 --- a/Bosses.py +++ b/Bosses.py @@ -1,8 +1,8 @@ import logging import RaceRandom as random -from BaseClasses import Boss -from Fill import FillError +from BaseClasses import Boss, FillError + def BossFactory(boss, player): if boss is None: @@ -61,8 +61,7 @@ def MothulaDefeatRule(state, player): # TODO: Not sure how much (if any) extend magic is needed for these two, since they only apply # to non-vanilla locations, so are harder to test, so sticking with what VT has for now: (state.has('Cane of Somaria', player) and state.can_extend_magic(player, 16)) or - (state.has('Cane of Byrna', player) and state.can_extend_magic(player, 16)) or - state.can_get_good_bee(player) + (state.has('Cane of Byrna', player) and state.can_extend_magic(player, 16)) ) def BlindDefeatRule(state, player): @@ -166,18 +165,19 @@ def place_bosses(world, player): all_bosses = sorted(boss_table.keys()) #s orted to be deterministic on older pythons placeable_bosses = [boss for boss in all_bosses if boss not in ['Agahnim', 'Agahnim2', 'Ganon']] - if world.boss_shuffle[player] in ["simple", "full"]: - # temporary hack for swordless kholdstare: + # temporary hack for swordless kholdstare: + if world.boss_shuffle[player] in ["simple", "full", "unique"]: if world.swords[player] == 'swordless': world.get_dungeon('Ice Palace', player).boss = BossFactory('Kholdstare', player) logging.getLogger('').debug('Placing boss Kholdstare at Ice Palace') boss_locations.remove(['Ice Palace', None]) placeable_bosses.remove('Kholdstare') + if world.boss_shuffle[player] in ["simple", "full"]: if world.boss_shuffle[player] == "simple": # vanilla bosses shuffled bosses = placeable_bosses + ['Armos Knights', 'Lanmolas', 'Moldorm'] else: # all bosses present, the three duplicates chosen at random - bosses = all_bosses + random.sample(placeable_bosses, 3) + bosses = placeable_bosses + random.sample(placeable_bosses, 3) logging.getLogger('').debug('Bosses chosen %s', bosses) @@ -189,12 +189,7 @@ def place_bosses(world, player): raise FillError('Could not place boss for location %s' % loc_text) bosses.remove(boss) - # GT Bosses can move dungeon - find the real dungeon to place them in - if level: - loc = [x.name for x in world.dungeons if x.player == player and level in x.bosses.keys()][0] - loc_text = loc + ' (' + level + ')' - logging.getLogger('').debug('Placing boss %s at %s', boss, loc_text) - world.get_dungeon(loc, player).bosses[level] = BossFactory(boss, player) + place_boss(boss, level, loc, loc_text, world, player) elif world.boss_shuffle[player] == "random": #all bosses chosen at random for [loc, level] in boss_locations: loc_text = loc + (' ('+level+')' if level else '') @@ -203,9 +198,31 @@ def place_bosses(world, player): except IndexError: raise FillError('Could not place boss for location %s' % loc_text) - # GT Bosses can move dungeon - find the real dungeon to place them in - if level: - loc = [x.name for x in world.dungeons if x.player == player and level in x.bosses.keys()][0] - loc_text = loc + ' (' + level + ')' - logging.getLogger('').debug('Placing boss %s at %s', boss, loc_text) - world.get_dungeon(loc, player).bosses[level] = BossFactory(boss, player) + place_boss(boss, level, loc, loc_text, world, player) + elif world.boss_shuffle[player] == 'unique': + bosses = list(placeable_bosses) + gt_bosses = [] + + for [loc, level] in boss_locations: + loc_text = loc + (' ('+level+')' if level else '') + try: + if level: + boss = random.choice([b for b in placeable_bosses if can_place_boss(world, player, b, loc, level) + and b not in gt_bosses]) + gt_bosses.append(boss) + else: + boss = random.choice([b for b in bosses if can_place_boss(world, player, b, loc, level)]) + bosses.remove(boss) + except IndexError: + raise FillError('Could not place boss for location %s' % loc_text) + + place_boss(boss, level, loc, loc_text, world, player) + + +def place_boss(boss, level, loc, loc_text, world, player): + # GT Bosses can move dungeon - find the real dungeon to place them in + if level: + loc = [x.name for x in world.dungeons if x.player == player and level in x.bosses.keys()][0] + loc_text = loc + ' (' + level + ')' + logging.getLogger('').debug('Placing boss %s at %s', boss, loc_text) + world.get_dungeon(loc, player).bosses[level] = BossFactory(boss, player) diff --git a/CLI.py b/CLI.py index bb9ab0a2..e212c935 100644 --- a/CLI.py +++ b/CLI.py @@ -88,6 +88,10 @@ def parse_cli(argv, no_defaults=False): if ret.keysanity: ret.mapshuffle, ret.compassshuffle, ret.keyshuffle, ret.bigkeyshuffle = [True] * 4 + if ret.keydropshuffle: + ret.dropshuffle = True + ret.pottery = 'keys' if ret.pottery == 'none' else ret.pottery + if multiargs.multi: defaults = copy.deepcopy(ret) for player in range(1, multiargs.multi + 1): @@ -96,14 +100,15 @@ def parse_cli(argv, no_defaults=False): for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality', 'shuffle', 'door_shuffle', 'intensity', 'crystals_ganon', 'crystals_gt', 'openpyramid', 'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory', - 'bombbag', + 'usestartinventory', 'bombbag', 'overworld_map', 'restrict_boss_items', 'triforce_pool_min', 'triforce_pool_max', 'triforce_goal_min', 'triforce_goal_max', 'triforce_min_difference', 'triforce_goal', 'triforce_pool', 'shufflelinks', 'pseudoboots', 'retro', 'accessibility', 'hints', 'beemizer', 'experimental', 'dungeon_counters', 'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage', 'shufflepots', - 'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor', 'heartbeep', - 'remote_items', 'shopsanity', 'keydropshuffle', 'mixed_travel', 'standardize_palettes', 'code', - 'reduce_flashing', 'shuffle_sfx']: + 'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor', + 'heartbeep', 'remote_items', 'shopsanity', 'dropshuffle', 'pottery', 'keydropshuffle', + 'mixed_travel', 'standardize_palettes', 'code', 'reduce_flashing', 'shuffle_sfx', + 'msu_resume', 'collection_rate', 'colorizepots']: value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name) if player == 1: setattr(ret, name, {1: value}) @@ -140,15 +145,18 @@ def parse_settings(): "progressive": "on", "accessibility": "items", "algorithm": "balanced", + 'mystery': False, + 'suppress_meta': False, + "restrict_boss_items": "none", # Shuffle Ganon defaults to TRUE "openpyramid": False, "shuffleganon": True, "shuffle": "vanilla", "shufflelinks": False, + "overworld_map": "default", "pseudoboots": False, - "shufflepots": False, "shuffleenemies": "none", "shufflebosses": "none", "enemy_damage": "default", @@ -156,21 +164,25 @@ def parse_settings(): "enemizercli": os.path.join(".", "EnemizerCLI", "EnemizerCLI.Core"), "shopsanity": False, - "keydropshuffle": False, + 'keydropshuffle': False, + 'dropshuffle': False, + 'pottery': 'none', + 'colorizepots': False, + 'shufflepots': False, "mapshuffle": False, "compassshuffle": False, "keyshuffle": False, "bigkeyshuffle": False, "keysanity": False, - "door_shuffle": "basic", - "intensity": 2, - "experimental": False, - "dungeon_counters": "default", - "mixed_travel": "prevent", - "standardize_palettes": "standardize", + 'door_shuffle': 'vanilla', + 'intensity': 2, + 'experimental': False, + 'dungeon_counters': 'default', + 'mixed_travel': 'prevent', + 'standardize_palettes': 'standardize', - "triforce_pool": 30, - "triforce_goal": 20, + "triforce_pool": 0, + "triforce_goal": 0, "triforce_pool_min": 0, "triforce_pool_max": 0, "triforce_goal_min": 0, @@ -193,6 +205,8 @@ def parse_settings(): "uw_palettes": "default", "reduce_flashing": False, "shuffle_sfx": False, + 'msu_resume': False, + 'collection_rate': False, # Spoiler defaults to TRUE # Playthrough defaults to TRUE @@ -200,9 +214,11 @@ def parse_settings(): "create_spoiler": True, "calc_playthrough": True, "create_rom": True, + "bps": False, "usestartinventory": False, "custom": False, "rom": os.path.join(".", "Zelda no Densetsu - Kamigami no Triforce (Japan).sfc"), + "patch": os.path.join(".", "Patch File.bps"), "seed": "", "count": 1, diff --git a/DoorShuffle.py b/DoorShuffle.py index 8c0b8e25..ea287d8f 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -6,6 +6,7 @@ from enum import unique, Flag from typing import DefaultDict, Dict, List from BaseClasses import RegionType, Region, Door, DoorType, Direction, Sector, CrystalBarrier, DungeonInfo, dungeon_keys +from BaseClasses import PotFlags, LocationType from Doors import reset_portals from Dungeons import dungeon_regions, region_starts, standard_starts, split_region_starts from Dungeons import dungeon_bigs, dungeon_hints @@ -45,10 +46,10 @@ def link_doors(world, player): reset_rooms(world, player) world.get_door("Skull Pinball WS", player).no_exit() world.swamp_patch_required[player] = orig_swamp_patch + link_doors_prep(world, player) -def link_doors_main(world, player): - +def link_doors_prep(world, player): # Drop-down connections & push blocks for exitName, regionName in logical_connections: connect_simple_door(world, exitName, regionName, player) @@ -100,6 +101,7 @@ def link_doors_main(world, player): analyze_portals(world, player) for portal in world.dungeon_portals[player]: connect_portal(portal, world, player) + if not world.doorShuffle[player] == 'vanilla': fix_big_key_doors_with_ugly_smalls(world, player) else: @@ -120,11 +122,14 @@ def link_doors_main(world, player): for ent, ext in default_one_way_connections: connect_one_way(world, ent, ext, player) vanilla_key_logic(world, player) - elif world.doorShuffle[player] == 'basic': + + +def link_doors_main(world, player): + if world.doorShuffle[player] == 'basic': within_dungeon(world, player) elif world.doorShuffle[player] == 'crossed': cross_dungeon(world, player) - else: + elif world.doorShuffle[player] != 'vanilla': logging.getLogger('').error('Invalid door shuffle setting: %s' % world.doorShuffle[player]) raise Exception('Invalid door shuffle setting: %s' % world.doorShuffle[player]) @@ -209,17 +214,22 @@ def vanilla_key_logic(world, player): key_layout = build_key_layout(builder, start_regions, doors, world, player) valid = validate_key_layout(key_layout, world, player) if not valid: - logging.getLogger('').warning('Vanilla key layout not valid %s', builder.name) + logging.getLogger('').info('Vanilla key layout not valid %s', builder.name) builder.key_door_proposal = doors if player not in world.key_logic.keys(): world.key_logic[player] = {} analyze_dungeon(key_layout, world, player) world.key_logic[player][builder.name] = key_layout.key_logic + world.key_layout[player][builder.name] = key_layout log_key_logic(builder.name, key_layout.key_logic) # if world.shuffle[player] == 'vanilla' and world.accessibility[player] == 'items' and not world.retro[player] and not world.keydropshuffle[player]: # validate_vanilla_key_logic(world, player) +def validate_vanilla_reservation(dungeon, world, player): + return validate_key_layout(world.key_layout[player][dungeon.name], world, player) + + # some useful functions oppositemap = { Direction.South: Direction.North, @@ -370,7 +380,7 @@ def choose_portals(world, player): if world.doorShuffle[player] in ['basic', 'crossed']: cross_flag = world.doorShuffle[player] == 'crossed' # key drops allow the big key in the right place in Desert Tiles 2 - bk_shuffle = world.bigkeyshuffle[player] or world.keydropshuffle[player] + bk_shuffle = world.bigkeyshuffle[player] or world.pottery[player] not in ['none', 'cave'] std_flag = world.mode[player] == 'standard' # roast incognito doors world.get_room(0x60, player).delete(5) @@ -404,7 +414,7 @@ def choose_portals(world, player): info.sole_entrance = inaccessible_portals[0] info.required_passage.clear() else: - raise Exception('please inspect this case') + raise Exception(f'No reachable entrances for {dungeon}') if len(reachable_portals) == 1: info.sole_entrance = reachable_portals[0] info_map[dungeon] = info @@ -511,7 +521,7 @@ def analyze_portals(world, player): info.sole_entrance = inaccessible_portals[0] info.required_passage.clear() else: - raise Exception('please inspect this case') + raise Exception(f'No reachable entrances for {dungeon}') if len(reachable_portals) == 1: info.sole_entrance = reachable_portals[0] if world.intensity[player] < 2 and world.doorShuffle[player] == 'basic' and dungeon == 'Desert Palace': @@ -978,18 +988,28 @@ def cross_dungeon(world, player): paths = determine_required_paths(world, player) check_required_paths(paths, world, player) + hc_compass = ItemFactory('Compass (Escape)', player) + at_compass = ItemFactory('Compass (Agahnims Tower)', player) + at_map = ItemFactory('Map (Agahnims Tower)', player) + if world.restrict_boss_items[player] != 'none': + hc_compass.advancement = at_compass.advancement = at_map.advancement = True hc = world.get_dungeon('Hyrule Castle', player) - hc.dungeon_items.append(ItemFactory('Compass (Escape)', player)) + hc.dungeon_items.append(hc_compass) at = world.get_dungeon('Agahnims Tower', player) - at.dungeon_items.append(ItemFactory('Compass (Agahnims Tower)', player)) - at.dungeon_items.append(ItemFactory('Map (Agahnims Tower)', player)) + at.dungeon_items.append(at_compass) + at.dungeon_items.append(at_map) assign_cross_keys(dungeon_builders, world, player) all_dungeon_items_cnt = len(list(y for x in world.dungeons if x.player == player for y in x.all_items)) - if world.keydropshuffle[player]: - target_items = 35 if world.retro[player] else 96 + target_items = 34 + if world.retro[player]: + target_items += 1 if world.dropshuffle[player] else 0 # the hc big key else: - target_items = 34 if world.retro[player] else 63 + target_items += 29 # small keys in chests + if world.dropshuffle[player]: + target_items += 14 # 13 dropped smalls + 1 big + if world.pottery[player] not in ['none', 'cave']: + target_items += 19 # 19 pot keys d_items = target_items - all_dungeon_items_cnt world.pool_adjustment[player] = d_items smooth_door_pairs(world, player) @@ -1047,7 +1067,11 @@ def assign_cross_keys(dungeon_builders, world, player): logging.getLogger('').info(world.fish.translate("cli", "cli", "shuffling.keydoors")) start = time.process_time() if world.retro[player]: - remaining = 61 if world.keydropshuffle[player] else 29 + remaining = 29 + if world.dropshuffle[player]: + remaining += 13 + if world.pottery[player] not in ['none', 'cave']: + remaining += 19 else: remaining = len(list(x for dgn in world.dungeons if dgn.player == player for x in dgn.small_keys)) total_keys = remaining @@ -1066,11 +1090,10 @@ def assign_cross_keys(dungeon_builders, world, player): total_candidates += builder.key_doors_num start_regions_map[name] = start_regions - # Step 2: Initial Key Number Assignment & Calculate Flexibility for name, builder in dungeon_builders.items(): calculated = int(round(builder.key_doors_num*total_keys/total_candidates)) - max_keys = builder.location_cnt - calc_used_dungeon_items(builder) + max_keys = max(0, builder.location_cnt - calc_used_dungeon_items(builder)) cand_len = max(0, len(builder.candidates) - builder.key_drop_cnt) limit = min(max_keys, cand_len) suggested = min(calculated, limit) @@ -1243,7 +1266,13 @@ def refine_hints(dungeon_builders): for region in builder.master_sector.regions: for location in region.locations: if not location.event and '- Boss' not in location.name and '- Prize' not in location.name and location.name != 'Sanctuary': - location.hint_text = dungeon_hints[name] + if location.type == LocationType.Pot and location.pot: + hint_text = ('under a block' if location.pot.flags & PotFlags.Block else 'in a pot') + location.hint_text = f'{hint_text} {dungeon_hints[name]}' + elif location.type == LocationType.Drop: + location.hint_text = f'dropped {dungeon_hints[name]}' + else: + location.hint_text = dungeon_hints[name] def refine_boss_exits(world, player): @@ -1279,6 +1308,7 @@ def refine_boss_exits(world, player): if 0 < len(filtered) < len(reachable_portals): reachable_portals = filtered chosen_one = random.choice(reachable_portals) if len(reachable_portals) > 1 else reachable_portals[0] + chosen_one.chosen = True if chosen_one != current_boss: chosen_one.change_boss_exit(current_boss.boss_exit_idx) current_boss.change_boss_exit(-1) @@ -1718,7 +1748,7 @@ def smooth_door_pairs(world, player): if type_b == DoorKind.SmallKey: remove_pair(door, world, player) else: - if valid_pair: + if valid_pair and not std_forbidden(door, world, player): bd_candidates[door.entrance.parent_region.dungeon].append(door) elif type_a in [DoorKind.Bombable, DoorKind.Dashable] or type_b in [DoorKind.Bombable, DoorKind.Dashable]: if type_a in [DoorKind.Bombable, DoorKind.Dashable]: @@ -1727,7 +1757,8 @@ def smooth_door_pairs(world, player): else: room_b.change(partner.doorListPos, DoorKind.Normal) remove_pair(partner, world, player) - elif valid_pair and type_a != DoorKind.SmallKey and type_b != DoorKind.SmallKey: + elif (valid_pair and type_a != DoorKind.SmallKey and type_b != DoorKind.SmallKey + and not std_forbidden(door, world, player)): bd_candidates[door.entrance.parent_region.dungeon].append(door) shuffle_bombable_dashable(bd_candidates, world, player) world.paired_doors[player] = [x for x in world.paired_doors[player] if x.pair or x.original] @@ -1766,12 +1797,37 @@ def stateful_door(door, kind): return False +def std_forbidden(door, world, player): + return (world.mode[player] == 'standard' and door.entrance.parent_region.dungeon.name == 'Hyrule Castle' and + 'Hyrule Castle Throne Room N' in [door.name, door.dest.name]) + + +dashable_forbidden = { + 'Swamp Trench 1 Key Ledge NW', 'Swamp Left Elbow WN', 'Swamp Right Elbow SE', 'Mire Hub WN', 'Mire Hub WS', + 'Mire Hub Top NW', 'Mire Hub NE', 'Ice Dead End WS' +} + +ohko_forbidden = { + 'GT Invisible Catwalk NE', 'GT Falling Bridge WN', 'GT Falling Bridge WS', 'GT Hidden Star ES', 'GT Hookshot EN', + 'GT Torch Cross WN', 'TR Torches WN', 'Mire Falling Bridge WS', 'Mire Falling Bridge W', 'Ice Hookshot Balcony SW', + 'Ice Catwalk WN', 'Ice Catwalk NW', 'Ice Bomb Jump NW', 'GT Cannonball Bridge SE' +} + + +def filter_dashable_candidates(candidates, world): + forbidden_set = dashable_forbidden + if world.timer in ['ohko', 'timed-ohko']: + forbidden_set = ohko_forbidden.union(dashable_forbidden) + return [x for x in candidates if x.name not in forbidden_set and x.dest.name not in forbidden_set] + + def shuffle_bombable_dashable(bd_candidates, world, player): if world.doorShuffle[player] == 'basic': for dungeon, candidates in bd_candidates.items(): diff = bomb_dash_counts[dungeon.name][1] if diff > 0: - for chosen in random.sample(candidates, min(diff, len(candidates))): + dash_candidates = filter_dashable_candidates(candidates, world) + for chosen in random.sample(dash_candidates, min(diff, len(candidates))): change_pair_type(chosen, DoorKind.Dashable, world, player) candidates.remove(chosen) diff = bomb_dash_counts[dungeon.name][0] @@ -1783,7 +1839,8 @@ def shuffle_bombable_dashable(bd_candidates, world, player): remove_pair_type_if_present(excluded, world, player) elif world.doorShuffle[player] == 'crossed': all_candidates = sum(bd_candidates.values(), []) - for chosen in random.sample(all_candidates, min(8, len(all_candidates))): + dash_candidates = filter_dashable_candidates(all_candidates, world) + for chosen in random.sample(dash_candidates, min(8, len(all_candidates))): change_pair_type(chosen, DoorKind.Dashable, world, player) all_candidates.remove(chosen) for chosen in random.sample(all_candidates, min(12, len(all_candidates))): @@ -1854,8 +1911,10 @@ def find_inaccessible_regions(world, player): def find_accessible_entrances(world, player, builder): entrances = [region.name for region in (portal.door.entrance.parent_region for portal in world.dungeon_portals[player]) if region.dungeon.name == builder.name] entrances.extend(drop_entrances[builder.name]) + hc_std = False if world.mode[player] == 'standard' and builder.name == 'Hyrule Castle': + hc_std = True start_regions = ['Hyrule Castle Courtyard'] elif world.mode[player] != 'inverted': start_regions = ['Links House', 'Sanctuary'] @@ -1880,6 +1939,8 @@ def find_accessible_entrances(world, player, builder): if connect not in queue and connect not in visited_regions: queue.append(connect) for ext in next_region.exits: + if hc_std and ext.name == 'Hyrule Castle Main Gate (North)': # just skip it + continue connect = ext.connected_region if connect is None or ext.door and ext.door.blocked: continue @@ -2100,6 +2161,7 @@ logical_connections = [ ('Hera Startile Wide Crystal Exit', 'Hera Startile Wide'), ('Hera Big Chest Hook Path', 'Hera Big Chest Landing'), ('Hera Big Chest Landing Exit', 'Hera 4F'), + ('Hera 5F Orange Path', 'Hera 5F Pot Block'), ('PoD Pit Room Block Path N', 'PoD Pit Room Blocked'), ('PoD Pit Room Block Path S', 'PoD Pit Room'), @@ -2163,6 +2225,7 @@ logical_connections = [ ('Swamp Trench 1 Departure Approach', 'Swamp Trench 1 Approach'), ('Swamp Trench 1 Departure Key', 'Swamp Trench 1 Key Ledge'), ('Swamp Hub Hook Path', 'Swamp Hub North Ledge'), + ('Swamp Hub Side Hook Path', 'Swamp Hub Side Ledges'), ('Swamp Hub North Ledge Drop Down', 'Swamp Hub'), ('Swamp Crystal Switch Outer to Inner Barrier - Blue', 'Swamp Crystal Switch Inner'), ('Swamp Crystal Switch Outer to Ranged Crystal', 'Swamp Crystal Switch Outer - Ranged Crystal'), @@ -2194,13 +2257,17 @@ logical_connections = [ ('Skull Pot Circle Star Path', 'Skull Map Room'), ('Skull Big Chest Hookpath', 'Skull 1 Lobby'), ('Skull Back Drop Star Path', 'Skull Small Hall'), + ('Skull 2 West Lobby Pits', 'Skull 2 West Lobby Ledge'), + ('Skull 2 West Lobby Ledge Pits', 'Skull 2 West Lobby'), ('Thieves Rail Ledge Drop Down', 'Thieves BK Corner'), ('Thieves Hellway Orange Barrier', 'Thieves Hellway S Crystal'), ('Thieves Hellway Crystal Orange Barrier', 'Thieves Hellway'), ('Thieves Hellway Blue Barrier', 'Thieves Hellway N Crystal'), ('Thieves Hellway Crystal Blue Barrier', 'Thieves Hellway'), ('Thieves Attic Orange Barrier', 'Thieves Attic Hint'), + ('Thieves Attic Blue Barrier', 'Thieves Attic Switch'), ('Thieves Attic Hint Orange Barrier', 'Thieves Attic'), + ('Thieves Attic Switch Blue Barrier', 'Thieves Attic'), ('Thieves Basement Block Path', 'Thieves Blocked Entry'), ('Thieves Blocked Entry Path', 'Thieves Basement Block'), ('Thieves Conveyor Bridge Block Path', 'Thieves Conveyor Block'), @@ -2259,6 +2326,8 @@ logical_connections = [ ('TR Main Lobby Gap', 'TR Lobby Ledge'), ('TR Lobby Ledge Gap', 'TR Main Lobby'), + ('TR Hub Path', 'TR Hub Ledges'), + ('TR Hub Ledges Path', 'TR Hub'), ('TR Pipe Ledge Drop Down', 'TR Pipe Pit'), ('TR Big Chest Gap', 'TR Big Chest Entrance'), ('TR Big Chest Entrance Gap', 'TR Big Chest'), @@ -2287,6 +2356,8 @@ logical_connections = [ ('TR Crystaroller Chest to Middle Barrier - Blue', 'TR Crystaroller Middle'), ('TR Crystaroller Middle Ranged Crystal Exit', 'TR Crystaroller Middle'), ('TR Crystaroller Bottom Ranged Crystal Exit', 'TR Crystaroller Bottom'), + ('TR Dark Ride Path', 'TR Dark Ride Ledges'), + ('TR Dark Ride Ledges Path', 'TR Dark Ride'), ('TR Crystal Maze Start to Interior Barrier - Blue', 'TR Crystal Maze Interior'), ('TR Crystal Maze Start to Crystal', 'TR Crystal Maze Start - Crystal'), ('TR Crystal Maze Start Crystal Exit', 'TR Crystal Maze Start'), @@ -2297,16 +2368,18 @@ logical_connections = [ ('TR Crystal Maze End to Interior Barrier - Blue', 'TR Crystal Maze Interior'), ('TR Crystal Maze End to Ranged Crystal', 'TR Crystal Maze End - Ranged Crystal'), ('TR Crystal Maze End Ranged Crystal Exit', 'TR Crystal Maze End'), + ('TR Final Abyss Balcony Path', 'TR Final Abyss Ledge'), + ('TR Final Abyss Ledge Path', 'TR Final Abyss Balcony'), ('GT Blocked Stairs Block Path', 'GT Big Chest'), ('GT Speed Torch South Path', 'GT Speed Torch'), ('GT Speed Torch North Path', 'GT Speed Torch Upper'), - ('GT Hookshot East-North Path', 'GT Hookshot North Platform'), - ('GT Hookshot East-South Path', 'GT Hookshot South Platform'), - ('GT Hookshot North-East Path', 'GT Hookshot East Platform'), - ('GT Hookshot North-South Path', 'GT Hookshot South Platform'), - ('GT Hookshot South-East Path', 'GT Hookshot East Platform'), - ('GT Hookshot South-North Path', 'GT Hookshot North Platform'), + ('GT Hookshot East-Mid Path', 'GT Hookshot Mid Platform'), + ('GT Hookshot Mid-East Path', 'GT Hookshot East Platform'), + ('GT Hookshot North-Mid Path', 'GT Hookshot Mid Platform'), + ('GT Hookshot Mid-North Path', 'GT Hookshot North Platform'), + ('GT Hookshot South-Mid Path', 'GT Hookshot Mid Platform'), + ('GT Hookshot Mid-South Path', 'GT Hookshot South Platform'), ('GT Hookshot Platform Blue Barrier', 'GT Hookshot South Entry'), ('GT Hookshot Platform Barrier Bypass', 'GT Hookshot South Entry'), ('GT Hookshot Entry Blue Barrier', 'GT Hookshot South Platform'), @@ -2337,8 +2410,8 @@ logical_connections = [ ('GT Crystal Conveyor to Corner Barrier - Blue', 'GT Crystal Conveyor Corner'), ('GT Crystal Conveyor to Ranged Crystal', 'GT Crystal Conveyor - Ranged Crystal'), ('GT Crystal Conveyor Corner to Left Bypass', 'GT Crystal Conveyor Left'), - ('GT Crystal Conveyor Corner to Barrier - Blue', 'GT Crystal Conveyor Left'), - ('GT Crystal Conveyor Corner to Barrier - Orange', 'GT Crystal Conveyor'), + ('GT Crystal Conveyor Corner to Barrier - Blue', 'GT Crystal Conveyor'), + ('GT Crystal Conveyor Corner to Barrier - Orange', 'GT Crystal Conveyor Left'), ('GT Crystal Conveyor Corner to Ranged Crystal', 'GT Crystal Conveyor Corner - Ranged Crystal'), ('GT Crystal Conveyor Left to Corner Barrier - Orange', 'GT Crystal Conveyor Corner'), ('GT Crystal Conveyor Ranged Crystal Exit', 'GT Crystal Conveyor'), @@ -3004,7 +3077,8 @@ palette_map = { 'Tower of Hera': (0x6, None), 'Thieves Town': (0x17, None), # the attic uses 0x23 'Turtle Rock': (0x18, 0x19, 'TR Boss SW'), - 'Ganons Tower': (0x28, 0x1b, 'GT Agahnim 2 SW'), # other palettes: 0x1a (other) 0x24 (Gauntlet - Lanmo) 0x25 (conveyor-torch-wizzrode moldorm pit f5?) + 'Ganons Tower': (0x28, 0x1b, 'GT Agahnim 2 SW'), + # other palettes: 0x1a (other) 0x24 (Gauntlet - Lanmo) 0x25 (conveyor-torch-wizzrobe moldorm pit f5?) } # implications: diff --git a/Doors.py b/Doors.py index a23f46f0..a7c1d9f3 100644 --- a/Doors.py +++ b/Doors.py @@ -309,6 +309,7 @@ def create_doors(world, player): create_door(player, 'Hera 5F Star Hole', Hole), create_door(player, 'Hera 5F Pothole Chain', Hole), create_door(player, 'Hera 5F Normal Holes', Hole), + create_door(player, 'Hera 5F Orange Path', Lgcl), create_door(player, 'Hera Fairies\' Warp', Warp), create_door(player, 'Hera Boss Down Stairs', Sprl).dir(Dn, 0x07, 0, HTH).ss(S, 0x61, 0xb0).kill(), create_door(player, 'Hera Boss Outer Hole', Hole), @@ -434,7 +435,7 @@ def create_doors(world, player): create_door(player, 'PoD Dark Basement W Up Stairs', Sprl).dir(Up, 0x6a, 0, HTH).ss(S, 0x1b, 0x3c, True), create_door(player, 'PoD Dark Basement E Up Stairs', Sprl).dir(Up, 0x6a, 1, HTH).ss(S, 0x1b, 0x9c, True), create_door(player, 'PoD Dark Alley NE', Nrml).dir(No, 0x6a, Right, High).big_key().pos(0), - create_door(player, 'PoD Mimics 2 SW', Nrml).dir(So, 0x1b, Left, High).pos(1).kill().portal(Z, 0x00), + create_door(player, 'PoD Mimics 2 SW', Nrml).dir(So, 0x1b, Left, High).pos(1).portal(Z, 0x00), create_door(player, 'PoD Mimics 2 NW', Intr).dir(No, 0x1b, Left, High).pos(0), create_door(player, 'PoD Bow Statue SW', Intr).dir(So, 0x1b, Left, High).pos(0), create_door(player, 'PoD Bow Statue Left to Right Barrier - Orange', Lgcl), @@ -500,6 +501,7 @@ def create_doors(world, player): create_door(player, 'Swamp Hub WS', Nrml).dir(We, 0x36, Bot, High).pos(3), create_door(player, 'Swamp Hub WN', Nrml).dir(We, 0x36, Top, High).small_key().pos(2), create_door(player, 'Swamp Hub Hook Path', Lgcl), + create_door(player, 'Swamp Hub Side Hook Path', Lgcl), create_door(player, 'Swamp Hub Dead Ledge EN', Nrml).dir(Ea, 0x36, Top, High).pos(0), create_door(player, 'Swamp Hub North Ledge N', Nrml).dir(No, 0x36, Mid, High).small_key().pos(1), create_door(player, 'Swamp Hub North Ledge Drop Down', Lgcl), @@ -612,6 +614,8 @@ def create_doors(world, player): create_door(player, 'Skull 2 West Lobby S', Nrml).dir(So, 0x56, Left, High).pos(1).portal(Z, 0x00), create_door(player, 'Skull 2 West Lobby ES', Intr).dir(Ea, 0x56, Bot, High).pos(2), create_door(player, 'Skull 2 West Lobby NW', Intr).dir(No, 0x56, Left, High).small_key().pos(0), + create_door(player, 'Skull 2 West Lobby Pits', Lgcl), + create_door(player, 'Skull 2 West Lobby Ledge Pits', Lgcl), create_door(player, 'Skull X Room SW', Intr).dir(So, 0x56, Left, High).small_key().pos(0), create_door(player, 'Skull Back Drop Star Path', Lgcl), create_door(player, 'Skull 3 Lobby SW', Nrml).dir(So, 0x59, Left, High).pos(1).portal(Z, 0x02), @@ -684,7 +688,9 @@ def create_doors(world, player): create_door(player, 'Thieves Attic Down Stairs', Sprl).dir(Dn, 0x64, 0, HTH).ss(Z, 0x11, 0x80, True, True), create_door(player, 'Thieves Attic ES', Intr).dir(Ea, 0x64, Bot, High).pos(0), create_door(player, 'Thieves Attic Orange Barrier', Lgcl), + create_door(player, 'Thieves Attic Blue Barrier', Lgcl), create_door(player, 'Thieves Attic Hint Orange Barrier', Lgcl), + create_door(player, 'Thieves Attic Switch Blue Barrier', Lgcl), create_door(player, 'Thieves Cricket Hall Left WS', Intr).dir(We, 0x64, Bot, High).pos(0), create_door(player, 'Thieves Cricket Hall Left Edge', Open).dir(Ea, 0x64, None, High).edge(0, X, 0x30), create_door(player, 'Thieves Cricket Hall Right Edge', Open).dir(We, 0x65, None, High).edge(0, Z, 0x30), @@ -955,6 +961,8 @@ def create_doors(world, player): create_door(player, 'TR Hub EN', Nrml).dir(Ea, 0xc6, Top, High).pos(2), create_door(player, 'TR Hub NW', Nrml).dir(No, 0xc6, Left, High).small_key().pos(0), create_door(player, 'TR Hub NE', Nrml).dir(No, 0xc6, Right, High).pos(1), + create_door(player, 'TR Hub Path', Lgcl), + create_door(player, 'TR Hub Ledges Path', Lgcl), create_door(player, 'TR Torches Ledge WS', Nrml).dir(We, 0xc7, Bot, High).pos(2), create_door(player, 'TR Torches WN', Nrml).dir(We, 0xc7, Top, High).pos(1), create_door(player, 'TR Torches NW', Nrml).dir(No, 0xc7, Left, High).trap(0x4).pos(0), @@ -1030,6 +1038,8 @@ def create_doors(world, player): create_door(player, 'TR Crystaroller Down Stairs', Sprl).dir(Dn, 0x04, 0, HTH).ss(A, 0x12, 0x80, True, True).small_key().pos(0), create_door(player, 'TR Dark Ride Up Stairs', Sprl).dir(Up, 0xb5, 0, HTH).ss(A, 0x1b, 0x6c), create_door(player, 'TR Dark Ride SW', Nrml).dir(So, 0xb5, Left, High).trap(0x4).pos(0).portal(Z, 0x22), + create_door(player, 'TR Dark Ride Path', Lgcl), + create_door(player, 'TR Dark Ride Ledges Path', Lgcl), create_door(player, 'TR Dash Bridge NW', Nrml).dir(No, 0xc5, Left, High).pos(1), create_door(player, 'TR Dash Bridge SW', Nrml).dir(So, 0xc5, Left, High).pos(2).portal(Z, 0x02), create_door(player, 'TR Dash Bridge WS', Nrml).dir(We, 0xc5, Bot, High).small_key().pos(0), @@ -1048,6 +1058,8 @@ def create_doors(world, player): create_door(player, 'TR Crystal Maze End Ranged Crystal Exit', Lgcl), create_door(player, 'TR Crystal Maze North Stairs', StrS).dir(No, 0xc4, Mid, High), create_door(player, 'TR Final Abyss South Stairs', StrS).dir(So, 0xb4, Mid, High), + create_door(player, 'TR Final Abyss Balcony Path', Lgcl), + create_door(player, 'TR Final Abyss Ledge Path', Lgcl), create_door(player, 'TR Final Abyss NW', Nrml).dir(No, 0xb4, Left, High).big_key().pos(0), create_door(player, 'TR Boss SW', Nrml).dir(So, 0xa4, Left, High).no_exit().trap(0x4).pos(0), # .portal(Z, 0x00), -enemizer doesn't work @@ -1098,12 +1110,12 @@ def create_doors(world, player): create_door(player, 'GT Conveyor Cross EN', Nrml).dir(Ea, 0x8b, Top, High).pos(2), create_door(player, 'GT Conveyor Cross WN', Intr).dir(We, 0x8b, Top, High).pos(0), create_door(player, 'GT Hookshot EN', Intr).dir(Ea, 0x8b, Top, High).pos(0), - create_door(player, 'GT Hookshot East-North Path', Lgcl), - create_door(player, 'GT Hookshot East-South Path', Lgcl), - create_door(player, 'GT Hookshot North-East Path', Lgcl), - create_door(player, 'GT Hookshot North-South Path', Lgcl), - create_door(player, 'GT Hookshot South-East Path', Lgcl), - create_door(player, 'GT Hookshot South-North Path', Lgcl), + create_door(player, 'GT Hookshot East-Mid Path', Lgcl), + create_door(player, 'GT Hookshot Mid-East Path', Lgcl), + create_door(player, 'GT Hookshot North-Mid Path', Lgcl), + create_door(player, 'GT Hookshot Mid-North Path', Lgcl), + create_door(player, 'GT Hookshot South-Mid Path', Lgcl), + create_door(player, 'GT Hookshot Mid-South Path', Lgcl), create_door(player, 'GT Hookshot Platform Blue Barrier', Lgcl), create_door(player, 'GT Hookshot Entry Blue Barrier', Lgcl), create_door(player, 'GT Hookshot Platform Barrier Bypass', Lgcl), @@ -1291,6 +1303,7 @@ def create_doors(world, player): world.get_door('Hera Beetles Holes Front', player).c_switch() world.get_door('Hera Beetles Holes Landing', player).c_switch() world.get_door('Hera Startile Wide Crystal Exit', player).c_switch() + world.get_door('Hera 5F Orange Path', player).barrier(CrystalBarrier.Orange) world.get_door('PoD Arena North to Landing Barrier - Orange', player).barrier(CrystalBarrier.Orange) world.get_door('PoD Arena Main to Landing Barrier - Blue', player).barrier(CrystalBarrier.Blue) @@ -1345,7 +1358,9 @@ def create_doors(world, player): world.get_door('Thieves Hellway Orange Barrier', player).barrier(CrystalBarrier.Orange) world.get_door('Thieves Hellway Crystal Orange Barrier', player).barrier(CrystalBarrier.Orange) world.get_door('Thieves Attic Orange Barrier', player).barrier(CrystalBarrier.Orange) + world.get_door('Thieves Attic Blue Barrier', player).barrier(CrystalBarrier.Blue) world.get_door('Thieves Attic Hint Orange Barrier', player).barrier(CrystalBarrier.Orange) + world.get_door('Thieves Attic Switch Blue Barrier', player).barrier(CrystalBarrier.Blue) world.get_door('Ice Bomb Drop SE', player).c_switch() world.get_door('Ice Conveyor Crystal Exit', player).c_switch() @@ -1421,8 +1436,7 @@ def create_doors(world, player): world.get_door('GT Crystal Conveyor Ranged Crystal Exit', player).c_switch() world.get_door('GT Crystal Conveyor Corner Ranged Crystal Exit', player).c_switch() world.get_door('GT Crystal Conveyor Corner to Left Bypass', player).barrier(CrystalBarrier.Blue) - world.get_door('GT Hookshot South-North Path', player).c_switch() - world.get_door('GT Hookshot South-East Path', player).c_switch() + world.get_door('GT Hookshot South-Mid Path', player).c_switch() world.get_door('GT Hookshot ES', player).c_switch() world.get_door('GT Hookshot Platform Barrier Bypass', player).barrier(CrystalBarrier.Orange) world.get_door('GT Hookshot Platform Blue Barrier', player).barrier(CrystalBarrier.Blue) @@ -1453,6 +1467,11 @@ def create_doors(world, player): world.get_door('GT Spike Crystal Right to Left Barrier - Orange', player).barrier(CrystalBarrier.Orange) world.get_door('GT Spike Crystal Left to Right Bypass', player).barrier(CrystalBarrier.Blue) + # kill certain doors + if world.intensity[player] == 1: # due to ladder & warp being fixed + world.get_door('PoD Mimics 2 SW', player).kill() + + # nifty dynamic logical doors: south_controller = world.get_door('Ice Cross Bottom SE', player) east_controller = world.get_door('Ice Cross Right ES', player) diff --git a/DungeonGenerator.py b/DungeonGenerator.py index f88499bd..f0038f45 100644 --- a/DungeonGenerator.py +++ b/DungeonGenerator.py @@ -218,7 +218,8 @@ def gen_dungeon_info(name, available_sectors, entrance_regions, all_regions, pro return name == 'Skull Woods 2' and d.name == 'Skull Pinball WS' original_state = extend_reachable_state_improved(entrance_regions, start, proposed_map, all_regions, valid_doors, bk_flag, world, player, exception) - dungeon['Origin'] = create_graph_piece_from_state(None, original_state, original_state, proposed_map, exception) + dungeon['Origin'] = create_graph_piece_from_state(None, original_state, original_state, proposed_map, exception, + world, player) either_crystal = True # if all hooks from the origin are either, explore all bits with either for hook, crystal in dungeon['Origin'].hooks.items(): if crystal != CrystalBarrier.Either: @@ -239,7 +240,7 @@ def gen_dungeon_info(name, available_sectors, entrance_regions, all_regions, pro o_state = extend_reachable_state_improved([parent], init_state, proposed_map, all_regions, valid_doors, bk_flag, world, player, exception) o_state_cache[door.name] = o_state - piece = create_graph_piece_from_state(door, o_state, o_state, proposed_map, exception) + piece = create_graph_piece_from_state(door, o_state, o_state, proposed_map, exception, world, player) dungeon[door.name] = piece check_blue_states(hanger_set, dungeon, o_state_cache, proposed_map, all_regions, valid_doors, group_flags, door_map, world, player, exception) @@ -339,7 +340,7 @@ def explore_blue_state(door, dungeon, o_state, proposed_map, all_regions, valid_ blue_start.big_key_special = o_state.big_key_special b_state = extend_reachable_state_improved([parent], blue_start, proposed_map, all_regions, valid_doors, bk_flag, world, player, exception) - dungeon[door.name] = create_graph_piece_from_state(door, o_state, b_state, proposed_map, exception) + dungeon[door.name] = create_graph_piece_from_state(door, o_state, b_state, proposed_map, exception, world, player) def make_a_choice(dungeon, hangers, avail_hooks, prev_choices, name): @@ -603,7 +604,7 @@ def winnow_hangers(hangers, hooks): hangers[hanger].remove(door) -def create_graph_piece_from_state(door, o_state, b_state, proposed_map, exception): +def create_graph_piece_from_state(door, o_state, b_state, proposed_map, exception, world, player): # todo: info about dungeon events - not sure about that graph_piece = GraphPiece() all_unattached = {} @@ -635,16 +636,15 @@ def create_graph_piece_from_state(door, o_state, b_state, proposed_map, exceptio graph_piece.visited_regions.update(o_state.visited_orange) graph_piece.visited_regions.update(b_state.visited_blue) graph_piece.visited_regions.update(b_state.visited_orange) - graph_piece.possible_bk_locations.update(filter_for_potential_bk_locations(o_state.bk_found)) - graph_piece.possible_bk_locations.update(filter_for_potential_bk_locations(b_state.bk_found)) + graph_piece.possible_bk_locations.update(filter_for_potential_bk_locations(o_state.bk_found, world, player)) + graph_piece.possible_bk_locations.update(filter_for_potential_bk_locations(b_state.bk_found, world, player)) graph_piece.pinball_used = o_state.pinball_used or b_state.pinball_used return graph_piece -def filter_for_potential_bk_locations(locations): - return [x for x in locations if - '- Big Chest' not in x.name and '- Prize' not in x.name and x.name not in dungeon_events - and not x.forced_item and x.name not in ['Agahnim 1', 'Agahnim 2']] +def filter_for_potential_bk_locations(locations, world, player): + return [x for x in locations if '- Big Chest' not in x.name and not reserved_location(x, world, player) and + not x.forced_item and not prize_or_event(x) and not blind_boss_unavail(x, locations, world, player)] type_map = { @@ -987,12 +987,8 @@ class ExplorationState(object): return self.crystal == CrystalBarrier.Either or door.crystal == self.crystal return True - def count_locations_exclude_specials(self): - cnt = 0 - for loc in self.found_locations: - if '- Big Chest' not in loc.name and '- Prize' not in loc.name and loc.name not in dungeon_events and not loc.forced_item: - cnt += 1 - return cnt + def count_locations_exclude_specials(self, world, player): + return count_locations_exclude_big_chest(self.found_locations, world, player) def validate(self, door, region, world, player): return self.can_traverse(door) and not self.visited(region) and valid_region_to_explore(region, self.dungeon, @@ -1033,6 +1029,32 @@ class ExplorationState(object): return 2 +def count_locations_exclude_big_chest(locations, world, player): + cnt = 0 + for loc in locations: + if ('- Big Chest' not in loc.name and not loc.forced_item and not reserved_location(loc, world, player) + and not prize_or_event(loc) and not blind_boss_unavail(loc, locations, world, player)): + cnt += 1 + return cnt + + +def prize_or_event(loc): + return loc.name in dungeon_events or '- Prize' in loc.name or loc.name in ['Agahnim 1', 'Agahnim 2'] + + +def reserved_location(loc, world, player): + return hasattr(world, 'item_pool_config') and loc.name in world.item_pool_config.reserved_locations[player] + + +def blind_boss_unavail(loc, locations, world, player): + if loc.name == "Thieves' Town - Boss": + return (loc.parent_region.dungeon.boss.name == 'Blind' and + (not any(x for x in locations if x.name == 'Suspicious Maiden') or + (world.get_region('Thieves Attic Window', player).dungeon.name == 'Thieves Town' and + not any(x for x in locations if x.name == 'Attic Cracked Floor')))) + return False + + class ExplorableDoor(object): def __init__(self, door, crystal, flag): @@ -1056,7 +1078,8 @@ def extend_reachable_state_improved(search_regions, state, proposed_map, all_reg explorable_door = local_state.next_avail_door() if explorable_door.door.bigKey: if bk_flag: - big_not_found = not special_big_key_found(local_state) if local_state.big_key_special else local_state.count_locations_exclude_specials() == 0 + big_not_found = (not special_big_key_found(local_state) if local_state.big_key_special + else local_state.count_locations_exclude_specials(world, player) == 0) if big_not_found: continue # we can't open this door if explorable_door.door in proposed_map: @@ -1139,6 +1162,8 @@ class DungeonBuilder(object): self.sectors = [] self.location_cnt = 0 self.key_drop_cnt = 0 + self.dungeon_items = None # during fill how many dungeon items are left + self.free_items = None # during fill how many dungeon items are left self.bk_required = False self.bk_provided = False self.c_switch_required = False @@ -1301,7 +1326,17 @@ def create_dungeon_builders(all_sectors, connections_tuple, world, player, polarized_sectors[sector] = None if bow_sectors: assign_bow_sectors(dungeon_map, bow_sectors, global_pole) - assign_location_sectors(dungeon_map, free_location_sectors, global_pole) + leftover = assign_location_sectors_minimal(dungeon_map, free_location_sectors, global_pole, world, player) + free_location_sectors = scatter_extra_location_sectors(dungeon_map, leftover, global_pole) + for sector in free_location_sectors: + if sector.c_switch: + crystal_switches[sector] = None + elif sector.blue_barrier: + crystal_barriers[sector] = None + elif sector.polarity().is_neutral(): + neutral_sectors[sector] = None + else: + polarized_sectors[sector] = None leftover = assign_crystal_switch_sectors(dungeon_map, crystal_switches, crystal_barriers, global_pole) ensure_crystal_switches_reachable(dungeon_map, leftover, polarized_sectors, crystal_barriers, global_pole) for sector in leftover: @@ -1452,6 +1487,7 @@ def define_sector_features(sectors): sector.bk_provided = True elif loc.name not in dungeon_events and not loc.forced_item: sector.chest_locations += 1 + sector.chest_location_set.add(loc.name) if '- Big Chest' in loc.name or loc.name in ["Hyrule Castle - Zelda's Chest", "Thieves' Town - Blind's Cell"]: sector.bk_required = True @@ -1532,26 +1568,85 @@ def assign_bow_sectors(dungeon_map, bow_sectors, global_pole): assign_sector(sector_list[i], builder, bow_sectors, global_pole) -def assign_location_sectors(dungeon_map, free_location_sectors, global_pole): +def scatter_extra_location_sectors(dungeon_map, free_location_sectors, global_pole): + population = [n for n in dungeon_map.keys()] + k = round(len(free_location_sectors) * .50) valid = False choices = None + candidates = [] + sector_list = list(free_location_sectors) + while not valid: + candidates = random.sample(sector_list, k=k) + choices = random.choices(population, k=len(candidates)) + sector_dict = defaultdict(list) + for i, choice in enumerate(choices): + builder = dungeon_map[choice] + sector_dict[builder].append(candidates[i]) + valid = global_pole.is_valid_multi_choice_2(dungeon_map, dungeon_map.values(), sector_dict) + for i, choice in enumerate(choices): + builder = dungeon_map[choice] + assign_sector(candidates[i], builder, free_location_sectors, global_pole) + return free_location_sectors + + +def assign_location_sectors_minimal(dungeon_map, free_location_sectors, global_pole, world, player): + valid = False + choices = defaultdict(list) sector_list = list(free_location_sectors) random.shuffle(sector_list) + orig_location_set = build_orig_location_set(dungeon_map) + num_dungeon_items = requested_dungeon_items(world, player) + d_idx = {builder.name: i for i, builder in enumerate(dungeon_map.values())} + next_sector = sector_list.pop() while not valid: - choices, d_idx, totals = weighted_random_locations(dungeon_map, sector_list) - for i, sector in enumerate(sector_list): - choice = d_idx[choices[i].name] - totals[choice] += sector.chest_locations - valid = True - for d_name, idx in d_idx.items(): - if totals[idx] < 5: # min locations for dungeons is 5 (bk exception) - valid = False - break - for i, choice in enumerate(choices): - builder = dungeon_map[choice.name] - assign_sector(sector_list[i], builder, free_location_sectors, global_pole) + choice, totals, location_set = weighted_random_location(dungeon_map, choices, orig_location_set, world, player) + if not choice: + break + choices[choice].append(next_sector) + if global_pole.is_valid_multi_choice_2(dungeon_map, dungeon_map.values(), choices): + idx = d_idx[choice.name] + totals[idx] += next_sector.chest_locations + location_set[choice.name].update(next_sector.chest_location_set) + valid = True + for d_name, idx in d_idx.items(): + free_items = count_reserved_locations(world, player, location_set[d_name]) + target = max(free_items, 2) + num_dungeon_items + if totals[idx] < target: + valid = False + break + if not valid: + if len(sector_list) == 0: + choices = defaultdict(list) + sector_list = list(free_location_sectors) + else: + next_sector = sector_list.pop() + else: + choices[choice].remove(next_sector) + for builder, choice_list in choices.items(): + for choice in choice_list: + assign_sector(choice, builder, free_location_sectors, global_pole) + return free_location_sectors +def weighted_random_location(dungeon_map, choices, orig_location_set, world, player): + population = [] + totals = [] + location_set = {x: set(y) for x, y in orig_location_set.items()} + num_dungeon_items = requested_dungeon_items(world, player) + for i, dungeon_builder in enumerate(dungeon_map.values()): + ttl = dungeon_builder.location_cnt + sum(sector.chest_locations for sector in choices[dungeon_builder]) + totals.append(ttl) + builder_set = location_set[dungeon_builder.name] + builder_set.update(set().union(*(s.chest_location_set for s in choices[dungeon_builder]))) + free_items = count_reserved_locations(world, player, builder_set) + target = max(free_items, 2) + num_dungeon_items + if ttl < target: + population.append(dungeon_builder) + choice = random.choice(population) if len(population) > 0 else None + return choice, totals, location_set + + +# deprecated def weighted_random_locations(dungeon_map, free_location_sectors): population = [] ttl_assigned = 0 @@ -1575,6 +1670,30 @@ def weighted_random_locations(dungeon_map, free_location_sectors): return choices, d_idx, totals +def build_orig_location_set(dungeon_map): + orig_locations = {} + for name, builder in dungeon_map.items(): + orig_locations[name] = set().union(*(s.chest_location_set for s in builder.sectors)) + return orig_locations + + +def requested_dungeon_items(world, player): + num = 0 + if not world.bigkeyshuffle[player]: + num += 1 + if not world.compassshuffle[player]: + num += 1 + if not world.mapshuffle[player]: + num += 1 + return num + + +def count_reserved_locations(world, player, proposed_set): + if world.item_pool_config: + return len([x for x in proposed_set if x in world.item_pool_config.reserved_locations[player]]) + return 2 + + def assign_crystal_switch_sectors(dungeon_map, crystal_switches, crystal_barriers, global_pole, assign_one=False): population = [] some_c_switches_present = False diff --git a/DungeonRandomizer.py b/DungeonRandomizer.py index d24e81d6..cf0f73bc 100755 --- a/DungeonRandomizer.py +++ b/DungeonRandomizer.py @@ -1,11 +1,11 @@ #!/usr/bin/env python3 -import argparse -import copy +if __name__ == '__main__': + from source.meta.check_requirements import check_requirements + check_requirements(console=True) + import os import logging import RaceRandom as random -import textwrap -import shlex import sys from source.classes.BabelFish import BabelFish diff --git a/Dungeons.py b/Dungeons.py index 6fe38cfb..188cf59f 100644 --- a/Dungeons.py +++ b/Dungeons.py @@ -1,8 +1,5 @@ -import RaceRandom as random - from BaseClasses import Dungeon from Bosses import BossFactory -from Fill import fill_restrictive from Items import ItemFactory @@ -36,119 +33,6 @@ def create_dungeons(world, player): world.dungeons += [ES, EP, DP, ToH, AT, PoD, TT, SW, SP, IP, MM, TR, GT] -def fill_dungeons(world): - freebes = ['Ganons Tower - Map Chest', 'Palace of Darkness - Harmless Hellway', 'Palace of Darkness - Big Key Chest', 'Turtle Rock - Big Key Chest'] - - all_state_base = world.get_all_state() - - for player in range(1, world.players + 1): - pinball_room = world.get_location('Skull Woods - Pinball Room', player) - if world.retro[player]: - world.push_item(pinball_room, ItemFactory('Small Key (Universal)', player), False) - else: - world.push_item(pinball_room, ItemFactory('Small Key (Skull Woods)', player), False) - pinball_room.event = True - pinball_room.locked = True - - dungeons = [(list(dungeon.regions), dungeon.big_key, list(dungeon.small_keys), list(dungeon.dungeon_items)) for dungeon in world.dungeons] - - loopcnt = 0 - while dungeons: - loopcnt += 1 - dungeon_regions, big_key, small_keys, dungeon_items = dungeons.pop(0) - # this is what we need to fill - dungeon_locations = [location for location in world.get_unfilled_locations() if location.parent_region.name in dungeon_regions] - random.shuffle(dungeon_locations) - - all_state = all_state_base.copy() - - # first place big key - if big_key is not None: - bk_location = None - for location in dungeon_locations: - if location.item_rule(big_key): - bk_location = location - break - - if bk_location is None: - raise RuntimeError('No suitable location for %s' % big_key) - - world.push_item(bk_location, big_key, False) - bk_location.event = True - bk_location.locked = True - dungeon_locations.remove(bk_location) - big_key = None - - # next place small keys - while small_keys: - small_key = small_keys.pop() - all_state.sweep_for_events() - sk_location = None - for location in dungeon_locations: - if location.name in freebes or (location.can_reach(all_state) and location.item_rule(small_key)): - sk_location = location - break - - if sk_location is None: - # need to retry this later - small_keys.append(small_key) - dungeons.append((dungeon_regions, big_key, small_keys, dungeon_items)) - # infinite regression protection - if loopcnt < (30 * world.players): - break - else: - raise RuntimeError('No suitable location for %s' % small_key) - - world.push_item(sk_location, small_key, False) - sk_location.event = True - sk_location.locked = True - dungeon_locations.remove(sk_location) - - if small_keys: - # key placement not finished, loop again - continue - - # next place dungeon items - for dungeon_item in dungeon_items: - di_location = dungeon_locations.pop() - world.push_item(di_location, dungeon_item, False) - - -def get_dungeon_item_pool(world): - return [item for dungeon in world.dungeons for item in dungeon.all_items] - -def fill_dungeons_restrictive(world, shuffled_locations): - all_state_base = world.get_all_state() - - # for player in range(1, world.players + 1): - # pinball_room = world.get_location('Skull Woods - Pinball Room', player) - # if world.retro[player]: - # world.push_item(pinball_room, ItemFactory('Small Key (Universal)', player), False) - # else: - # world.push_item(pinball_room, ItemFactory('Small Key (Skull Woods)', player), False) - # pinball_room.event = True - # pinball_room.locked = True - # shuffled_locations.remove(pinball_room) - - # with shuffled dungeon items they are distributed as part of the normal item pool - for item in world.get_items(): - if (item.smallkey and world.keyshuffle[item.player]) or (item.bigkey and world.bigkeyshuffle[item.player]): - item.advancement = True - elif (item.map and world.mapshuffle[item.player]) or (item.compass and world.compassshuffle[item.player]): - item.priority = True - - dungeon_items = [item for item in get_dungeon_item_pool(world) if ((item.smallkey and not world.keyshuffle[item.player]) - or (item.bigkey and not world.bigkeyshuffle[item.player]) - or (item.map and not world.mapshuffle[item.player]) - or (item.compass and not world.compassshuffle[item.player]))] - - # sort in the order Big Key, Small Key, Other before placing dungeon items - sort_order = {"BigKey": 3, "SmallKey": 2} - dungeon_items.sort(key=lambda item: sort_order.get(item.type, 1)) - - fill_restrictive(world, all_state_base, shuffled_locations, dungeon_items, - keys_in_itempool={player: not world.keyshuffle[player] for player in range(1, world.players+1)}, single_player_placement=True) - dungeon_music_addresses = {'Eastern Palace - Prize': [0x1559A], 'Desert Palace - Prize': [0x1559B, 0x1559C, 0x1559D, 0x1559E], @@ -200,7 +84,7 @@ hera_regions = [ 'Hera Back - Ranged Crystal', 'Hera Basement Cage', 'Hera Basement Cage - Crystal', 'Hera Tile Room', 'Hera Tridorm', 'Hera Tridorm - Crystal', 'Hera Torches', 'Hera Beetles', 'Hera Startile Corner', 'Hera Startile Wide', 'Hera Startile Wide - Crystal', 'Hera 4F', 'Hera Big Chest Landing', 'Hera 5F', - 'Hera Fairies', 'Hera Boss', 'Hera Portal' + 'Hera 5F Pot Block', 'Hera Fairies', 'Hera Boss', 'Hera Portal' ] tower_regions = [ @@ -226,24 +110,24 @@ pod_regions = [ swamp_regions = [ 'Swamp Lobby', 'Swamp Entrance', 'Swamp Pot Row', 'Swamp Map Ledge', 'Swamp Trench 1 Approach', 'Swamp Trench 1 Nexus', 'Swamp Trench 1 Alcove', 'Swamp Trench 1 Key Ledge', 'Swamp Trench 1 Departure', - 'Swamp Hammer Switch', 'Swamp Hub', 'Swamp Hub Dead Ledge', 'Swamp Hub North Ledge', 'Swamp Donut Top', - 'Swamp Donut Bottom', 'Swamp Compass Donut', 'Swamp Crystal Switch Outer', 'Swamp Crystal Switch Outer - Ranged Crystal', - 'Swamp Crystal Switch Inner', 'Swamp Crystal Switch Inner - Crystal', 'Swamp Shortcut', 'Swamp Trench 2 Pots', - 'Swamp Trench 2 Blocks', 'Swamp Trench 2 Alcove', 'Swamp Trench 2 Departure', 'Swamp Big Key Ledge', - 'Swamp West Shallows', 'Swamp West Block Path', 'Swamp West Ledge', 'Swamp Barrier Ledge', 'Swamp Barrier', - 'Swamp Attic', 'Swamp Push Statue', 'Swamp Shooters', 'Swamp Left Elbow', 'Swamp Right Elbow', 'Swamp Drain Left', - 'Swamp Drain Right', 'Swamp Flooded Room', 'Swamp Flooded Spot', 'Swamp Basement Shallows', 'Swamp Waterfall Room', - 'Swamp Refill', 'Swamp Behind Waterfall', 'Swamp C', 'Swamp Waterway', 'Swamp I', 'Swamp T', 'Swamp Boss', - 'Swamp Portal' + 'Swamp Hammer Switch', 'Swamp Hub', 'Swamp Hub Side Ledges', 'Swamp Hub Dead Ledge', 'Swamp Hub North Ledge', + 'Swamp Donut Top', 'Swamp Donut Bottom', 'Swamp Compass Donut', 'Swamp Crystal Switch Outer', + 'Swamp Crystal Switch Outer - Ranged Crystal', 'Swamp Crystal Switch Inner', 'Swamp Crystal Switch Inner - Crystal', + 'Swamp Shortcut', 'Swamp Trench 2 Pots', 'Swamp Trench 2 Blocks', 'Swamp Trench 2 Alcove', + 'Swamp Trench 2 Departure', 'Swamp Big Key Ledge', 'Swamp West Shallows', 'Swamp West Block Path', + 'Swamp West Ledge', 'Swamp Barrier Ledge', 'Swamp Barrier', 'Swamp Attic', 'Swamp Push Statue', 'Swamp Shooters', + 'Swamp Left Elbow', 'Swamp Right Elbow', 'Swamp Drain Left', 'Swamp Drain Right', 'Swamp Flooded Room', + 'Swamp Flooded Spot', 'Swamp Basement Shallows', 'Swamp Waterfall Room', 'Swamp Refill', 'Swamp Behind Waterfall', + 'Swamp C', 'Swamp Waterway', 'Swamp I', 'Swamp T', 'Swamp Boss', 'Swamp Portal' ] skull_regions = [ 'Skull 1 Lobby', 'Skull Map Room', 'Skull Pot Circle', 'Skull Pull Switch', 'Skull Big Chest', 'Skull Pinball', 'Skull Pot Prison', 'Skull Compass Room', 'Skull Left Drop', 'Skull 2 East Lobby', 'Skull Big Key', - 'Skull Lone Pot', 'Skull Small Hall', 'Skull Back Drop', 'Skull 2 West Lobby', 'Skull X Room', 'Skull 3 Lobby', - 'Skull East Bridge', 'Skull West Bridge Nook', 'Skull Star Pits', 'Skull Torch Room', 'Skull Vines', - 'Skull Spike Corner', 'Skull Final Drop', 'Skull Boss', 'Skull 1 Portal', 'Skull 2 East Portal', - 'Skull 2 West Portal', 'Skull 3 Portal' + 'Skull Lone Pot', 'Skull Small Hall', 'Skull Back Drop', 'Skull 2 West Lobby', 'Skull 2 West Lobby Ledge', + 'Skull X Room', 'Skull 3 Lobby', 'Skull East Bridge', 'Skull West Bridge Nook', 'Skull Star Pits', + 'Skull Torch Room', 'Skull Vines', 'Skull Spike Corner', 'Skull Final Drop', 'Skull Boss', + 'Skull 1 Portal', 'Skull 2 East Portal', 'Skull 2 West Portal', 'Skull 3 Portal' ] thieves_regions = [ @@ -251,10 +135,10 @@ thieves_regions = [ 'Thieves Big Chest Nook', 'Thieves Hallway', 'Thieves Boss', 'Thieves Pot Alcove Mid', 'Thieves Pot Alcove Bottom', 'Thieves Pot Alcove Top', 'Thieves Conveyor Maze', 'Thieves Spike Track', 'Thieves Hellway', 'Thieves Hellway N Crystal', 'Thieves Hellway S Crystal', 'Thieves Triple Bypass', 'Thieves Spike Switch', - 'Thieves Attic', 'Thieves Attic Hint', 'Thieves Cricket Hall Left', 'Thieves Cricket Hall Right', - 'Thieves Attic Window', 'Thieves Basement Block', 'Thieves Blocked Entry', 'Thieves Lonely Zazak', - "Thieves Blind's Cell", "Thieves Blind's Cell Interior", 'Thieves Conveyor Bridge', 'Thieves Conveyor Block', - 'Thieves Big Chest Room', 'Thieves Trap', 'Thieves Town Portal' + 'Thieves Attic', 'Thieves Attic Hint', 'Thieves Attic Switch', 'Thieves Cricket Hall Left', + 'Thieves Cricket Hall Right', 'Thieves Attic Window', 'Thieves Basement Block', 'Thieves Blocked Entry', + 'Thieves Lonely Zazak', "Thieves Blind's Cell", "Thieves Blind's Cell Interior", 'Thieves Conveyor Bridge', + 'Thieves Conveyor Block', 'Thieves Big Chest Room', 'Thieves Trap', 'Thieves Town Portal' ] ice_regions = [ @@ -284,29 +168,31 @@ mire_regions = [ ] tr_regions = [ - 'TR Main Lobby', 'TR Lobby Ledge', 'TR Compass Room', 'TR Hub', 'TR Torches Ledge', 'TR Torches', 'TR Roller Room', - 'TR Tile Room', 'TR Refill', 'TR Pokey 1', 'TR Chain Chomps Top', 'TR Chain Chomps Top - Crystal', - 'TR Chain Chomps Bottom', 'TR Chain Chomps Bottom - Ranged Crystal', 'TR Pipe Pit', 'TR Pipe Ledge', 'TR Lava Dual Pipes', - 'TR Lava Island', 'TR Lava Escape', 'TR Pokey 2 Top', 'TR Pokey 2 Top - Crystal', 'TR Pokey 2 Bottom', 'TR Pokey 2 Bottom - Ranged Crystal', - 'TR Twin Pokeys', 'TR Hallway', 'TR Dodgers', 'TR Big View','TR Big Chest', 'TR Big Chest Entrance', - 'TR Lazy Eyes', 'TR Dash Room', 'TR Tongue Pull', 'TR Rupees', 'TR Crystaroller Bottom', - 'TR Crystaroller Middle', 'TR Crystaroller Top', 'TR Crystaroller Top - Crystal', 'TR Crystaroller Chest', - 'TR Crystaroller Middle - Ranged Crystal', 'TR Crystaroller Bottom - Ranged Crystal', 'TR Dark Ride', 'TR Dash Bridge', 'TR Eye Bridge', + 'TR Main Lobby', 'TR Lobby Ledge', 'TR Compass Room', 'TR Hub', 'TR Hub Ledges', 'TR Torches Ledge', 'TR Torches', + 'TR Roller Room', 'TR Tile Room', 'TR Refill', 'TR Pokey 1', 'TR Chain Chomps Top', 'TR Chain Chomps Top - Crystal', + 'TR Chain Chomps Bottom', 'TR Chain Chomps Bottom - Ranged Crystal', 'TR Pipe Pit', 'TR Pipe Ledge', + 'TR Lava Dual Pipes', 'TR Lava Island', 'TR Lava Escape', 'TR Pokey 2 Top', 'TR Pokey 2 Top - Crystal', + 'TR Pokey 2 Bottom', 'TR Pokey 2 Bottom - Ranged Crystal', 'TR Twin Pokeys', 'TR Hallway', 'TR Dodgers', + 'TR Big View', 'TR Big Chest', 'TR Big Chest Entrance', 'TR Lazy Eyes', 'TR Dash Room', 'TR Tongue Pull', + 'TR Rupees', 'TR Crystaroller Bottom', 'TR Crystaroller Middle', 'TR Crystaroller Top', + 'TR Crystaroller Top - Crystal', 'TR Crystaroller Chest', 'TR Crystaroller Middle - Ranged Crystal', + 'TR Crystaroller Bottom - Ranged Crystal', 'TR Dark Ride', 'TR Dark Ride Ledges', 'TR Dash Bridge', 'TR Eye Bridge', 'TR Crystal Maze Start', 'TR Crystal Maze Start - Crystal', 'TR Crystal Maze Interior', 'TR Crystal Maze End', - 'TR Crystal Maze End - Ranged Crystal', 'TR Final Abyss', 'TR Boss', 'Turtle Rock Main Portal', - 'Turtle Rock Lazy Eyes Portal', 'Turtle Rock Chest Portal', 'Turtle Rock Eye Bridge Portal' + 'TR Crystal Maze End - Ranged Crystal', 'TR Final Abyss Balcony', 'TR Final Abyss Ledge', 'TR Boss', + 'Turtle Rock Main Portal', 'Turtle Rock Lazy Eyes Portal', 'Turtle Rock Chest Portal', + 'Turtle Rock Eye Bridge Portal' ] gt_regions = [ 'GT Lobby', 'GT Bob\'s Torch', 'GT Hope Room', 'GT Big Chest', 'GT Blocked Stairs', 'GT Bob\'s Room', 'GT Tile Room', 'GT Speed Torch', 'GT Speed Torch Upper', 'GT Pots n Blocks', 'GT Crystal Conveyor', 'GT Crystal Conveyor Corner', 'GT Crystal Conveyor Left', 'GT Crystal Conveyor - Ranged Crystal', - 'GT Crystal Conveyor Corner - Ranged Crystal', - 'GT Compass Room', 'GT Invisible Bridges', 'GT Invisible Catwalk', 'GT Conveyor Cross', 'GT Hookshot East Platform', - 'GT Hookshot North Platform', 'GT Hookshot South Platform', 'GT Hookshot South Entry', 'GT Hookshot South Entry - Ranged Crystal', 'GT Map Room', + 'GT Crystal Conveyor Corner - Ranged Crystal', 'GT Compass Room', 'GT Invisible Bridges', 'GT Invisible Catwalk', + 'GT Conveyor Cross', 'GT Hookshot East Platform', 'GT Hookshot Mid Platform', 'GT Hookshot North Platform', + 'GT Hookshot South Platform', 'GT Hookshot South Entry', 'GT Hookshot South Entry - Ranged Crystal', 'GT Map Room', 'GT Double Switch Entry', 'GT Double Switch Pot Corners - Ranged Switches', 'GT Double Switch Pot Corners', - 'GT Double Switch Left', 'GT Double Switch Left - Crystal', - 'GT Double Switch Entry - Ranged Switches', 'GT Double Switch Exit', 'GT Spike Crystal Left', + 'GT Double Switch Left', 'GT Double Switch Left - Crystal', 'GT Double Switch Entry - Ranged Switches', + 'GT Double Switch Exit', 'GT Spike Crystal Left', 'GT Spike Crystal Right', 'GT Warp Maze - Left Section', 'GT Warp Maze - Mid Section', 'GT Warp Maze - Right Section', 'GT Warp Maze - Pit Section', 'GT Warp Maze - Pit Exit Warp Spot', 'GT Warp Maze Exit Section', 'GT Firesnake Room', 'GT Firesnake Room Ledge', 'GT Warp Maze - Rail Choice', @@ -378,8 +264,8 @@ flexible_starts = { class DungeonInfo: - def __init__(self, free, keys, bk, map, compass, bk_drop, drops, prize=None): - # todo reduce static maps ideas: prize, bk_name, sm_name, cmp_name, map_name): + def __init__(self, free, keys, bk, map, compass, bk_drop, drops, prize, midx): + # todo reduce static maps ideas: prize, bk_name, sm_name, cmp_name, map_name): self.free_items = free self.key_num = keys self.bk_present = bk @@ -389,21 +275,23 @@ class DungeonInfo: self.key_drops = drops self.prize = prize + self.map_index = midx + dungeon_table = { - 'Hyrule Castle': DungeonInfo(6, 1, False, True, False, True, 3, None), - 'Eastern Palace': DungeonInfo(3, 0, True, True, True, False, 2, 'Eastern Palace - Prize'), - 'Desert Palace': DungeonInfo(2, 1, True, True, True, False, 3, 'Desert Palace - Prize'), - 'Tower of Hera': DungeonInfo(2, 1, True, True, True, False, 0, 'Tower of Hera - Prize'), - 'Agahnims Tower': DungeonInfo(0, 2, False, False, False, False, 2, None), - 'Palace of Darkness': DungeonInfo(5, 6, True, True, True, False, 0, 'Palace of Darkness - Prize'), - 'Swamp Palace': DungeonInfo(6, 1, True, True, True, False, 5, 'Swamp Palace - Prize'), - 'Skull Woods': DungeonInfo(2, 3, True, True, True, False, 2, 'Skull Woods - Prize'), - 'Thieves Town': DungeonInfo(4, 1, True, True, True, False, 2, "Thieves' Town - Prize"), - 'Ice Palace': DungeonInfo(3, 2, True, True, True, False, 4, 'Ice Palace - Prize'), - 'Misery Mire': DungeonInfo(2, 3, True, True, True, False, 3, 'Misery Mire - Prize'), - 'Turtle Rock': DungeonInfo(5, 4, True, True, True, False, 2, 'Turtle Rock - Prize'), - 'Ganons Tower': DungeonInfo(20, 4, True, True, True, False, 4, None), + 'Hyrule Castle': DungeonInfo(6, 1, False, True, False, True, 3, None, 0xc), + 'Eastern Palace': DungeonInfo(3, 0, True, True, True, False, 2, 'Eastern Palace - Prize', 0x0), + 'Desert Palace': DungeonInfo(2, 1, True, True, True, False, 3, 'Desert Palace - Prize', 0x2), + 'Tower of Hera': DungeonInfo(2, 1, True, True, True, False, 0, 'Tower of Hera - Prize', 0x1), + 'Agahnims Tower': DungeonInfo(0, 2, False, False, False, False, 2, None, 0xb), + 'Palace of Darkness': DungeonInfo(5, 6, True, True, True, False, 0, 'Palace of Darkness - Prize', 0x3), + 'Swamp Palace': DungeonInfo(6, 1, True, True, True, False, 5, 'Swamp Palace - Prize', 0x9), + 'Skull Woods': DungeonInfo(2, 3, True, True, True, False, 2, 'Skull Woods - Prize', 0x4), + 'Thieves Town': DungeonInfo(4, 1, True, True, True, False, 2, "Thieves' Town - Prize", 0x6), + 'Ice Palace': DungeonInfo(3, 2, True, True, True, False, 4, 'Ice Palace - Prize', 0x8), + 'Misery Mire': DungeonInfo(2, 3, True, True, True, False, 3, 'Misery Mire - Prize', 0x7), + 'Turtle Rock': DungeonInfo(5, 4, True, True, True, False, 2, 'Turtle Rock - Prize', 0x5), + 'Ganons Tower': DungeonInfo(20, 4, True, True, True, False, 4, None, 0xa), } @@ -439,7 +327,6 @@ dungeon_bigs = { 'Ganons Tower': 'Big Key (Ganons Tower)' } - dungeon_hints = { 'Hyrule Castle': 'in Hyrule Castle', 'Eastern Palace': 'in Eastern Palace', diff --git a/EntranceShuffle.py b/EntranceShuffle.py index b8715b06..f67c089b 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -63,9 +63,10 @@ def link_entrances(world, player): connect_caves(world, lw_entrances, [], hyrule_castle_exits, player) elif world.doorShuffle[player] != 'vanilla': # sanc is in light world, so must all of HC if door shuffle is on - connect_mandatory_exits(world, lw_entrances, - [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)', 'Hyrule Castle Exit (South)')], + hyrule_castle_exits = [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)', 'Hyrule Castle Exit (South)')] + connect_mandatory_exits(world, lw_entrances, hyrule_castle_exits, list(LW_Dungeon_Entrances_Must_Exit), player) + connect_caves(world, lw_entrances, [], hyrule_castle_exits, player) else: connect_mandatory_exits(world, lw_entrances, dungeon_exits, list(LW_Dungeon_Entrances_Must_Exit), player) connect_mandatory_exits(world, dw_entrances, dungeon_exits, list(DW_Dungeon_Entrances_Must_Exit), player) @@ -2557,10 +2558,10 @@ Isolated_LH_Doors_Open = ['Mimic Cave', 'Desert Palace Entrance (South)', 'Desert Palace Entrance (North)', 'Capacity Upgrade', - 'Ice Palace', - 'Skull Woods Final Section', - 'Dark World Hammer Peg Cave', - 'Turtle Rock Isolated Ledge Entrance'] + 'Ice Palace', 'Dark World Shop', 'Dark World Potion Shop', + 'Skull Woods Final Section', 'Skull Woods Second Section Door (West)', + 'Dark World Hammer Peg Cave', 'Dark Death Mountain Ledge (West)', + 'Turtle Rock Isolated Ledge Entrance', 'Dark Death Mountain Ledge (East)'] DW_Single_Cave_Doors = ['Bonk Fairy (Dark)', 'Dark Sanctuary Hint', @@ -3033,10 +3034,10 @@ Isolated_LH_Doors = ['Kings Grave', 'Desert Palace Entrance (South)', 'Desert Palace Entrance (North)', 'Capacity Upgrade', - 'Ice Palace', - 'Skull Woods Final Section', - 'Dark World Hammer Peg Cave', - 'Turtle Rock Isolated Ledge Entrance'] + 'Ice Palace', 'Dark World Shop', 'Dark World Potion Shop', + 'Skull Woods Final Section', 'Skull Woods Second Section Door (West)', + 'Dark World Hammer Peg Cave', 'Dark Death Mountain Ledge (West)', + 'Turtle Rock Isolated Ledge Entrance', 'Dark Death Mountain Ledge (East)'] # Entrances that cannot be used to access a must_exit entrance - symmetrical to allow reverse lookups Must_Exit_Invalid_Connections = defaultdict(set, { @@ -3066,16 +3067,20 @@ mandatory_connections = [('Links House S&Q', 'Links House'), ('Lake Hylia Central Island Pier', 'Lake Hylia Central Island'), ('Lake Hylia Central Island Teleporter', 'Dark Lake Hylia Central Island'), ('Zoras River', 'Zoras River'), + ('Zora Waterfall Entryway', 'Zora Waterfall Entryway'), + ('Zora Waterfall Water Drop', 'Light World'), ('Kings Grave Outer Rocks', 'Kings Grave Area'), ('Kings Grave Inner Rocks', 'Light World'), ('Kings Grave Mirror Spot', 'Kings Grave Area'), ('Kakariko Well (top to bottom)', 'Kakariko Well (bottom)'), + ('Kakariko Well (top to back)', 'Kakariko Well (back)'), ('Master Sword Meadow', 'Master Sword Meadow'), ('Hobo Bridge', 'Hobo Bridge'), ('Bat Cave Drop Ledge', 'Bat Cave Drop Ledge'), ('Bat Cave Door', 'Bat Cave (left)'), ('Lost Woods Hideout (top to bottom)', 'Lost Woods Hideout (bottom)'), ('Lumberjack Tree (top to bottom)', 'Lumberjack Tree (bottom)'), + ('Blinds Hideout N', 'Blinds Hideout (Top)'), ('Desert Palace Stairs', 'Desert Palace Stairs'), ('Desert Palace Stairs Drop', 'Light World'), ('Desert Palace Entrance (North) Rocks', 'Desert Palace Entrance (North) Spot'), @@ -3088,6 +3093,8 @@ mandatory_connections = [('Links House S&Q', 'Links House'), ('Death Mountain Entrance Drop', 'Light World'), ('Spectacle Rock Cave Drop', 'Spectacle Rock Cave (Bottom)'), ('Spectacle Rock Cave Peak Drop', 'Spectacle Rock Cave (Bottom)'), + ('Death Mountain Return Cave E', 'Death Mountain Return Cave (right)'), + ('Death Mountain Return Cave W', 'Death Mountain Return Cave (left)'), ('Death Mountain Return Ledge Drop', 'Light World'), ('Old Man Cave Dropdown', 'Old Man Cave'), ('Old Man House Front to Back', 'Old Man House Back'), @@ -3146,6 +3153,7 @@ mandatory_connections = [('Links House S&Q', 'Links House'), ('Death Mountain Teleporter', 'Dark Death Mountain (West Bottom)'), ('Paradox Cave Push Block Reverse', 'Paradox Cave Chest Area'), ('Paradox Cave Push Block', 'Paradox Cave Front'), + ('Paradox Cave Chest Area NE', 'Paradox Cave Bomb Area'), ('Paradox Cave Bomb Jump', 'Paradox Cave'), ('Paradox Cave Drop', 'Paradox Cave Chest Area'), ('Light World Death Mountain Shop', 'Light World Death Mountain Shop'), @@ -3166,6 +3174,8 @@ mandatory_connections = [('Links House S&Q', 'Links House'), ('Hookshot Cave Middle to Front', 'Hookshot Cave (Front)'), ('Hookshot Cave Middle to Back', 'Hookshot Cave (Back)'), ('Hookshot Cave Back to Middle', 'Hookshot Cave (Middle)'), + ('Hookshot Cave Bonk Path', 'Hookshot Cave (Bonk Islands)'), + ('Hookshot Cave Hook Path', 'Hookshot Cave (Hook Islands)'), ('Turtle Rock Teleporter', 'Turtle Rock (Top)'), ('Turtle Rock Drop', 'Dark Death Mountain (Top)'), ('Floating Island Drop', 'Dark Death Mountain (Top)'), @@ -3178,7 +3188,14 @@ mandatory_connections = [('Links House S&Q', 'Links House'), ('Bombos Tablet Mirror Spot', 'Bombos Tablet Ledge'), ('Graveyard Ledge Mirror Spot', 'Graveyard Ledge'), ('Ganon Drop', 'Bottom of Pyramid'), - ('Pyramid Drop', 'East Dark World') + ('Pyramid Drop', 'East Dark World'), + ('Maze Race Ledge Drop', 'Light World'), + ('Graveyard Ledge Drop', 'Light World'), + ('Cave 45 Ledge Drop', 'Light World'), + ('Checkerboard Ledge Drop', 'Light World'), + ('Desert Ledge Drop', 'Light World'), + ('Hyrule Castle Main Gate (North)', 'Light World'), + ('Hyrule Castle Ledge Drop', 'Light World'), ] inverted_mandatory_connections = [('Links House S&Q', 'Inverted Links House'), @@ -3195,12 +3212,14 @@ inverted_mandatory_connections = [('Links House S&Q', 'Inverted Links House'), ('Kings Grave Outer Rocks', 'Kings Grave Area'), ('Kings Grave Inner Rocks', 'Light World'), ('Kakariko Well (top to bottom)', 'Kakariko Well (bottom)'), + ('Kakariko Well (top to back)', 'Kakariko Well (back)'), ('Master Sword Meadow', 'Master Sword Meadow'), ('Hobo Bridge', 'Hobo Bridge'), ('Bat Cave Drop Ledge', 'Bat Cave Drop Ledge'), ('Bat Cave Door', 'Bat Cave (left)'), ('Lost Woods Hideout (top to bottom)', 'Lost Woods Hideout (bottom)'), ('Lumberjack Tree (top to bottom)', 'Lumberjack Tree (bottom)'), + ('Blinds Hideout N', 'Blinds Hideout (Top)'), ('Desert Palace Stairs', 'Desert Palace Stairs'), ('Desert Palace Stairs Drop', 'Light World'), ('Desert Palace Entrance (North) Rocks', 'Desert Palace Entrance (North) Spot'), @@ -3208,6 +3227,8 @@ inverted_mandatory_connections = [('Links House S&Q', 'Inverted Links House'), ('Sewer Drop', 'Sewers Rat Path'), ('Death Mountain Entrance Rock', 'Death Mountain Entrance'), ('Death Mountain Entrance Drop', 'Light World'), + ('Death Mountain Return Cave E', 'Death Mountain Return Cave (right)'), + ('Death Mountain Return Cave W', 'Death Mountain Return Cave (left)'), ('Spectacle Rock Cave Drop', 'Spectacle Rock Cave (Bottom)'), ('Spectacle Rock Cave Peak Drop', 'Spectacle Rock Cave (Bottom)'), ('Death Mountain Return Ledge Drop', 'Light World'), @@ -3251,6 +3272,7 @@ inverted_mandatory_connections = [('Links House S&Q', 'Inverted Links House'), ('Skull Woods Forest', 'Skull Woods Forest'), ('Paradox Cave Push Block Reverse', 'Paradox Cave Chest Area'), ('Paradox Cave Push Block', 'Paradox Cave Front'), + ('Paradox Cave Chest Area NE', 'Paradox Cave Bomb Area'), ('Paradox Cave Bomb Jump', 'Paradox Cave'), ('Paradox Cave Drop', 'Paradox Cave Chest Area'), ('Light World Death Mountain Shop', 'Light World Death Mountain Shop'), @@ -3288,6 +3310,8 @@ inverted_mandatory_connections = [('Links House S&Q', 'Inverted Links House'), ('Hookshot Cave Middle to Front', 'Hookshot Cave (Front)'), ('Hookshot Cave Middle to Back', 'Hookshot Cave (Back)'), ('Hookshot Cave Back to Middle', 'Hookshot Cave (Middle)'), + ('Hookshot Cave Bonk Path', 'Hookshot Cave (Bonk Islands)'), + ('Hookshot Cave Hook Path', 'Hookshot Cave (Hook Islands)'), ('Desert Ledge Drop', 'Light World'), ('Floating Island Drop', 'Dark Death Mountain'), ('Dark Lake Hylia Central Island Teleporter', 'Lake Hylia Central Island'), @@ -3344,7 +3368,8 @@ inverted_mandatory_connections = [('Links House S&Q', 'Inverted Links House'), ('Bush Covered Lawn Mirror Spot', 'Dark Grassy Lawn'), ('Bomb Hut Inner Bushes', 'Light World'), ('Bomb Hut Outer Bushes', 'Bomb Hut Area'), - ('Bomb Hut Mirror Spot', 'West Dark World')] + ('Bomb Hut Mirror Spot', 'West Dark World'), + ('Maze Race Ledge Drop', 'Light World')] # non-shuffled entrance links default_connections = [('Links House', 'Links House'), ('Links House Exit', 'Light World'), @@ -3426,8 +3451,8 @@ default_connections = [('Links House', 'Links House'), ('Old Man House Exit (Bottom)', 'Death Mountain'), ('Old Man House (Top)', 'Old Man House Back'), ('Old Man House Exit (Top)', 'Death Mountain'), - ('Death Mountain Return Cave (East)', 'Death Mountain Return Cave'), - ('Death Mountain Return Cave (West)', 'Death Mountain Return Cave'), + ('Death Mountain Return Cave (East)', 'Death Mountain Return Cave (right)'), + ('Death Mountain Return Cave (West)', 'Death Mountain Return Cave (left)'), ('Death Mountain Return Cave Exit (West)', 'Death Mountain Return Ledge'), ('Death Mountain Return Cave Exit (East)', 'Death Mountain'), ('Spectacle Rock Cave Peak', 'Spectacle Rock Cave (Peak)'), @@ -3631,7 +3656,7 @@ inverted_default_connections = [('Waterfall of Wishing', 'Waterfall of Wishing' ('Inverted Dark Sanctuary', 'Inverted Dark Sanctuary'), ('Inverted Dark Sanctuary Exit', 'West Dark World'), ('Old Man Cave (West)', 'Bumper Cave'), - ('Old Man Cave (East)', 'Death Mountain Return Cave'), + ('Old Man Cave (East)', 'Death Mountain Return Cave (left)'), ('Old Man Cave Exit (West)', 'West Dark World'), ('Old Man Cave Exit (East)', 'Dark Death Mountain'), ('Dark Death Mountain Fairy', 'Old Man Cave'), @@ -3640,7 +3665,7 @@ inverted_default_connections = [('Waterfall of Wishing', 'Waterfall of Wishing' ('Bumper Cave Exit (Top)', 'Death Mountain Return Ledge'), ('Bumper Cave Exit (Bottom)', 'Light World'), ('Death Mountain Return Cave (West)', 'Bumper Cave'), - ('Death Mountain Return Cave (East)', 'Death Mountain Return Cave'), + ('Death Mountain Return Cave (East)', 'Death Mountain Return Cave (right)'), ('Death Mountain Return Cave Exit (West)', 'Death Mountain'), ('Death Mountain Return Cave Exit (East)', 'Death Mountain'), ('Hookshot Cave Front Exit', 'Dark Death Mountain'), @@ -3778,8 +3803,6 @@ indirect_connections = { # | ([addr], None) # holes # exitdata = (room_id, ow_area, vram_loc, scroll_y, scroll_x, link_y, link_x, camera_y, camera_x, unknown_1, unknown_2, door_1, door_2) -# ToDo somehow merge this with creation of the locations - # ToDo somehow merge this with creation of the locations door_addresses = {'Links House': (0x00, (0x0104, 0x2c, 0x0506, 0x0a9a, 0x0832, 0x0ae8, 0x08b8, 0x0b07, 0x08bf, 0x06, 0xfe, 0x0816, 0x0000)), 'Inverted Big Bomb Shop': (0x00, (0x0104, 0x2c, 0x0506, 0x0a9a, 0x0832, 0x0ae8, 0x08b8, 0x0b07, 0x08bf, 0x06, 0xfe, 0x0816, 0x0000)), @@ -4079,3 +4102,137 @@ exit_ids = {'Links House Exit': (0x01, 0x00), 'Skull Pinball': 0x78, 'Skull Pot Circle': 0x76, 'Pyramid': 0x7B} + +ow_prize_table = {'Links House': (0x8b1, 0xb2d), 'Inverted Big Bomb Shop': (0x8b1, 0xb2d), + 'Desert Palace Entrance (South)': (0x108, 0xd70), 'Desert Palace Entrance (West)': (0x031, 0xca0), + 'Desert Palace Entrance (North)': (0x0e1, 0xba0), 'Desert Palace Entrance (East)': (0x191, 0xca0), + 'Eastern Palace': (0xf31, 0x620), 'Tower of Hera': (0x8D0, 0x080), + 'Hyrule Castle Entrance (South)': (0x7b0, 0x730), 'Hyrule Castle Entrance (West)': (0x700, 0x640), + 'Hyrule Castle Entrance (East)': (0x8a0, 0x640), 'Inverted Pyramid Entrance': (0x720, 0x700), + 'Agahnims Tower': (0x7e0, 0x640), 'Inverted Ganons Tower': (0x7e0, 0x640), + 'Thieves Town': (0x1d0, 0x780), 'Skull Woods First Section Door': (0x240, 0x280), + 'Skull Woods Second Section Door (East)': (0x1a0, 0x240), + 'Skull Woods Second Section Door (West)': (0x0c0, 0x1c0), 'Skull Woods Final Section': (0x082, 0x0b0), + 'Ice Palace': (0xca0, 0xda0), + 'Misery Mire': (0x100, 0xca0), + 'Palace of Darkness': (0xf40, 0x620), 'Swamp Palace': (0x759, 0xED0), + 'Turtle Rock': (0xf11, 0x103), + 'Dark Death Mountain Ledge (West)': (0xb80, 0x180), + 'Dark Death Mountain Ledge (East)': (0xc80, 0x180), + 'Turtle Rock Isolated Ledge Entrance': (0xc00, 0x240), + 'Hyrule Castle Secret Entrance Stairs': (0x850, 0x700), + 'Kakariko Well Cave': (0x060, 0x680), + 'Bat Cave Cave': (0x540, 0x8f0), + 'Elder House (East)': (0x2b0, 0x6a0), + 'Elder House (West)': (0x230, 0x6a0), + 'North Fairy Cave': (0xa80, 0x440), + 'Lost Woods Hideout Stump': (0x240, 0x280), + 'Lumberjack Tree Cave': (0x4e0, 0x004), + 'Two Brothers House (East)': (0x200, 0x0b60), + 'Two Brothers House (West)': (0x180, 0x0b60), + 'Sanctuary': (0x720, 0x4a0), + 'Old Man Cave (West)': (0x580, 0x2c0), + 'Old Man Cave (East)': (0x620, 0x2c0), + 'Old Man House (Bottom)': (0x720, 0x320), + 'Old Man House (Top)': (0x820, 0x220), + 'Death Mountain Return Cave (East)': (0x600, 0x220), + 'Death Mountain Return Cave (West)': (0x500, 0x1c0), + 'Spectacle Rock Cave Peak': (0x720, 0x0a0), + 'Spectacle Rock Cave': (0x790, 0x1a0), + 'Spectacle Rock Cave (Bottom)': (0x710, 0x0a0), + 'Paradox Cave (Bottom)': (0xd80, 0x180), + 'Paradox Cave (Middle)': (0xd80, 0x380), + 'Paradox Cave (Top)': (0xd80, 0x020), + 'Fairy Ascension Cave (Bottom)': (0xcc8, 0x2a0), + 'Fairy Ascension Cave (Top)': (0xc00, 0x240), + 'Spiral Cave': (0xb80, 0x180), + 'Spiral Cave (Bottom)': (0xb80, 0x2c0), + 'Bumper Cave (Bottom)': (0x580, 0x2c0), + 'Bumper Cave (Top)': (0x500, 0x1c0), + 'Superbunny Cave (Top)': (0xd80, 0x020), + 'Superbunny Cave (Bottom)': (0xd00, 0x180), + 'Hookshot Cave': (0xc80, 0x0c0), + 'Hookshot Cave Back Entrance': (0xcf0, 0x004), + 'Ganons Tower': (0x8D0, 0x080), + 'Inverted Agahnims Tower': (0x8D0, 0x080), + 'Pyramid Entrance': (0x640, 0x7c0), + 'Skull Woods First Section Hole (West)': None, + 'Skull Woods First Section Hole (East)': None, + 'Skull Woods First Section Hole (North)': None, + 'Skull Woods Second Section Hole': None, + 'Pyramid Hole': None, + 'Inverted Pyramid Hole': None, + 'Waterfall of Wishing': (0xe80, 0x280), + 'Dam': (0x759, 0xED0), + 'Blinds Hideout': (0x190, 0x6c0), + 'Hyrule Castle Secret Entrance Drop': None, + 'Bonk Fairy (Light)': (0x740, 0xa80), + 'Lake Hylia Fairy': (0xd40, 0x9f0), + 'Light Hype Fairy': (0x940, 0xc80), + 'Desert Fairy': (0x420, 0xe00), + 'Kings Grave': (0x920, 0x520), + 'Tavern North': None, # can't mark this one technically + 'Chicken House': (0x120, 0x880), + 'Aginahs Cave': (0x2e0, 0xd00), + 'Sahasrahlas Hut': (0xcf0, 0x6c0), + 'Cave Shop (Lake Hylia)': (0xbc0, 0xc00), + 'Capacity Upgrade': (0xca0, 0xda0), + 'Kakariko Well Drop': None, + 'Blacksmiths Hut': (0x4a0, 0x880), + 'Bat Cave Drop': None, + 'Sick Kids House': (0x220, 0x880), + 'North Fairy Cave Drop': None, + 'Lost Woods Gamble': (0x240, 0x080), + 'Fortune Teller (Light)': (0x2c0, 0x4c0), + 'Snitch Lady (East)': (0x310, 0x7a0), + 'Snitch Lady (West)': (0x800, 0x7a0), + 'Bush Covered House': (0x2e0, 0x880), + 'Tavern (Front)': (0x270, 0x980), + 'Light World Bomb Hut': (0x070, 0x980), + 'Kakariko Shop': (0x170, 0x980), + 'Lost Woods Hideout Drop': None, + 'Lumberjack Tree Tree': None, + 'Cave 45': (0x440, 0xca0), 'Graveyard Cave': (0x8f0, 0x430), + 'Checkerboard Cave': (0x260, 0xc00), + 'Mini Moldorm Cave': (0xa40, 0xe80), + 'Long Fairy Cave': (0xf60, 0xb00), + 'Good Bee Cave': (0xec0, 0xc00), + '20 Rupee Cave': (0xe80, 0xca0), + '50 Rupee Cave': (0x4d0, 0xed0), + 'Ice Rod Cave': (0xe00, 0xc00), + 'Bonk Rock Cave': (0x5f0, 0x460), + 'Library': (0x270, 0xaa0), + 'Potion Shop': (0xc80, 0x4c0), + 'Sanctuary Grave': None, + 'Hookshot Fairy': (0xd00, 0x180), + 'Pyramid Fairy': (0x740, 0x740), + 'East Dark World Hint': (0xf60, 0xb00), + 'Palace of Darkness Hint': (0xd60, 0x7c0), + 'Dark Lake Hylia Fairy': (0xd40, 0x9f0), + 'Dark Lake Hylia Ledge Fairy': (0xe00, 0xc00), + 'Dark Lake Hylia Ledge Spike Cave': (0xe80, 0xca0), + 'Dark Lake Hylia Ledge Hint': (0xec0, 0xc00), + 'Hype Cave': (0x940, 0xc80), + 'Bonk Fairy (Dark)': (0x740, 0xa80), + 'Brewery': (0x170, 0x980), 'C-Shaped House': (0x310, 0x7a0), 'Chest Game': (0x800, 0x7a0), + 'Dark World Hammer Peg Cave': (0x4c0, 0x940), + 'Red Shield Shop': (0x500, 0x680), + 'Dark Sanctuary Hint': (0x720, 0x4a0), + 'Inverted Dark Sanctuary': (0x720, 0x4a0), + 'Fortune Teller (Dark)': (0x2c0, 0x4c0), + 'Dark World Shop': (0x2e0, 0x880), + 'Dark World Lumberjack Shop': (0x4e0, 0x0d0), + 'Dark World Potion Shop': (0xc80, 0x4c0), + 'Archery Game': (0x2f0, 0xaf0), + 'Mire Shed': (0x060, 0xc90), + 'Dark Desert Hint': (0x2e0, 0xd00), + 'Dark Desert Fairy': (0x1c0, 0xc90), + 'Spike Cave': (0x860, 0x180), + 'Cave Shop (Dark Death Mountain)': (0xd80, 0x180), + 'Dark Death Mountain Fairy': (0x620, 0x2c0), + 'Mimic Cave': (0xc80, 0x180), + 'Big Bomb Shop': (0x8b1, 0xb2d), 'Inverted Links House': (0x8b1, 0xb2d), + 'Dark Lake Hylia Shop': (0xa40, 0xc40), + 'Lumberjack House': (0x4e0, 0x0d0), + 'Lake Hylia Fortune Teller': (0xa40, 0xc40), + 'Kakariko Gamble Game': (0x2f0, 0xaf0)} diff --git a/Fill.py b/Fill.py index 6a5a8fc9..23058688 100644 --- a/Fill.py +++ b/Fill.py @@ -2,174 +2,76 @@ import RaceRandom as random import collections import itertools import logging +import math +from contextlib import suppress -from BaseClasses import CollectionState +from BaseClasses import CollectionState, FillError, LocationType from Items import ItemFactory from Regions import shop_to_location_table, retro_shops +from source.item.FillUtil import filter_locations, classify_major_items, replace_trash_item, vanilla_fallback +from source.item.FillUtil import filter_pot_locations, valid_pot_items -class FillError(RuntimeError): - pass - -def distribute_items_cutoff(world, cutoffrate=0.33): - # get list of locations to fill in - fill_locations = world.get_unfilled_locations() - random.shuffle(fill_locations) - - # get items to distribute - random.shuffle(world.itempool) - itempool = world.itempool - - total_advancement_items = len([item for item in itempool if item.advancement]) - placed_advancement_items = 0 - - progress_done = False - advancement_placed = False - - # sweep once to pick up preplaced items - world.state.sweep_for_events() - - while itempool and fill_locations: - candidate_item_to_place = None - item_to_place = None - for item in itempool: - if advancement_placed or (progress_done and (item.advancement or item.priority)): - item_to_place = item - break - if item.advancement: - candidate_item_to_place = item - if world.unlocks_new_location(item): - item_to_place = item - placed_advancement_items += 1 - break - - if item_to_place is None: - # check if we can reach all locations and that is why we find no new locations to place - if not progress_done and len(world.get_reachable_locations()) == len(world.get_locations()): - progress_done = True - continue - # check if we have now placed all advancement items - if progress_done: - advancement_placed = True - continue - # we might be in a situation where all new locations require multiple items to reach. If that is the case, just place any advancement item we've found and continue trying - if candidate_item_to_place is not None: - item_to_place = candidate_item_to_place - placed_advancement_items += 1 - else: - # we placed all available progress items. Maybe the game can be beaten anyway? - if world.can_beat_game(): - logging.getLogger('').warning('Not all locations reachable. Game beatable anyway.') - progress_done = True - continue - raise FillError('No more progress items left to place.') - - spot_to_fill = None - for location in fill_locations if placed_advancement_items / total_advancement_items < cutoffrate else reversed(fill_locations): - if location.can_fill(world.state, item_to_place): - spot_to_fill = location - break - - if spot_to_fill is None: - # we filled all reachable spots. Maybe the game can be beaten anyway? - if world.can_beat_game(): - logging.getLogger('').warning('Not all items placed. Game beatable anyway.') - break - raise FillError('No more spots to place %s' % item_to_place) - - world.push_item(spot_to_fill, item_to_place, True) - itempool.remove(item_to_place) - fill_locations.remove(spot_to_fill) - unplaced = [item.name for item in itempool] - unfilled = [location.name for location in fill_locations] - if unplaced or unfilled: - logging.warning('Unplaced items: %s - Unfilled Locations: %s', unplaced, unfilled) +def get_dungeon_item_pool(world): + return [item for dungeon in world.dungeons for item in dungeon.all_items] -def distribute_items_staleness(world): - # get list of locations to fill in - fill_locations = world.get_unfilled_locations() - random.shuffle(fill_locations) +def promote_dungeon_items(world): + world.itempool += get_dungeon_item_pool(world) - # get items to distribute - random.shuffle(world.itempool) - itempool = world.itempool + for item in world.get_items(): + if item.smallkey or item.bigkey: + item.advancement = True + elif item.map or item.compass: + item.priority = True + dungeon_tracking(world) - progress_done = False - advancement_placed = False - # sweep once to pick up preplaced items - world.state.sweep_for_events() +def dungeon_tracking(world): + for dungeon in world.dungeons: + layout = world.dungeon_layouts[dungeon.player][dungeon.name] + layout.dungeon_items = len([i for i in dungeon.all_items if i.is_inside_dungeon_item(world)]) + layout.free_items = layout.location_cnt - layout.dungeon_items - while itempool and fill_locations: - candidate_item_to_place = None - item_to_place = None - for item in itempool: - if advancement_placed or (progress_done and (item.advancement or item.priority)): - item_to_place = item - break - if item.advancement: - candidate_item_to_place = item - if world.unlocks_new_location(item): - item_to_place = item - break - if item_to_place is None: - # check if we can reach all locations and that is why we find no new locations to place - if not progress_done and len(world.get_reachable_locations()) == len(world.get_locations()): - progress_done = True - continue - # check if we have now placed all advancement items - if progress_done: - advancement_placed = True - continue - # we might be in a situation where all new locations require multiple items to reach. If that is the case, just place any advancement item we've found and continue trying - if candidate_item_to_place is not None: - item_to_place = candidate_item_to_place - else: - # we placed all available progress items. Maybe the game can be beaten anyway? - if world.can_beat_game(): - logging.getLogger('').warning('Not all locations reachable. Game beatable anyway.') - progress_done = True - continue - raise FillError('No more progress items left to place.') +def fill_dungeons_restrictive(world, shuffled_locations): + dungeon_tracking(world) - spot_to_fill = None - for location in fill_locations: - # increase likelyhood of skipping a location if it has been found stale - if not progress_done and random.randint(0, location.staleness_count) > 2: - continue + # with shuffled dungeon items they are distributed as part of the normal item pool + for item in world.get_items(): + if (item.smallkey and world.keyshuffle[item.player]) or (item.bigkey and world.bigkeyshuffle[item.player]): + item.advancement = True + elif (item.map and world.mapshuffle[item.player]) or (item.compass and world.compassshuffle[item.player]): + item.priority = True - if location.can_fill(world.state, item_to_place): - spot_to_fill = location - break - else: - location.staleness_count += 1 + dungeon_items = [item for item in get_dungeon_item_pool(world) if item.is_inside_dungeon_item(world)] + bigs, smalls, others = [], [], [] + for i in dungeon_items: + (bigs if i.bigkey else smalls if i.smallkey else others).append(i) + unplaced_smalls = list(smalls) + for i in world.itempool: + if i.smallkey and world.keyshuffle[i.player]: + unplaced_smalls.append(i) - # might have skipped too many locations due to potential staleness. Do not check for staleness now to find a candidate - if spot_to_fill is None: - for location in fill_locations: - if location.can_fill(world.state, item_to_place): - spot_to_fill = location - break + def fill(base_state, items, key_pool): + fill_restrictive(world, base_state, shuffled_locations, items, key_pool, True) - if spot_to_fill is None: - # we filled all reachable spots. Maybe the game can be beaten anyway? - if world.can_beat_game(): - logging.getLogger('').warning('Not all items placed. Game beatable anyway.') - break - raise FillError('No more spots to place %s' % item_to_place) + all_state_base = world.get_all_state() + big_state_base = all_state_base.copy() + for x in smalls + others: + big_state_base.collect(x, True) + fill(big_state_base, bigs, unplaced_smalls) + random.shuffle(shuffled_locations) + small_state_base = all_state_base.copy() + for x in others: + small_state_base.collect(x, True) + fill(small_state_base, smalls, unplaced_smalls) + random.shuffle(shuffled_locations) + fill(all_state_base, others, None) - world.push_item(spot_to_fill, item_to_place, True) - itempool.remove(item_to_place) - fill_locations.remove(spot_to_fill) - unplaced = [item.name for item in itempool] - unfilled = [location.name for location in fill_locations] - if unplaced or unfilled: - logging.warning('Unplaced items: %s - Unfilled Locations: %s', unplaced, unfilled) - -def fill_restrictive(world, base_state, locations, itempool, keys_in_itempool = None, single_player_placement = False): +def fill_restrictive(world, base_state, locations, itempool, key_pool=None, single_player_placement=False, + vanilla=False): def sweep_from_pool(): new_state = base_state.copy() for item in itempool: @@ -201,59 +103,96 @@ def fill_restrictive(world, base_state, locations, itempool, keys_in_itempool = spot_to_fill = None - for location in locations: - if item_to_place.smallkey or item_to_place.bigkey: # a better test to see if a key can go there - location.item = item_to_place - test_state = maximum_exploration_state.copy() - test_state.stale[item_to_place.player] = True - else: - test_state = maximum_exploration_state - if (not single_player_placement or location.player == item_to_place.player)\ - and location.can_fill(test_state, item_to_place, perform_access_check)\ - and valid_key_placement(item_to_place, location, itempool if (keys_in_itempool and keys_in_itempool[item_to_place.player]) else world.itempool, world): - spot_to_fill = location + item_locations = filter_locations(item_to_place, locations, world, vanilla) + for location in item_locations: + spot_to_fill = verify_spot_to_fill(location, item_to_place, maximum_exploration_state, + single_player_placement, perform_access_check, key_pool, world) + if spot_to_fill: break - if item_to_place.smallkey or item_to_place.bigkey: - location.item = None - if spot_to_fill is None: - # we filled all reachable spots. Maybe the game can be beaten anyway? - unplaced_items.insert(0, item_to_place) - if world.can_beat_game(): - if world.accessibility[item_to_place.player] != 'none': - logging.getLogger('').warning('Not all items placed. Game beatable anyway. (Could not place %s)' % item_to_place) + if vanilla: + unplaced_items.insert(0, item_to_place) continue - spot_to_fill = last_ditch_placement(item_to_place, locations, world, maximum_exploration_state, - base_state, itempool, keys_in_itempool, single_player_placement) + spot_to_fill = recovery_placement(item_to_place, locations, world, maximum_exploration_state, + base_state, itempool, perform_access_check, item_locations, + key_pool, single_player_placement) if spot_to_fill is None: + # we filled all reachable spots. Maybe the game can be beaten anyway? + unplaced_items.insert(0, item_to_place) + if world.can_beat_game(): + if world.accessibility[item_to_place.player] != 'none': + logging.getLogger('').warning('Not all items placed. Game beatable anyway.' + f' (Could not place {item_to_place})') + continue raise FillError('No more spots to place %s' % item_to_place) world.push_item(spot_to_fill, item_to_place, False) + if item_to_place.smallkey: + with suppress(ValueError): + key_pool.remove(item_to_place) track_outside_keys(item_to_place, spot_to_fill, world) + track_dungeon_items(item_to_place, spot_to_fill, world) locations.remove(spot_to_fill) spot_to_fill.event = True itempool.extend(unplaced_items) -def valid_key_placement(item, location, itempool, world): - if (not item.smallkey and not item.bigkey) or item.player != location.player or world.retro[item.player] or world.logic[item.player] == 'nologic': +def verify_spot_to_fill(location, item_to_place, max_exp_state, single_player_placement, perform_access_check, + key_pool, world): + if item_to_place.smallkey or item_to_place.bigkey: # a better test to see if a key can go there + location.item = item_to_place + test_state = max_exp_state.copy() + test_state.stale[item_to_place.player] = True + else: + test_state = max_exp_state + if not single_player_placement or location.player == item_to_place.player: + if location.can_fill(test_state, item_to_place, perform_access_check): + if valid_key_placement(item_to_place, location, key_pool, world): + if item_to_place.crystal or valid_dungeon_placement(item_to_place, location, world): + return location + if item_to_place.smallkey or item_to_place.bigkey: + location.item = None + return None + + +def valid_key_placement(item, location, key_pool, world): + if not valid_reserved_placement(item, location, world): + return False + if ((not item.smallkey and not item.bigkey) or item.player != location.player + or world.retro[item.player] or world.logic[item.player] == 'nologic'): return True dungeon = location.parent_region.dungeon if dungeon: if dungeon.name not in item.name and (dungeon.name != 'Hyrule Castle' or 'Escape' not in item.name): return True key_logic = world.key_logic[item.player][dungeon.name] - unplaced_keys = len([x for x in itempool if x.name == key_logic.small_key_name and x.player == item.player]) + unplaced_keys = len([x for x in key_pool if x.name == key_logic.small_key_name and x.player == item.player]) prize_loc = None if key_logic.prize_location: prize_loc = world.get_location(key_logic.prize_location, location.player) cr_count = world.crystals_needed_for_gt[location.player] - return key_logic.check_placement(unplaced_keys, location if item.bigkey else None, prize_loc, cr_count) + wild_keys = world.keyshuffle[item.player] + return key_logic.check_placement(unplaced_keys, wild_keys, location if item.bigkey else None, prize_loc, cr_count) else: - inside_dungeon_item = ((item.smallkey and not world.keyshuffle[item.player]) - or (item.bigkey and not world.bigkeyshuffle[item.player])) - return not inside_dungeon_item + return not item.is_inside_dungeon_item(world) + + +def valid_reserved_placement(item, location, world): + if item.player == location.player and item.is_inside_dungeon_item(world): + return location.name not in world.item_pool_config.reserved_locations[location.player] + return True + + +def valid_dungeon_placement(item, location, world): + if location.parent_region.dungeon: + layout = world.dungeon_layouts[location.player][location.parent_region.dungeon.name] + if not is_dungeon_item(item, world) or item.player != location.player: + return layout.free_items > 0 + else: + # the second half probably doesn't matter much - should always return true + return item.dungeon == location.parent_region.dungeon.name and layout.dungeon_items > 0 + return not is_dungeon_item(item, world) def track_outside_keys(item, location, world): @@ -267,8 +206,74 @@ def track_outside_keys(item, location, world): world.key_logic[item.player][item_dungeon].outside_keys += 1 +def track_dungeon_items(item, location, world): + if location.parent_region.dungeon and not item.crystal: + layout = world.dungeon_layouts[location.player][location.parent_region.dungeon.name] + if is_dungeon_item(item, world) and item.player == location.player: + layout.dungeon_items -= 1 + else: + layout.free_items -= 1 + + +def is_dungeon_item(item, world): + return ((item.smallkey and not world.keyshuffle[item.player]) + or (item.bigkey and not world.bigkeyshuffle[item.player]) + or (item.compass and not world.compassshuffle[item.player]) + or (item.map and not world.mapshuffle[item.player])) + + +def recovery_placement(item_to_place, locations, world, state, base_state, itempool, perform_access_check, attempted, + key_pool=None, single_player_placement=False): + logging.getLogger('').debug(f'Could not place {item_to_place} attempting recovery') + if world.algorithm in ['balanced', 'equitable']: + return last_ditch_placement(item_to_place, locations, world, state, base_state, itempool, key_pool, + single_player_placement) + elif world.algorithm == 'vanilla_fill': + if item_to_place.type == 'Crystal': + possible_swaps = [x for x in state.locations_checked if x.item.type == 'Crystal'] + return try_possible_swaps(possible_swaps, item_to_place, locations, world, base_state, itempool, + key_pool, single_player_placement) + else: + i, config = 0, world.item_pool_config + tried = set(attempted) + if not item_to_place.is_inside_dungeon_item(world): + while i < len(config.location_groups[item_to_place.player]): + fallback_locations = config.location_groups[item_to_place.player][i].locations + other_locs = [x for x in locations if x.name in fallback_locations] + for location in other_locs: + spot_to_fill = verify_spot_to_fill(location, item_to_place, state, single_player_placement, + perform_access_check, key_pool, world) + if spot_to_fill: + return spot_to_fill + i += 1 + tried.update(other_locs) + else: + other_locations = vanilla_fallback(item_to_place, locations, world) + for location in other_locations: + spot_to_fill = verify_spot_to_fill(location, item_to_place, state, single_player_placement, + perform_access_check, key_pool, world) + if spot_to_fill: + return spot_to_fill + tried.update(other_locations) + other_locations = [x for x in locations if x not in tried] + for location in other_locations: + spot_to_fill = verify_spot_to_fill(location, item_to_place, state, single_player_placement, + perform_access_check, key_pool, world) + if spot_to_fill: + return spot_to_fill + return None + else: + other_locations = [x for x in locations if x not in attempted] + for location in other_locations: + spot_to_fill = verify_spot_to_fill(location, item_to_place, state, single_player_placement, + perform_access_check, key_pool, world) + if spot_to_fill: + return spot_to_fill + return None + + def last_ditch_placement(item_to_place, locations, world, state, base_state, itempool, - keys_in_itempool=None, single_player_placement=False): + key_pool=None, single_player_placement=False): def location_preference(loc): if not loc.item.advancement: return 1 @@ -285,17 +290,22 @@ def last_ditch_placement(item_to_place, locations, world, state, base_state, ite possible_swaps = [x for x in state.locations_checked if x.item.type not in ['Event', 'Crystal'] and not x.forced_item] swap_locations = sorted(possible_swaps, key=location_preference) + return try_possible_swaps(swap_locations, item_to_place, locations, world, base_state, itempool, + key_pool, single_player_placement) + +def try_possible_swaps(swap_locations, item_to_place, locations, world, base_state, itempool, + key_pool=None, single_player_placement=False): for location in swap_locations: old_item = location.item new_pool = list(itempool) + [old_item] new_spot = find_spot_for_item(item_to_place, [location], world, base_state, new_pool, - keys_in_itempool, single_player_placement) + key_pool, single_player_placement) if new_spot: restore_item = new_spot.item new_spot.item = item_to_place swap_spot = find_spot_for_item(old_item, locations, world, base_state, itempool, - keys_in_itempool, single_player_placement) + key_pool, single_player_placement) if swap_spot: logging.getLogger('').debug(f'Swapping {old_item} for {item_to_place}') world.push_item(swap_spot, old_item, False) @@ -348,19 +358,54 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None random.shuffle(fill_locations) # get items to distribute + classify_major_items(world) + # handle pot shuffle + pots_used = False + pot_item_pool = collections.defaultdict(list) + for item in world.itempool: + if item.name in ['Chicken', 'Big Magic']: # can only fill these in that players world + pot_item_pool[item.player].append(item) + for player, pot_pool in pot_item_pool.items(): + if pot_pool: + for pot_item in pot_pool: + world.itempool.remove(pot_item) + pot_locations = [location for location in fill_locations + if location.type == LocationType.Pot and location.player == player] + pot_locations = filter_pot_locations(pot_locations, world) + fast_fill_helper(world, pot_pool, pot_locations) + pots_used = True + if pots_used: + fill_locations = world.get_unfilled_locations() + random.shuffle(fill_locations) + random.shuffle(world.itempool) progitempool = [item for item in world.itempool if item.advancement] prioitempool = [item for item in world.itempool if not item.advancement and item.priority] restitempool = [item for item in world.itempool if not item.advancement and not item.priority] + gftower_trash &= world.algorithm in ['balanced', 'equitable', 'dungeon_only'] + # dungeon only may fill up the dungeon... and push items out into the overworld + # fill in gtower locations with trash first for player in range(1, world.players + 1): - if not gftower_trash or not world.ganonstower_vanilla[player] or world.doorShuffle[player] == 'crossed' or world.logic[player] in ['owglitches', 'nologic']: + if (not gftower_trash or not world.ganonstower_vanilla[player] + or world.logic[player] in ['owglitches', 'nologic']): continue + gt_count, total_count = calc_trash_locations(world, player) + scale_factor = .75 * (world.crystals_needed_for_gt[player] / 7) + if world.algorithm == 'dungeon_only': + reserved_space = sum(1 for i in progitempool+prioitempool if i.player == player) + max_trash = max(0, min(gt_count, total_count - reserved_space)) + else: + max_trash = gt_count + scaled_trash = math.floor(max_trash * scale_factor) + if world.goal[player] in ['triforcehunt', 'trinity']: + gftower_trash_count = random.randint(scaled_trash, max_trash) + else: + gftower_trash_count = random.randint(0, scaled_trash) - gftower_trash_count = (random.randint(15, 50) if world.goal[player] == 'triforcehunt' else random.randint(0, 15)) - - gtower_locations = [location for location in fill_locations if 'Ganons Tower' in location.name and location.player == player] + gtower_locations = [location for location in fill_locations if location.parent_region.dungeon + and location.parent_region.dungeon.name == 'Ganons Tower' and location.player == player] random.shuffle(gtower_locations) trashcnt = 0 while gtower_locations and restitempool and trashcnt < gftower_trash_count: @@ -376,20 +421,101 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None # Make sure the escape small key is placed first in standard with key shuffle to prevent running out of spots # todo: crossed progitempool.sort(key=lambda item: 1 if item.name == 'Small Key (Escape)' and world.keyshuffle[item.player] and world.mode[item.player] == 'standard' else 0) + key_pool = [x for x in progitempool if x.smallkey] - fill_restrictive(world, world.state, fill_locations, progitempool, - keys_in_itempool={player: world.keyshuffle[player] for player in range(1, world.players + 1)}) - + # sort maps and compasses to the back -- this may not be viable in equitable & ambrosia + progitempool.sort(key=lambda item: 0 if item.map or item.compass else 1) + if world.algorithm == 'vanilla_fill': + fill_restrictive(world, world.state, fill_locations, progitempool, key_pool, vanilla=True) + fill_restrictive(world, world.state, fill_locations, progitempool, key_pool) random.shuffle(fill_locations) + if world.algorithm == 'balanced': + fast_fill(world, prioitempool, fill_locations) + elif world.algorithm == 'vanilla_fill': + fast_vanilla_fill(world, prioitempool, fill_locations) + elif world.algorithm in ['major_only', 'dungeon_only', 'district']: + filtered_fill(world, prioitempool, fill_locations) + else: # just need to ensure dungeon items still get placed in dungeons + fast_equitable_fill(world, prioitempool, fill_locations) + # placeholder work + if world.algorithm == 'district': + random.shuffle(fill_locations) + placeholder_items = [item for item in world.itempool if item.name == 'Rupee (1)'] + num_ph_items = len(placeholder_items) + if num_ph_items > 0: + placeholder_locations = filter_locations('Placeholder', fill_locations, world) + num_ph_locations = len(placeholder_locations) + if num_ph_items < num_ph_locations < len(fill_locations): + for _ in range(num_ph_locations - num_ph_items): + placeholder_items.append(replace_trash_item(restitempool, 'Rupee (1)')) + assert len(placeholder_items) == len(placeholder_locations) + for i in placeholder_items: + restitempool.remove(i) + for l in placeholder_locations: + fill_locations.remove(l) + filtered_fill(world, placeholder_items, placeholder_locations) - fast_fill(world, prioitempool, fill_locations) - - fast_fill(world, restitempool, fill_locations) + if world.players > 1: + fast_fill_pot_for_multiworld(world, restitempool, fill_locations) + if world.algorithm == 'vanilla_fill': + fast_vanilla_fill(world, restitempool, fill_locations) + else: + fast_fill(world, restitempool, fill_locations) unplaced = [item.name for item in prioitempool + restitempool] unfilled = [location.name for location in fill_locations] if unplaced or unfilled: logging.warning('Unplaced items: %s - Unfilled Locations: %s', unplaced, unfilled) + ensure_good_pots(world) + + +def calc_trash_locations(world, player): + total_count, gt_count = 0, 0 + for loc in world.get_locations(): + if (loc.player == player and loc.item is None + and (loc.type not in {LocationType.Pot, LocationType.Drop, LocationType.Normal} or not loc.forced_item) + and (loc.type != LocationType.Shop or world.shopsanity[player]) + and loc.parent_region.dungeon): + total_count += 1 + if loc.parent_region.dungeon.name == 'Ganons Tower': + gt_count += 1 + return gt_count, total_count + + +def ensure_good_pots(world, write_skips=False): + for loc in world.get_locations(): + # convert Arrows 5 and Nothing when necessary + if (loc.item.name in {'Arrows (5)', 'Nothing'} + and (loc.type != LocationType.Pot or loc.item.player != loc.player)): + loc.item = ItemFactory(invalid_location_replacement[loc.item.name], loc.item.player) + # can be placed here by multiworld balancing or shop balancing + # change it to something normal for the player it got swapped to + elif (loc.item.name in {'Chicken', 'Big Magic'} + and (loc.type != LocationType.Pot or loc.item.player != loc.player)): + if loc.type == LocationType.Pot: + loc.item.player = loc.player + else: + loc.item = ItemFactory(invalid_location_replacement[loc.item.name], loc.player) + # do the arrow retro check + if world.retro[loc.item.player] and loc.item.name in {'Arrows (5)', 'Arrows (10)'}: + loc.item = ItemFactory('Rupees (5)', loc.item.player) + # don't write out all pots to spoiler + if write_skips: + if loc.type == LocationType.Pot and loc.item.name in valid_pot_items: + loc.skip = True + + +invalid_location_replacement = {'Arrows (5)': 'Arrows (10)', 'Nothing': 'Rupees (5)', + 'Chicken': 'Rupees (5)', 'Big Magic': 'Small Magic'} + + +def fast_fill_helper(world, item_pool, fill_locations): + if world.algorithm == 'vanilla_fill': + fast_vanilla_fill(world, item_pool, fill_locations) + else: + fast_fill(world, item_pool, fill_locations) + # todo: other fast fill methods? + def fast_fill(world, item_pool, fill_locations): while item_pool and fill_locations: @@ -398,77 +524,81 @@ def fast_fill(world, item_pool, fill_locations): world.push_item(spot_to_fill, item_to_place, False) -def flood_items(world): - # get items to distribute - random.shuffle(world.itempool) - itempool = world.itempool - progress_done = False +def fast_fill_pot_for_multiworld(world, item_pool, fill_locations): + pot_item_pool = collections.defaultdict(list) + pot_fill_locations = collections.defaultdict(list) + for item in item_pool: + if item.name in valid_pot_items: + pot_item_pool[item.player].append(item) + for loc in fill_locations: + if loc.type == LocationType.Pot: + pot_fill_locations[loc.player].append(loc) + for player in range(1, world.players+1): + flex = 256 - world.pot_contents[player].multiworld_count + fill_count = len(pot_fill_locations[player]) - flex + if fill_count > 0: + fill_spots = random.sample(pot_fill_locations[player], fill_count) + fill_items = random.sample(pot_item_pool[player], fill_count) + for x in fill_items: + item_pool.remove(x) + for x in fill_spots: + fill_locations.remove(x) + fast_fill(world, fill_items, fill_spots) + + +def filtered_fill(world, item_pool, fill_locations): + while item_pool and fill_locations: + item_to_place = item_pool.pop() + item_locations = filter_locations(item_to_place, fill_locations, world) + spot_to_fill = next(iter(item_locations)) + fill_locations.remove(spot_to_fill) + world.push_item(spot_to_fill, item_to_place, False) # sweep once to pick up preplaced items world.state.sweep_for_events() - # fill world from top of itempool while we can - while not progress_done: - location_list = world.get_unfilled_locations() - random.shuffle(location_list) - spot_to_fill = None - for location in location_list: - if location.can_fill(world.state, itempool[0]): - spot_to_fill = location - break - if spot_to_fill: - item = itempool.pop(0) - world.push_item(spot_to_fill, item, True) - continue +def fast_vanilla_fill(world, item_pool, fill_locations): + next_item_pool = [] + while item_pool and fill_locations: + item_to_place = item_pool.pop() + locations = filter_locations(item_to_place, fill_locations, world, vanilla_skip=True) + if len(locations): + spot_to_fill = locations.pop() + fill_locations.remove(spot_to_fill) + world.push_item(spot_to_fill, item_to_place, False) + else: + next_item_pool.append(item_to_place) + while next_item_pool and fill_locations: + item_to_place = next_item_pool.pop() + spot_to_fill = next(iter(filter_locations(item_to_place, fill_locations, world))) + fill_locations.remove(spot_to_fill) + world.push_item(spot_to_fill, item_to_place, False) - # ran out of spots, check if we need to step in and correct things - if len(world.get_reachable_locations()) == len(world.get_locations()): - progress_done = True - continue - # need to place a progress item instead of an already placed item, find candidate - item_to_place = None - candidate_item_to_place = None - for item in itempool: - if item.advancement: - candidate_item_to_place = item - if world.unlocks_new_location(item): - item_to_place = item - break +def filtered_equitable_fill(world, item_pool, fill_locations): + while item_pool and fill_locations: + item_to_place = item_pool.pop() + item_locations = filter_locations(item_to_place, fill_locations, world) + spot_to_fill = next(l for l in item_locations if valid_dungeon_placement(item_to_place, l, world)) + fill_locations.remove(spot_to_fill) + world.push_item(spot_to_fill, item_to_place, False) + track_dungeon_items(item_to_place, spot_to_fill, world) - # we might be in a situation where all new locations require multiple items to reach. If that is the case, just place any advancement item we've found and continue trying - if item_to_place is None: - if candidate_item_to_place is not None: - item_to_place = candidate_item_to_place - else: - raise FillError('No more progress items left to place.') - # find item to replace with progress item - location_list = world.get_reachable_locations() - random.shuffle(location_list) - for location in location_list: - if location.item is not None and not location.item.advancement and not location.item.priority and not location.item.smallkey and not location.item.bigkey: - # safe to replace - replace_item = location.item - replace_item.location = None - itempool.append(replace_item) - world.push_item(location, item_to_place, True) - itempool.remove(item_to_place) - break +def fast_equitable_fill(world, item_pool, fill_locations): + while item_pool and fill_locations: + item_to_place = item_pool.pop() + spot_to_fill = next(l for l in fill_locations if valid_dungeon_placement(item_to_place, l, world)) + fill_locations.remove(spot_to_fill) + world.push_item(spot_to_fill, item_to_place, False) + track_dungeon_items(item_to_place, spot_to_fill, world) def lock_shop_locations(world, player): for shop, loc_names in shop_to_location_table.items(): for loc in loc_names: - world.get_location(loc, player).event = True world.get_location(loc, player).locked = True - # I don't believe these locations exist in non-shopsanity - # if world.retro[player]: - # for shop, loc_names in retro_shops.items(): - # for loc in loc_names: - # world.get_location(loc, player).event = True - # world.get_location(loc, player).locked = True def sell_potions(world, player): @@ -479,7 +609,7 @@ def sell_potions(world, player): loc_choices += [world.get_location(loc, player) for loc in shop_to_location_table[shop.region.name]] locations = [loc for loc in loc_choices if not loc.item] for potion in ['Green Potion', 'Blue Potion', 'Red Potion']: - location = random.choice(locations) + location = random.choice(filter_locations(ItemFactory(potion, player), locations, world, potion=True)) locations.remove(location) p_item = next(item for item in world.itempool if item.name == potion and item.player == player) world.push_item(location, p_item, collect=False) @@ -490,7 +620,9 @@ def sell_keys(world, player): # exclude the old man or take any caves because free keys are too good shop_names = {shop.region.name: shop for shop in world.shops[player] if shop.region.name in shop_to_location_table} choices = [(world.get_location(loc, player), shop) for shop in shop_names for loc in shop_to_location_table[shop]] - locations = [(loc, shop) for loc, shop in choices if not loc.item] + locations = [l for l, shop in choices] + locations = filter_locations(ItemFactory('Small Key (Universal)', player), locations, world) + locations = [(loc, shop) for loc, shop in choices if not loc.item and loc in locations] location, shop = random.choice(locations) universal_key = next(i for i in world.itempool if i.name == 'Small Key (Universal)' and i.player == player) world.push_item(location, universal_key, collect=False) diff --git a/Gui.py b/Gui.py index 55defa7a..3c640979 100755 --- a/Gui.py +++ b/Gui.py @@ -1,3 +1,7 @@ +if __name__ == '__main__': + from source.meta.check_requirements import check_requirements + check_requirements() + import json import os import sys @@ -104,7 +108,7 @@ def guiMain(args=None): self.pages["startinventory"] = ttk.Frame(self.notebook) self.pages["custom"] = ttk.Frame(self.notebook) self.notebook.add(self.pages["randomizer"], text='Randomize') - self.notebook.add(self.pages["adjust"], text='Adjust') + self.notebook.add(self.pages["adjust"], text='Adjust/Patch') self.notebook.add(self.pages["startinventory"], text='Starting Inventory') self.notebook.add(self.pages["custom"], text='Custom Item Pool') self.notebook.pack() diff --git a/InitialSram.py b/InitialSram.py new file mode 100644 index 00000000..772e1d46 --- /dev/null +++ b/InitialSram.py @@ -0,0 +1,253 @@ +from dataclasses import dataclass, field +from typing import List + +from BaseClasses import CollectionState +from Utils import count_set_bits + +SRAM_SIZE = 0x500 +ROOM_DATA = 0x000 +OVERWORLD_DATA = 0x280 + +def _new_default_sram(): + sram_buf = [0x00] * 0x500 + sram_buf[ROOM_DATA+0x20D] = 0xF0 + sram_buf[ROOM_DATA+0x20F] = 0xF0 + sram_buf[0x379] = 0x68 + sram_buf[0x401] = 0xFF + sram_buf[0x402] = 0xFF + return sram_buf + +@dataclass +class InitialSram: + _initial_sram_bytes: List[int] = field(default_factory=_new_default_sram) + + def _set_value(self, idx: int, val:int): + if idx > SRAM_SIZE: + raise IndexError('SRAM index out of bounds: {idx}') + if not (-1 < val < 256): + raise ValueError('SRAM value must be between 0 and 255: {val}') + self._initial_sram_bytes[idx] = val + + def _or_value(self, idx: int, val:int): + if idx > SRAM_SIZE: + raise IndexError('SRAM index out of bounds: {idx}') + if not (-1 < val < 256): + raise ValueError('SRAM value must be between 0 and 255: {val}') + self._initial_sram_bytes[idx] |= val + + def pre_open_aga_curtains(self): + self._or_value(ROOM_DATA+0x61, 0x80) + + def pre_open_skullwoods_curtains(self): + self._or_value(ROOM_DATA+0x93, 0x80) + + def pre_open_lumberjack(self): + self._or_value(OVERWORLD_DATA+0x02, 0x20) + + def pre_open_castle_gate(self): + self._or_value(OVERWORLD_DATA+0x1B, 0x20) + + def pre_open_ganons_tower(self): + self._or_value(OVERWORLD_DATA+0x43, 0x20) + + def pre_open_pyramid_hole(self): + self._or_value(OVERWORLD_DATA+0x5B, 0x20) + + def set_starting_equipment(self, world: object, player: int): + equip = [0] * (0x340 + 0x4F) + equip[0x36C] = 0x18 + equip[0x36D] = 0x18 + equip[0x379] = 0x68 + if world.bombbag[player]: + starting_max_bombs = 0 + else: + starting_max_bombs = 10 + starting_max_arrows = 30 + starting_bomb_cap_upgrades = 0 + starting_arrow_cap_upgrades = 0 + starting_bombs = 0 + starting_arrows = 0 + + startingstate = CollectionState(world) + + if startingstate.has('Bow', player): + equip[0x340] = 3 if startingstate.has('Silver Arrows', player) else 1 + equip[0x38E] |= 0x20 # progressive flag to get the correct hint in all cases + if not world.retro[player]: + equip[0x38E] |= 0x80 + if startingstate.has('Silver Arrows', player): + equip[0x38E] |= 0x40 + + if startingstate.has('Titans Mitts', player): + equip[0x354] = 2 + elif startingstate.has('Power Glove', player): + equip[0x354] = 1 + + if startingstate.has('Golden Sword', player): + equip[0x359] = 4 + elif startingstate.has('Tempered Sword', player): + equip[0x359] = 3 + elif startingstate.has('Master Sword', player): + equip[0x359] = 2 + elif startingstate.has('Fighter Sword', player): + equip[0x359] = 1 + + if startingstate.has('Mirror Shield', player): + equip[0x35A] = 3 + elif startingstate.has('Red Shield', player): + equip[0x35A] = 2 + elif startingstate.has('Blue Shield', player): + equip[0x35A] = 1 + + if startingstate.has('Red Mail', player): + equip[0x35B] = 2 + elif startingstate.has('Blue Mail', player): + equip[0x35B] = 1 + + if startingstate.has('Magic Upgrade (1/4)', player): + equip[0x37B] = 2 + equip[0x36E] = 0x80 + elif startingstate.has('Magic Upgrade (1/2)', player): + equip[0x37B] = 1 + equip[0x36E] = 0x80 + + for item in world.precollected_items: + if item.player != player: + continue + + if item.name in ['Bow', 'Silver Arrows', 'Progressive Bow', 'Progressive Bow (Alt)', + 'Titans Mitts', 'Power Glove', 'Progressive Glove', + 'Golden Sword', 'Tempered Sword', 'Master Sword', 'Fighter Sword', 'Progressive Sword', + 'Mirror Shield', 'Red Shield', 'Blue Shield', 'Progressive Shield', + 'Red Mail', 'Blue Mail', 'Progressive Armor', + 'Magic Upgrade (1/4)', 'Magic Upgrade (1/2)']: + continue + + set_table = {'Book of Mudora': (0x34E, 1), 'Hammer': (0x34B, 1), 'Bug Catching Net': (0x34D, 1), 'Hookshot': (0x342, 1), 'Magic Mirror': (0x353, 2), + 'Cape': (0x352, 1), 'Lamp': (0x34A, 1), 'Moon Pearl': (0x357, 1), 'Cane of Somaria': (0x350, 1), 'Cane of Byrna': (0x351, 1), + 'Fire Rod': (0x345, 1), 'Ice Rod': (0x346, 1), 'Bombos': (0x347, 1), 'Ether': (0x348, 1), 'Quake': (0x349, 1)} + or_table = {'Green Pendant': (0x374, 0x04), 'Red Pendant': (0x374, 0x01), 'Blue Pendant': (0x374, 0x02), + 'Crystal 1': (0x37A, 0x02), 'Crystal 2': (0x37A, 0x10), 'Crystal 3': (0x37A, 0x40), 'Crystal 4': (0x37A, 0x20), + 'Crystal 5': (0x37A, 0x04), 'Crystal 6': (0x37A, 0x01), 'Crystal 7': (0x37A, 0x08), + 'Big Key (Eastern Palace)': (0x367, 0x20), 'Compass (Eastern Palace)': (0x365, 0x20), 'Map (Eastern Palace)': (0x369, 0x20), + 'Big Key (Desert Palace)': (0x367, 0x10), 'Compass (Desert Palace)': (0x365, 0x10), 'Map (Desert Palace)': (0x369, 0x10), + 'Big Key (Tower of Hera)': (0x366, 0x20), 'Compass (Tower of Hera)': (0x364, 0x20), 'Map (Tower of Hera)': (0x368, 0x20), + 'Big Key (Escape)': (0x367, 0xC0), 'Compass (Escape)': (0x365, 0xC0), 'Map (Escape)': (0x369, 0xC0), + 'Big Key (Agahnims Tower)': (0x367, 0x08), 'Compass (Agahnims Tower)': (0x365, 0x08), 'Map (Agahnims Tower)': (0x369, 0x08), + 'Big Key (Palace of Darkness)': (0x367, 0x02), 'Compass (Palace of Darkness)': (0x365, 0x02), 'Map (Palace of Darkness)': (0x369, 0x02), + 'Big Key (Thieves Town)': (0x366, 0x10), 'Compass (Thieves Town)': (0x364, 0x10), 'Map (Thieves Town)': (0x368, 0x10), + 'Big Key (Skull Woods)': (0x366, 0x80), 'Compass (Skull Woods)': (0x364, 0x80), 'Map (Skull Woods)': (0x368, 0x80), + 'Big Key (Swamp Palace)': (0x367, 0x04), 'Compass (Swamp Palace)': (0x365, 0x04), 'Map (Swamp Palace)': (0x369, 0x04), + 'Big Key (Ice Palace)': (0x366, 0x40), 'Compass (Ice Palace)': (0x364, 0x40), 'Map (Ice Palace)': (0x368, 0x40), + 'Big Key (Misery Mire)': (0x367, 0x01), 'Compass (Misery Mire)': (0x365, 0x01), 'Map (Misery Mire)': (0x369, 0x01), + 'Big Key (Turtle Rock)': (0x366, 0x08), 'Compass (Turtle Rock)': (0x364, 0x08), 'Map (Turtle Rock)': (0x368, 0x08), + 'Big Key (Ganons Tower)': (0x366, 0x04), 'Compass (Ganons Tower)': (0x364, 0x04), 'Map (Ganons Tower)': (0x368, 0x04)} + set_or_table = {'Flippers': (0x356, 1, 0x379, 0x02),'Pegasus Boots': (0x355, 1, 0x379, 0x04), + 'Shovel': (0x34C, 1, 0x38C, 0x04), 'Ocarina': (0x34C, 3, 0x38C, 0x01), + 'Mushroom': (0x344, 1, 0x38C, 0x20 | 0x08), 'Magic Powder': (0x344, 2, 0x38C, 0x10), + 'Blue Boomerang': (0x341, 1, 0x38C, 0x80), 'Red Boomerang': (0x341, 2, 0x38C, 0x40)} + keys = {'Small Key (Eastern Palace)': [0x37E], 'Small Key (Desert Palace)': [0x37F], + 'Small Key (Tower of Hera)': [0x386], + 'Small Key (Agahnims Tower)': [0x380], 'Small Key (Palace of Darkness)': [0x382], + 'Small Key (Thieves Town)': [0x387], + 'Small Key (Skull Woods)': [0x384], 'Small Key (Swamp Palace)': [0x381], + 'Small Key (Ice Palace)': [0x385], + 'Small Key (Misery Mire)': [0x383], 'Small Key (Turtle Rock)': [0x388], + 'Small Key (Ganons Tower)': [0x389], + 'Small Key (Universal)': [0x38B], 'Small Key (Escape)': [0x37C, 0x37D]} + bottles = {'Bottle': 2, 'Bottle (Red Potion)': 3, 'Bottle (Green Potion)': 4, 'Bottle (Blue Potion)': 5, + 'Bottle (Fairy)': 6, 'Bottle (Bee)': 7, 'Bottle (Good Bee)': 8} + rupees = {'Rupee (1)': 1, 'Rupees (5)': 5, 'Rupees (20)': 20, 'Rupees (50)': 50, 'Rupees (100)': 100, 'Rupees (300)': 300} + bomb_caps = {'Bomb Upgrade (+5)': 5, 'Bomb Upgrade (+10)': 10} + arrow_caps = {'Arrow Upgrade (+5)': 5, 'Arrow Upgrade (+10)': 10} + bombs = {'Single Bomb': 1, 'Bombs (3)': 3, 'Bombs (10)': 10} + arrows = {'Single Arrow': 1, 'Arrows (10)': 10} + + if item.name in set_table: + equip[set_table[item.name][0]] = set_table[item.name][1] + elif item.name in or_table: + equip[or_table[item.name][0]] |= or_table[item.name][1] + elif item.name in set_or_table: + equip[set_or_table[item.name][0]] = set_or_table[item.name][1] + equip[set_or_table[item.name][2]] |= set_or_table[item.name][3] + elif item.name in keys: + for address in keys[item.name]: + equip[address] = min(equip[address] + 1, 99) + elif item.name in bottles: + if equip[0x34F] < world.difficulty_requirements[player].progressive_bottle_limit: + equip[0x35C + equip[0x34F]] = bottles[item.name] + equip[0x34F] += 1 + elif item.name in rupees: + equip[0x360:0x362] = list(min(equip[0x360] + (equip[0x361] << 8) + rupees[item.name], 9999).to_bytes(2, byteorder='little', signed=False)) + equip[0x362:0x364] = list(min(equip[0x362] + (equip[0x363] << 8) + rupees[item.name], 9999).to_bytes(2, byteorder='little', signed=False)) + elif item.name in bomb_caps: + starting_bomb_cap_upgrades += bomb_caps[item.name] + elif item.name in arrow_caps: + starting_arrow_cap_upgrades += arrow_caps[item.name] + elif item.name in bombs: + starting_bombs += bombs[item.name] + elif item.name in arrows: + if world.retro[player]: + equip[0x38E] |= 0x80 + starting_arrows = 1 + else: + starting_arrows += arrows[item.name] + elif item.name in ['Piece of Heart', 'Boss Heart Container', 'Sanctuary Heart Container']: + if item.name == 'Piece of Heart': + equip[0x36B] = (equip[0x36B] + 1) % 4 + if item.name != 'Piece of Heart' or equip[0x36B] == 0: + equip[0x36C] = min(equip[0x36C] + 0x08, 0xA0) + equip[0x36D] = min(equip[0x36D] + 0x08, 0xA0) + else: + raise RuntimeError(f'Unsupported item in starting equipment: {item.name}') + + equip[0x370] = min(starting_bomb_cap_upgrades, 50) + equip[0x371] = min(starting_arrow_cap_upgrades, 70) + equip[0x343] = min(starting_bombs, (equip[0x370] + starting_max_bombs)) + equip[0x377] = min(starting_arrows, (equip[0x371] + starting_max_arrows)) + + # Assertion and copy equip to initial_sram_bytes + assert equip[:0x340] == [0] * 0x340 + self._initial_sram_bytes[0x340:0x38F] = equip[0x340:0x38F] + + # Set counters and highest equipment values + self._initial_sram_bytes[0x471] = count_set_bits(self._initial_sram_bytes[0x37A]) + self._initial_sram_bytes[0x429] = count_set_bits(self._initial_sram_bytes[0x374]) + self._initial_sram_bytes[0x417] = self._initial_sram_bytes[0x359] + self._initial_sram_bytes[0x422] = self._initial_sram_bytes[0x35A] + self._initial_sram_bytes[0x46E] = self._initial_sram_bytes[0x35B] + + if world.swords[player] == "swordless": + self._initial_sram_bytes[0x359] = 0xFF + self._initial_sram_bytes[0x417] = 0x00 + + def set_starting_rupees(self, rupees: int): + if not (-1 < rupees < 10000): + raise ValueError("Starting rupees must be between 0 and 9999") + self._initial_sram_bytes[0x362] = self._initial_sram_bytes[0x360] = rupees & 0xFF + self._initial_sram_bytes[0x363] = self._initial_sram_bytes[0x361] = rupees >> 8 + + def set_progress_indicator(self, indicator: int): + self._set_value(0x3C5, indicator) + + def set_progress_flags(self, flags: int): + self._set_value(0x3C6, flags) + + def set_starting_entrance(self, entrance: int): + self._set_value(0x3C8, entrance) + + def set_starting_timer(self, seconds: int): + timer = (seconds * 60).to_bytes(4, "little") + self._initial_sram_bytes[0x454] = timer[0] + self._initial_sram_bytes[0x455] = timer[1] + self._initial_sram_bytes[0x456] = timer[2] + self._initial_sram_bytes[0x457] = timer[3] + + def set_swordless_curtains(self): + self._or_value(ROOM_DATA+0x61, 0x80) + self._or_value(ROOM_DATA+0x93, 0x80) + + def get_initial_sram(self): + assert len(self._initial_sram_bytes) == SRAM_SIZE + + return self._initial_sram_bytes[:] diff --git a/InvertedRegions.py b/InvertedRegions.py index 589f6f87..175b2b76 100644 --- a/InvertedRegions.py +++ b/InvertedRegions.py @@ -2,7 +2,6 @@ import collections from BaseClasses import RegionType from Regions import create_lw_region, create_dw_region, create_cave_region, create_dungeon_region, create_menu_region -# todo: shopsanity locations def create_inverted_regions(world, player): world.regions += [ @@ -25,11 +24,10 @@ def create_inverted_regions(world, player): create_lw_region(player, 'Hyrule Castle Secret Entrance Area', None, ['Hyrule Castle Secret Entrance Stairs', 'Secret Passage Inner Bushes']), create_lw_region(player, 'Death Mountain Entrance', None, ['Old Man Cave (West)', 'Death Mountain Entrance Drop', 'Bumper Cave Entrance Mirror Spot']), create_lw_region(player, 'Lake Hylia Central Island', None, ['Capacity Upgrade', 'Lake Hylia Central Island Mirror Spot']), - create_cave_region(player, 'Blinds Hideout', 'a bounty of five items', ["Blind\'s Hideout - Top", - "Blind\'s Hideout - Left", - "Blind\'s Hideout - Right", - "Blind\'s Hideout - Far Left", - "Blind\'s Hideout - Far Right"]), + create_cave_region(player, 'Blinds Hideout', 'a bounty of five items', + ["Blind's Hideout - Left", "Blind's Hideout - Right", "Blind's Hideout - Far Left", + "Blind's Hideout - Far Right"], ['Blinds Hideout N']), + create_cave_region(player, 'Blinds Hideout (Top)', 'a bounty of five items', ["Blind's Hideout - Top"]), create_lw_region(player, 'Northeast Light World', None, ['Zoras River', 'Waterfall of Wishing Cave', 'Potion Shop Outer Rock', 'Catfish Mirror Spot', 'Northeast Light World Warp']), create_lw_region(player, 'Waterfall of Wishing Cave', None, ['Waterfall of Wishing', 'Northeast Light World Return']), create_lw_region(player, 'Potion Shop Area', None, ['Potion Shop', 'Potion Shop Inner Bushes', 'Potion Shop Inner Rock', 'Potion Shop Mirror Spot', 'Potion Shop River Drop']), @@ -68,12 +66,15 @@ def create_inverted_regions(world, player): create_cave_region(player, 'Chicken House', 'a house with a chest', ['Chicken House']), create_cave_region(player, 'Aginahs Cave', 'a cave with a chest', ['Aginah\'s Cave']), create_cave_region(player, 'Sahasrahlas Hut', 'Sahasrahla', ['Sahasrahla\'s Hut - Left', 'Sahasrahla\'s Hut - Middle', 'Sahasrahla\'s Hut - Right', 'Sahasrahla']), - create_cave_region(player, 'Kakariko Well (top)', 'a drop\'s exit', ['Kakariko Well - Top', 'Kakariko Well - Left', 'Kakariko Well - Middle', - 'Kakariko Well - Right', 'Kakariko Well - Bottom'], ['Kakariko Well (top to bottom)']), + create_cave_region(player, 'Kakariko Well (top)', 'a drop', + ['Kakariko Well - Left', 'Kakariko Well - Middle', 'Kakariko Well - Right', + 'Kakariko Well - Bottom'], + ['Kakariko Well (top to bottom)', 'Kakariko Well (top to back)']), + create_cave_region(player, 'Kakariko Well (back)', 'a drop', ['Kakariko Well - Top']), create_cave_region(player, 'Kakariko Well (bottom)', 'a drop\'s exit', None, ['Kakariko Well Exit']), create_cave_region(player, 'Blacksmiths Hut', 'the smith', ['Blacksmith', 'Missing Smith']), create_lw_region(player, 'Bat Cave Drop Ledge', None, ['Bat Cave Drop']), - create_cave_region(player, 'Bat Cave (right)', 'a drop\'s exit', ['Magic Bat'], ['Bat Cave Door']), + create_cave_region(player, 'Bat Cave (right)', 'a drop', ['Magic Bat'], ['Bat Cave Door']), create_cave_region(player, 'Bat Cave (left)', 'a drop\'s exit', None, ['Bat Cave Exit']), create_cave_region(player, 'Sick Kids House', 'the sick kid', ['Sick Kid']), create_lw_region(player, 'Hobo Bridge', ['Hobo']), @@ -99,7 +100,7 @@ def create_inverted_regions(world, player): create_lw_region(player, 'Lake Hylia Island', ['Lake Hylia Island']), create_cave_region(player, 'Capacity Upgrade', 'the queen of fairies', ['Capacity Upgrade - Left', 'Capacity Upgrade - Right']), create_cave_region(player, 'Two Brothers House', 'a connector', None, ['Two Brothers House Exit (East)', 'Two Brothers House Exit (West)']), - create_lw_region(player, 'Maze Race Ledge', ['Maze Race'], ['Two Brothers House (West)', 'Maze Race Mirror Spot']), + create_lw_region(player, 'Maze Race Ledge', ['Maze Race'], ['Two Brothers House (West)', 'Maze Race Mirror Spot', 'Maze Race Ledge Drop']), create_cave_region(player, '50 Rupee Cave', 'a cave with some cash'), create_lw_region(player, 'Desert Ledge', ['Desert Ledge'], ['Desert Palace Entrance (North) Rocks', 'Desert Palace Entrance (West)', 'Desert Ledge Drop']), create_lw_region(player, 'Desert Palace Stairs', None, ['Desert Palace Entrance (South)', 'Desert Palace Stairs Mirror Spot']), @@ -114,7 +115,8 @@ def create_inverted_regions(world, player): create_cave_region(player, 'Old Man House Back', 'a connector', None, ['Old Man House Exit (Top)', 'Old Man House Back to Front']), create_lw_region(player, 'Death Mountain', None, ['Old Man Cave (East)', 'Old Man House (Bottom)', 'Old Man House (Top)', 'Death Mountain Return Cave (East)', 'Spectacle Rock Cave', 'Spectacle Rock Cave Peak', 'Spectacle Rock Cave (Bottom)', 'Broken Bridge (West)', 'Death Mountain Mirror Spot']), - create_cave_region(player, 'Death Mountain Return Cave', 'a connector', None, ['Death Mountain Return Cave Exit (West)', 'Death Mountain Return Cave Exit (East)']), + create_cave_region(player, 'Death Mountain Return Cave (left)', 'a connector', None, ['Death Mountain Return Cave Exit (West)', 'Death Mountain Return Cave E']), + create_cave_region(player, 'Death Mountain Return Cave (right)', 'a connector', None, ['Death Mountain Return Cave Exit (East)', 'Death Mountain Return Cave W']), create_lw_region(player, 'Death Mountain Return Ledge', None, ['Death Mountain Return Ledge Drop', 'Death Mountain Return Cave (West)', 'Bumper Cave Ledge Mirror Spot']), create_cave_region(player, 'Spectacle Rock Cave (Top)', 'a connector', ['Spectacle Rock Cave'], ['Spectacle Rock Cave Drop', 'Spectacle Rock Cave Exit (Top)']), create_cave_region(player, 'Spectacle Rock Cave (Bottom)', 'a connector', None, ['Spectacle Rock Cave Exit']), @@ -127,10 +129,10 @@ def create_inverted_regions(world, player): 'Paradox Cave Lower - Left', 'Paradox Cave Lower - Right', 'Paradox Cave Lower - Far Right', - 'Paradox Cave Lower - Middle', - 'Paradox Cave Upper - Left', - 'Paradox Cave Upper - Right'], - ['Paradox Cave Push Block', 'Paradox Cave Bomb Jump']), + 'Paradox Cave Lower - Middle'], + ['Paradox Cave Push Block', 'Paradox Cave Bomb Jump', 'Paradox Cave Chest Area NE']), + create_cave_region(player, 'Paradox Cave Bomb Area', 'a connector', ['Paradox Cave Upper - Left', + 'Paradox Cave Upper - Right']), create_cave_region(player, 'Paradox Cave', 'a connector', None, ['Paradox Cave Exit (Middle)', 'Paradox Cave Exit (Top)', 'Paradox Cave Drop']), create_cave_region(player, 'Light World Death Mountain Shop', 'a common shop', ['Paradox Shop - Left', 'Paradox Shop - Middle', 'Paradox Shop - Right']), create_lw_region(player, 'East Death Mountain (Top)', ['Floating Island'], ['Paradox Cave (Top)', 'Death Mountain (Top)', 'Spiral Cave Ledge Access', 'East Death Mountain Drop', 'East Death Mountain Mirror Spot (Top)', 'Fairy Ascension Ledge Access', 'Mimic Cave Ledge Access', @@ -199,8 +201,10 @@ def create_inverted_regions(world, player): create_cave_region(player, 'Superbunny Cave (Top)', 'a connector', ['Superbunny Cave - Top', 'Superbunny Cave - Bottom'], ['Superbunny Cave Exit (Top)']), create_cave_region(player, 'Superbunny Cave (Bottom)', 'a connector', None, ['Superbunny Cave Climb', 'Superbunny Cave Exit (Bottom)']), create_cave_region(player, 'Spike Cave', 'Spike Cave', ['Spike Cave']), - create_cave_region(player, 'Hookshot Cave (Front)', 'a connector', ['Hookshot Cave - Top Right', 'Hookshot Cave - Top Left', 'Hookshot Cave - Bottom Right', 'Hookshot Cave - Bottom Left'], - ['Hookshot Cave Front to Middle', 'Hookshot Cave Front Exit']), + create_cave_region(player, 'Hookshot Cave (Front)', 'a connector', None, + ['Hookshot Cave Front to Middle', 'Hookshot Cave Front Exit', 'Hookshot Cave Bonk Path', 'Hookshot Cave Hook Path']), + create_cave_region(player, 'Hookshot Cave (Bonk Islands)', 'a connector', ['Hookshot Cave - Bottom Right']), + create_cave_region(player, 'Hookshot Cave (Hook Islands)', 'a connector', ['Hookshot Cave - Top Right', 'Hookshot Cave - Top Left', 'Hookshot Cave - Bottom Left']), create_cave_region(player, 'Hookshot Cave (Back)', 'a connector', None, ['Hookshot Cave Back to Middle', 'Hookshot Cave Back Exit']), create_cave_region(player, 'Hookshot Cave (Middle)', 'a connector', None, ['Hookshot Cave Middle to Back', 'Hookshot Cave Middle to Front']), diff --git a/ItemList.py b/ItemList.py index 9e0b0411..279fec61 100644 --- a/ItemList.py +++ b/ItemList.py @@ -3,13 +3,15 @@ import logging import math import RaceRandom as random -from BaseClasses import Region, RegionType, Shop, ShopType, Location, CollectionState -from Dungeons import get_dungeon_item_pool +from BaseClasses import Region, RegionType, Shop, ShopType, Location, CollectionState, PotItem from EntranceShuffle import connect_entrance -from Regions import shop_to_location_table, retro_shops, shop_table_by_location -from Fill import FillError, fill_restrictive, fast_fill +from Regions import shop_to_location_table, retro_shops, shop_table_by_location, valid_pot_location +from Fill import FillError, fill_restrictive, fast_fill, get_dungeon_item_pool +from PotShuffle import vanilla_pots from Items import ItemFactory +from source.item.FillUtil import trash_items, pot_items + import source.classes.constants as CONST @@ -42,6 +44,7 @@ Difficulty = namedtuple('Difficulty', 'progressive_bow_limit', 'heart_piece_limit', 'boss_heart_container_limit']) total_items_to_place = 153 +max_goal = 850 difficulties = { 'normal': Difficulty( @@ -178,7 +181,7 @@ def get_custom_array_key(item): def generate_itempool(world, player): - if (world.difficulty[player] not in ['normal', 'hard', 'expert'] or world.goal[player] not in ['ganon', 'pedestal', 'dungeons', 'triforcehunt', 'crystals'] + if (world.difficulty[player] not in ['normal', 'hard', 'expert'] or world.goal[player] not in ['ganon', 'pedestal', 'dungeons', 'triforcehunt', 'trinity', 'crystals'] or world.mode[player] not in ['open', 'standard', 'inverted'] or world.timer not in ['none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown'] or world.progressive not in ['on', 'off', 'random']): raise NotImplementedError('Not supported yet') @@ -190,8 +193,8 @@ def generate_itempool(world, player): else: world.push_item(world.get_location('Ganon', player), ItemFactory('Triforce', player), False) - if world.goal[player] in ['triforcehunt']: - region = world.get_region('Light World',player) + if world.goal[player] in ['triforcehunt', 'trinity']: + region = world.get_region('Light World', player) loc = Location(player, "Murahdahla", parent=region) region.locations.append(loc) world.dynamic_locations.append(loc) @@ -201,6 +204,7 @@ def generate_itempool(world, player): world.push_item(loc, ItemFactory('Triforce', player), False) loc.event = True loc.locked = True + loc.forced_item = loc.item world.get_location('Ganon', player).event = True world.get_location('Ganon', player).locked = True @@ -243,6 +247,9 @@ def generate_itempool(world, player): world.push_item(world.get_location('Ice Block Drop', player), ItemFactory('Convenient Block', player), False) world.get_location('Ice Block Drop', player).event = True world.get_location('Ice Block Drop', player).locked = True + world.push_item(world.get_location('Skull Star Tile', player), ItemFactory('Hidden Pits', player), False) + world.get_location('Skull Star Tile', player).event = True + world.get_location('Skull Star Tile', player).locked = True if world.mode[player] == 'standard': world.push_item(world.get_location('Zelda Pickup', player), ItemFactory('Zelda Herself', player), False) world.get_location('Zelda Pickup', player).event = True @@ -261,8 +268,12 @@ def generate_itempool(world, player): if player in world.pool_adjustment.keys(): amt = world.pool_adjustment[player] if amt < 0: - for _ in range(amt, 0): - pool.remove(next(iter([x for x in pool if x in ['Rupees (20)', 'Rupees (5)', 'Rupee (1)']]))) + trash_options = [x for x in pool if x in trash_items] + random.shuffle(trash_options) + trash_options = sorted(trash_options, key=lambda x: trash_items[x], reverse=True) + while amt > 0 and len(trash_options) > 0: + pool.remove(trash_options.pop()) + amt -= 1 elif amt > 0: for _ in range(0, amt): pool.append('Rupees (20)') @@ -327,14 +338,11 @@ def generate_itempool(world, player): if clock_mode is not None: world.clock_mode = clock_mode - if world.goal[player] == 'triforcehunt': - if world.treasure_hunt_count[player] == 0: - world.treasure_hunt_count[player] = 20 - if world.treasure_hunt_total[player] == 0: - world.treasure_hunt_total[player] = 30 + goal = world.goal[player] + if goal in ['triforcehunt', 'trinity']: + g, t = set_default_triforce(goal, world.treasure_hunt_count[player], world.treasure_hunt_total[player]) + world.treasure_hunt_count[player], world.treasure_hunt_total[player] = g, t world.treasure_hunt_icon[player] = 'Triforce Piece' - if world.custom: - world.treasure_hunt_count[player] = treasure_hunt_count world.itempool.extend([item for item in get_dungeon_item_pool(world) if item.player == player and ((item.smallkey and world.keyshuffle[player]) @@ -383,11 +391,16 @@ def generate_itempool(world, player): if world.retro[player]: set_up_take_anys(world, player) - if world.keydropshuffle[player]: - world.itempool += [ItemFactory('Small Key (Universal)', player)] * 32 + if world.dropshuffle[player]: + world.itempool += [ItemFactory('Small Key (Universal)', player)] * 13 + if world.pottery[player] not in ['none', 'cave']: + world.itempool += [ItemFactory('Small Key (Universal)', player)] * 19 create_dynamic_shop_locations(world, player) + if world.pottery[player] not in ['none', 'keys']: + add_pot_contents(world, player) + take_any_locations = [ 'Snitch Lady (East)', 'Snitch Lady (West)', 'Bush Covered House', 'Light World Bomb Hut', @@ -407,7 +420,9 @@ def set_up_take_anys(world, player): if 'Archery Game' in take_any_locations: take_any_locations.remove('Archery Game') - regions = random.sample(take_any_locations, 5) + take_any_candidates = [x for x in take_any_locations if len(world.get_region(x, player).locations) == 0] + + regions = random.sample(take_any_candidates, 5) old_man_take_any = Region("Old Man Sword Cave", RegionType.Cave, 'the sword cave', player) world.regions.append(old_man_take_any) @@ -499,7 +514,7 @@ def fill_prizes(world, attempts=15): continue break else: - raise FillError('Unable to place dungeon prizes') + raise FillError(f'Unable to place dungeon prizes {", ".join(list(map(lambda d: d.hint_text, prize_locs)))}') def set_up_shops(world, player): @@ -540,7 +555,7 @@ def set_up_shops(world, player): removals = [item for item in world.itempool if item.name == 'Bomb Upgrade (+5)' and item.player == player] for remove in removals: world.itempool.remove(remove) - world.itempool.append(ItemFactory('Rupees (50)', player)) # replace the bomb upgrade + world.itempool.append(ItemFactory('Rupees (50)', player)) # replace the bomb upgrade else: cap_shop = world.get_region('Capacity Upgrade', player).shop cap_shop.inventory[0] = cap_shop.inventory[1] # remove bomb capacity upgrades in bombbag @@ -743,15 +758,26 @@ rupee_chart = {'Rupee (1)': 1, 'Rupees (5)': 5, 'Rupees (20)': 20, 'Rupees (50)' 'Rupees (100)': 100, 'Rupees (300)': 300} +def add_pot_contents(world, player): + for super_tile, pot_list in vanilla_pots.items(): + for pot in pot_list: + if pot.item not in [PotItem.Hole, PotItem.Key, PotItem.Switch]: + if valid_pot_location(pot, world.pot_pool[player], world, player): + item = ('Rupees (5)' if world.retro[player] and pot_items[pot.item] == 'Arrows (5)' + else pot_items[pot.item]) + world.itempool.append(ItemFactory(item, player)) + + def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer, goal, mode, swords, retro, bombbag, door_shuffle, logic): pool = [] placed_items = {} precollected_items = [] clock_mode = None - if goal == 'triforcehunt': + if goal in ['triforcehunt', 'trinity']: if treasure_hunt_total == 0: - treasure_hunt_total = 30 - triforcepool = ['Triforce Piece'] * int(treasure_hunt_total) + treasure_hunt_total = 30 if goal == 'triforcehunt' else 10 + # triforce pieces max out + triforcepool = ['Triforce Piece'] * min(treasure_hunt_total, max_goal) pool.extend(alwaysitems) @@ -776,7 +802,7 @@ def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer, lamps_needed_for_dark_rooms = 1 # insanity shuffle doesn't have fake LW/DW logic so for now guaranteed Mirror and Moon Pearl at the start - if shuffle == 'insanity_legacy': + if shuffle == 'insanity_legacy': place_item('Link\'s House', 'Magic Mirror') place_item('Sanctuary', 'Moon Pearl') else: @@ -830,10 +856,11 @@ def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer, place_item('Link\'s Uncle', swords_to_use.pop()) place_item('Blacksmith', swords_to_use.pop()) place_item('Pyramid Fairy - Left', swords_to_use.pop()) - if goal != 'pedestal': + if goal not in ['pedestal', 'trinity']: place_item('Master Sword Pedestal', swords_to_use.pop()) else: place_item('Master Sword Pedestal', 'Triforce') + pool.append(swords_to_use.pop()) else: pool.extend(diff.progressivesword if want_progressives() else diff.basicsword) if swords == 'assured': @@ -845,28 +872,21 @@ def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer, pool.remove('Fighter Sword') pool.extend(['Rupees (50)']) - extraitems = total_items_to_place - len(pool) - len(placed_items) - if timer in ['timed', 'timed-countdown']: pool.extend(diff.timedother) - extraitems -= len(diff.timedother) clock_mode = 'stopwatch' if timer == 'timed' else 'countdown' elif timer == 'timed-ohko': pool.extend(diff.timedohko) - extraitems -= len(diff.timedohko) clock_mode = 'countdown-ohko' - if goal == 'triforcehunt': + if goal in ['triforcehunt', 'trinity']: pool.extend(triforcepool) - extraitems -= len(triforcepool) for extra in diff.extras: - if extraitems > 0: - if len(extra) > extraitems: - extra = random.choices(extra, k=extraitems) - pool.extend(extra) - extraitems -= len(extra) + pool.extend(extra) - if goal == 'pedestal' and swords != 'vanilla': + # note: massage item pool now handles shrinking the pool appropriately + + if goal in ['pedestal', 'trinity'] and swords != 'vanilla': place_item('Master Sword Pedestal', 'Triforce') if retro: pool = [item.replace('Single Arrow','Rupees (5)') for item in pool] @@ -889,6 +909,7 @@ def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer, pool.extend(['Small Key (Universal)']) return (pool, placed_items, precollected_items, clock_mode, lamps_needed_for_dark_rooms) + def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, swords, retro, bombbag, customitemarray): if isinstance(customitemarray,dict) and 1 in customitemarray: customitemarray = customitemarray[1] @@ -914,6 +935,11 @@ def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, s if customitemarray["triforce"] > total_items_to_place: customitemarray["triforce"] = total_items_to_place + # Triforce Pieces + if goal in ['triforcehunt', 'trinity']: + g, t = set_default_triforce(goal, customitemarray["triforcepiecesgoal"], customitemarray["triforcepieces"]) + customitemarray["triforcepiecesgoal"], customitemarray["triforcepieces"] = g, t + itemtotal = 0 # Bow to Silver Arrows Upgrade, including Generic Keys & Rupoors for x in [*range(0, 66 + 1), 68, 69]: @@ -944,10 +970,12 @@ def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, s pool.append(thisbottle) if customitemarray["triforcepieces"] > 0 or customitemarray["triforcepiecesgoal"] > 0: - treasure_hunt_count = max(min(customitemarray["triforcepiecesgoal"], 99), 1) #To display, count must be between 1 and 99. + # Location pool doesn't support larger values + treasure_hunt_count = max(min(customitemarray["triforcepiecesgoal"], max_goal), 1) treasure_hunt_icon = 'Triforce Piece' # Ensure game is always possible to complete here, force sufficient pieces if the player is unwilling. - if (customitemarray["triforcepieces"] < treasure_hunt_count) and (goal == 'triforcehunt') and (customitemarray["triforce"] == 0): + if ((customitemarray["triforcepieces"] < treasure_hunt_count) and (goal in ['triforcehunt', 'trinity']) + and (customitemarray["triforce"] == 0)): extrapieces = treasure_hunt_count - customitemarray["triforcepieces"] pool.extend(['Triforce Piece'] * extrapieces) itemtotal = itemtotal + extrapieces @@ -959,7 +987,7 @@ def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, s elif timer == 'ohko': clock_mode = 'ohko' - if goal == 'pedestal': + if goal in ['pedestal', 'trinity']: place_item('Master Sword Pedestal', 'Triforce') itemtotal = itemtotal + 1 @@ -994,10 +1022,58 @@ def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, s return (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) + +def make_customizer_pool(world, player): + pool = [] + placed_items = {} + precollected_items = [] + clock_mode = None + + def place_item(loc, item): + assert loc not in placed_items + placed_items[loc] = item + + diff = difficulties[world.difficulty[player]] + for item_name, amount in world.customizer.get_item_pool()[player].items(): + if isinstance(amount, int): + if item_name == 'Bottle (Random)': + for _ in range(amount): + pool.append(random.choice(diff.bottles)) + else: + pool.extend([item_name] * amount) + + timer = world.timer[player] + if timer in ['display', 'timed', 'timed-countdown']: + clock_mode = 'countdown' if timer == 'timed-countdown' else 'stopwatch' + elif timer == 'timed-ohko': + clock_mode = 'countdown-ohko' + elif timer == 'ohko': + clock_mode = 'ohko' + + if world.goal[player] == 'pedestal': + place_item('Master Sword Pedestal', 'Triforce') + + return pool, placed_items, precollected_items, clock_mode, 1 + + +# location pool doesn't support larger values at this time +def set_default_triforce(goal, custom_goal, custom_total): + triforce_goal, triforce_total = 0, 0 + if goal == 'triforcehunt': + triforce_goal, triforce_total = 20, 30 + elif goal == 'trinity': + triforce_goal, triforce_total = 8, 10 + if custom_goal > 0: + triforce_goal = max(min(custom_goal, max_goal), 1) + if custom_total > 0: + triforce_total = max(min(custom_total, max_goal), triforce_goal) + return triforce_goal, triforce_total + + # A quick test to ensure all combinations generate the correct amount of items. def test(): for difficulty in ['normal', 'hard', 'expert']: - for goal in ['ganon', 'triforcehunt', 'pedestal']: + for goal in ['ganon', 'triforcehunt', 'pedestal', 'trinity']: for timer in ['none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown']: for mode in ['open', 'standard', 'inverted', 'retro']: for swords in ['random', 'assured', 'swordless', 'vanilla']: @@ -1011,7 +1087,7 @@ def test(): count = len(out[0]) + len(out[1]) correct_count = total_items_to_place - if goal == 'pedestal' and swords != 'vanilla': + if goal in ['pedestal', 'trinity'] and swords != 'vanilla': # pedestal goals generate one extra item correct_count += 1 if retro: @@ -1023,27 +1099,3 @@ def test(): if __name__ == '__main__': test() - - -def fill_specific_items(world): - keypool = [item for item in world.itempool if item.smallkey] - cage = world.get_location('Tower of Hera - Basement Cage', 1) - c_dungeon = cage.parent_region.dungeon - key_item = next(x for x in keypool if c_dungeon.name in x.name or (c_dungeon.name == 'Hyrule Castle' and 'Escape' in x.name)) - world.itempool.remove(key_item) - all_state = world.get_all_state(True) - fill_restrictive(world, all_state, [cage], [key_item]) - - location = world.get_location('Tower of Hera - Map Chest', 1) - key_item = next(x for x in world.itempool if 'Byrna' in x.name) - world.itempool.remove(key_item) - fast_fill(world, [key_item], [location]) - - - # somaria = next(item for item in world.itempool if item.name == 'Cane of Somaria') - # shooter = world.get_location('Palace of Darkness - Shooter Room', 1) - # world.itempool.remove(somaria) - # all_state = world.get_all_state(True) - # fill_restrictive(world, all_state, [shooter], [somaria]) - - diff --git a/Items.py b/Items.py index 808a0740..90b518cb 100644 --- a/Items.py +++ b/Items.py @@ -22,80 +22,85 @@ def ItemFactory(items, player): return ret -# Format: Name: (Advancement, Priority, Type, ItemCode, Pedestal Hint Text, Pedestal Credit Text, Sick Kid Credit Text, Zora Credit Text, Witch Credit Text, Flute Boy Credit Text, Hint Text) -item_table = {'Bow': (True, False, None, 0x0B, 200, 'You have\nchosen the\narcher class.', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again', 'the Bow'), - 'Progressive Bow': (True, False, None, 0x64, 150, 'You have\nchosen the\narcher class.', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again', 'a Bow'), - 'Progressive Bow (Alt)': (True, False, None, 0x65, 150, 'You have\nchosen the\narcher class.', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again', 'a Bow'), - 'Book of Mudora': (True, False, None, 0x1D, 150, 'This is a\nparadox?!', 'and the story book', 'the scholarly kid', 'moon runes for sale', 'drugs for literacy', 'book-worm boy can read again', 'the Book'), - 'Hammer': (True, False, None, 0x09, 250, 'stop\nhammer time!', 'and m c hammer', 'hammer-smashing kid', 'm c hammer for sale', 'stop... hammer time', 'stop, hammer time', 'the hammer'), +# Format: Name: (Advancement, Priority, Type, ItemCode, BasePrice, Pedestal Hint Text, Pedestal Credit Text, Sick Kid Credit Text, Zora Credit Text, Witch Credit Text, Flute Boy Credit Text, Hint Text) +item_table = {'Bow': (True, False, None, 0x0B, 200, 'You have\nchosen the\narcher class.', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again', 'the bow'), + 'Progressive Bow': (True, False, None, 0x64, 150, 'You have\nchosen the\narcher class.', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again', 'a bow'), + 'Progressive Bow (Alt)': (True, False, None, 0x65, 150, 'You have\nchosen the\narcher class.', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again', 'a bow'), + 'Book of Mudora': (True, False, None, 0x1D, 150, 'This is a\nparadox?!', 'and the story book', 'the scholarly kid', 'moon runes for sale', 'drugs for literacy', 'book-worm boy can read again', 'the book'), + 'Hammer': (True, False, None, 0x09, 250, 'Stop\nhammer time!', 'and m c hammer', 'hammer-smashing kid', 'm c hammer for sale', 'stop... hammer time', 'stop, hammer time', 'the hammer'), 'Hookshot': (True, False, None, 0x0A, 250, 'BOING!!!\nBOING!!!\nBOING!!!', 'and the tickle beam', 'tickle-monster kid', 'tickle beam for sale', 'witch and tickle boy', 'beam boy tickles again', 'the Hookshot'), - 'Magic Mirror': (True, False, None, 0x1A, 250, 'Isn\'t your\nreflection so\npretty?', 'the face reflector', 'the narcissistic kid', 'your face for sale', 'trades looking-glass', 'narcissistic boy is happy again', 'the Mirror'), - 'Ocarina': (True, False, None, 0x14, 250, 'Save the duck\nand fly to\nfreedom!', 'and the duck call', 'the duck-call kid', 'duck call for sale', 'duck-calls for trade', 'ocarina boy plays again', 'the Flute'), - 'Pegasus Boots': (True, False, None, 0x4B, 250, 'Gotta go fast!', 'and the sprint shoes', 'the running-man kid', 'sprint shoe for sale', 'shrooms for speed', 'gotta-go-fast boy runs again', 'the Boots'), - 'Power Glove': (True, False, None, 0x1B, 100, 'Now you can\nlift weak\nstuff!', 'and the grey mittens', 'body-building kid', 'lift glove for sale', 'fungus for gloves', 'body-building boy lifts again', 'the glove'), + 'Magic Mirror': (True, False, None, 0x1A, 250, 'Isn\'t your\nreflection so\npretty?', 'the face reflector', 'the narcissistic kid', 'your face for sale', 'trades looking-glass', 'narcissistic boy is happy again', 'the mirror'), + 'Ocarina': (True, False, None, 0x14, 250, 'Save the duck\nand fly to\nfreedom!', 'and the duck call', 'the duck-call kid', 'duck call for sale', 'duck-calls for trade', 'ocarina boy plays again', 'the flute'), + 'Pegasus Boots': (True, False, None, 0x4B, 250, 'Gotta go fast!', 'and the sprint shoes', 'the running-man kid', 'sprint shoe for sale', 'shrooms for speed', 'gotta-go-fast boy runs again', 'the boots'), + 'Power Glove': (True, False, None, 0x1B, 100, 'Now you can\nlift weak\nstuff!', 'and the grey mittens', 'body-building kid', 'lift glove for sale', 'fungus for gloves', 'body-building boy lifts again', 'the Glove'), 'Cape': (True, False, None, 0x19, 50, 'Wear this to\nbecome\ninvisible!', 'the camouflage cape', 'red riding-hood kid', 'red hood for sale', 'hood from a hood', 'dapper boy hides again', 'the cape'), 'Mushroom': (True, False, None, 0x29, 50, 'I\'m a fun guy!\n\nI\'m a funghi!', 'and the legal drugs', 'the drug-dealing kid', 'legal drugs for sale', 'shroom swap', 'shroom boy sells drugs again', 'the mushroom'), 'Shovel': (True, False, None, 0x13, 50, 'Can\n You\n Dig it?', 'and the spade', 'archaeologist kid', 'dirt spade for sale', 'can you dig it', 'shovel boy digs again', 'the shovel'), 'Lamp': (True, False, None, 0x12, 150, 'Baby, baby,\nbaby.\nLight my way!', 'and the flashlight', 'light-shining kid', 'flashlight for sale', 'fungus for illumination', 'illuminated boy can see again', 'the lamp'), 'Magic Powder': (True, False, None, 0x0D, 50, 'you can turn\nanti-faeries\ninto faeries', 'and the magic sack', 'the sack-holding kid', 'magic sack for sale', 'the witch and assistant', 'magic boy plays marbles again', 'the powder'), - 'Moon Pearl': (True, False, None, 0x1F, 200, ' Bunny Link\n be\n gone!', 'and the jaw breaker', 'fortune-telling kid', 'lunar orb for sale', 'shrooms for moon rock', 'moon boy plays ball again', 'the moon pearl'), + 'Moon Pearl': (True, False, None, 0x1F, 200, ' Bunny Link\n be\n gone!', 'and the jaw breaker', 'fortune-telling kid', 'lunar orb for sale', 'shrooms for moon rock', 'moon boy plays ball again', 'the Moon Pearl'), 'Cane of Somaria': (True, False, None, 0x15, 250, 'I make blocks\nto hold down\nswitches!', 'and the red blocks', 'the block-making kid', 'block stick for sale', 'block stick for trade', 'cane boy makes blocks again', 'the red cane'), - 'Fire Rod': (True, False, None, 0x07, 250, 'I\'m the hot\nrod. I make\nthings burn!', 'and the flamethrower', 'fire-starting kid', 'rage rod for sale', 'fungus for rage-rod', 'firestarter boy burns again', 'the fire rod'), - 'Flippers': (True, False, None, 0x1E, 250, 'fancy a swim?', 'and the toewebs', 'the swimming kid', 'finger webs for sale', 'shrooms let you swim', 'swimming boy swims again', 'the flippers'), - 'Ice Rod': (True, False, None, 0x08, 250, 'I\'m the cold\nrod. I make\nthings freeze!', 'and the freeze ray', 'the ice-bending kid', 'freeze ray for sale', 'fungus for ice-rod', 'ice-cube boy freezes again', 'the ice rod'), - 'Titans Mitts': (True, False, None, 0x1C, 200, 'Now you can\nlift heavy\nstuff!', 'and the golden glove', 'body-building kid', 'carry glove for sale', 'fungus for bling-gloves', 'body-building boy has gold again', 'the mitts'), + 'Fire Rod': (True, False, None, 0x07, 250, 'I\'m the hot\nrod. I make\nthings burn!', 'and the flamethrower', 'fire-starting kid', 'rage rod for sale', 'fungus for rage-rod', 'firestarter boy burns again', 'the Fire Rod'), + 'Flippers': (True, False, None, 0x1E, 250, 'Fancy a swim?', 'and the toewebs', 'the swimming kid', 'finger webs for sale', 'shrooms let you swim', 'swimming boy swims again', 'the flippers'), + 'Ice Rod': (True, False, None, 0x08, 250, 'I\'m the cold\nrod. I make\nthings freeze!', 'and the freeze ray', 'the ice-bending kid', 'freeze ray for sale', 'fungus for ice-rod', 'ice-cube boy freezes again', 'the Ice Rod'), + 'Titans Mitts': (True, False, None, 0x1C, 200, 'Now you can\nlift heavy\nstuff!', 'and the golden glove', 'body-building kid', 'carry glove for sale', 'fungus for bling-gloves', 'body-building boy has gold again', 'the Mitts'), 'Bombos': (True, False, None, 0x0F, 100, 'Burn, baby,\nburn! Fear my\nring of fire!', 'and the swirly coin', 'coin-collecting kid', 'swirly coin for sale', 'shrooms for swirly-coin', 'medallion boy melts room again', 'Bombos'), 'Ether': (True, False, None, 0x10, 100, 'This magic\ncoin freezes\neverything!', 'and the bolt coin', 'coin-collecting kid', 'bolt coin for sale', 'shrooms for bolt-coin', 'medallion boy sees floor again', 'Ether'), 'Quake': (True, False, None, 0x11, 100, 'Maxing out the\nRichter scale\nis what I do!', 'and the wavy coin', 'coin-collecting kid', 'wavy coin for sale', 'shrooms for wavy-coin', 'medallion boy shakes dirt again', 'Quake'), - 'Bottle': (True, False, None, 0x16, 50, 'Now you can\nstore potions\nand stuff!', 'and the terrarium', 'the terrarium kid', 'terrarium for sale', 'special promotion', 'bottle boy has terrarium again', 'a Bottle'), - 'Bottle (Red Potion)': (True, False, None, 0x2B, 70, 'Hearty red goop!', 'and the red goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has red goo again', 'a Bottle'), - 'Bottle (Green Potion)': (True, False, None, 0x2C, 60, 'Refreshing green goop!', 'and the green goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has green goo again', 'a Bottle'), - 'Bottle (Blue Potion)': (True, False, None, 0x2D, 80, 'Delicious blue goop!', 'and the blue goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has blue goo again', 'a Bottle'), - 'Bottle (Fairy)': (True, False, None, 0x3D, 70, 'Save me and I will revive you', 'and the captive', 'the tingle kid', 'hostage for sale', 'fairy dust and shrooms', 'bottle boy has friend again', 'a Bottle'), - 'Bottle (Bee)': (True, False, None, 0x3C, 50, 'I will sting your foes a few times', 'and the sting buddy', 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'bottle boy has mad bee again', 'a Bottle'), - 'Bottle (Good Bee)': (True, False, None, 0x48, 60, 'I will sting your foes a whole lot!', 'and the sparkle sting', 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'bottle boy has beetor again', 'a Bottle'), + 'Bottle': (True, False, None, 0x16, 50, 'Now you can\nstore potions\nand stuff!', 'and the terrarium', 'the terrarium kid', 'terrarium for sale', 'special promotion', 'bottle boy has terrarium again', 'a bottle'), + 'Bottle (Red Potion)': (True, False, None, 0x2B, 70, 'Hearty red goop!', 'and the red goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has red goo again', 'a bottle'), + 'Bottle (Green Potion)': (True, False, None, 0x2C, 60, 'Refreshing green goop!', 'and the green goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has green goo again', 'a bottle'), + 'Bottle (Blue Potion)': (True, False, None, 0x2D, 80, 'Delicious blue goop!', 'and the blue goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has blue goo again', 'a bottle'), + 'Bottle (Fairy)': (True, False, None, 0x3D, 70, 'Save me and I will revive you', 'and the captive', 'the tingle kid', 'hostage for sale', 'fairy dust and shrooms', 'bottle boy has friend again', 'a bottle'), + 'Bottle (Bee)': (True, False, None, 0x3C, 50, 'I will sting your foes a few times', 'and the sting buddy', 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'bottle boy has mad bee again', 'a bottle'), + 'Bottle (Good Bee)': (True, False, None, 0x48, 60, 'I will sting your foes a whole lot!', 'and the sparkle sting', 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'bottle boy has beetor again', 'a bottle'), 'Master Sword': (True, False, 'Sword', 0x50, 100, 'I beat barries and pigs alike', 'and the master sword', 'sword-wielding kid', 'glow sword for sale', 'fungus for blue slasher', 'sword boy fights again', 'the Master Sword'), 'Tempered Sword': (True, False, 'Sword', 0x02, 150, 'I stole the\nblacksmith\'s\njob!', 'the tempered sword', 'sword-wielding kid', 'flame sword for sale', 'fungus for red slasher', 'sword boy fights again', 'the Tempered Sword'), 'Fighter Sword': (True, False, 'Sword', 0x49, 50, 'A pathetic\nsword rests\nhere!', 'the tiny sword', 'sword-wielding kid', 'tiny sword for sale', 'fungus for tiny slasher', 'sword boy fights again', 'the small sword'), + 'Sword and Shield': (True, False, 'Sword', 0x00, 'An uncle\nsword rests\nhere!', 'the sword and shield', 'sword and shield-wielding kid', 'training set for sale', 'fungus for training set', 'sword and shield boy fights again', 'the small sword and shield'), 'Golden Sword': (True, False, 'Sword', 0x03, 200, 'The butter\nsword rests\nhere!', 'and the butter sword', 'sword-wielding kid', 'butter for sale', 'cap churned to butter', 'sword boy fights again', 'the Golden Sword'), - 'Progressive Sword': (True, False, 'Sword', 0x5E, 150, 'a better copy\nof your sword\nfor your time', 'the unknown sword', 'sword-wielding kid', 'sword for sale', 'fungus for some slasher', 'sword boy fights again', 'a sword'), - 'Progressive Glove': (True, False, None, 0x61, 150, 'a way to lift\nheavier things', 'and the lift upgrade', 'body-building kid', 'some glove for sale', 'fungus for gloves', 'body-building boy lifts again', 'a glove'), + 'Progressive Sword': (True, False, 'Sword', 0x5E, 150, 'A better copy\nof your sword\nfor your time', 'the unknown sword', 'sword-wielding kid', 'sword for sale', 'fungus for some slasher', 'sword boy fights again', 'a sword'), + 'Progressive Glove': (True, False, None, 0x61, 150, 'A way to lift\nheavier things', 'and the lift upgrade', 'body-building kid', 'some glove for sale', 'fungus for gloves', 'body-building boy lifts again', 'a glove'), 'Silver Arrows': (True, False, None, 0x58, 100, 'Do you fancy\nsilver tipped\narrows?', 'and the ganonsbane', 'ganon-killing kid', 'ganon doom for sale', 'fungus for pork', 'archer boy shines again', 'the silver arrows'), - 'Green Pendant': (True, False, 'Crystal', [0x04, 0x38, 0x62, 0x00, 0x69, 0x01], 999, None, None, None, None, None, None, None), - 'Blue Pendant': (True, False, 'Crystal', [0x02, 0x34, 0x60, 0x00, 0x69, 0x02], 999, None, None, None, None, None, None, None), - 'Red Pendant': (True, False, 'Crystal', [0x01, 0x32, 0x60, 0x00, 0x69, 0x03], 999, None, None, None, None, None, None, None), + 'Green Pendant': (True, False, 'Crystal', [0x04, 0x38, 0x62, 0x00, 0x69, 0x01, 0x08], 999, None, None, None, None, None, None, None), + 'Blue Pendant': (True, False, 'Crystal', [0x02, 0x34, 0x60, 0x00, 0x69, 0x02, 0x09], 999, None, None, None, None, None, None, None), + 'Red Pendant': (True, False, 'Crystal', [0x01, 0x32, 0x60, 0x00, 0x69, 0x03, 0x0a], 999, None, None, None, None, None, None, None), 'Triforce': (True, False, None, 0x6A, 777, '\n YOU WIN!', 'and the triforce', 'victorious kid', 'victory for sale', 'fungus for the win', 'greedy boy wins game again', 'the Triforce'), - 'Power Star': (True, False, None, 0x6B, 100, 'a small victory', 'and the power star', 'star-struck kid', 'star for sale', 'see stars with shroom', 'mario powers up again', 'a Power Star'), - 'Triforce Piece': (True, False, None, 0x6C, 100, 'a small victory', 'and the thirdforce', 'triangular kid', 'triangle for sale', 'fungus for triangle', 'wise boy has triangle again', 'a Triforce Piece'), - 'Crystal 1': (True, False, 'Crystal', [0x02, 0x34, 0x64, 0x40, 0x7F, 0x06], 999, None, None, None, None, None, None, None), - 'Crystal 2': (True, False, 'Crystal', [0x10, 0x34, 0x64, 0x40, 0x79, 0x06], 999, None, None, None, None, None, None, None), - 'Crystal 3': (True, False, 'Crystal', [0x40, 0x34, 0x64, 0x40, 0x6C, 0x06], 999, None, None, None, None, None, None, None), - 'Crystal 4': (True, False, 'Crystal', [0x20, 0x34, 0x64, 0x40, 0x6D, 0x06], 999, None, None, None, None, None, None, None), - 'Crystal 5': (True, False, 'Crystal', [0x04, 0x32, 0x64, 0x40, 0x6E, 0x06], 999, None, None, None, None, None, None, None), - 'Crystal 6': (True, False, 'Crystal', [0x01, 0x32, 0x64, 0x40, 0x6F, 0x06], 999, None, None, None, None, None, None, None), - 'Crystal 7': (True, False, 'Crystal', [0x08, 0x34, 0x64, 0x40, 0x7C, 0x06], 999, None, None, None, None, None, None, None), - 'Single Arrow': (False, False, None, 0x43, 3, 'a lonely arrow\nsits here.', 'and the arrow', 'stick-collecting kid', 'sewing needle for sale', 'fungus for arrow', 'archer boy sews again', 'an arrow'), + 'Power Star': (True, False, None, 0x6B, 100, 'A small victory', 'and the power star', 'star-struck kid', 'star for sale', 'see stars with shroom', 'mario powers up again', 'a Power Star'), + 'Triforce Piece': (True, False, None, 0x6C, 100, 'A small victory', 'and the thirdforce', 'triangular kid', 'triangle for sale', 'fungus for triangle', 'wise boy has triangle again', 'a Triforce piece'), + 'Crystal 1': (True, False, 'Crystal', [0x02, 0x34, 0x64, 0x40, 0x7F, 0x06, 0x01], 999, None, None, None, None, None, None, None), + 'Crystal 2': (True, False, 'Crystal', [0x10, 0x34, 0x64, 0x40, 0x79, 0x06, 0x02], 999, None, None, None, None, None, None, None), + 'Crystal 3': (True, False, 'Crystal', [0x40, 0x34, 0x64, 0x40, 0x6C, 0x06, 0x03], 999, None, None, None, None, None, None, None), + 'Crystal 4': (True, False, 'Crystal', [0x20, 0x34, 0x64, 0x40, 0x6D, 0x06, 0x04], 999, None, None, None, None, None, None, None), + 'Crystal 5': (True, False, 'Crystal', [0x04, 0x32, 0x64, 0x40, 0x6E, 0x06, 0x05], 999, None, None, None, None, None, None, None), + 'Crystal 6': (True, False, 'Crystal', [0x01, 0x32, 0x64, 0x40, 0x6F, 0x06, 0x06], 999, None, None, None, None, None, None, None), + 'Crystal 7': (True, False, 'Crystal', [0x08, 0x34, 0x64, 0x40, 0x7C, 0x06, 0x07], 999, None, None, None, None, None, None, None), + 'Single Arrow': (False, False, None, 0x43, 3, 'A lonely arrow\nsits here.', 'and the arrow', 'stick-collecting kid', 'sewing needle for sale', 'fungus for arrow', 'archer boy sews again', 'an arrow'), 'Arrows (10)': (False, False, None, 0x44, 30, 'This will give\nyou ten shots\nwith your bow!', 'and the arrow pack', 'stick-collecting kid', 'sewing kit for sale', 'fungus for arrows', 'archer boy sews again', 'ten arrows'), - 'Arrow Upgrade (+10)': (False, False, None, 0x54, 100, 'increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'), - 'Arrow Upgrade (+5)': (False, False, None, 0x53, 100, 'increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'), + 'Arrow Upgrade (+10)': (False, False, None, 0x54, 100, 'Increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'), + 'Arrow Upgrade (+5)': (False, False, None, 0x53, 100, 'Increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'), 'Single Bomb': (False, False, None, 0x27, 5, 'I make things\ngo BOOM! But\njust once.', 'and the explosion', 'the bomb-holding kid', 'firecracker for sale', 'blend fungus into bomb', '\'splosion boy explodes again', 'a bomb'), + 'Arrows (5)': (False, False, None, 0x5A, 15, 'This will give\nyou five shots\nwith your bow!', 'and the arrow pack', 'stick-collecting kid', 'sewing kit for sale', 'fungus for arrows', 'archer boy sews again', 'five arrows'), + 'Small Magic': (False, False, None, 0x45, 5, 'A bit of magic', 'and the bit of magic', 'bit-o-magic kid', 'magic bit for sale', 'fungus for magic', 'magic boy conjures again', 'a bit of magic'), + 'Big Magic': (False, False, None, 0x5A, 40, 'A lot of magic', 'and lots of magic', 'lot-o-magic kid', 'magic refill for sale', 'fungus for magic', 'magic boy conjures again', 'a magic refill'), + 'Chicken': (False, False, None, 0x5A, 999, 'Cucco of Legend', 'and the legendary cucco', 'chicken kid', 'fried chicken for sale', 'fungus for chicken', 'cucco boy clucks again', 'a cucco'), 'Bombs (3)': (False, False, None, 0x28, 15, 'I make things\ngo triple\nBOOM!!!', 'and the explosions', 'the bomb-holding kid', 'firecrackers for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'three bombs'), 'Bombs (10)': (False, False, None, 0x31, 50, 'I make things\ngo BOOM! Ten\ntimes!', 'and the explosions', 'the bomb-holding kid', 'firecrackers for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'ten bombs'), - 'Bomb Upgrade (+10)': (False, False, None, 0x52, 100, 'increase bomb\nstorage, low\nlow price', 'and the bomb bag', 'boom-enlarging kid', 'bomb boost for sale', 'the shroom goes boom', 'upgrade boy explodes more again', 'bomb capacity'), - 'Bomb Upgrade (+5)': (False, False, None, 0x51, 100, 'increase bomb\nstorage, low\nlow price', 'and the bomb bag', 'boom-enlarging kid', 'bomb boost for sale', 'the shroom goes boom', 'upgrade boy explodes more again', 'bomb capacity'), + 'Bomb Upgrade (+10)': (False, False, None, 0x52, 100, 'Increase bomb\nstorage, low\nlow price', 'and the bomb bag', 'boom-enlarging kid', 'bomb boost for sale', 'the shroom goes boom', 'upgrade boy explodes more again', 'bomb capacity'), + 'Bomb Upgrade (+5)': (False, False, None, 0x51, 100, 'Increase bomb\nstorage, low\nlow price', 'and the bomb bag', 'boom-enlarging kid', 'bomb boost for sale', 'the shroom goes boom', 'upgrade boy explodes more again', 'bomb capacity'), 'Blue Mail': (False, True, None, 0x22, 50, 'Now you\'re a\nblue elf!', 'and the banana hat', 'the protected kid', 'banana hat for sale', 'the clothing store', 'tailor boy banana hatted again', 'the blue mail'), 'Red Mail': (False, True, None, 0x23, 100, 'Now you\'re a\nred elf!', 'and the eggplant hat', 'well-protected kid', 'purple hat for sale', 'the nice clothing store', 'tailor boy fears nothing again', 'the red mail'), - 'Progressive Armor': (False, True, None, 0x60, 50, 'time for a\nchange of\nclothes?', 'and the unknown hat', 'the protected kid', 'new hat for sale', 'the clothing store', 'tailor boy has threads again', 'some armor'), + 'Progressive Armor': (False, True, None, 0x60, 50, 'Time for a\nchange of\nclothes?', 'and the unknown hat', 'the protected kid', 'new hat for sale', 'the clothing store', 'tailor boy has threads again', 'some armor'), 'Blue Boomerang': (True, False, None, 0x0C, 50, 'No matter what\nyou do, blue\nreturns to you', 'and the bluemarang', 'the bat-throwing kid', 'bent stick for sale', 'fungus for puma-stick', 'throwing boy plays fetch again', 'the blue boomerang'), 'Red Boomerang': (True, False, None, 0x2A, 50, 'No matter what\nyou do, red\nreturns to you', 'and the badmarang', 'the bat-throwing kid', 'air foil for sale', 'fungus for return-stick', 'magical boy plays fetch again', 'the red boomerang'), - 'Blue Shield': (False, True, None, 0x04, 50, 'Now you can\ndefend against\npebbles!', 'and the stone blocker', 'shield-wielding kid', 'shield for sale', 'fungus for shield', 'shield boy defends again', 'the blue shield'), - 'Red Shield': (False, True, None, 0x05, 500, 'Now you can\ndefend against\nfireballs!', 'and the shot blocker', 'shield-wielding kid', 'fire shield for sale', 'fungus for fire shield', 'shield boy defends again', 'the red shield'), - 'Mirror Shield': (True, False, None, 0x06, 200, 'Now you can\ndefend against\nlasers!', 'and the laser blocker', 'shield-wielding kid', 'face shield for sale', 'fungus for face shield', 'shield boy defends again', 'the mirror shield'), - 'Progressive Shield': (True, False, None, 0x5F, 50, 'have a better\nblocker in\nfront of you', 'and the new shield', 'shield-wielding kid', 'shield for sale', 'fungus for shield', 'shield boy defends again', 'a shield'), + 'Blue Shield': (False, True, None, 0x04, 50, 'Now you can\ndefend against\npebbles!', 'and the stone blocker', 'shield-wielding kid', 'shield for sale', 'fungus for shield', 'shield boy defends again', 'a blue shield'), + 'Red Shield': (False, True, None, 0x05, 500, 'Now you can\ndefend against\nfireballs!', 'and the shot blocker', 'shield-wielding kid', 'fire shield for sale', 'fungus for fire shield', 'shield boy defends again', 'a red shield'), + 'Mirror Shield': (True, False, None, 0x06, 200, 'Now you can\ndefend against\nlasers!', 'and the laser blocker', 'shield-wielding kid', 'face shield for sale', 'fungus for face shield', 'shield boy defends again', 'the Mirror Shield'), + 'Progressive Shield': (True, False, None, 0x5F, 50, 'Have a better\nblocker in\nfront of you', 'and the new shield', 'shield-wielding kid', 'shield for sale', 'fungus for shield', 'shield boy defends again', 'a shield'), 'Bug Catching Net': (True, False, None, 0x21, 50, 'Let\'s catch\nsome bees and\nfaeries!', 'and the bee catcher', 'the bug-catching kid', 'stick web for sale', 'fungus for butterflies', 'wrong boy catches bees again', 'the bug net'), - 'Cane of Byrna': (True, False, None, 0x18, 50, 'Use this to\nbecome\ninvincible!', 'and the bad cane', 'the spark-making kid', 'spark stick for sale', 'spark-stick for trade', 'cane boy encircles again', 'the blue cane'), - 'Boss Heart Container': (False, False, None, 0x3E, 40, 'Maximum health\nincreased!\nYeah!', 'and the full heart', 'the life-giving kid', 'love for sale', 'fungus for life', 'life boy feels love again', 'a heart'), - 'Sanctuary Heart Container': (False, False, None, 0x3F, 50, 'Maximum health\nincreased!\nYeah!', 'and the full heart', 'the life-giving kid', 'love for sale', 'fungus for life', 'life boy feels love again', 'a heart'), + 'Cane of Byrna': (True, False, None, 0x18, 50, 'Use this to\nbecome\ninvincible!', 'and the bad cane', 'the spark-making kid', 'spark stick for sale', 'spark-stick for trade', 'cane boy encircles again', 'the blue Cane'), + 'Boss Heart Container': (False, True, None, 0x3E, 40, 'Maximum health\nincreased!\nYeah!', 'and the full heart', 'the life-giving kid', 'love for sale', 'fungus for life', 'life boy feels love again', 'a heart'), + 'Sanctuary Heart Container': (False, True, None, 0x3F, 50, 'Maximum health\nincreased!\nYeah!', 'and the full heart', 'the life-giving kid', 'love for sale', 'fungus for life', 'life boy feels love again', 'a heart'), 'Piece of Heart': (False, False, None, 0x17, 10, 'Just a little\npiece of love!', 'and the broken heart', 'the life-giving kid', 'little love for sale', 'fungus for life', 'life boy feels some love again', 'a heart piece'), 'Rupee (1)': (False, False, None, 0x34, 0, 'Just pocket\nchange. Move\nright along.', 'the pocket change', 'poverty-struck kid', 'life lesson for sale', 'buying cheap drugs', 'destitute boy has snack again', 'a green rupee'), 'Rupees (5)': (False, False, None, 0x35, 2, 'Just pocket\nchange. Move\nright along.', 'the pocket change', 'poverty-struck kid', 'life lesson for sale', 'buying cheap drugs', 'destitute boy has snack again', 'a blue rupee'), @@ -103,14 +108,14 @@ item_table = {'Bow': (True, False, None, 0x0B, 200, 'You have\nchosen the\narche 'Rupees (50)': (False, False, None, 0x41, 25, 'A rupee pile!\nOkay?', 'and the rupee pile', 'the well-off kid', 'life lesson for sale', 'buying okay drugs', 'destitute boy has dinner again', 'fifty rupees'), 'Rupees (100)': (False, False, None, 0x40, 50, 'A rupee stash!\nHell yeah!', 'and the rupee stash', 'the kind-of-rich kid', 'life lesson for sale', 'buying good drugs', 'affluent boy goes drinking again', 'one hundred rupees'), 'Rupees (300)': (False, False, None, 0x46, 150, 'A rupee hoard!\nHell yeah!', 'and the rupee hoard', 'the really-rich kid', 'life lesson for sale', 'buying the best drugs', 'fat-cat boy is rich again', 'three hundred rupees'), - 'Rupoor': (False, False, None, 0x59, 0, 'a debt collector', 'and the toll-booth', 'the toll-booth kid', 'double loss for sale', 'witch stole your rupees', 'affluent boy steals rupees', 'a rupoor'), - 'Red Clock': (False, True, None, 0x5B, 0, 'a waste of time', 'the ruby clock', 'the ruby-time kid', 'red time for sale', 'for ruby time', 'moment boy travels time again', 'a red clock'), - 'Blue Clock': (False, True, None, 0x5C, 50, 'a bit of time', 'the sapphire clock', 'sapphire-time kid', 'blue time for sale', 'for sapphire time', 'moment boy time travels again', 'a blue clock'), - 'Green Clock': (False, True, None, 0x5D, 200, 'a lot of time', 'the emerald clock', 'the emerald-time kid', 'green time for sale', 'for emerald time', 'moment boy adjusts time again', 'a red clock'), - 'Single RNG': (False, True, None, 0x62, 300, 'something you don\'t yet have', None, None, None, None, 'unknown boy somethings again', 'a new mystery'), - 'Multi RNG': (False, True, None, 0x63, 100, 'something you may already have', None, None, None, None, 'unknown boy somethings again', 'a total mystery'), - 'Magic Upgrade (1/2)': (True, False, None, 0x4E, 50, 'Your magic\npower has been\ndoubled!', 'and the spell power', 'the magic-saving kid', 'wizardry for sale', 'mekalekahi mekahiney ho', 'magic boy saves magic again', 'half magic'), # can be required to beat mothula in an open seed in very very rare circumstance - 'Magic Upgrade (1/4)': (True, False, None, 0x4F, 100, 'Your magic\npower has been\nquadrupled!', 'and the spell power', 'the magic-saving kid', 'wizardry for sale', 'mekalekahi mekahiney ho', 'magic boy saves magic again', 'quarter magic'), # can be required to beat mothula in an open seed in very very rare circumstance + 'Rupoor': (False, False, None, 0x59, 0, 'A debt collector', 'and the toll-booth', 'the toll-booth kid', 'double loss for sale', 'witch stole your rupees', 'affluent boy steals rupees', 'a rupoor'), + 'Red Clock': (False, True, None, 0x5B, 0, 'A waste of time', 'the ruby clock', 'the ruby-time kid', 'red time for sale', 'for ruby time', 'moment boy travels time again', 'a red clock'), + 'Blue Clock': (False, True, None, 0x5C, 50, 'A bit of time', 'the sapphire clock', 'sapphire-time kid', 'blue time for sale', 'for sapphire time', 'moment boy time travels again', 'a blue clock'), + 'Green Clock': (False, True, None, 0x5D, 200, 'A lot of time', 'the emerald clock', 'the emerald-time kid', 'green time for sale', 'for emerald time', 'moment boy adjusts time again', 'a red clock'), + 'Single RNG': (False, True, None, 0x62, 300, 'Something you don\'t yet have', None, None, None, None, 'unknown boy somethings again', 'a new mystery'), + 'Multi RNG': (False, True, None, 0x63, 100, 'Something you may already have', None, None, None, None, 'unknown boy somethings again', 'a total mystery'), + 'Magic Upgrade (1/2)': (True, False, None, 0x4E, 50, 'Your magic\npower has been\ndoubled!', 'and the spell power', 'the magic-saving kid', 'wizardry for sale', 'mekalekahi mekahiney ho', 'magic boy saves magic again', 'Half Magic'), # can be required to beat mothula in an open seed in very very rare circumstance + 'Magic Upgrade (1/4)': (True, False, None, 0x4F, 100, 'Your magic\npower has been\nquadrupled!', 'and the spell power', 'the magic-saving kid', 'wizardry for sale', 'mekalekahi mekahiney ho', 'magic boy saves magic again', 'Quarter Magic'), # can be required to beat mothula in an open seed in very very rare circumstance 'Small Key (Eastern Palace)': (False, False, 'SmallKey', 0xA2, 40, 'A small key to Armos Knights', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Eastern Palace'), 'Big Key (Eastern Palace)': (False, False, 'BigKey', 0x9D, 60, 'A big key to Armos Knights', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Eastern Palace'), 'Compass (Eastern Palace)': (False, True, 'Compass', 0x8D, 10, 'Now you can find the Armos Knights!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Eastern Palace'), @@ -161,7 +166,7 @@ item_table = {'Bow': (True, False, None, 0x0B, 200, 'You have\nchosen the\narche 'Map (Turtle Rock)': (False, True, 'Map', 0x73, 20, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Turtle Rock'), 'Small Key (Ganons Tower)': (False, False, 'SmallKey', 0xAD, 40, 'A small key to the evil tower', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Ganon\'s Tower'), 'Big Key (Ganons Tower)': (False, False, 'BigKey', 0x92, 60, 'A big key to the evil tower', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Ganon\'s Tower'), - 'Compass (Ganons Tower)': (False, True, 'Compass', 0x82, 10, 'Now you can find Agahnim!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a comapss to Ganon\'s Tower'), + 'Compass (Ganons Tower)': (False, True, 'Compass', 0x82, 10, 'Now you can find Agahnim!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Ganon\'s Tower'), 'Map (Ganons Tower)': (False, True, 'Map', 0x72, 10, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Ganon\'s Tower'), 'Small Key (Universal)': (False, True, None, 0xAF, 100, 'A small key for any door', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key'), 'Nothing': (False, False, None, 0x5A, 1, 'Some Hot Air', 'and the Nothing', 'the zen kid', 'outright theft', 'shroom theft', 'empty boy is bored again', 'nothing'), @@ -184,6 +189,7 @@ item_table = {'Bow': (True, False, None, 0x0B, 200, 'You have\nchosen the\narche 'Maiden Rescued': (True, False, 'Event', 999, None, None, None, None, None, None, None, None), 'Maiden Unmasked': (True, False, 'Event', 999, None, None, None, None, None, None, None, None), 'Convenient Block': (True, False, 'Event', 999, None, None, None, None, None, None, None, None), + 'Hidden Pits': (True, False, 'Event', 999, None, None, None, None, None, None, None, None), 'Zelda Herself': (True, False, 'Event', 999, None, None, None, None, None, None, None, None), 'Zelda Delivered': (True, False, 'Event', 999, None, None, None, None, None, None, None, None), } diff --git a/KeyDoorShuffle.py b/KeyDoorShuffle.py index 945ad601..b600f814 100644 --- a/KeyDoorShuffle.py +++ b/KeyDoorShuffle.py @@ -5,7 +5,8 @@ from collections import defaultdict, deque from BaseClasses import DoorType, dungeon_keys, KeyRuleType, RegionType from Regions import dungeon_events from Dungeons import dungeon_keys, dungeon_bigs, dungeon_table -from DungeonGenerator import ExplorationState, special_big_key_doors +from DungeonGenerator import ExplorationState, special_big_key_doors, count_locations_exclude_big_chest, prize_or_event +from DungeonGenerator import reserved_location, blind_boss_unavail class KeyLayout(object): @@ -61,9 +62,9 @@ class KeyLogic(object): self.sm_doors = {} self.prize_location = None - def check_placement(self, unplaced_keys, big_key_loc=None, prize_loc=None, cr_count=7): + def check_placement(self, unplaced_keys, wild_keys, big_key_loc=None, prize_loc=None, cr_count=7): for rule in self.placement_rules: - if not rule.is_satisfiable(self.outside_keys, unplaced_keys, big_key_loc, prize_loc, cr_count): + if not rule.is_satisfiable(self.outside_keys, wild_keys, unplaced_keys, big_key_loc, prize_loc, cr_count): return False if big_key_loc: for rule_a, rule_b in itertools.combinations(self.placement_rules, 2): @@ -157,7 +158,7 @@ class PlacementRule(object): left -= rule_needed return False - def is_satisfiable(self, outside_keys, unplaced_keys, big_key_loc, prize_location, cr_count): + def is_satisfiable(self, outside_keys, wild_keys, unplaced_keys, big_key_loc, prize_location, cr_count): if self.prize_relevance and prize_location: if self.prize_relevance == 'BigBomb': if prize_location.item.name not in ['Crystal 5', 'Crystal 6']: @@ -185,15 +186,20 @@ class PlacementRule(object): if not bk_blocked and check_locations is None: return True available_keys = outside_keys - empty_chests = 0 + # todo: sometimes we need an extra empty chest to accomodate the big key too + # dungeon bias seed 563518200 for example threshold = self.needed_keys_wo_bk if bk_blocked else self.needed_keys_w_bk - for loc in check_locations: - if not loc.item: - empty_chests += 1 - elif loc.item and loc.item.name == self.small_key: - available_keys += 1 - place_able_keys = min(empty_chests, unplaced_keys) - available_keys += place_able_keys + if not wild_keys: + empty_chests = 0 + for loc in check_locations: + if not loc.item: + empty_chests += 1 + elif loc.item and loc.item.name == self.small_key: + available_keys += 1 + place_able_keys = min(empty_chests, unplaced_keys) + available_keys += place_able_keys + else: + available_keys += unplaced_keys return available_keys >= threshold @@ -1100,40 +1106,30 @@ def location_is_bk_locked(loc, key_logic): return loc in key_logic.bk_chests or loc in key_logic.bk_locked -def prize_or_event(loc): - return loc.name in dungeon_events or '- Prize' in loc.name or loc.name in ['Agahnim 1', 'Agahnim 2'] - - -def boss_unavail(loc, world, player): - # todo: ambrosia - # return world.bossdrops[player] == 'ambrosia' and "- Boss" in loc.name - return False - - -def blind_boss_unavail(loc, state, world, player): - if loc.name == "Thieves' Town - Boss": - # todo: check attic - return (loc.parent_region.dungeon.boss.name == 'Blind' and - (not any(x for x in state.found_locations if x.name == 'Suspicious Maiden') or - (world.get_region('Thieves Attic Window', player).dungeon.name == 'Thieves Town' and - not any(x for x in state.found_locations if x.name == 'Attic Cracked Floor')))) - return False +# todo: verfiy this code is defunct +# def prize_or_event(loc): +# return loc.name in dungeon_events or '- Prize' in loc.name or loc.name in ['Agahnim 1', 'Agahnim 2'] +# +# +# def reserved_location(loc, world, player): +# return loc in world.item_pool.config.reserved_locations[player] +# +# +# def blind_boss_unavail(loc, state, world, player): +# if loc.name == "Thieves' Town - Boss": +# return (loc.parent_region.dungeon.boss.name == 'Blind' and +# (not any(x for x in state.found_locations if x.name == 'Suspicious Maiden') or +# (world.get_region('Thieves Attic Window', player).dungeon.name == 'Thieves Town' and +# not any(x for x in state.found_locations if x.name == 'Attic Cracked Floor')))) +# return False +# counts free locations for keys - hence why reserved locations don't count def count_free_locations(state, world, player): cnt = 0 for loc in state.found_locations: - if (not prize_or_event(loc) and not loc.forced_item and not boss_unavail(loc, world, player) - and not blind_boss_unavail(loc, state, world, player)): - cnt += 1 - return cnt - - -def count_locations_exclude_big_chest(state, world, player): - cnt = 0 - for loc in state.found_locations: - if ('- Big Chest' not in loc.name and not loc.forced_item and not boss_unavail(loc, world, player) - and not prize_or_event(loc) and not blind_boss_unavail(loc, state, world, player)): + if (not prize_or_event(loc) and not loc.forced_item and not reserved_location(loc, world, player) + and not blind_boss_unavail(loc, state.found_locations, world, player)): cnt += 1 return cnt @@ -1437,7 +1433,7 @@ def validate_key_layout_sub_loop(key_layout, state, checked_states, flat_proposa if state.big_key_opened: ttl_locations = count_free_locations(state, world, player) else: - ttl_locations = count_locations_exclude_big_chest(state, world, player) + ttl_locations = count_locations_exclude_big_chest(state.found_locations, world, player) ttl_small_key_only = count_small_key_only_locations(state) available_small_locations = cnt_avail_small_locations(ttl_locations, ttl_small_key_only, state, world, player) available_big_locations = cnt_avail_big_locations(ttl_locations, state, world, player) @@ -1663,7 +1659,7 @@ def can_open_door(door, state, world, player): if state.big_key_opened: ttl_locations = count_free_locations(state, world, player) else: - ttl_locations = count_locations_exclude_big_chest(state, world, player) + ttl_locations = count_locations_exclude_big_chest(state.found_locations, world, player) if door.smallKey: ttl_small_key_only = count_small_key_only_locations(state) available_small_locations = cnt_avail_small_locations(ttl_locations, ttl_small_key_only, state, world, player) diff --git a/Main.py b/Main.py index 7dc012e7..d1f4542d 100644 --- a/Main.py +++ b/Main.py @@ -1,4 +1,3 @@ -from collections import OrderedDict import copy from itertools import zip_longest import json @@ -14,22 +13,25 @@ from Bosses import place_bosses from Items import ItemFactory from KeyDoorShuffle import validate_key_placement from OverworldGlitchRules import create_owg_connections -from PotShuffle import shuffle_pots +from PotShuffle import shuffle_pots, shuffle_pot_switches from Regions import create_regions, create_shops, mark_light_world_regions, create_dungeon_regions, adjust_locations from InvertedRegions import create_inverted_regions, mark_dark_world_regions from EntranceShuffle import link_entrances, link_inverted_entrances from Rom import patch_rom, patch_race_rom, patch_enemizer, apply_rom_settings, LocalRom, JsonRom, get_hash_string from Doors import create_doors -from DoorShuffle import link_doors, connect_portal +from DoorShuffle import link_doors, connect_portal, link_doors_prep from RoomData import create_rooms from Rules import set_rules -from Dungeons import create_dungeons, fill_dungeons, fill_dungeons_restrictive -from Fill import distribute_items_cutoff, distribute_items_staleness, distribute_items_restrictive, flood_items +from Dungeons import create_dungeons +from Fill import distribute_items_restrictive, promote_dungeon_items, fill_dungeons_restrictive, ensure_good_pots from Fill import sell_potions, sell_keys, balance_multiworld_progression, balance_money_progression, lock_shop_locations from ItemList import generate_itempool, difficulties, fill_prizes, customize_shops from Utils import output_path, parse_player_names -__version__ = '1.0.1-dev' +from source.item.FillUtil import create_item_pool_config, massage_item_pool, district_item_pool_config +from source.tools.BPS import create_bps_from_data + +__version__ = '1.1.0-dev' from source.classes.BabelFish import BabelFish @@ -94,16 +96,21 @@ def main(args, seed=None, fish=None): world.intensity = {player: random.randint(1, 3) if args.intensity[player] == 'random' else int(args.intensity[player]) for player in range(1, world.players + 1)} world.experimental = args.experimental.copy() world.dungeon_counters = args.dungeon_counters.copy() - world.potshuffle = args.shufflepots.copy() world.fish = fish world.shopsanity = args.shopsanity.copy() - world.keydropshuffle = args.keydropshuffle.copy() + world.dropshuffle = args.dropshuffle.copy() + world.pottery = args.pottery.copy() + world.potshuffle = args.shufflepots.copy() world.mixed_travel = args.mixed_travel.copy() world.standardize_palettes = args.standardize_palettes.copy() - world.treasure_hunt_count = args.triforce_goal.copy() - world.treasure_hunt_total = args.triforce_pool.copy() + world.treasure_hunt_count = {k: int(v) for k, v in args.triforce_goal.items()} + world.treasure_hunt_total = {k: int(v) for k, v in args.triforce_pool.items()} world.shufflelinks = args.shufflelinks.copy() world.pseudoboots = args.pseudoboots.copy() + world.overworld_map = args.overworld_map.copy() + world.restrict_boss_items = args.restrict_boss_items.copy() + world.collection_rate = args.collection_rate.copy() + world.colorizepots = args.colorizepots.copy() world.rom_seeds = {player: random.randint(0, 999999999) for player in range(1, world.players + 1)} @@ -123,6 +130,8 @@ def main(args, seed=None, fish=None): world.player_names[player].append(name) logger.info('') + outfilebase = f'DR_{args.outputname if args.outputname else world.seed}' + for player in range(1, world.players + 1): world.difficulty_requirements[player] = difficulties[world.difficulty[player]] @@ -130,11 +139,19 @@ def main(args, seed=None, fish=None): if hasattr(world,"escape_assist") and player in world.escape_assist: world.escape_assist[player].append('bombs') # enemized escape assumes infinite bombs available and will likely be unbeatable without it - for tok in filter(None, args.startinventory[player].split(',')): - item = ItemFactory(tok.strip(), player) - if item: - world.push_precollected(item) + if args.usestartinventory[player]: + for tok in filter(None, args.startinventory[player].split(',')): + item = ItemFactory(tok.strip(), player) + if item: + world.push_precollected(item) + if args.create_spoiler and not args.jsonout: + logger.info(world.fish.translate("cli", "cli", "create.meta")) + world.spoiler.meta_to_file(output_path(f'{outfilebase}_Spoiler.txt')) + if args.mystery and not args.suppress_meta: + world.spoiler.mystery_meta_to_file(output_path(f'{outfilebase}_meta.txt')) + + for player in range(1, world.players + 1): if world.mode[player] != 'inverted': create_regions(world, player) else: @@ -153,7 +170,10 @@ def main(args, seed=None, fish=None): logger.info(world.fish.translate("cli", "cli", "shuffling.pots")) for player in range(1, world.players + 1): if world.potshuffle[player]: - shuffle_pots(world, player) + if world.pottery[player] in ['none', 'cave', 'keys', 'cavekeys']: + shuffle_pots(world, player) + else: + shuffle_pot_switches(world, player) logger.info(world.fish.translate("cli","cli","shuffling.world")) @@ -163,7 +183,13 @@ def main(args, seed=None, fish=None): else: link_inverted_entrances(world, player) - logger.info(world.fish.translate("cli","cli","shuffling.dungeons")) + logger.info(world.fish.translate("cli", "cli", "shuffling.prep")) + for player in range(1, world.players + 1): + link_doors_prep(world, player) + + create_item_pool_config(world) + + logger.info(world.fish.translate("cli", "cli", "shuffling.dungeons")) for player in range(1, world.players + 1): link_doors(world, player) @@ -171,8 +197,7 @@ def main(args, seed=None, fish=None): mark_light_world_regions(world, player) else: mark_dark_world_regions(world, player) - logger.info(world.fish.translate("cli","cli","generating.itempool")) - logger.info(world.fish.translate("cli","cli","generating.itempool")) + logger.info(world.fish.translate("cli", "cli", "generating.itempool")) for player in range(1, world.players + 1): generate_itempool(world, player) @@ -182,6 +207,7 @@ def main(args, seed=None, fish=None): for player in range(1, world.players + 1): set_rules(world, player) + district_item_pool_config(world) for player in range(1, world.players + 1): if world.shopsanity[player]: sell_potions(world, player) @@ -190,8 +216,8 @@ def main(args, seed=None, fish=None): else: lock_shop_locations(world, player) - - logger.info(world.fish.translate("cli","cli","placing.dungeon.prizes")) + massage_item_pool(world) + logger.info(world.fish.translate("cli", "cli", "placing.dungeon.prizes")) fill_prizes(world) @@ -200,14 +226,12 @@ def main(args, seed=None, fish=None): logger.info(world.fish.translate("cli","cli","placing.dungeon.items")) - shuffled_locations = None - if args.algorithm in ['balanced', 'vt26'] or any(list(args.mapshuffle.values()) + list(args.compassshuffle.values()) + - list(args.keyshuffle.values()) + list(args.bigkeyshuffle.values())): + if args.algorithm != 'equitable': shuffled_locations = world.get_unfilled_locations() random.shuffle(shuffled_locations) fill_dungeons_restrictive(world, shuffled_locations) else: - fill_dungeons(world) + promote_dungeon_items(world) for player in range(1, world.players+1): if world.logic[player] != 'nologic': @@ -225,41 +249,28 @@ def main(args, seed=None, fish=None): logger.info(world.fish.translate("cli","cli","fill.world")) - if args.algorithm == 'flood': - flood_items(world) # different algo, biased towards early game progress items - elif args.algorithm == 'vt21': - distribute_items_cutoff(world, 1) - elif args.algorithm == 'vt22': - distribute_items_cutoff(world, 0.66) - elif args.algorithm == 'freshness': - distribute_items_staleness(world) - elif args.algorithm == 'vt25': - distribute_items_restrictive(world, False) - elif args.algorithm == 'vt26': - - distribute_items_restrictive(world, True, shuffled_locations) - elif args.algorithm == 'balanced': - distribute_items_restrictive(world, True) + distribute_items_restrictive(world, True) if world.players > 1: - logger.info(world.fish.translate("cli","cli","balance.multiworld")) - balance_multiworld_progression(world) + logger.info(world.fish.translate("cli", "cli", "balance.multiworld")) + if args.algorithm in ['balanced', 'equitable']: + balance_multiworld_progression(world) # if we only check for beatable, we can do this sanity check first before creating the rom if not world.can_beat_game(log_error=True): - raise RuntimeError(world.fish.translate("cli","cli","cannot.beat.game")) + raise RuntimeError(world.fish.translate("cli", "cli", "cannot.beat.game")) for player in range(1, world.players+1): if world.shopsanity[player]: customize_shops(world, player) - balance_money_progression(world) - - outfilebase = f'DR_{args.outputname if args.outputname else world.seed}' + if args.algorithm in ['balanced', 'equitable']: + balance_money_progression(world) + ensure_good_pots(world, True) rom_names = [] jsonout = {} enemized = False - if not args.suppress_rom: + if not args.suppress_rom or args.bps: logger.info(world.fish.translate("cli","cli","patching.rom")) for team in range(world.teams): for player in range(1, world.players + 1): @@ -285,7 +296,7 @@ def main(args, seed=None, fish=None): logging.warning(enemizerMsg) raise EnemizerError(enemizerMsg) - patch_rom(world, rom, player, team, enemized, bool(args.outputname)) + patch_rom(world, rom, player, team, enemized, bool(args.mystery)) if args.race: patch_race_rom(rom) @@ -296,7 +307,7 @@ def main(args, seed=None, fish=None): apply_rom_settings(rom, args.heartbeep[player], args.heartcolor[player], args.quickswap[player], args.fastmenu[player], args.disablemusic[player], args.sprite[player], args.ow_palettes[player], args.uw_palettes[player], args.reduce_flashing[player], - args.shuffle_sfx[player]) + args.shuffle_sfx[player], args.msu_resume[player]) if args.jsonout: jsonout[f'patch_t{team}_p{player}'] = rom.patches @@ -307,7 +318,14 @@ def main(args, seed=None, fish=None): if world.players > 1 or world.teams > 1: outfilepname += f"_{world.player_names[player][team].replace(' ', '_')}" if world.player_names[player][team] != 'Player %d' % player else '' outfilesuffix = f'_{Settings.make_code(world, player)}' if not args.outputname else '' - rom.write_to_file(output_path(f'{outfilebase}{outfilepname}{outfilesuffix}.sfc')) + if args.bps: + patchfile = output_path(f'{outfilebase}{outfilepname}{outfilesuffix}.bps') + patch = create_bps_from_data(LocalRom(args.rom, patch=False).buffer, rom.buffer) + with open(patchfile, 'wb') as stream: + stream.write(patch.binary_ba) + if not args.suppress_rom: + sfc_file = output_path(f'{outfilebase}{outfilepname}{outfilesuffix}.sfc') + rom.write_to_file(sfc_file) if world.players > 1: multidata = zlib.compress(json.dumps({"names": parsed_names, @@ -323,6 +341,14 @@ def main(args, seed=None, fish=None): with open(output_path('%s_multidata' % outfilebase), 'wb') as f: f.write(multidata) + if args.mystery and not args.suppress_meta: + world.spoiler.hashes_to_file(output_path(f'{outfilebase}_meta.txt')) + elif args.create_spoiler and not args.jsonout: + world.spoiler.hashes_to_file(output_path(f'{outfilebase}_Spoiler.txt')) + if args.create_spoiler and not args.jsonout: + logger.info(world.fish.translate("cli", "cli", "patching.spoiler")) + world.spoiler.to_file(output_path(f'{outfilebase}_Spoiler.txt')) + if not args.skip_playthrough: logger.info(world.fish.translate("cli","cli","calc.playthrough")) create_playthrough(world) @@ -335,7 +361,7 @@ def main(args, seed=None, fish=None): with open(output_path('%s_Spoiler.json' % outfilebase), 'w') as outfile: outfile.write(world.spoiler.to_json()) else: - world.spoiler.to_file(output_path('%s_Spoiler.txt' % outfilebase)) + world.spoiler.playthrough_to_file(output_path(f'{outfilebase}_Spoiler.txt')) YES = world.fish.translate("cli","cli","yes") NO = world.fish.translate("cli","cli","no") @@ -401,11 +427,12 @@ def copy_world(world): ret.intensity = world.intensity.copy() ret.experimental = world.experimental.copy() ret.shopsanity = world.shopsanity.copy() - ret.keydropshuffle = world.keydropshuffle.copy() + ret.dropshuffle = world.dropshuffle.copy() + ret.pottery = world.pottery.copy() + ret.potshuffle = world.potshuffle.copy() ret.mixed_travel = world.mixed_travel.copy() ret.standardize_palettes = world.standardize_palettes.copy() - - ret.exp_cache = world.exp_cache.copy() + ret.restrict_boss_items = world.restrict_boss_items.copy() for player in range(1, world.players + 1): if world.mode[player] != 'inverted': @@ -419,6 +446,9 @@ def copy_world(world): if world.logic[player] in ('owglitches', 'nologic'): create_owg_connections(ret, player) + # there are region references here they must be migrated to preserve integrity + # ret.exp_cache = world.exp_cache.copy() + copy_dynamic_regions_and_locations(world, ret) for player in range(1, world.players + 1): if world.mode[player] == 'standard': @@ -467,6 +497,7 @@ def copy_world(world): new_location.access_rule = lambda state: True new_location.item_rule = lambda state: True new_location.forced_item = location.forced_item + new_location.pot = location.pot # copy remaining itempool. No item in itempool should have an assigned location for item in world.itempool: @@ -518,11 +549,7 @@ def copy_dynamic_regions_and_locations(world, ret): for location in world.dynamic_locations: new_reg = ret.get_region(location.parent_region.name, location.parent_region.player) new_loc = Location(location.player, location.name, location.address, location.crystal, location.hint_text, new_reg) - # todo: this is potentially dangerous. later refactor so we - # can apply dynamic region rules on top of copied world like other rules - new_loc.access_rule = location.access_rule - new_loc.always_allow = location.always_allow - new_loc.item_rule = location.item_rule + new_loc.type = location.type new_reg.locations.append(new_loc) ret.clear_location_cache() @@ -535,7 +562,7 @@ def create_playthrough(world): # get locations containing progress items prog_locations = [location for location in world.get_filled_locations() if location.item.advancement] - optional_locations = ['Trench 1 Switch', 'Trench 2 Switch', 'Ice Block Drop'] + optional_locations = ['Trench 1 Switch', 'Trench 2 Switch', 'Ice Block Drop', 'Skull Star Tile'] state_cache = [None] collection_spheres = [] state = CollectionState(world) @@ -578,11 +605,11 @@ def create_playthrough(world): # todo: this is not very efficient, but I'm not sure how else to do it for this backwards logic # world.clear_exp_cache() if world.can_beat_game(state_cache[num]): - # logging.getLogger('').debug(f'{old_item.name} (Player {old_item.player}) is not required') + logging.getLogger('').debug(f'{old_item.name} (Player {old_item.player}) is not required') to_delete.add(location) else: # still required, got to keep it around - # logging.getLogger('').debug(f'{old_item.name} (Player {old_item.player}) is required') + logging.getLogger('').debug(f'{old_item.name} (Player {old_item.player}) is required') location.item = old_item # cull entries in spheres for spoiler walkthrough at end diff --git a/MultiClient.py b/MultiClient.py index 501630e0..fbde673d 100644 --- a/MultiClient.py +++ b/MultiClient.py @@ -8,8 +8,10 @@ import shlex import urllib.parse import websockets +from BaseClasses import PotItem, PotFlags import Items import Regions +import PotShuffle class ReceivedItem: @@ -57,8 +59,13 @@ class Context: self.key_drop_mode = False self.shop_mode = False self.retro_mode = False + self.pottery_mode = False + self.mystery_mode = False self.ignore_count = 0 + self.lookup_name_to_id = {} + self.lookup_id_to_name = {} + def color_code(*args): codes = {'reset': 0, 'bold': 1, 'underline': 4, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33, 'blue': 34, 'magenta': 35, 'cyan': 36, 'white': 37 , 'black_bg': 40, 'red_bg': 41, 'green_bg': 42, 'yellow_bg': 43, @@ -83,6 +90,12 @@ INGAME_MODES = {0x07, 0x09, 0x0b} SAVEDATA_START = WRAM_START + 0xF000 SAVEDATA_SIZE = 0x500 +POT_ITEMS_SRAM_START = WRAM_START + 0x016018 # 2 bytes per room +SPRITE_ITEMS_SRAM_START = WRAM_START + 0x016268 # 2 bytes per room +SHOP_SRAM_START = WRAM_START + 0x0164B8 # 2 bytes? +ITEM_SRAM_SIZE = 0x250 +SHOP_SRAM_LEN = 0x29 # 41 tracked items + RECV_PROGRESS_ADDR = SAVEDATA_START + 0x4D0 # 2 bytes RECV_ITEM_ADDR = SAVEDATA_START + 0x4D2 # 1 byte RECV_ITEM_PLAYER_ADDR = SAVEDATA_START + 0x4D3 # 1 byte @@ -92,11 +105,9 @@ SCOUT_LOCATION_ADDR = SAVEDATA_START + 0x4D7 # 1 byte SCOUTREPLY_LOCATION_ADDR = SAVEDATA_START + 0x4D8 # 1 byte SCOUTREPLY_ITEM_ADDR = SAVEDATA_START + 0x4D9 # 1 byte SCOUTREPLY_PLAYER_ADDR = SAVEDATA_START + 0x4DA # 1 byte -SHOP_ADDR = SAVEDATA_START + 0x302 # 2 bytes? DYNAMIC_TOTAL_ADDR = SAVEDATA_START + 0x33E # 2 bytes MODE_FLAGS = SAVEDATA_START + 0x33D # 1 byte -SHOP_SRAM_LEN = 0x29 # 41 tracked items location_shop_order = [Regions.shop_to_location_table.keys()] + [Regions.retro_shops.keys()] location_shop_ids = {0x0112, 0x0110, 0x010F, 0x00FF, 0x011F, 0x0109, 0x0115} @@ -349,6 +360,8 @@ location_table_misc = {'Bottle Merchant': (0x3c9, 0x2), 'Purple Chest': (0x3c9, 0x10), "Link's Uncle": (0x3c6, 0x1), 'Hobo': (0x3c9, 0x1)} +location_table_pot_items = {} +location_table_sprite_items = {} SNES_DISCONNECTED = 0 SNES_CONNECTING = 1 @@ -676,7 +689,7 @@ async def process_server_cmd(ctx : Context, cmd, args): ctx.player_names = {p: n for p, n in args[1]} msgs = [] if ctx.locations_checked: - msgs.append(['LocationChecks', [Regions.lookup_name_to_id[loc] for loc in ctx.locations_checked]]) + msgs.append(['LocationChecks', [ctx.lookup_name_to_id[loc] for loc in ctx.locations_checked]]) if ctx.locations_scouted: msgs.append(['LocationScouts', list(ctx.locations_scouted)]) if msgs: @@ -689,7 +702,7 @@ async def process_server_cmd(ctx : Context, cmd, args): elif start_index != len(ctx.items_received): sync_msg = [['Sync']] if ctx.locations_checked: - sync_msg.append(['LocationChecks', [Regions.lookup_name_to_id[loc] for loc in ctx.locations_checked]]) + sync_msg.append(['LocationChecks', [ctx.lookup_name_to_id[loc] for loc in ctx.locations_checked]]) await send_msgs(ctx.socket, sync_msg) if start_index == len(ctx.items_received): for item in items: @@ -701,7 +714,7 @@ async def process_server_cmd(ctx : Context, cmd, args): if location not in ctx.locations_info: replacements = {0xA2: 'Small Key', 0x9D: 'Big Key', 0x8D: 'Compass', 0x7D: 'Map'} item_name = replacements.get(item, get_item_name_from_id(item)) - logging.info(f"Saw {color(item_name, 'red', 'bold')} at {list(Regions.lookup_id_to_name.keys())[location - 1]}") + logging.info(f"Saw {color(item_name, 'red', 'bold')} at {list(ctx.lookup_id_to_name.keys())[location - 1]}") ctx.locations_info[location] = (item, player) ctx.watcher_event.set() @@ -710,7 +723,8 @@ async def process_server_cmd(ctx : Context, cmd, args): item = color(get_item_name_from_id(item), 'cyan' if player_sent != ctx.slot else 'green') player_sent = color(ctx.player_names[player_sent], 'yellow' if player_sent != ctx.slot else 'magenta') player_recvd = color(ctx.player_names[player_recvd], 'yellow' if player_recvd != ctx.slot else 'magenta') - logging.info('%s sent %s to %s (%s)' % (player_sent, item, player_recvd, get_location_name_from_address(location))) + location_name = get_location_name_from_address(ctx, location) + logging.info('%s sent %s to %s (%s)' % (player_sent, item, player_recvd, location_name)) if cmd == 'Print': logging.info(args) @@ -779,10 +793,10 @@ async def console_loop(ctx : Context): for index, item in enumerate(ctx.items_received, 1): logging.info('%s from %s (%s) (%d/%d in list)' % ( color(get_item_name_from_id(item.item), 'red', 'bold'), color(ctx.player_names[item.player], 'yellow'), - get_location_name_from_address(item.location), index, len(ctx.items_received))) + get_location_name_from_address(ctx, item.location), index, len(ctx.items_received))) if command[0] == '/missing': - for location in [k for k, v in Regions.lookup_name_to_id.items() + for location in [k for k, v in ctx.lookup_name_to_id.items() if type(v) is int and not filter_location(ctx, k)]: if location not in ctx.locations_checked: logging.info('Missing: ' + location) @@ -804,15 +818,19 @@ def get_item_name_from_id(code): return items[0] if items else f'Unknown item (ID:{code})' -def get_location_name_from_address(address): +def get_location_name_from_address(ctx, address): if type(address) is str: return address - return Regions.lookup_id_to_name.get(address, f'Unknown location (ID:{address})') + return ctx.lookup_id_to_name.get(address, f'Unknown location (ID:{address})') def filter_location(ctx, location): - if not ctx.key_drop_mode and ('Key Drop' in location or 'Pot Key' in location): + if (not ctx.key_drop_mode and location in PotShuffle.key_drop_data + and PotShuffle.key_drop_data[location][0] == 'Drop'): + return True + if (not ctx.pottery_mode and location in PotShuffle.key_drop_data + and PotShuffle.key_drop_data[location][0] == 'Pot'): return True if not ctx.shop_mode and location in Regions.flat_normal_shops: return True @@ -821,6 +839,31 @@ def filter_location(ctx, location): return False +def init_lookups(ctx): + ctx.lookup_id_to_name = {x: y for x, y in Regions.lookup_id_to_name.items()} + ctx.lookup_name_to_id = {x: y for x, y in Regions.lookup_name_to_id.items()} + for location, datum in PotShuffle.key_drop_data.items(): + type = datum[0] + if type == 'Drop': + location_id, super_tile, sprite_index = datum[1] + location_table_sprite_items[location] = (2 * super_tile, 0x8000 >> sprite_index) + ctx.lookup_name_to_id[location] = location_id + ctx.lookup_id_to_name[location_id] = location + for super_tile, pot_list in PotShuffle.vanilla_pots.items(): + for pot_index, pot in enumerate(pot_list): + if pot.item != PotItem.Hole: + if pot.item == PotItem.Key: + loc_name = next(loc for loc, datum in PotShuffle.key_drop_data.items() + if datum[1] == super_tile) + else: + descriptor = 'Large Block' if pot.flags & PotFlags.Block else f'Pot #{pot_index+1}' + loc_name = f'{pot.room} {descriptor}' + location_table_pot_items[loc_name] = (2 * super_tile, 0x8000 >> pot_index) + location_id = Regions.pot_address(pot_index, super_tile) + ctx.lookup_name_to_id[loc_name] = location_id + ctx.lookup_id_to_name[location_id] = loc_name + + async def track_locations(ctx : Context, roomid, roomdata): new_locations = [] @@ -832,9 +875,12 @@ async def track_locations(ctx : Context, roomid, roomdata): if ctx.mode_flags is None: flags = await snes_read(ctx, MODE_FLAGS, 1) + ctx.mode_flags = flags ctx.key_drop_mode = flags[0] & 0x1 ctx.shop_mode = flags[0] & 0x2 ctx.retro_mode = flags[0] & 0x4 + ctx.pottery_mode = flags[0] & 0x8 + ctx.mystery_mode = flags[0] & 0x10 def new_check(location): ctx.locations_checked.add(location) @@ -842,12 +888,13 @@ async def track_locations(ctx : Context, roomid, roomdata): if ignored: ctx.ignore_count += 1 else: - logging.info(f"New check: {location} ({len(ctx.locations_checked)-ctx.ignore_count}/{ctx.total_locations})") - new_locations.append(Regions.lookup_name_to_id[location]) + total = '???' if ctx.mystery_mode else ctx.total_locations + logging.info(f"New check: {location} ({len(ctx.locations_checked)-ctx.ignore_count}/{total})") + new_locations.append(ctx.lookup_name_to_id[location]) try: if ctx.shop_mode or ctx.retro_mode: - misc_data = await snes_read(ctx, SHOP_ADDR, SHOP_SRAM_LEN) + misc_data = await snes_read(ctx, SHOP_SRAM_START, SHOP_SRAM_LEN) for cnt, b in enumerate(misc_data): my_check = Regions.shop_table_by_location_id[0x400000 + cnt] if int(b) > 0 and my_check not in ctx.locations_checked: @@ -908,6 +955,22 @@ async def track_locations(ctx : Context, roomid, roomdata): if misc_data[offset - 0x3c6] & mask != 0 and location not in ctx.locations_checked: new_check(location) + if not all([location in ctx.locations_checked for location in location_table_pot_items.keys()]): + pot_items_data = await snes_read(ctx, POT_ITEMS_SRAM_START, ITEM_SRAM_SIZE) + if pot_items_data is not None: + for location, (offset, mask) in location_table_pot_items.items(): + pot_value = pot_items_data[offset] | (pot_items_data[offset + 1] << 8) + if pot_value & mask != 0 and location not in ctx.locations_checked: + new_check(location) + + if not all([location in ctx.locations_checked for location in location_table_sprite_items.keys()]): + sprite_items_data = await snes_read(ctx, SPRITE_ITEMS_SRAM_START, ITEM_SRAM_SIZE) + if sprite_items_data is not None: + for location, (offset, mask) in location_table_sprite_items.items(): + sprite_value = sprite_items_data[offset] | (sprite_items_data[offset + 1] << 8) + if sprite_value & mask != 0 and location not in ctx.locations_checked: + new_check(location) + await send_msgs(ctx.socket, [['LocationChecks', new_locations]]) async def game_watcher(ctx : Context): @@ -955,7 +1018,7 @@ async def game_watcher(ctx : Context): item = ctx.items_received[recv_index] logging.info('Received %s from %s (%s) (%d/%d in list)' % ( color(get_item_name_from_id(item.item), 'red', 'bold'), color(ctx.player_names[item.player], 'yellow'), - get_location_name_from_address(item.location), recv_index + 1, len(ctx.items_received))) + get_location_name_from_address(ctx, item.location), recv_index + 1, len(ctx.items_received))) recv_index += 1 snes_buffered_write(ctx, RECV_PROGRESS_ADDR, bytes([recv_index & 0xFF, (recv_index >> 8) & 0xFF])) snes_buffered_write(ctx, RECV_ITEM_ADDR, bytes([item.item])) @@ -969,7 +1032,7 @@ async def game_watcher(ctx : Context): if scout_location > 0 and scout_location not in ctx.locations_scouted: ctx.locations_scouted.add(scout_location) - logging.info(f'Scouting item at {list(Regions.lookup_id_to_name.keys())[scout_location - 1]}') + logging.info(f'Scouting item at {list(ctx.lookup_id_to_name.keys())[scout_location - 1]}') await send_msgs(ctx.socket, [['LocationScouts', [scout_location]]]) await track_locations(ctx, roomid, roomdata) @@ -984,6 +1047,7 @@ async def main(): logging.basicConfig(format='%(message)s', level=getattr(logging, args.loglevel.upper(), logging.INFO)) ctx = Context(args.snes, args.connect, args.password) + init_lookups(ctx) input_task = asyncio.create_task(console_loop(ctx)) diff --git a/MultiServer.py b/MultiServer.py index cd333edf..4108b8dd 100644 --- a/MultiServer.py +++ b/MultiServer.py @@ -6,12 +6,15 @@ import json import logging import re import shlex +import ssl import urllib.request import websockets import zlib +from BaseClasses import PotItem, PotFlags import Items import Regions +import PotShuffle from MultiClient import ReceivedItem, get_item_name_from_id, get_location_name_from_address class Client: @@ -40,6 +43,9 @@ class Context: self.clients = [] self.received_items = {} + self.lookup_name_to_id = {} + self.lookup_id_to_name = {} + async def send_msgs(websocket, msgs): if not websocket or not websocket.open or websocket.closed: return @@ -154,8 +160,7 @@ def send_new_items(ctx : Context): client.send_index = len(items) def forfeit_player(ctx : Context, team, slot): - all_locations = {values[0] for values in Regions.location_table.values() if type(values[0]) is int} - all_locations.update({values[1] for values in Regions.key_drop_data.values()}) + all_locations = set(ctx.lookup_id_to_name.keys()) notify_all(ctx, "%s (Team #%d) has forfeited" % (ctx.player_names[(team, slot)], team + 1)) register_location_checks(ctx, team, slot, all_locations) @@ -176,7 +181,8 @@ def register_location_checks(ctx : Context, team, slot, locations): recvd_items.append(new_item) if slot != target_player: broadcast_team(ctx, team, [['ItemSent', (slot, location, target_player, target_item)]]) - logging.info('(Team #%d) %s sent %s to %s (%s)' % (team+1, ctx.player_names[(team, slot)], get_item_name_from_id(target_item), ctx.player_names[(team, target_player)], get_location_name_from_address(location))) + loc_name = get_location_name_from_address(ctx, location) + logging.info('(Team #%d) %s sent %s to %s (%s)' % (team+1, ctx.player_names[(team, slot)], get_item_name_from_id(target_item), ctx.player_names[(team, target_player)], loc_name)) found_items = True send_new_items(ctx) @@ -249,11 +255,11 @@ async def process_client_cmd(ctx : Context, client : Client, cmd, args): return locs = [] for location in args: - if type(location) is not int or 0 >= location > len(Regions.lookup_id_to_name.keys()): + if type(location) is not int or 0 >= location > len(ctx.lookup_id_to_name.keys()): await send_msgs(client.socket, [['InvalidArguments', 'LocationScouts']]) return - loc_name = list(Regions.lookup_id_to_name.keys())[location - 1] - target_item, target_player = ctx.locations[(Regions.lookup_name_to_id[loc_name], client.slot)] + loc_name = list(ctx.lookup_id_to_name.keys())[location - 1] + target_item, target_player = ctx.locations[(ctx.lookup_name_to_id[loc_name], client.slot)] replacements = {'SmallKey': 0xA2, 'BigKey': 0x9D, 'Compass': 0x8D, 'Map': 0x7D} item_type = [i[2] for i in Items.item_table.values() if type(i[3]) is int and i[3] == target_item] @@ -339,6 +345,30 @@ async def console(ctx : Context): if command[0][0] != '/': notify_all(ctx, '[Server]: ' + input) + +def init_lookups(ctx): + ctx.lookup_id_to_name = {x: y for x, y in Regions.lookup_id_to_name.items()} + ctx.lookup_name_to_id = {x: y for x, y in Regions.lookup_name_to_id.items()} + for location, datum in PotShuffle.key_drop_data.items(): + type = datum[0] + if type == 'Drop': + location_id = datum[1][0] + ctx.lookup_name_to_id[location] = location_id + ctx.lookup_id_to_name[location_id] = location + for super_tile, pot_list in PotShuffle.vanilla_pots.items(): + for pot_index, pot in enumerate(pot_list): + if pot.item != PotItem.Hole: + if pot.item == PotItem.Key: + loc_name = next(loc for loc, datum in PotShuffle.key_drop_data.items() + if datum[1] == super_tile) + else: + descriptor = 'Large Block' if pot.flags & PotFlags.Block else f'Pot #{pot_index+1}' + loc_name = f'{pot.room} {descriptor}' + location_id = Regions.pot_address(pot_index, super_tile) + ctx.lookup_name_to_id[loc_name] = location_id + ctx.lookup_id_to_name[location_id] = loc_name + + async def main(): parser = argparse.ArgumentParser() parser.add_argument('--host', default=None) @@ -353,7 +383,7 @@ async def main(): logging.basicConfig(format='[%(asctime)s] %(message)s', level=getattr(logging, args.loglevel.upper(), logging.INFO)) ctx = Context(args.host, args.port, args.password) - + init_lookups(ctx) ctx.data_filename = args.multidata try: @@ -376,7 +406,7 @@ async def main(): logging.error('Failed to read multiworld data (%s)' % e) return - ip = urllib.request.urlopen('https://v4.ident.me').read().decode('utf8') if not ctx.host else ctx.host + ip = urllib.request.urlopen('https://v4.ident.me', context=ssl._create_unverified_context()).read().decode('utf8') if not ctx.host else ctx.host logging.info('Hosting game at %s:%d (%s)' % (ip, ctx.port, 'No password' if not ctx.password else 'Password: %s' % ctx.password)) ctx.disable_save = args.disable_save diff --git a/Mystery.py b/Mystery.py index 9cf0e729..568ff6a8 100644 --- a/Mystery.py +++ b/Mystery.py @@ -1,5 +1,7 @@ import argparse import logging +from pathlib import Path +import os import RaceRandom as random import urllib.request import urllib.parse @@ -28,6 +30,9 @@ def main(): parser.add_argument('--names', default='') parser.add_argument('--teams', default=1, type=lambda value: max(int(value), 1)) parser.add_argument('--create_spoiler', action='store_true') + parser.add_argument('--suppress_rom', action='store_true') + parser.add_argument('--suppress_meta', action='store_true') + parser.add_argument('--bps', action='store_true') parser.add_argument('--rom') parser.add_argument('--enemizercli') parser.add_argument('--outputpath') @@ -61,16 +66,22 @@ def main(): erargs.seed = seed erargs.names = args.names erargs.create_spoiler = args.create_spoiler + erargs.suppress_rom = args.suppress_rom + erargs.suppress_meta = args.suppress_meta + erargs.bps = args.bps erargs.race = True erargs.outputname = seedname erargs.outputpath = args.outputpath erargs.loglevel = args.loglevel + erargs.mystery = True if args.rom: erargs.rom = args.rom if args.enemizercli: erargs.enemizercli = args.enemizercli + mw_settings = {'algorithm': False} + settings_cache = {k: (roll_settings(v) if args.samesettings else None) for k, v in weights_cache.items()} for player in range(1, args.multi + 1): @@ -79,7 +90,12 @@ def main(): settings = settings_cache[path] if settings_cache[path] else roll_settings(weights_cache[path]) for k, v in vars(settings).items(): if v is not None: - getattr(erargs, k)[player] = v + if k == 'algorithm': # multiworld wide parameters + if not mw_settings[k]: # only use the first roll + setattr(erargs, k, v) + mw_settings[k] = True + else: + getattr(erargs, k)[player] = v else: raise RuntimeError(f'No weights specified for player {player}') @@ -90,13 +106,11 @@ def main(): DRMain(erargs, seed, BabelFish()) def get_weights(path): - try: - if urllib.parse.urlparse(path).scheme: - return yaml.load(urllib.request.urlopen(path), Loader=yaml.FullLoader) - with open(path, 'r', encoding='utf-8') as f: + if os.path.exists(Path(path)): + with open(path, "r", encoding="utf-8") as f: return yaml.load(f, Loader=yaml.SafeLoader) - except Exception as e: - raise Exception(f'Failed to read weights file: {e}') + elif urllib.parse.urlparse(path).scheme in ['http', 'https']: + return yaml.load(urllib.request.urlopen(path), Loader=yaml.FullLoader) def roll_settings(weights): def get_choice(option, root=None): @@ -127,12 +141,16 @@ def roll_settings(weights): ret = argparse.Namespace() + ret.algorithm = get_choice('algorithm') + + glitch_map = {'none': 'noglitches', 'no_logic': 'nologic', 'owglitches': 'owglitches', + 'owg': 'owglitches', 'minorglitches': 'minorglitches'} glitches_required = get_choice('glitches_required') if glitches_required is not None: - if glitches_required not in ['none', 'no_logic']: - print("Only NMG and No Logic supported") + if glitches_required not in glitch_map.keys(): + print(f'Logic did not match one of: {", ".join(glitch_map.keys())}') glitches_required = 'none' - ret.logic = {'none': 'noglitches', 'no_logic': 'nologic'}[glitches_required] + ret.logic = glitch_map[glitches_required] item_placement = get_choice('item_placement') # not supported in ER @@ -144,13 +162,17 @@ def roll_settings(weights): ret.bigkeyshuffle = get_choice('bigkey_shuffle') == 'on' if 'bigkey_shuffle' in weights else dungeon_items in ['full'] ret.accessibility = get_choice('accessibility') + ret.restrict_boss_items = get_choice('restrict_boss_items') entrance_shuffle = get_choice('entrance_shuffle') ret.shuffle = entrance_shuffle if entrance_shuffle != 'none' else 'vanilla' + overworld_map = get_choice('overworld_map') + ret.overworld_map = overworld_map if overworld_map != 'default' else 'default' door_shuffle = get_choice('door_shuffle') ret.door_shuffle = door_shuffle if door_shuffle != 'none' else 'vanilla' ret.intensity = get_choice('intensity') ret.experimental = get_choice('experimental') == 'on' + ret.collection_rate = get_choice('collection_rate') == 'on' ret.dungeon_counters = get_choice('dungeon_counters') if 'dungeon_counters' in weights else 'default' if ret.dungeon_counters == 'default': @@ -159,7 +181,10 @@ def roll_settings(weights): ret.shufflelinks = get_choice('shufflelinks') == 'on' ret.pseudoboots = get_choice('pseudoboots') == 'on' ret.shopsanity = get_choice('shopsanity') == 'on' - ret.keydropshuffle = get_choice('keydropshuffle') == 'on' + ret.dropshuffle = get_choice('dropshuffle') == 'on' + ret.pottery = get_choice('pottery') if 'pottery' in weights else 'none' + ret.colorizepots = get_choice('colorizepots') == 'on' + ret.shufflepots = get_choice('pot_shuffle') == 'on' ret.mixed_travel = get_choice('mixed_travel') if 'mixed_travel' in weights else 'prevent' ret.standardize_palettes = get_choice('standardize_palettes') if 'standardize_palettes' in weights else 'standardize' @@ -169,20 +194,23 @@ def roll_settings(weights): 'fast_ganon': 'crystals', 'dungeons': 'dungeons', 'pedestal': 'pedestal', - 'triforce-hunt': 'triforcehunt' + 'triforce-hunt': 'triforcehunt', + 'trinity': 'trinity' }[goal] - ret.openpyramid = goal == 'fast_ganon' if ret.shuffle in ['vanilla', 'dungeonsfull', 'dungeonssimple'] else False + ret.openpyramid = goal in ['fast_ganon', 'trinity'] if ret.shuffle in ['vanilla', 'dungeonsfull', 'dungeonssimple'] else False ret.crystals_gt = get_choice('tower_open') ret.crystals_ganon = get_choice('ganon_open') - goal_min = get_choice_default('triforce_goal_min', default=20) - goal_max = get_choice_default('triforce_goal_max', default=20) - pool_min = get_choice_default('triforce_pool_min', default=30) - pool_max = get_choice_default('triforce_pool_max', default=30) + from ItemList import set_default_triforce + default_tf_goal, default_tf_pool = set_default_triforce(ret.goal, 0, 0) + goal_min = get_choice_default('triforce_goal_min', default=default_tf_goal) + goal_max = get_choice_default('triforce_goal_max', default=default_tf_goal) + pool_min = get_choice_default('triforce_pool_min', default=default_tf_pool) + pool_max = get_choice_default('triforce_pool_max', default=default_tf_pool) ret.triforce_goal = random.randint(int(goal_min), int(goal_max)) - min_diff = get_choice_default('triforce_min_difference', default=10) + min_diff = get_choice_default('triforce_min_difference', default=default_tf_pool-default_tf_goal) ret.triforce_pool = random.randint(max(int(pool_min), ret.triforce_goal + int(min_diff)), int(pool_max)) ret.mode = get_choice('world_state') @@ -229,8 +257,6 @@ def roll_settings(weights): ret.enemy_health = get_choice('enemy_health') - ret.shufflepots = get_choice('pot_shuffle') == 'on' - ret.beemizer = get_choice('beemizer') if 'beemizer' in weights else '0' inventoryweights = weights.get('startinventory', {}) @@ -239,6 +265,8 @@ def roll_settings(weights): if get_choice(item, inventoryweights) == 'on': startitems.append(item) ret.startinventory = ','.join(startitems) + if len(startitems) > 0: + ret.usestartinventory = True if 'rom' in weights: romweights = weights['rom'] @@ -246,6 +274,7 @@ def roll_settings(weights): ret.disablemusic = get_choice('disablemusic', romweights) == 'on' ret.quickswap = get_choice('quickswap', romweights) == 'on' ret.reduce_flashing = get_choice('reduce_flashing', romweights) == 'on' + ret.msu_resume = get_choice('msu_resume', romweights) == 'on' ret.fastmenu = get_choice('menuspeed', romweights) ret.heartcolor = get_choice('heartcolor', romweights) ret.heartbeep = get_choice('heartbeep', romweights) diff --git a/PotShuffle.py b/PotShuffle.py index a0c048bf..a2a10c0f 100644 --- a/PotShuffle.py +++ b/PotShuffle.py @@ -1,11 +1,15 @@ +import RaceRandom as random + from collections import defaultdict -from BaseClasses import PotItem, Pot, PotFlags, CrystalBarrier -from Regions import key_drop_data +from BaseClasses import PotItem, Pot, PotFlags, CrystalBarrier, LocationType, RegionType +from Utils import int16_as_bytes, pc_to_snes, snes_to_pc + +from source.dungeon.RoomObject import RoomObject, Shuffled_Pot movable_switch_rooms = defaultdict(lambda: [], {'PoD Stalfos Basement': ['PoD Basement Ledge'], - 'Thieves Attic': ['Thieves Attic Hint'], + 'Thieves Attic Switch': ['Thieves Attic Hint'], 'Mire Hub Switch': ['Mire Hub', 'Mire Hub Right']}) invalid_key_rooms = { @@ -18,269 +22,849 @@ invalid_key_rooms = { } vanilla_pots = { - 2: [Pot(80, 6, PotItem.Nothing, 'Sewers Yet More Rats'), Pot(80, 8, PotItem.Nothing, 'Sewers Yet More Rats'), Pot(44, 8, PotItem.Nothing, 'Sewers Yet More Rats'), Pot(44, 10, PotItem.Nothing, 'Sewers Yet More Rats')], - 4: [Pot(162, 25, PotItem.Nothing, 'TR Dash Room'), Pot(152, 25, PotItem.Nothing, 'TR Dash Room'), Pot(152, 22, PotItem.Nothing, 'TR Dash Room'), Pot(162, 22, PotItem.Nothing, 'TR Dash Room'), Pot(204, 19, PotItem.Bomb, 'TR Tongue Pull'), - Pot(240, 19, PotItem.Bomb, 'TR Tongue Pull')], - 9: [Pot(12, 4, PotItem.OneRupee, 'PoD Shooter Room'), Pot(48, 4, PotItem.Heart, 'PoD Shooter Room'), Pot(12, 12, PotItem.Switch, 'PoD Shooter Room')], - 10: [Pot(96, 8, PotItem.Heart, 'PoD Stalfos Basement'), Pot(104, 8, PotItem.Heart, 'PoD Stalfos Basement'), Pot(204, 11, PotItem.Switch, 'PoD Stalfos Basement'), Pot(100, 9, PotItem.Nothing, 'PoD Stalfos Basement'), - Pot(156, 17, PotItem.Bomb, 'PoD Basement Ledge', PotFlags.SwitchLogicChange), Pot(160, 17, PotItem.FiveArrows, 'PoD Basement Ledge', PotFlags.SwitchLogicChange)], - 11: [Pot(202, 3, PotItem.Bomb, 'PoD Dark Pegs Left'), Pot(202, 12, PotItem.Bomb, 'PoD Dark Pegs Left')], - 17: [Pot(152, 19, PotItem.Nothing, 'Sewers Secret Room'), Pot(152, 15, PotItem.Nothing, 'Sewers Secret Room'), Pot(144, 15, PotItem.Heart, 'Sewers Secret Room'), Pot(160, 15, PotItem.Heart, 'Sewers Secret Room'), - Pot(144, 19, PotItem.Heart, 'Sewers Secret Room'), Pot(160, 19, PotItem.Heart, 'Sewers Secret Room')], - 21: [Pot(96, 4, PotItem.Bomb, 'TR Pipe Pit'), Pot(100, 4, PotItem.SmallMagic, 'TR Pipe Pit'), Pot(104, 4, PotItem.Heart, 'TR Pipe Pit'), Pot(108, 4, PotItem.SmallMagic, 'TR Pipe Pit'), Pot(112, 4, PotItem.FiveArrows, 'TR Pipe Pit'), - Pot(12, 6, PotItem.OneRupee, 'TR Pipe Pit'), Pot(16, 6, PotItem.FiveArrows, 'TR Pipe Pit'), Pot(20, 6, PotItem.FiveRupees, 'TR Pipe Pit'), Pot(70, 11, PotItem.BigMagic, 'TR Pipe Ledge')], - 22: [Pot(188, 3, PotItem.Heart, 'Swamp I'), Pot(192, 3, PotItem.Heart, 'Swamp I'), Pot(188, 4, PotItem.SmallMagic, 'Swamp I'), Pot(192, 4, PotItem.SmallMagic, 'Swamp I'), Pot(188, 5, PotItem.FiveArrows, 'Swamp I'), - Pot(192, 5, PotItem.FiveArrows, 'Swamp I'), Pot(188, 6, PotItem.Bomb, 'Swamp I'), Pot(192, 6, PotItem.Bomb, 'Swamp I'), Pot(240, 19, PotItem.Key, 'Swamp Waterway')], - 23: [Pot(100, 13, PotItem.Heart, 'Hera 5F'), Pot(100, 14, PotItem.Heart, 'Hera 5F'), Pot(100, 15, PotItem.Heart, 'Hera 5F'), Pot(100, 16, PotItem.Heart, 'Hera 5F'), Pot(100, 17, PotItem.Heart, 'Hera 5F'), Pot(100, 18, PotItem.Heart, 'Hera 5F'), - Pot(104, 13, PotItem.Heart, 'Hera 5F'), Pot(104, 14, PotItem.Heart, 'Hera 5F'), Pot(104, 15, PotItem.Heart, 'Hera 5F'), Pot(104, 16, PotItem.Heart, 'Hera 5F'), Pot(104, 17, PotItem.Heart, 'Hera 5F'), Pot(104, 18, PotItem.Heart, 'Hera 5F')], - 26: [Pot(28, 5, PotItem.Bomb, 'PoD Falling Bridge Ledge'), Pot(32, 5, PotItem.Bomb, 'PoD Falling Bridge Ledge'), Pot(28, 27, PotItem.Bomb, 'PoD Falling Bridge'), Pot(32, 27, PotItem.Bomb, 'PoD Falling Bridge'), - Pot(232, 19, PotItem.Nothing, 'PoD Harmless Hellway'), Pot(212, 19, PotItem.Nothing, 'PoD Harmless Hellway')], - 27: [Pot(20, 23, PotItem.FiveArrows, 'PoD Mimics 2'), Pot(40, 23, PotItem.FiveArrows, 'PoD Mimics 2')], - 30: [Pot(84, 9, PotItem.Bomb, 'Ice Bomb Drop')], - 31: [Pot(28, 25, PotItem.Switch, 'Ice Pengator Switch'), Pot(28, 23, PotItem.Nothing, 'Ice Pengator Switch'), Pot(86, 26, PotItem.Nothing, 'Ice Big Key'), Pot(86, 27, PotItem.Nothing, 'Ice Big Key')], - 33: [Pot(160, 20, PotItem.Nothing, 'Sewers Key Rat'), Pot(168, 24, PotItem.SmallMagic, 'Sewers Key Rat'), Pot(48, 28, PotItem.Heart, 'Sewers Key Rat'), Pot(82, 28, PotItem.SmallMagic, 'Sewers Key Rat'), - Pot(100, 28, PotItem.Nothing, 'Sewers Key Rat'), Pot(104, 28, PotItem.Nothing, 'Sewers Key Rat')], - 35: [Pot(86, 26, PotItem.OneRupee, 'TR Lazy Eyes'), Pot(90, 26, PotItem.Heart, 'TR Lazy Eyes'), Pot(94, 26, PotItem.OneRupee, 'TR Lazy Eyes'), Pot(98, 26, PotItem.Bomb, 'TR Lazy Eyes'), Pot(102, 26, PotItem.OneRupee, 'TR Lazy Eyes')], - 36: [Pot(12, 4, PotItem.FiveRupees, 'TR Twin Pokeys'), Pot(48, 4, PotItem.Heart, 'TR Twin Pokeys'), Pot(12, 12, PotItem.SmallMagic, 'TR Twin Pokeys'), Pot(48, 12, PotItem.OneRupee, 'TR Twin Pokeys')], - 38: [Pot(28, 4, PotItem.Bomb, 'Swamp Shooters'), Pot(12, 8, PotItem.SmallMagic, 'Swamp Shooters'), Pot(150, 19, PotItem.Switch, 'Swamp Push Statue'), Pot(22, 26, PotItem.FiveRupees, 'Swamp Push Statue'), - Pot(220, 26, PotItem.FiveArrows, 'Swamp Push Statue', PotFlags.SwitchLogicChange)], - 39: [Pot(214, 19, PotItem.Nothing, 'Hera 4F'), Pot(214, 20, PotItem.Nothing, 'Hera 4F'), Pot(166, 20, PotItem.Bomb, 'Hera 4F'), Pot(214, 21, PotItem.Heart, 'Hera 4F'), Pot(40, 28, PotItem.OneRupee, 'Hera 4F'), - Pot(44, 28, PotItem.OneRupee, 'Hera 4F'), Pot(80, 28, PotItem.FiveRupees, 'Hera 4F'), Pot(84, 28, PotItem.FiveRupees, 'Hera 4F'), Pot(102, 17, PotItem.Nothing, 'Hera 4F'), Pot(98, 17, PotItem.Nothing, 'Hera 4F'), - Pot(106, 17, PotItem.Nothing, 'Hera 4F'), Pot(166, 21, PotItem.Nothing, 'Hera 4F'), Pot(166, 19, PotItem.Nothing, 'Hera 4F'), Pot(92, 12, PotItem.Nothing, 'Hera 4F'), Pot(160, 12, PotItem.Nothing, 'Hera 4F')], - 42: [Pot(80, 12, PotItem.OneRupee, 'PoD Arena Main'), Pot(80, 19, PotItem.Heart, 'PoD Arena Main')], - 43: [Pot(16, 5, PotItem.Heart, 'PoD Sexy Statue'), Pot(44, 5, PotItem.Switch, 'PoD Sexy Statue'), Pot(16, 6, PotItem.Heart, 'PoD Sexy Statue'), Pot(44, 6, PotItem.Bomb, 'PoD Sexy Statue'), Pot(16, 7, PotItem.Heart, 'PoD Sexy Statue'), - Pot(44, 7, PotItem.Bomb, 'PoD Sexy Statue'), Pot(146, 21, PotItem.Bomb, 'PoD Map Balcony'), Pot(170, 21, PotItem.FiveArrows, 'PoD Map Balcony'), Pot(146, 22, PotItem.Bomb, 'PoD Map Balcony'), - Pot(170, 22, PotItem.FiveArrows, 'PoD Map Balcony')], - 44: [Pot(108, 24, PotItem.Heart, 'Hookshot Cave (Middle)'), Pot(112, 24, PotItem.Heart, 'Hookshot Cave (Middle)')], - 47: [Pot(28, 7, PotItem.Heart, 'Kakariko Well (top)'), Pot(32, 7, PotItem.Heart, 'Kakariko Well (top)'), Pot(28, 9, PotItem.FiveRupees, 'Kakariko Well (top)'), Pot(32, 9, PotItem.FiveRupees, 'Kakariko Well (top)'), - Pot(172, 19, PotItem.FiveRupees, 'Kakariko Well (top)'), Pot(180, 19, PotItem.FiveRupees, 'Kakariko Well (top)'), Pot(104, 27, PotItem.Heart, 'Kakariko Well (bottom)'), Pot(104, 28, PotItem.Heart, 'Kakariko Well (bottom)')], - 49: [Pot(92, 28, PotItem.Bomb, 'Hera Beetles'), Pot(96, 28, PotItem.Nothing, 'Hera Beetles')], - 50: [Pot(28, 13, PotItem.SmallMagic, 'Sewers Dark Cross')], - 52: [Pot(78, 8, PotItem.FiveRupees, 'Swamp Barrier Ledge'), Pot(92, 8, PotItem.FiveRupees, 'Swamp Barrier Ledge')], - 53: [Pot(60, 6, PotItem.Key, 'Swamp Trench 2 Alcove'), Pot(20, 8, PotItem.FiveRupees, 'Swamp Big Key Ledge'), Pot(24, 8, PotItem.FiveRupees, 'Swamp Big Key Ledge'), Pot(28, 8, PotItem.FiveRupees, 'Swamp Big Key Ledge'), - Pot(32, 8, PotItem.FiveRupees, 'Swamp Big Key Ledge'), Pot(36, 8, PotItem.FiveRupees, 'Swamp Big Key Ledge'), Pot(48, 20, PotItem.Heart, 'Swamp Trench 2 Departure'), Pot(76, 23, PotItem.Nothing, 'Swamp Trench 2 Pots'), - Pot(88, 23, PotItem.Nothing, 'Swamp Trench 2 Pots'), Pot(100, 27, PotItem.Nothing, 'Swamp Trench 2 Pots'), Pot(242, 28, PotItem.Nothing, 'Swamp Trench 2 Pots'), Pot(240, 22, PotItem.Heart, 'Swamp Trench 2 Pots'), - Pot(76, 28, PotItem.Heart, 'Swamp Trench 2 Pots')], - 54: [Pot(108, 4, PotItem.Bomb, 'Swamp Hub Dead Ledge'), Pot(112, 4, PotItem.FiveRupees, 'Swamp Hub Dead Ledge'), Pot(10, 16, PotItem.Heart, 'Swamp Hub'), Pot(154, 15, PotItem.Nothing, 'Swamp Hub'), Pot(114, 16, PotItem.Key, 'Swamp Hub'), - Pot(222, 15, PotItem.Nothing, 'Swamp Hub'), Pot(188, 5, PotItem.Nothing, 'Swamp Hub North Ledge'), Pot(192, 5, PotItem.Nothing, 'Swamp Hub North Ledge')], - 55: [Pot(60, 6, PotItem.Key, 'Swamp Trench 1 Alcove'), Pot(48, 20, PotItem.Nothing, 'Swamp Trench 1 Key Ledge')], - 56: [Pot(164, 12, PotItem.Bomb, 'Swamp Pot Row'), Pot(164, 13, PotItem.FiveRupees, 'Swamp Pot Row'), Pot(164, 18, PotItem.Bomb, 'Swamp Pot Row'), Pot(164, 19, PotItem.Key, 'Swamp Pot Row')], - 57: [Pot(12, 20, PotItem.Heart, 'Skull Spike Corner'), Pot(48, 28, PotItem.FiveArrows, 'Skull Spike Corner'), Pot(100, 22, PotItem.SmallMagic, 'Skull Final Drop'), Pot(100, 26, PotItem.FiveArrows, 'Skull Final Drop')], - 60: [Pot(24, 8, PotItem.SmallMagic, 'Hookshot Cave (Front)'), Pot(64, 12, PotItem.FiveRupees, 'Hookshot Cave (Front)'), Pot(20, 14, PotItem.OneRupee, 'Hookshot Cave (Front)'), Pot(20, 19, PotItem.Nothing, 'Hookshot Cave (Front)'), - Pot(68, 18, PotItem.FiveRupees, 'Hookshot Cave (Front)'), Pot(96, 19, PotItem.Heart, 'Hookshot Cave (Front)'), Pot(64, 20, PotItem.FiveRupees, 'Hookshot Cave (Front)'), Pot(64, 26, PotItem.FiveRupees, 'Hookshot Cave (Front)')], - 61: [Pot(76, 12, PotItem.Bomb, 'GT Mini Helmasaur Room'), Pot(112, 12, PotItem.Bomb, 'GT Mini Helmasaur Room'), Pot(24, 22, PotItem.Heart, 'GT Crystal Inner Circle'), Pot(40, 22, PotItem.FiveArrows, 'GT Crystal Inner Circle'), - Pot(32, 24, PotItem.Heart, 'GT Crystal Inner Circle'), Pot(20, 26, PotItem.FiveRupees, 'GT Crystal Inner Circle'), Pot(36, 26, PotItem.BigMagic, 'GT Crystal Inner Circle')], - 62: [Pot(96, 6, PotItem.Bomb, 'Ice Stalfos Hint'), Pot(100, 6, PotItem.SmallMagic, 'Ice Stalfos Hint'), Pot(88, 10, PotItem.Heart, 'Ice Stalfos Hint'), Pot(92, 10, PotItem.SmallMagic, 'Ice Stalfos Hint')], - 63: [Pot(12, 25, PotItem.OneRupee, 'Ice Hammer Block'), Pot(20, 25, PotItem.OneRupee, 'Ice Hammer Block'), Pot(12, 26, PotItem.Bomb, 'Ice Hammer Block'), Pot(20, 26, PotItem.Bomb, 'Ice Hammer Block'), - Pot(12, 27, PotItem.Switch, 'Ice Hammer Block'), Pot(20, 27, PotItem.Heart, 'Ice Hammer Block'), Pot(28, 23, PotItem.Key, 'Ice Hammer Block')], - 65: [Pot(100, 10, PotItem.Heart, 'Sewers Behind Tapestry'), Pot(52, 15, PotItem.OneRupee, 'Sewers Behind Tapestry'), Pot(52, 16, PotItem.SmallMagic, 'Sewers Behind Tapestry'), Pot(148, 22, PotItem.SmallMagic, 'Sewers Behind Tapestry')], - 67: [Pot(66, 4, PotItem.FiveArrows, 'Desert Wall Slide'), Pot(78, 4, PotItem.SmallMagic, 'Desert Wall Slide'), Pot(66, 9, PotItem.Heart, 'Desert Wall Slide'), Pot(78, 9, PotItem.Heart, 'Desert Wall Slide'), - Pot(112, 28, PotItem.Nothing, 'Desert Tiles 2'), Pot(76, 28, PotItem.Nothing, 'Desert Tiles 2'), Pot(76, 20, PotItem.Nothing, 'Desert Tiles 2'), Pot(112, 20, PotItem.Key, 'Desert Tiles 2')], - 69: [Pot(12, 4, PotItem.FiveArrows, 'Thieves Basement Block'), Pot(48, 12, PotItem.FiveArrows, 'Thieves Basement Block'), Pot(92, 11, PotItem.Nothing, "Thieves Blind's Cell"), Pot(108, 11, PotItem.Heart, "Thieves Blind's Cell"), - Pot(220, 16, PotItem.SmallMagic, "Thieves Blind's Cell"), Pot(236, 16, PotItem.Heart, "Thieves Blind's Cell")], - 70: [Pot(96, 5, PotItem.Heart, 'Swamp Donut Top'), Pot(28, 27, PotItem.Heart, 'Swamp Donut Bottom')], - 73: [Pot(104, 15, PotItem.SmallMagic, 'Skull Torch Room'), Pot(104, 16, PotItem.SmallMagic, 'Skull Torch Room'), Pot(156, 27, PotItem.Nothing, 'Skull Star Pits'), Pot(172, 24, PotItem.Nothing, 'Skull Star Pits'), - Pot(172, 23, PotItem.Nothing, 'Skull Star Pits'), Pot(144, 20, PotItem.Nothing, 'Skull Star Pits'), Pot(144, 19, PotItem.SmallMagic, 'Skull Star Pits'), Pot(172, 20, PotItem.Heart, 'Skull Star Pits'), - Pot(144, 27, PotItem.Heart, 'Skull Star Pits'), Pot(172, 28, PotItem.SmallMagic, 'Skull Star Pits'), Pot(160, 27, PotItem.Nothing, 'Skull Star Pits')], - 74: [Pot(14, 5, PotItem.Switch, 'PoD Left Cage'), Pot(32, 5, PotItem.Bomb, 'PoD Left Cage'), Pot(14, 11, PotItem.Heart, 'PoD Left Cage'), Pot(32, 11, PotItem.OneRupee, 'PoD Left Cage'), Pot(56, 8, PotItem.Bomb, 'PoD Middle Cage'), - Pot(68, 8, PotItem.Bomb, 'PoD Middle Cage'), Pot(92, 5, PotItem.Bomb, 'PoD Middle Cage'), Pot(110, 5, PotItem.Switch, 'PoD Middle Cage'), Pot(92, 11, PotItem.OneRupee, 'PoD Middle Cage'), Pot(110, 11, PotItem.Heart, 'PoD Middle Cage')], - 75: [Pot(20, 6, PotItem.FiveArrows, 'PoD Mimics 1'), Pot(40, 6, PotItem.Heart, 'PoD Mimics 1')], - 78: [Pot(140, 7, PotItem.Nothing, 'Ice Bomb Jump Catwalk'), Pot(48, 10, PotItem.Nothing, 'Ice Bomb Jump Catwalk'), Pot(140, 11, PotItem.Switch, 'Ice Bomb Jump Catwalk'), Pot(28, 12, PotItem.Heart, 'Ice Bomb Jump Catwalk'), - Pot(112, 12, PotItem.SmallMagic, 'Ice Narrow Corridor')], - 80: [Pot(96, 38, PotItem.Heart, 'Hyrule Castle West Hall'), Pot(100, 38, PotItem.Heart, 'Hyrule Castle West Hall')], - 82: [Pot(138, 3, PotItem.Heart, 'Hyrule Castle East Hall'), Pot(194, 26, PotItem.Heart, 'Hyrule Castle East Hall')], - 83: [Pot(92, 11, PotItem.Heart, 'Desert Beamos Hall'), Pot(96, 11, PotItem.SmallMagic, 'Desert Beamos Hall'), Pot(100, 11, PotItem.Key, 'Desert Beamos Hall'), Pot(104, 11, PotItem.Heart, 'Desert Beamos Hall')], - 84: [Pot(186, 25, PotItem.FiveRupees, 'Swamp Attic'), Pot(186, 26, PotItem.Heart, 'Swamp Attic'), Pot(186, 27, PotItem.Heart, 'Swamp Attic'), Pot(186, 28, PotItem.Heart, 'Swamp Attic')], - 85: [Pot(230, 24, PotItem.SmallMagic, 'Secret Passage'), Pot(230, 25, PotItem.SmallMagic, 'Secret Passage')], - 86: [Pot(100, 6, PotItem.Nothing, 'Skull Back Drop'), Pot(96, 10, PotItem.Nothing, 'Skull Back Drop'), Pot(92, 10, PotItem.Nothing, 'Skull Back Drop'), Pot(20, 6, PotItem.SmallMagic, 'Skull X Room'), - Pot(40, 6, PotItem.SmallMagic, 'Skull X Room'), Pot(24, 7, PotItem.SmallMagic, 'Skull X Room'), Pot(36, 7, PotItem.SmallMagic, 'Skull X Room'), Pot(12, 8, PotItem.Heart, 'Skull X Room'), Pot(48, 8, PotItem.Heart, 'Skull X Room'), - Pot(24, 9, PotItem.SmallMagic, 'Skull X Room'), Pot(36, 9, PotItem.SmallMagic, 'Skull X Room'), Pot(20, 10, PotItem.FiveRupees, 'Skull X Room'), Pot(40, 10, PotItem.FiveRupees, 'Skull X Room'), Pot(12, 20, PotItem.Key, 'Skull 2 West Lobby'), - Pot(48, 20, PotItem.Nothing, 'Skull 2 West Lobby')], - 87: [Pot(92, 7, PotItem.BigMagic, 'Skull Lone Pot'), Pot(32, 4, PotItem.Nothing, 'Skull Big Key'), Pot(92, 23, PotItem.Bomb, 'Skull Pot Prison'), Pot(100, 23, PotItem.SmallMagic, 'Skull Pot Prison'), - Pot(84, 25, PotItem.FiveRupees, 'Skull Pot Prison'), Pot(76, 27, PotItem.Heart, 'Skull Pot Prison'), Pot(12, 20, PotItem.SmallMagic, 'Skull 2 East Lobby'), Pot(48, 20, PotItem.SmallMagic, 'Skull 2 East Lobby'), - Pot(30, 22, PotItem.Switch, 'Skull 2 East Lobby')], - 88: [Pot(12, 7, PotItem.SmallMagic, 'Skull Pull Switch'), Pot(16, 7, PotItem.Nothing, 'Skull Pull Switch'), Pot(16, 8, PotItem.SmallMagic, 'Skull Pull Switch'), Pot(12, 12, PotItem.Nothing, 'Skull Pull Switch'), - Pot(96, 9, PotItem.Nothing, 'Skull Pot Circle'), Pot(92, 8, PotItem.Nothing, 'Skull Pot Circle'), Pot(108, 8, PotItem.Nothing, 'Skull Pot Circle'), Pot(108, 6, PotItem.Nothing, 'Skull Pot Circle'), - Pot(104, 5, PotItem.Nothing, 'Skull Pot Circle'), Pot(92, 6, PotItem.Nothing, 'Skull Pot Circle'), Pot(96, 5, PotItem.Bomb, 'Skull Pot Circle'), Pot(100, 5, PotItem.SmallMagic, 'Skull Pot Circle'), - Pot(92, 7, PotItem.Heart, 'Skull Pot Circle'), Pot(108, 7, PotItem.Heart, 'Skull Pot Circle'), Pot(100, 9, PotItem.SmallMagic, 'Skull Pot Circle'), Pot(104, 9, PotItem.Bomb, 'Skull Pot Circle')], - 89: [Pot(26, 43, PotItem.Heart, 'Skull 3 Lobby'), Pot(32, 40, PotItem.Nothing, 'Skull 3 Lobby'), Pot(76, 28, PotItem.Nothing, 'Skull East Bridge'), Pot(112, 28, PotItem.Nothing, 'Skull East Bridge')], - 91: [Pot(218, 37, PotItem.Nothing, 'GT Hidden Spikes'), Pot(222, 37, PotItem.Switch, 'GT Hidden Spikes'), Pot(226, 37, PotItem.Nothing, 'GT Hidden Spikes')], - 92: [Pot(228, 25, PotItem.Nothing, 'GT Refill'), Pot(104, 24, PotItem.Nothing, 'GT Refill'), Pot(228, 22, PotItem.Nothing, 'GT Refill'), Pot(216, 25, PotItem.Nothing, 'GT Refill'), Pot(84, 24, PotItem.Nothing, 'GT Refill'), - Pot(216, 22, PotItem.Nothing, 'GT Refill'), Pot(94, 22, PotItem.Bomb, 'GT Refill'), Pot(94, 26, PotItem.BigMagic, 'GT Refill')], - 93: [Pot(16, 5, PotItem.Bomb, 'GT Gauntlet 2'), Pot(44, 5, PotItem.FiveRupees, 'GT Gauntlet 2'), Pot(16, 11, PotItem.OneRupee, 'GT Gauntlet 2'), Pot(44, 11, PotItem.FiveArrows, 'GT Gauntlet 2'), Pot(12, 20, PotItem.FiveArrows, 'GT Gauntlet 3'), - Pot(48, 20, PotItem.Bomb, 'GT Gauntlet 3'), Pot(12, 28, PotItem.SmallMagic, 'GT Gauntlet 3'), Pot(48, 28, PotItem.Bomb, 'GT Gauntlet 3')], - 94: [Pot(92, 4, PotItem.SmallMagic, 'Ice Falling Square'), Pot(96, 4, PotItem.SmallMagic, 'Ice Falling Square'), Pot(76, 8, PotItem.Heart, 'Ice Falling Square'), Pot(112, 8, PotItem.Heart, 'Ice Falling Square')], - 95: [Pot(44, 27, PotItem.Switch, 'Ice Spike Room')], - 96: [Pot(76, 4, PotItem.Heart, 'Hyrule Castle West Lobby'), Pot(112, 4, PotItem.Heart, 'Hyrule Castle West Lobby')], - 98: [Pot(208, 21, PotItem.Heart, 'Hyrule Castle East Lobby')], - 99: [Pot(48, 4, PotItem.Nothing, 'Desert Tiles 1'), Pot(12, 4, PotItem.Nothing, 'Desert Tiles 1'), Pot(12, 8, PotItem.Nothing, 'Desert Tiles 1'), Pot(48, 12, PotItem.Nothing, 'Desert Tiles 1'), Pot(48, 8, PotItem.Heart, 'Desert Tiles 1'), - Pot(12, 12, PotItem.Key, 'Desert Tiles 1')], - 100: [Pot(12, 22, PotItem.Bomb, 'Thieves Attic Hint', PotFlags.SwitchLogicChange), Pot(16, 22, PotItem.Bomb, 'Thieves Attic Hint', PotFlags.SwitchLogicChange), Pot(20, 22, PotItem.Bomb, 'Thieves Attic Hint', PotFlags.SwitchLogicChange), - Pot(36, 28, PotItem.Bomb, 'Thieves Attic'), Pot(40, 28, PotItem.SmallMagic, 'Thieves Attic'), - Pot(44, 28, PotItem.SmallMagic, 'Thieves Attic'), Pot(48, 28, PotItem.Switch, 'Thieves Attic')], - 101: [Pot(100, 28, PotItem.Bomb, 'Thieves Attic Window'), Pot(104, 28, PotItem.Bomb, 'Thieves Attic Window')], - 102: [Pot(48, 37, PotItem.FiveArrows, 'Swamp Refill'), Pot(52, 37, PotItem.Bomb, 'Swamp Refill'), Pot(56, 37, PotItem.FiveRupees, 'Swamp Refill'), Pot(48, 38, PotItem.FiveArrows, 'Swamp Refill'), Pot(52, 38, PotItem.Bomb, 'Swamp Refill'), - Pot(56, 38, PotItem.FiveRupees, 'Swamp Refill'), Pot(84, 5, PotItem.Heart, 'Swamp Behind Waterfall'), Pot(104, 5, PotItem.FiveArrows, 'Swamp Behind Waterfall'), Pot(84, 6, PotItem.Heart, 'Swamp Behind Waterfall'), - Pot(104, 6, PotItem.Bomb, 'Swamp Behind Waterfall')], - 103: [Pot(22, 26, PotItem.Nothing, 'Skull Left Drop'), Pot(18, 22, PotItem.Nothing, 'Skull Left Drop'), Pot(12, 7, PotItem.FiveArrows, 'Skull Left Drop'), Pot(48, 7, PotItem.SmallMagic, 'Skull Left Drop'), - Pot(18, 23, PotItem.SmallMagic, 'Skull Left Drop'), Pot(18, 26, PotItem.Heart, 'Skull Left Drop'), Pot(96, 19, PotItem.Heart, 'Skull Compass Room'), Pot(74, 20, PotItem.SmallMagic, 'Skull Compass Room'), - Pot(92, 9, PotItem.Nothing, 'Skull Compass Room'), Pot(84, 28, PotItem.Nothing, 'Skull Compass Room'), Pot(104, 28, PotItem.Heart, 'Skull Compass Room')], - 104: [Pot(84, 14, PotItem.Nothing, 'Skull Pinball'), Pot(84, 13, PotItem.Nothing, 'Skull Pinball'), Pot(88, 12, PotItem.Nothing, 'Skull Pinball'), Pot(88, 6, PotItem.Nothing, 'Skull Pinball'), Pot(88, 5, PotItem.Nothing, 'Skull Pinball'), - Pot(88, 4, PotItem.Nothing, 'Skull Pinball'), Pot(64, 17, PotItem.Nothing, 'Skull Pinball'), Pot(64, 15, PotItem.Nothing, 'Skull Pinball'), Pot(64, 7, PotItem.Heart, 'Skull Pinball'), Pot(88, 7, PotItem.SmallMagic, 'Skull Pinball'), - Pot(64, 16, PotItem.Heart, 'Skull Pinball'), Pot(64, 24, PotItem.SmallMagic, 'Skull Pinball'), Pot(64, 25, PotItem.Heart, 'Skull Pinball')], - 107: [Pot(28, 5, PotItem.Heart, 'GT Crystal Paths'), Pot(44, 5, PotItem.Nothing, 'GT Crystal Paths'), Pot(28, 8, PotItem.Nothing, 'GT Crystal Paths'), Pot(44, 8, PotItem.SmallMagic, 'GT Crystal Paths'), - Pot(28, 11, PotItem.SmallMagic, 'GT Crystal Paths'), Pot(44, 11, PotItem.Nothing, 'GT Crystal Paths'), Pot(94, 25, PotItem.Nothing, 'GT Mimics 2'), Pot(98, 25, PotItem.FiveArrows, 'GT Mimics 2')], - 108: [Pot(20, 6, PotItem.Heart, 'GT Quad Pot'), Pot(40, 6, PotItem.FiveArrows, 'GT Quad Pot'), Pot(20, 10, PotItem.Bomb, 'GT Quad Pot'), Pot(40, 10, PotItem.SmallMagic, 'GT Quad Pot')], - 109: [Pot(28, 26, PotItem.Heart, 'GT Gauntlet 5'), Pot(32, 26, PotItem.Heart, 'GT Gauntlet 5'), Pot(28, 27, PotItem.SmallMagic, 'GT Gauntlet 5'), Pot(32, 27, PotItem.SmallMagic, 'GT Gauntlet 5')], - 115: [Pot(154, 21, PotItem.FiveArrows, 'Desert Circle of Pots'), Pot(158, 21, PotItem.OneRupee, 'Desert Circle of Pots'), Pot(20, 23, PotItem.Switch, 'Desert Circle of Pots'), Pot(36, 23, PotItem.FiveRupees, 'Desert Circle of Pots'), - Pot(144, 24, PotItem.Heart, 'Desert Circle of Pots'), Pot(168, 24, PotItem.FiveArrows, 'Desert Circle of Pots'), Pot(20, 26, PotItem.SmallMagic, 'Desert Circle of Pots'), Pot(36, 26, PotItem.Heart, 'Desert Circle of Pots'), - Pot(154, 27, PotItem.OneRupee, 'Desert Circle of Pots'), Pot(158, 27, PotItem.FiveRupees, 'Desert Circle of Pots')], - 116: [Pot(30, 5, PotItem.SmallMagic, 'Desert Map Room'), Pot(62, 5, PotItem.Switch, 'Desert Map Room'), Pot(94, 5, PotItem.SmallMagic, 'Desert Map Room'), Pot(14, 11, PotItem.Heart, 'Desert Map Room'), - Pot(46, 11, PotItem.FiveArrows, 'Desert Map Room'), Pot(78, 11, PotItem.FiveArrows, 'Desert Map Room'), Pot(110, 11, PotItem.Heart, 'Desert Map Room')], - 117: [Pot(148, 22, PotItem.SmallMagic, 'Desert Arrow Pot Corner'), Pot(160, 22, PotItem.FiveArrows, 'Desert Arrow Pot Corner'), Pot(172, 22, PotItem.Heart, 'Desert Arrow Pot Corner')], - 118: [Pot(112, 12, PotItem.Heart, 'Swamp Drain Right'), Pot(84, 23, PotItem.Heart, 'Swamp Flooded Spot'), Pot(96, 23, PotItem.Heart, 'Swamp Flooded Spot')], - 123: [Pot(48, 10, PotItem.Nothing, 'GT Conveyor Star Pits'), Pot(88, 10, PotItem.Nothing, 'GT Conveyor Star Pits'), Pot(76, 7, PotItem.Nothing, 'GT Conveyor Star Pits'), Pot(60, 4, PotItem.Heart, 'GT Conveyor Star Pits'), - Pot(64, 4, PotItem.Key, 'GT Conveyor Star Pits')], - 124: [Pot(36, 21, PotItem.Nothing, 'GT Falling Bridge'), Pot(24, 11, PotItem.Nothing, 'GT Falling Bridge'), Pot(28, 4, PotItem.Heart, 'GT Falling Bridge'), Pot(32, 4, PotItem.Heart, 'GT Falling Bridge')], - 125: [Pot(44, 12, PotItem.Nothing, 'GT Firesnake Room'), Pot(44, 6, PotItem.Nothing, 'GT Firesnake Room'), Pot(112, 6, PotItem.Heart, 'GT Firesnake Room'), Pot(108, 20, PotItem.FiveArrows, 'GT Warp Maze - Pot Rail'), - Pot(114, 20, PotItem.Bomb, 'GT Petting Zoo'), Pot(76, 28, PotItem.Bomb, 'GT Petting Zoo')], - 126: [Pot(86, 15, PotItem.Heart, 'Ice Tall Hint'), Pot(82, 26, PotItem.SmallMagic, 'Ice Tall Hint'), Pot(100, 26, PotItem.Switch, 'Ice Tall Hint'), Pot(104, 26, PotItem.Nothing, 'Ice Tall Hint')], - 128: [Pot(48, 4, PotItem.Heart, 'Hyrule Dungeon Cellblock'), Pot(52, 4, PotItem.Heart, 'Hyrule Dungeon Cellblock'), Pot(56, 4, PotItem.Heart, 'Hyrule Dungeon Cellblock')], - 130: [Pot(50, 5, PotItem.Nothing, 'Hyrule Dungeon South Abyss'), Pot(50, 10, PotItem.Nothing, 'Hyrule Dungeon South Abyss'), Pot(76, 50, PotItem.Heart, 'Hyrule Dungeon South Abyss')], - 131: [Pot(76, 4, PotItem.FiveArrows, 'Desert West Wing'), Pot(80, 4, PotItem.OneRupee, 'Desert West Wing'), Pot(76, 28, PotItem.FiveRupees, 'Desert West Wing'), Pot(80, 28, PotItem.FiveArrows, 'Desert West Wing')], - 132: [Pot(64, 17, PotItem.Nothing, 'Desert Main Lobby'), Pot(60, 17, PotItem.Nothing, 'Desert Main Lobby'), Pot(80, 14, PotItem.Nothing, 'Desert Main Lobby'), Pot(44, 14, PotItem.Nothing, 'Desert Main Lobby'), - Pot(100, 6, PotItem.Nothing, 'Desert Main Lobby'), Pot(24, 6, PotItem.Nothing, 'Desert Main Lobby'), Pot(24, 7, PotItem.FiveArrows, 'Desert Main Lobby'), Pot(100, 7, PotItem.FiveArrows, 'Desert Main Lobby')], - 133: [Pot(44, 28, PotItem.Heart, 'Desert East Wing'), Pot(48, 28, PotItem.FiveArrows, 'Desert East Wing')], - 135: [Pot(12, 11, PotItem.Nothing, 'Hera Tile Room'), Pot(16, 12, PotItem.Nothing, 'Hera Tile Room'), Pot(40, 12, PotItem.Nothing, 'Hera Tile Room'), Pot(32, 12, PotItem.Nothing, 'Hera Tile Room'), Pot(24, 12, PotItem.Nothing, 'Hera Tile Room'), - Pot(16, 11, PotItem.Nothing, 'Hera Tile Room'), Pot(76, 20, PotItem.SmallMagic, 'Hera Torches'), Pot(112, 20, PotItem.BigMagic, 'Hera Torches')], - 139: [Pot(76, 12, PotItem.Nothing, 'GT Conveyor Cross'), Pot(112, 12, PotItem.Key, 'GT Conveyor Cross'), Pot(32, 23, PotItem.Nothing, 'GT Hookshot South Platform'), Pot(28, 23, PotItem.Nothing, 'GT Hookshot South Platform'), - Pot(32, 9, PotItem.SmallMagic, 'GT Hookshot East Platform'), Pot(76, 20, PotItem.Nothing, 'GT Map Room'), Pot(76, 28, PotItem.Heart, 'GT Map Room')], - 140: [Pot(76, 12, PotItem.Switch, 'GT Hope Room'), Pot(112, 12, PotItem.SmallMagic, 'GT Hope Room'), Pot(76, 20, PotItem.Bomb, "GT Bob's Room"), Pot(92, 20, PotItem.Bomb, "GT Bob's Room"), Pot(100, 21, PotItem.FiveArrows, "GT Bob's Room"), - Pot(104, 26, PotItem.Bomb, "GT Bob's Room"), Pot(88, 27, PotItem.Bomb, "GT Bob's Room")], - 141: [Pot(204, 11, PotItem.Nothing, 'GT Speed Torch Upper'), Pot(204, 14, PotItem.BigMagic, 'GT Speed Torch Upper'), Pot(28, 23, PotItem.Heart, 'GT Pots n Blocks'), Pot(36, 23, PotItem.Heart, 'GT Pots n Blocks'), - Pot(32, 24, PotItem.BigMagic, 'GT Pots n Blocks')], - 142: [Pot(80, 5, PotItem.FiveArrows, 'Ice Lonely Freezor'), Pot(80, 6, PotItem.Nothing, 'Ice Lonely Freezor')], - 145: [Pot(84, 4, PotItem.Heart, 'Mire Falling Foes'), Pot(104, 4, PotItem.SmallMagic, 'Mire Falling Foes')], - 146: [Pot(86, 23, PotItem.Nothing, 'Mire Tall Dark and Roomy'), Pot(92, 23, PotItem.Nothing, 'Mire Tall Dark and Roomy'), Pot(98, 23, PotItem.Nothing, 'Mire Tall Dark and Roomy'), Pot(104, 23, PotItem.Nothing, 'Mire Tall Dark and Roomy')], - 147: [Pot(28, 7, PotItem.Switch, 'Mire Dark Shooters'), Pot(96, 7, PotItem.Heart, 'Mire Dark Shooters', PotFlags.NoSwitch)], - 150: [Pot(14, 18, PotItem.Nothing, 'GT Torch Cross'), Pot(32, 5, PotItem.Nothing, 'GT Torch Cross'), Pot(32, 17, PotItem.SmallMagic, 'GT Torch Cross'), Pot(32, 24, PotItem.SmallMagic, 'GT Torch Cross'), - Pot(14, 24, PotItem.Nothing, 'GT Torch Cross'), Pot(76, 21, PotItem.Heart, 'GT Staredown'), Pot(112, 21, PotItem.BigMagic, 'GT Staredown')], - 153: [Pot(40, 20, PotItem.SmallMagic, 'Eastern Darkness'), Pot(84, 20, PotItem.Heart, 'Eastern Darkness')], - 155: [Pot(48, 4, PotItem.SmallMagic, 'GT Double Switch Pot Corners'), Pot(48, 12, PotItem.Key, 'GT Double Switch Pot Corners'), Pot(28, 24, PotItem.Nothing, 'GT Warp Maze - Mid Section'), Pot(32, 24, PotItem.Nothing, 'GT Warp Maze - Mid Section')], - 156: [Pot(56, 8, PotItem.SmallMagic, 'GT Invisible Catwalk'), Pot(56, 9, PotItem.FiveArrows, 'GT Invisible Catwalk')], - 157: [Pot(76, 4, PotItem.Bomb, 'GT Crystal Conveyor Left'), Pot(84, 4, PotItem.SmallMagic, 'GT Crystal Conveyor Left'), Pot(32, 7, PotItem.Nothing, 'GT Compass Room'), Pot(40, 9, PotItem.Nothing, 'GT Compass Room')], - 159: [Pot(138, 20, PotItem.Nothing, 'Ice Many Pots'), Pot(138, 19, PotItem.Heart, 'Ice Many Pots'), Pot(178, 19, PotItem.Heart, 'Ice Many Pots'), Pot(40, 21, PotItem.Switch, 'Ice Many Pots'), Pot(138, 21, PotItem.Key, 'Ice Many Pots'), - Pot(20, 27, PotItem.Heart, 'Ice Many Pots'), Pot(138, 27, PotItem.Heart, 'Ice Many Pots'), Pot(178, 28, PotItem.Heart, 'Ice Many Pots'), Pot(178, 21, PotItem.Nothing, 'Ice Many Pots'), Pot(178, 20, PotItem.Nothing, 'Ice Many Pots'), - Pot(40, 27, PotItem.Nothing, 'Ice Many Pots'), Pot(178, 27, PotItem.Nothing, 'Ice Many Pots'), Pot(178, 26, PotItem.Nothing, 'Ice Many Pots'), Pot(138, 28, PotItem.Nothing, 'Ice Many Pots'), Pot(138, 26, PotItem.Nothing, 'Ice Many Pots'), - Pot(20, 21, PotItem.Nothing, 'Ice Many Pots')], - 161: [Pot(150, 6, PotItem.Key, 'Mire Fishbone'), Pot(100, 11, PotItem.SmallMagic, 'Mire Fishbone'), Pot(104, 12, PotItem.Heart, 'Mire Fishbone'), Pot(108, 13, PotItem.SmallMagic, 'Mire Fishbone'), Pot(112, 14, PotItem.Heart, 'Mire Fishbone'), - Pot(96, 27, PotItem.Nothing, 'Mire South Fish'), Pot(92, 21, PotItem.Nothing, 'Mire South Fish'), Pot(96, 23, PotItem.Heart, 'Mire South Fish'), Pot(92, 25, PotItem.Nothing, 'Mire South Fish'), - Pot(76, 28, PotItem.Nothing, 'Mire South Fish'), Pot(112, 28, PotItem.Nothing, 'Mire South Fish')], - 162: [Pot(12, 28, PotItem.BigMagic, 'Mire Left Bridge')], - 168: [Pot(138, 28, PotItem.Nothing, 'Eastern Stalfos Spawn'), Pot(178, 28, PotItem.Nothing, 'Eastern Stalfos Spawn'), Pot(178, 19, PotItem.Nothing, 'Eastern Stalfos Spawn'), Pot(138, 19, PotItem.Heart, 'Eastern Stalfos Spawn'), - Pot(30, 24, PotItem.OneRupee, 'Eastern Stalfos Spawn')], - 169: [Pot(144, 43, PotItem.FiveArrows, 'Eastern Courtyard'), Pot(236, 43, PotItem.FiveArrows, 'Eastern Courtyard'), Pot(144, 44, PotItem.FiveArrows, 'Eastern Courtyard'), Pot(236, 44, PotItem.Heart, 'Eastern Courtyard'), - Pot(12, 19, PotItem.Nothing, 'Eastern Courtyard Ledge'), Pot(112, 19, PotItem.Nothing, 'Eastern Courtyard Ledge'), Pot(16, 20, PotItem.Heart, 'Eastern Courtyard Ledge'), Pot(108, 20, PotItem.Heart, 'Eastern Courtyard Ledge')], - 170: [Pot(212, 10, PotItem.Nothing, 'Eastern Pot Switch'), Pot(232, 10, PotItem.Nothing, 'Eastern Pot Switch'), Pot(232, 5, PotItem.Nothing, 'Eastern Pot Switch'), Pot(212, 5, PotItem.Heart, 'Eastern Pot Switch'), - Pot(94, 8, PotItem.Switch, 'Eastern Pot Switch'), Pot(108, 55, PotItem.Heart, 'Eastern Map Balcony'), Pot(108, 56, PotItem.Heart, 'Eastern Map Balcony'), Pot(108, 57, PotItem.Heart, 'Eastern Map Balcony')], - 171: [Pot(20, 24, PotItem.Key, 'Thieves Spike Switch')], - 174: [Pot(76, 12, PotItem.Switch, 'Iced T')], - 176: [Pot(20, 27, PotItem.Nothing, 'Tower Circle of Pots'), Pot(24, 24, PotItem.Nothing, 'Tower Circle of Pots'), Pot(44, 25, PotItem.Nothing, 'Tower Circle of Pots'), Pot(20, 21, PotItem.Bomb, 'Tower Circle of Pots'), - Pot(28, 21, PotItem.OneRupee, 'Tower Circle of Pots'), Pot(32, 21, PotItem.FiveRupees, 'Tower Circle of Pots'), Pot(40, 21, PotItem.FiveArrows, 'Tower Circle of Pots'), Pot(16, 23, PotItem.FiveRupees, 'Tower Circle of Pots'), - Pot(44, 23, PotItem.OneRupee, 'Tower Circle of Pots'), Pot(36, 24, PotItem.Heart, 'Tower Circle of Pots'), Pot(16, 25, PotItem.Heart, 'Tower Circle of Pots'), Pot(28, 27, PotItem.FiveArrows, 'Tower Circle of Pots'), - Pot(40, 27, PotItem.Bomb, 'Tower Circle of Pots'), Pot(32, 27, PotItem.Nothing, 'Tower Circle of Pots')], - 177: [Pot(76, 4, PotItem.Heart, 'Mire Spike Barrier'), Pot(112, 4, PotItem.OneRupee, 'Mire Spike Barrier')], - 178: [Pot(48, 40, PotItem.OneRupee, 'Mire BK Door Room'), Pot(76, 40, PotItem.OneRupee, 'Mire BK Door Room'), Pot(48, 41, PotItem.Nothing, 'Mire BK Door Room'), Pot(76, 41, PotItem.Heart, 'Mire BK Door Room'), - Pot(48, 42, PotItem.Nothing, 'Mire BK Door Room'), Pot(76, 40, PotItem.Nothing, 'Mire BK Door Room')], - 179: [Pot(12, 20, PotItem.Key, 'Mire Spikes'), Pot(48, 20, PotItem.SmallMagic, 'Mire Spikes'), Pot(48, 28, PotItem.Switch, 'Mire Spikes')], - 180: [Pot(44, 28, PotItem.BigMagic, 'TR Final Abyss'), Pot(48, 28, PotItem.Heart, 'TR Final Abyss')], - 181: [Pot(112, 4, PotItem.FiveRupees, 'TR Dark Ride'), Pot(112, 15, PotItem.Heart, 'TR Dark Ride'), Pot(76, 16, PotItem.Switch, 'TR Dark Ride'), Pot(112, 16, PotItem.BigMagic, 'TR Dark Ride'), Pot(112, 17, PotItem.Heart, 'TR Dark Ride'), - Pot(112, 28, PotItem.Bomb, 'TR Dark Ride')], - 182: [Pot(94, 9, PotItem.BigMagic, 'TR Refill')], - 183: [Pot(30, 5, PotItem.SmallMagic, 'TR Roller Room')], - 184: [Pot(96, 13, PotItem.Switch, 'Eastern Big Key'), Pot(88, 16, PotItem.Heart, 'Eastern Big Key'), Pot(104, 16, PotItem.Heart, 'Eastern Big Key')], - 185: [Pot(92, 18, PotItem.OneRupee, 'Eastern Cannonball'), Pot(96, 18, PotItem.FiveRupees, 'Eastern Cannonball'), Pot(104, 18, PotItem.FiveRupees, 'Eastern Cannonball'), Pot(108, 18, PotItem.OneRupee, 'Eastern Cannonball')], - 186: [Pot(100, 8, PotItem.Nothing, 'Eastern Dark Pots'), Pot(88, 8, PotItem.Nothing, 'Eastern Dark Pots'), Pot(94, 4, PotItem.OneRupee, 'Eastern Dark Pots'), Pot(76, 6, PotItem.Heart, 'Eastern Dark Pots'), - Pot(112, 6, PotItem.Key, 'Eastern Dark Pots'), Pot(76, 10, PotItem.Heart, 'Eastern Dark Pots'), Pot(112, 10, PotItem.SmallMagic, 'Eastern Dark Pots'), Pot(94, 12, PotItem.OneRupee, 'Eastern Dark Pots')], - 188: [Pot(86, 4, PotItem.Heart, 'Thieves Hallway'), Pot(102, 4, PotItem.Key, 'Thieves Hallway'), Pot(138, 3, PotItem.Bomb, 'Thieves Conveyor Maze'), Pot(178, 3, PotItem.Switch, 'Thieves Conveyor Maze'), - Pot(138, 12, PotItem.Heart, 'Thieves Conveyor Maze'), Pot(178, 12, PotItem.Bomb, 'Thieves Conveyor Maze'), Pot(12, 20, PotItem.Nothing, 'Thieves Pot Alcove Mid'), Pot(48, 20, PotItem.Bomb, 'Thieves Pot Alcove Mid'), - Pot(12, 28, PotItem.Bomb, 'Thieves Pot Alcove Mid'), Pot(48, 28, PotItem.Bomb, 'Thieves Pot Alcove Mid'), Pot(28, 21, PotItem.FiveRupees, 'Thieves Pot Alcove Top'), Pot(32, 21, PotItem.FiveRupees, 'Thieves Pot Alcove Top'), - Pot(28, 27, PotItem.FiveRupees, 'Thieves Pot Alcove Bottom'), Pot(32, 27, PotItem.FiveRupees, 'Thieves Pot Alcove Bottom')], - 190: [Pot(92, 25, PotItem.Switch, 'Ice Switch Room')], - 191: [Pot(40, 20, PotItem.FiveArrows, 'Ice Refill'), Pot(44, 20, PotItem.Heart, 'Ice Refill'), Pot(48, 20, PotItem.Bomb, 'Ice Refill'), Pot(40, 28, PotItem.SmallMagic, 'Ice Refill'), Pot(44, 28, PotItem.SmallMagic, 'Ice Refill'), - Pot(48, 28, PotItem.SmallMagic, 'Ice Refill')], - 192: [Pot(48, 10, PotItem.Bomb, 'Tower Dark Pits'), Pot(12, 14, PotItem.FiveRupees, 'Tower Dark Pits'), Pot(12, 26, PotItem.Heart, 'Tower Dark Pits'), Pot(28, 27, PotItem.OneRupee, 'Tower Dark Pits')], - 194: [Pot(180, 7, PotItem.Switch, 'Mire Hub Switch'), Pot(100, 46, PotItem.SmallMagic, 'Mire Hub Right'), Pot(68, 48, PotItem.OneRupee, 'Mire Hub'), Pot(64, 52, PotItem.FiveArrows, 'Mire Hub')], - 196: [Pot(84, 9, PotItem.Bomb, 'TR Crystal Maze Interior'), Pot(24, 14, PotItem.Heart, 'TR Crystal Maze Interior'), Pot(56, 17, PotItem.FiveRupees, 'TR Crystal Maze Interior'), Pot(84, 17, PotItem.Bomb, 'TR Crystal Maze Interior'), - Pot(12, 21, PotItem.FiveArrows, 'TR Crystal Maze Interior'), Pot(76, 23, PotItem.OneRupee, 'TR Crystal Maze Interior'), Pot(48, 25, PotItem.SmallMagic, 'TR Crystal Maze Interior'), Pot(12, 26, PotItem.Heart, 'TR Crystal Maze Interior')], - 198: [Pot(12, 7, PotItem.BigMagic, 'TR Hub'), Pot(12, 25, PotItem.Heart, 'TR Hub')], - 199: [Pot(12, 10, PotItem.Heart, 'TR Torches'), Pot(12, 11, PotItem.BigMagic, 'TR Torches'), Pot(12, 22, PotItem.SmallMagic, 'TR Torches Ledge'), Pot(12, 28, PotItem.FiveArrows, 'TR Torches Ledge')], - 201: [Pot(30, 22, PotItem.OneRupee, 'Eastern Lobby'), Pot(94, 22, PotItem.OneRupee, 'Eastern Lobby'), Pot(60, 22, PotItem.Switch, 'Eastern Lobby')], - 203: [Pot(80, 4, PotItem.Nothing, 'Thieves Ambush'), Pot(80, 28, PotItem.Nothing, 'Thieves Ambush'), Pot(88, 16, PotItem.Heart, 'Thieves Ambush'), Pot(88, 28, PotItem.FiveRupees, 'Thieves Ambush')], - 204: [Pot(36, 4, PotItem.FiveRupees, 'Thieves Rail Ledge'), Pot(36, 28, PotItem.FiveRupees, 'Thieves Rail Ledge'), Pot(112, 4, PotItem.Heart, 'Thieves BK Corner'), Pot(112, 28, PotItem.Bomb, 'Thieves BK Corner')], - 206: [Pot(76, 8, PotItem.SmallMagic, 'Ice Antechamber'), Pot(80, 8, PotItem.SmallMagic, 'Ice Antechamber'), Pot(108, 12, PotItem.Bomb, 'Ice Antechamber'), Pot(112, 12, PotItem.FiveArrows, 'Ice Antechamber'), - Pot(204, 11, PotItem.Hole, 'Ice Antechamber')], - 208: [Pot(158, 5, PotItem.SmallMagic, 'Tower Dark Maze'), Pot(140, 11, PotItem.OneRupee, 'Tower Dark Maze'), Pot(42, 13, PotItem.SmallMagic, 'Tower Dark Maze'), Pot(48, 16, PotItem.Heart, 'Tower Dark Maze'), - Pot(176, 20, PotItem.OneRupee, 'Tower Dark Maze'), Pot(146, 23, PotItem.FiveRupees, 'Tower Dark Maze'), Pot(12, 28, PotItem.Heart, 'Tower Dark Maze')], - 209: [Pot(48, 4, PotItem.BigMagic, 'Mire Conveyor Barrier'), Pot(168, 7, PotItem.OneRupee, 'Mire Conveyor Barrier'), Pot(76, 4, PotItem.OneRupee, 'Mire Neglected Room'), Pot(112, 4, PotItem.FiveArrows, 'Mire Neglected Room'), - Pot(76, 12, PotItem.Nothing, 'Mire Neglected Room'), Pot(112, 12, PotItem.OneRupee, 'Mire Neglected Room')], - 214: [Pot(92, 22, PotItem.BigMagic, 'TR Main Lobby'), Pot(96, 22, PotItem.Bomb, 'TR Main Lobby')], - 216: [Pot(202, 8, PotItem.Heart, 'Eastern Duo Eyegores'), Pot(242, 8, PotItem.FiveArrows, 'Eastern Duo Eyegores'), Pot(202, 10, PotItem.FiveArrows, 'Eastern Duo Eyegores'), Pot(242, 10, PotItem.FiveArrows, 'Eastern Duo Eyegores'), - Pot(202, 12, PotItem.FiveArrows, 'Eastern Duo Eyegores'), Pot(242, 12, PotItem.Heart, 'Eastern Duo Eyegores'), Pot(92, 24, PotItem.Heart, 'Eastern Single Eyegore'), Pot(96, 24, PotItem.FiveArrows, 'Eastern Single Eyegore')], - 217: [Pot(92, 20, PotItem.FiveArrows, 'Eastern False Switches'), Pot(92, 28, PotItem.Heart, 'Eastern False Switches')], - 218: [Pot(24, 23, PotItem.FiveArrows, 'Eastern Attic Start'), Pot(36, 23, PotItem.FiveArrows, 'Eastern Attic Start'), Pot(24, 25, PotItem.Switch, 'Eastern Attic Start'), Pot(36, 25, PotItem.Heart, 'Eastern Attic Start')], - 219: [Pot(12, 4, PotItem.Nothing, 'Thieves Lobby'), Pot(62, 19, PotItem.Nothing, 'Thieves Lobby'), Pot(112, 4, PotItem.FiveRupees, 'Thieves Lobby'), Pot(88, 16, PotItem.Heart, 'Thieves Lobby')], - 220: [Pot(56, 4, PotItem.FiveRupees, 'Thieves Compass Room'), Pot(112, 4, PotItem.Bomb, 'Thieves Compass Room'), Pot(68, 16, PotItem.Heart, 'Thieves Compass Room'), Pot(12, 28, PotItem.FiveArrows, 'Thieves Compass Room')], - 227: [Pot(88, 55, PotItem.Nothing, 'Bat Cave (right)'), Pot(100, 57, PotItem.OneRupee, 'Bat Cave (right)')], - 228: [Pot(32, 9, PotItem.FiveRupees, 'Old Man House'), Pot(112, 10, PotItem.OneRupee, 'Old Man House')], - 229: [Pot(48, 4, PotItem.OneRupee, 'Old Man House Back'), Pot(76, 4, PotItem.OneRupee, 'Old Man House Back'), Pot(112, 16, PotItem.OneRupee, 'Old Man House Back'), Pot(64, 18, PotItem.FiveRupees, 'Old Man House Back')], - 230: [Pot(108, 12, PotItem.FiveArrows, 'Death Mountain Return Cave'), Pot(88, 16, PotItem.Heart, 'Death Mountain Return Cave'), Pot(72, 20, PotItem.Nothing, 'Death Mountain Return Cave'), - Pot(56, 24, PotItem.OneRupee, 'Death Mountain Return Cave')], - 231: [Pot(68, 5, PotItem.OneRupee, 'Death Mountain Return Cave'), Pot(72, 5, PotItem.OneRupee, 'Death Mountain Return Cave')], - 232: [Pot(96, 4, PotItem.Heart, 'Superbunny Cave')], - 235: [Pot(206, 8, PotItem.FiveRupees, 'Bumper Cave'), Pot(210, 8, PotItem.FiveRupees, 'Bumper Cave'), Pot(88, 14, PotItem.SmallMagic, 'Bumper Cave'), Pot(92, 14, PotItem.Heart, 'Bumper Cave'), Pot(96, 14, PotItem.SmallMagic, 'Bumper Cave')], - 241: [Pot(64, 5, PotItem.Heart, 'Old Man Cave')], - 248: [Pot(242, 13, PotItem.BigMagic, 'Superbunny Cave')], - 253: [Pot(88, 6, PotItem.FiveRupees, 'Fairy Ascension Cave (Top)'), Pot(100, 6, PotItem.FiveRupees, 'Fairy Ascension Cave (Top)'), Pot(84, 23, PotItem.FiveRupees, 'Fairy Ascension Cave (Bottom)'), - Pot(84, 24, PotItem.FiveRupees, 'Fairy Ascension Cave (Bottom)')], - 255: [Pot(92, 8, PotItem.Heart, 'Paradox Cave'), Pot(96, 8, PotItem.Heart, 'Paradox Cave'), Pot(112, 28, PotItem.OneRupee, 'Paradox Cave Front')], - 257: [Pot(12, 20, PotItem.Heart, 'Snitch Lady (East)'), Pot(224, 19, PotItem.Chicken, 'Snitch Lady (West)'), Pot(228, 19, PotItem.Heart, 'Snitch Lady (West)')], - 258: [Pot(146, 19, PotItem.Heart, 'Sick Kids House'), Pot(150, 19, PotItem.Heart, 'Sick Kids House')], - 259: [Pot(140, 7, PotItem.Chicken, 'Tavern'), Pot(140, 9, PotItem.Nothing, 'Tavern'), Pot(12, 12, PotItem.Heart, 'Tavern (Front)')], - 260: [Pot(202, 21, PotItem.Heart, 'Links House'), Pot(202, 22, PotItem.Heart, 'Links House'), Pot(202, 23, PotItem.Heart, 'Links House')], - 261: [Pot(30, 20, PotItem.Heart, 'Sahasrahlas Hut'), Pot(28, 21, PotItem.Heart, 'Sahasrahlas Hut'), Pot(32, 21, PotItem.Heart, 'Sahasrahlas Hut')], - 263: [Pot(214, 23, PotItem.Bomb, 'Light World Bomb Hut'), Pot(222, 23, PotItem.FiveArrows, 'Light World Bomb Hut'), Pot(230, 23, PotItem.Bomb, 'Light World Bomb Hut'), Pot(214, 25, PotItem.OneRupee, 'Light World Bomb Hut'), - Pot(222, 25, PotItem.Nothing, 'Light World Bomb Hut'), Pot(230, 25, PotItem.OneRupee, 'Light World Bomb Hut'), Pot(214, 27, PotItem.Bomb, 'Light World Bomb Hut'), Pot(230, 27, PotItem.Bomb, 'Light World Bomb Hut')], - 264: [Pot(166, 19, PotItem.Chicken, 'Chicken House')], - 268: [Pot(88, 14, PotItem.Heart, 'Hookshot Fairy')], - 276: [Pot(92, 4, PotItem.Heart, 'Dark Desert Hint'), Pot(96, 4, PotItem.Heart, 'Dark Desert Hint'), Pot(92, 5, PotItem.Bomb, 'Dark Desert Hint'), Pot(96, 5, PotItem.Bomb, 'Dark Desert Hint'), Pot(92, 10, PotItem.FiveArrows, 'Dark Desert Hint'), - Pot(96, 10, PotItem.Heart, 'Dark Desert Hint')], - 279: [Pot(138, 3, PotItem.Heart, 'Spike Cave'), Pot(142, 3, PotItem.Heart, 'Spike Cave'), Pot(166, 3, PotItem.Heart, 'Spike Cave'), Pot(170, 3, PotItem.Heart, 'Spike Cave'), Pot(138, 4, PotItem.Heart, 'Spike Cave'), - Pot(142, 4, PotItem.Heart, 'Spike Cave'), Pot(166, 4, PotItem.Heart, 'Spike Cave'), Pot(170, 4, PotItem.Heart, 'Spike Cave')], - 281: [Pot(44, 28, PotItem.Heart, 'Blinds Hideout'), Pot(48, 28, PotItem.OneRupee, 'Blinds Hideout'), Pot(76, 28, PotItem.Heart, 'Blinds Hideout'), Pot(80, 28, PotItem.Heart, 'Blinds Hideout')], - 282: [Pot(214, 10, PotItem.Heart, 'Palace of Darkness Hint'), Pot(218, 10, PotItem.Heart, 'Palace of Darkness Hint'), Pot(226, 10, PotItem.Heart, 'Palace of Darkness Hint'), Pot(230, 10, PotItem.Heart, 'Palace of Darkness Hint')], - 283: [Pot(24, 53, PotItem.Nothing, 'Cave 45'), Pot(24, 54, PotItem.Heart, 'Cave 45'), Pot(32, 54, PotItem.Heart, 'Cave 45'), Pot(40, 54, PotItem.Heart, 'Cave 45'), Pot(24, 55, PotItem.Heart, 'Cave 45'), Pot(28, 56, PotItem.Heart, 'Cave 45'), - Pot(92, 22, PotItem.Bomb, 'Graveyard Cave'), Pot(96, 22, PotItem.Heart, 'Graveyard Cave'), Pot(92, 23, PotItem.Bomb, 'Graveyard Cave'), Pot(96, 23, PotItem.Heart, 'Graveyard Cave'), Pot(92, 24, PotItem.Bomb, 'Graveyard Cave'), - Pot(96, 24, PotItem.Heart, 'Graveyard Cave'), Pot(92, 25, PotItem.Bomb, 'Graveyard Cave'), Pot(96, 25, PotItem.Heart, 'Graveyard Cave')], - 285: [Pot(60, 6, PotItem.FiveRupees, 'Blinds Hideout'), Pot(64, 6, PotItem.FiveRupees, 'Blinds Hideout'), Pot(60, 7, PotItem.FiveRupees, 'Blinds Hideout'), Pot(64, 7, PotItem.FiveRupees, 'Blinds Hideout'), - Pot(60, 8, PotItem.FiveRupees, 'Blinds Hideout'), Pot(64, 8, PotItem.FiveRupees, 'Blinds Hideout')], - 287: [Pot(174, 28, PotItem.Heart, 'Lumberjack House'), Pot(178, 28, PotItem.Heart, 'Lumberjack House')], - 292: [Pot(20, 20, PotItem.FiveRupees, '50 Rupee Cave'), Pot(40, 20, PotItem.FiveRupees, '50 Rupee Cave'), Pot(20, 21, PotItem.FiveRupees, '50 Rupee Cave'), Pot(40, 21, PotItem.FiveRupees, '50 Rupee Cave'), - Pot(20, 22, PotItem.FiveRupees, '50 Rupee Cave'), Pot(40, 22, PotItem.FiveRupees, '50 Rupee Cave'), Pot(24, 24, PotItem.FiveRupees, '50 Rupee Cave'), Pot(28, 24, PotItem.FiveRupees, '50 Rupee Cave'), - Pot(32, 24, PotItem.FiveRupees, '50 Rupee Cave'), Pot(36, 24, PotItem.FiveRupees, '50 Rupee Cave')], - 293: [Pot(24, 25, PotItem.FiveRupees, '20 Rupee Cave'), Pot(28, 25, PotItem.FiveRupees, '20 Rupee Cave'), Pot(32, 25, PotItem.FiveRupees, '20 Rupee Cave'), Pot(36, 25, PotItem.FiveRupees, '20 Rupee Cave'), - Pot(88, 22, PotItem.Heart, 'Dev Cave Hint'), Pot(100, 22, PotItem.Heart, 'Dev Cave Hint'), Pot(88, 28, PotItem.Heart, 'Dev Cave Hint'), Pot(100, 28, PotItem.Heart, 'Dev Cave Hint')], - 295: [Pot(24, 25, PotItem.Nothing, 'Hammer Pegs Cave'), Pot(28, 25, PotItem.Nothing, 'Hammer Pegs Cave'), Pot(32, 25, PotItem.Nothing, 'Hammer Pegs Cave'), Pot(36, 25, PotItem.Nothing, 'Hammer Pegs Cave')], + 2: [Pot(80, 6, PotItem.Nothing, 'Sewers Yet More Rats', PotFlags.LowerRegion, obj=RoomObject(0x0A8B57, [0xA3, 0x33, 0xFA])), + Pot(80, 8, PotItem.Nothing, 'Sewers Yet More Rats', PotFlags.LowerRegion, obj=RoomObject(0x0A8B5A, [0xA3, 0x43, 0xFA])), + Pot(44, 8, PotItem.Nothing, 'Sewers Yet More Rats', PotFlags.LowerRegion, obj=RoomObject(0x0A8B5D, [0x5B, 0x43, 0xFA])), + Pot(44, 10, PotItem.Nothing, 'Sewers Yet More Rats', PotFlags.LowerRegion, obj=RoomObject(0x0A8B60, [0x5B, 0x53, 0xFA]))], + 4: [Pot(162, 25, PotItem.Nothing, 'TR Dash Room', obj=RoomObject(0x1FE220, [0x47, 0xCF, 0xFA])), + Pot(152, 25, PotItem.Nothing, 'TR Dash Room', obj=RoomObject(0x1FE21D, [0x33, 0xCF, 0xFA])), + Pot(152, 22, PotItem.Nothing, 'TR Dash Room', obj=RoomObject(0x1FE217, [0x33, 0xB7, 0xFA])), + Pot(162, 22, PotItem.Nothing, 'TR Dash Room', obj=RoomObject(0x1FE21A, [0x47, 0xB7, 0xFA])), + Pot(204, 19, PotItem.Bomb, 'TR Tongue Pull', obj=RoomObject(0x1FE241, [0x9B, 0x9F, 0xFA])), + Pot(240, 19, PotItem.Bomb, 'TR Tongue Pull', obj=RoomObject(0x1FE244, [0xE3, 0x9F, 0xFA]))], + 9: [Pot(12, 4, PotItem.OneRupee, 'PoD Shooter Room', obj=RoomObject(0x1FACEC, [0x1B, 0x23, 0xFA])), + Pot(48, 4, PotItem.Heart, 'PoD Shooter Room', obj=RoomObject(0x1FACEF, [0x63, 0x23, 0xFA])), + Pot(12, 12, PotItem.Switch, 'PoD Shooter Room', obj=RoomObject(0x1FACF5, [0x1B, 0x63, 0xFA]))], + 0xa: [Pot(96, 8, PotItem.Heart, 'PoD Stalfos Basement', obj=RoomObject(0x1FA6EC, [0xC3, 0x43, 0xFA])), + Pot(104, 8, PotItem.Heart, 'PoD Stalfos Basement', obj=RoomObject(0x1FA6F2, [0xD3, 0x43, 0xFA])), + Pot(204, 11, PotItem.Switch, 'PoD Stalfos Basement', obj=RoomObject(0x1FA6FB, [0x9B, 0x5F, 0xFA])), + Pot(100, 9, PotItem.Nothing, 'PoD Stalfos Basement', obj=RoomObject(0x1FA6EF, [0xCB, 0x4B, 0xFA])), + Pot(100, 7, PotItem.Nothing, 'PoD Stalfos Basement', obj=RoomObject(0x1FA6F5, [0xCB, 0x3B, 0xFA])), + Pot(156, 17, PotItem.Bomb, 'PoD Basement Ledge', PotFlags.SwitchLogicChange, obj=RoomObject(0x1FA6E6, [0x3B, 0x8F, 0xFA])), + Pot(160, 17, PotItem.FiveArrows, 'PoD Basement Ledge', PotFlags.SwitchLogicChange, obj=RoomObject(0x1FA6E9, [0x43, 0x8F, 0xFA]))], + 0xb: [Pot(202, 3, PotItem.Bomb, 'PoD Dark Pegs Left', obj=RoomObject(0x1FAB48, [0x97, 0x1F, 0xFA])), + Pot(202, 12, PotItem.Bomb, 'PoD Dark Pegs Left', obj=RoomObject(0x1FAB4E, [0x97, 0x67, 0xFA]))], + 0x11: [Pot(152, 19, PotItem.Nothing, 'Sewers Secret Room', obj=RoomObject(0x0A8C07, [0x33, 0x9F, 0xFA])), + Pot(152, 15, PotItem.Nothing, 'Sewers Secret Room', obj=RoomObject(0x0A8BF5, [0x33, 0x7F, 0xFA])), + Pot(144, 15, PotItem.Heart, 'Sewers Secret Room', obj=RoomObject(0x0A8BF2, [0x23, 0x7F, 0xFA])), + Pot(160, 15, PotItem.Heart, 'Sewers Secret Room', obj=RoomObject(0x0A8BF8, [0x43, 0x7F, 0xFA])), + Pot(144, 19, PotItem.Heart, 'Sewers Secret Room', obj=RoomObject(0x0A8C04, [0x23, 0x9F, 0xFA])), + Pot(160, 19, PotItem.Heart, 'Sewers Secret Room', obj=RoomObject(0x0A8C0A, [0x43, 0x9F, 0xFA]))], + 0x15: [Pot(96, 4, PotItem.Bomb, 'TR Pipe Pit', obj=RoomObject(0x1FE551, [0xC3, 0x23, 0xFA])), + Pot(100, 4, PotItem.SmallMagic, 'TR Pipe Pit', obj=RoomObject(0x1FE554, [0xCB, 0x23, 0xFA])), + Pot(104, 4, PotItem.Heart, 'TR Pipe Pit', obj=RoomObject(0x1FE557, [0xD3, 0x23, 0xFA])), + Pot(108, 4, PotItem.SmallMagic, 'TR Pipe Pit', obj=RoomObject(0x1FE55A, [0xDB, 0x23, 0xFA])), + Pot(112, 4, PotItem.FiveArrows, 'TR Pipe Pit', obj=RoomObject(0x1FE55D, [0xE3, 0x23, 0xFA])), + Pot(12, 6, PotItem.OneRupee, 'TR Pipe Pit', obj=RoomObject(0x1FE548, [0x1B, 0x33, 0xFA])), + Pot(16, 6, PotItem.FiveArrows, 'TR Pipe Pit', obj=RoomObject(0x1FE54B, [0x23, 0x33, 0xFA])), + Pot(20, 6, PotItem.FiveRupees, 'TR Pipe Pit', obj=RoomObject(0x1FE54E, [0x2B, 0x33, 0xFA])), + Pot(70, 11, PotItem.BigMagic, 'TR Pipe Ledge', obj=RoomObject(0x1FE545, [0x8F, 0x5B, 0xFA]))], + 0x16: [Pot(188, 3, PotItem.Heart, 'Swamp I', obj=RoomObject(0x1FA09C, [0x7B, 0x1F, 0xFA])), + Pot(192, 3, PotItem.Heart, 'Swamp I', obj=RoomObject(0x1FA09F, [0x83, 0x1F, 0xFA])), + Pot(188, 4, PotItem.SmallMagic, 'Swamp I', obj=RoomObject(0x1FA0A2, [0x7B, 0x27, 0xFA])), + Pot(192, 4, PotItem.SmallMagic, 'Swamp I', obj=RoomObject(0x1FA0A5, [0x83, 0x27, 0xFA])), + Pot(188, 5, PotItem.FiveArrows, 'Swamp I', obj=RoomObject(0x1FA0A8, [0x7B, 0x2F, 0xFA])), + Pot(192, 5, PotItem.FiveArrows, 'Swamp I', obj=RoomObject(0x1FA0AB, [0x83, 0x2F, 0xFA])), + Pot(188, 6, PotItem.Bomb, 'Swamp I', obj=RoomObject(0x1FA0AE, [0x7B, 0x37, 0xFA])), + Pot(192, 6, PotItem.Bomb, 'Swamp I', obj=RoomObject(0x1FA0B1, [0x83, 0x37, 0xFA])), + Pot(240, 19, PotItem.Key, 'Swamp Waterway', obj=RoomObject(0x1FA0D8, [0xE3, 0x9F, 0xFA]))], + 0x17: [Pot(100, 13, PotItem.Heart, 'Hera 5F Pot Block', obj=RoomObject(0x1FCCE0, [0xCB, 0x6B, 0xFA])), + Pot(100, 14, PotItem.Heart, 'Hera 5F Pot Block', obj=RoomObject(0x1FCCE3, [0xCB, 0x73, 0xFA])), + Pot(100, 15, PotItem.Heart, 'Hera 5F Pot Block', obj=RoomObject(0x1FCCE6, [0xCB, 0x7B, 0xFA])), + Pot(100, 16, PotItem.Heart, 'Hera 5F Pot Block', obj=RoomObject(0x1FCCE9, [0xCB, 0x83, 0xFA])), + Pot(100, 17, PotItem.Heart, 'Hera 5F Pot Block', obj=RoomObject(0x1FCCEC, [0xCB, 0x8B, 0xFA])), + Pot(100, 18, PotItem.Heart, 'Hera 5F Pot Block', obj=RoomObject(0x1FCCEF, [0xCB, 0x93, 0xFA])), + Pot(104, 13, PotItem.Heart, 'Hera 5F Pot Block', obj=RoomObject(0x1FCCF2, [0xD3, 0x6B, 0xFA])), + Pot(104, 14, PotItem.Heart, 'Hera 5F Pot Block', obj=RoomObject(0x1FCCF5, [0xD3, 0x73, 0xFA])), + Pot(104, 15, PotItem.Heart, 'Hera 5F Pot Block', obj=RoomObject(0x1FCCF8, [0xD3, 0x7B, 0xFA])), + Pot(104, 16, PotItem.Heart, 'Hera 5F Pot Block', obj=RoomObject(0x1FCCFB, [0xD3, 0x83, 0xFA])), + Pot(104, 17, PotItem.Heart, 'Hera 5F Pot Block', obj=RoomObject(0x1FCCFE, [0xD3, 0x8B, 0xFA])), + Pot(104, 18, PotItem.Heart, 'Hera 5F Pot Block', obj=RoomObject(0x1FCD01, [0xD3, 0x93, 0xFA]))], + 0x1A: [Pot(28, 5, PotItem.Bomb, 'PoD Falling Bridge Ledge', obj=RoomObject(0x1FA60D, [0x3B, 0x2B, 0xFA])), + Pot(32, 5, PotItem.Bomb, 'PoD Falling Bridge Ledge', obj=RoomObject(0x1FA610, [0x43, 0x2B, 0xFA])), + Pot(28, 27, PotItem.Bomb, 'PoD Falling Bridge', obj=RoomObject(0x1FA5F5, [0x3B, 0xDB, 0xFA])), + Pot(32, 27, PotItem.Bomb, 'PoD Falling Bridge', obj=RoomObject(0x1FA5F8, [0x43, 0xDB, 0xFA])), + Pot(232, 19, PotItem.Nothing, 'PoD Harmless Hellway', obj=RoomObject(0x1FA64C, [0xD3, 0x9F, 0xFA])), + Pot(212, 19, PotItem.Nothing, 'PoD Harmless Hellway', obj=RoomObject(0x1FA649, [0xAB, 0x9F, 0xFA]))], + 0x1B: [Pot(20, 23, PotItem.FiveArrows, 'PoD Mimics 2', obj=RoomObject(0x1FAAFE, [0x2B, 0xBB, 0xFA])), + Pot(40, 23, PotItem.FiveArrows, 'PoD Mimics 2', obj=RoomObject(0x1FAB01, [0x53, 0xBB, 0xFA]))], + 0x1E: [Pot(84, 9, PotItem.Bomb, 'Ice Bomb Drop', obj=RoomObject(0x1FC325, [0xAB, 0x4B, 0xFA]))], + 0x1F: [Pot(28, 25, PotItem.Switch, 'Ice Pengator Switch', obj=RoomObject(0x1FC38B, [0x3B, 0xCB, 0xFA])), + Pot(28, 23, PotItem.Nothing, 'Ice Pengator Switch', obj=RoomObject(0x1FC388, [0x3B, 0xBB, 0xFA])), + Pot(86, 26, PotItem.Nothing, 'Ice Big Key', obj=RoomObject(0x1FC397, [0xAF, 0xD3, 0xFA])), + Pot(86, 27, PotItem.Nothing, 'Ice Big Key', obj=RoomObject(0x1FC39A, [0xAF, 0xDB, 0xFA]))], + 0x21: [Pot(160, 20, PotItem.Nothing, 'Sewers Key Rat', obj=RoomObject(0x0A8C71, [0x43, 0xA7, 0xFA])), + Pot(168, 24, PotItem.SmallMagic, 'Sewers Key Rat', obj=RoomObject(0x0A8C7A, [0x53, 0xC7, 0xFA])), + Pot(48, 28, PotItem.Heart, 'Sewers Key Rat', obj=RoomObject(0x0A8C80, [0x63, 0xE3, 0xFA])), + Pot(82, 28, PotItem.SmallMagic, 'Sewers Key Rat', obj=RoomObject(0x0A8C7D, [0xA7, 0xE3, 0xFA])), + Pot(100, 28, PotItem.Nothing, 'Sewers Key Rat', obj=RoomObject(0x0A8C74, [0xCB, 0xE3, 0xFA])), + Pot(104, 28, PotItem.Nothing, 'Sewers Key Rat', obj=RoomObject(0x0A8C77, [0xD3, 0xE3, 0xFA]))], + 0x23: [Pot(86, 26, PotItem.OneRupee, 'TR Lazy Eyes', obj=RoomObject(0x1FED09, [0xAF, 0xD3, 0xFA])), + Pot(90, 26, PotItem.Heart, 'TR Lazy Eyes', obj=RoomObject(0x1FED0C, [0xB7, 0xD3, 0xFA])), + Pot(94, 26, PotItem.OneRupee, 'TR Lazy Eyes', obj=RoomObject(0x1FED0F, [0xBF, 0xD3, 0xFA])), + Pot(98, 26, PotItem.Bomb, 'TR Lazy Eyes', obj=RoomObject(0x1FED12, [0xC7, 0xD3, 0xFA])), + Pot(102, 26, PotItem.OneRupee, 'TR Lazy Eyes', obj=RoomObject(0x1FED15, [0xCF, 0xD3, 0xFA]))], + 0x24: [Pot(12, 4, PotItem.FiveRupees, 'TR Twin Pokeys', obj=RoomObject(0x1FE646, [0x1B, 0x23, 0xFA])), + Pot(48, 4, PotItem.Heart, 'TR Twin Pokeys', obj=RoomObject(0x1FE649, [0x63, 0x23, 0xFA])), + Pot(12, 12, PotItem.SmallMagic, 'TR Twin Pokeys', obj=RoomObject(0x1FE64C, [0x1B, 0x63, 0xFA])), + Pot(48, 12, PotItem.OneRupee, 'TR Twin Pokeys', obj=RoomObject(0x1FE64F, [0x63, 0x63, 0xFA]))], + 0x26: [Pot(28, 4, PotItem.Bomb, 'Swamp Shooters', obj=RoomObject(0x1F9BDD, [0x3B, 0x23, 0xFA])), + Pot(12, 8, PotItem.SmallMagic, 'Swamp Shooters', obj=RoomObject(0x1F9BDA, [0x1B, 0x43, 0xFA])), + Pot(150, 19, PotItem.Switch, 'Swamp Push Statue', obj=RoomObject(0x1F9C46, [0x2F, 0x9F, 0xFA])), + Pot(22, 26, PotItem.FiveRupees, 'Swamp Push Statue', obj=RoomObject(0x1F9C49, [0x2F, 0xD3, 0xFA])), + Pot(220, 26, PotItem.FiveArrows, 'Swamp Push Statue', PotFlags.SwitchLogicChange, obj=RoomObject(0x1F9C52, [0xBB, 0xD7, 0xFA]))], + 0x27: [Pot(214, 19, PotItem.Nothing, 'Hera 4F', obj=RoomObject(0x1FCE19, [0xAF, 0x9F, 0xFA])), + Pot(214, 20, PotItem.Nothing, 'Hera 4F', obj=RoomObject(0x1FCE1C, [0xAF, 0xA7, 0xFA])), + Pot(166, 20, PotItem.Bomb, 'Hera 4F', obj=RoomObject(0x1FCE28, [0x4F, 0xA7, 0xFA])), + Pot(214, 21, PotItem.Heart, 'Hera 4F', obj=RoomObject(0x1FCE1F, [0xAF, 0xAF, 0xFA])), + Pot(40, 28, PotItem.OneRupee, 'Hera 4F', obj=RoomObject(0x1FCE4C, [0x53, 0xE3, 0xFA])), + Pot(44, 28, PotItem.OneRupee, 'Hera 4F', obj=RoomObject(0x1FCE4F, [0x5B, 0xE3, 0xFA])), + Pot(80, 28, PotItem.FiveRupees, 'Hera 4F', obj=RoomObject(0x1FCE52, [0xA3, 0xE3, 0xFA])), + Pot(84, 28, PotItem.FiveRupees, 'Hera 4F', obj=RoomObject(0x1FCE55, [0xAB, 0xE3, 0xFA])), + Pot(102, 17, PotItem.Nothing, 'Hera 4F', obj=RoomObject(0x1FCE07, [0xCF, 0x8B, 0xFA])), + Pot(98, 17, PotItem.Nothing, 'Hera 4F', obj=RoomObject(0x1FCE04, [0xC7, 0x8B, 0xFA])), + Pot(106, 17, PotItem.Nothing, 'Hera 4F', obj=RoomObject(0x1FCE0A, [0xD7, 0x8B, 0xFA])), + Pot(166, 21, PotItem.Nothing, 'Hera 4F', obj=RoomObject(0x1FCE2B, [0x4F, 0xAF, 0xFA])), + Pot(166, 19, PotItem.Nothing, 'Hera 4F', obj=RoomObject(0x1FCE25, [0x4F, 0x9F, 0xFA])), + Pot(92, 12, PotItem.Nothing, 'Hera 4F', obj=RoomObject(0x1FCDF8, [0xBB, 0x63, 0xFA])), + Pot(160, 12, PotItem.Nothing, 'Hera 4F', obj=RoomObject(0x1FCDE0, [0x43, 0x67, 0xFA]))], + 0x2A: [Pot(80, 12, PotItem.OneRupee, 'PoD Arena Main', obj=RoomObject(0x1FA57F, [0xA3, 0x63, 0xFA])), + Pot(80, 19, PotItem.Heart, 'PoD Arena Main', obj=RoomObject(0x1FA582, [0xA3, 0x9B, 0xFA]))], + 0x2B: [Pot(16, 5, PotItem.Heart, 'PoD Sexy Statue', obj=RoomObject(0x1FAA14, [0x23, 0x2B, 0xFA])), + Pot(44, 5, PotItem.Switch, 'PoD Sexy Statue', obj=RoomObject(0x1FAA1D, [0x5B, 0x2B, 0xFA])), + Pot(16, 6, PotItem.Heart, 'PoD Sexy Statue', obj=RoomObject(0x1FAA17, [0x23, 0x33, 0xFA])), + Pot(44, 6, PotItem.Bomb, 'PoD Sexy Statue', obj=RoomObject(0x1FAA20, [0x5B, 0x33, 0xFA])), + Pot(16, 7, PotItem.Heart, 'PoD Sexy Statue', obj=RoomObject(0x1FAA1A, [0x23, 0x3B, 0xFA])), + Pot(44, 7, PotItem.Bomb, 'PoD Sexy Statue', obj=RoomObject(0x1FAA23, [0x5B, 0x3B, 0xFA])), + Pot(146, 21, PotItem.Bomb, 'PoD Map Balcony', obj=RoomObject(0x1FAA44, [0x27, 0xAF, 0xFA])), + Pot(170, 21, PotItem.FiveArrows, 'PoD Map Balcony', obj=RoomObject(0x1FAA4A, [0x57, 0xAF, 0xFA])), + Pot(146, 22, PotItem.Bomb, 'PoD Map Balcony', obj=RoomObject(0x1FAA47, [0x27, 0xB7, 0xFA])), + Pot(170, 22, PotItem.FiveArrows, 'PoD Map Balcony', obj=RoomObject(0x1FAA4D, [0x57, 0xB7, 0xFA]))], + 0x2C: [Pot(108, 24, PotItem.Heart, 'Hookshot Cave (Middle)', obj=RoomObject(0x0A889F, [0xDB, 0xC3, 0xFA])), + Pot(112, 24, PotItem.Heart, 'Hookshot Cave (Middle)', obj=RoomObject(0x0A88A2, [0xE3, 0xC3, 0xFA]))], + 0x2F: [Pot(28, 7, PotItem.Heart, 'Kakariko Well (back)', obj=RoomObject(0x0A8744, [0x3B, 0x3B, 0xFA])), + Pot(32, 7, PotItem.Heart, 'Kakariko Well (back)', obj=RoomObject(0x0A8747, [0x43, 0x3B, 0xFA])), + Pot(28, 9, PotItem.FiveRupees, 'Kakariko Well (back)', obj=RoomObject(0x0A874A, [0x3B, 0x4B, 0xFA])), + Pot(32, 9, PotItem.FiveRupees, 'Kakariko Well (back)', obj=RoomObject(0x0A874D, [0x43, 0x4B, 0xFA])), + Pot(172, 19, PotItem.FiveRupees, 'Kakariko Well (top)', obj=RoomObject(0x0A8762, [0x5B, 0x9F, 0xFA])), + Pot(180, 19, PotItem.FiveRupees, 'Kakariko Well (top)', obj=RoomObject(0x0A8765, [0x6B, 0x9F, 0xFA])), + Pot(104, 27, PotItem.Heart, 'Kakariko Well (bottom)', obj=RoomObject(0x0A8771, [0xD3, 0xDB, 0xFA])), + Pot(104, 28, PotItem.Heart, 'Kakariko Well (bottom)', obj=RoomObject(0x0A8774, [0xD3, 0xE3, 0xFA]))], + 0x31: [Pot(92, 28, PotItem.Bomb, 'Hera Beetles', obj=RoomObject(0x1FCF10, [0xBB, 0xE3, 0xFA])), + Pot(96, 28, PotItem.Nothing, 'Hera Beetles', obj=RoomObject(0x1FCF13, [0xC3, 0xE3, 0xFA]))], + 0x32: [Pot(28, 13, PotItem.SmallMagic, 'Sewers Dark Cross', obj=RoomObject(0x0A8E12, [0x3B, 0x6B, 0xFA]))], + 0x34: [Pot(78, 8, PotItem.FiveRupees, 'Swamp Barrier Ledge', obj=RoomObject(0x1F98A3, [0x9F, 0x43, 0xFA])), + Pot(92, 8, PotItem.FiveRupees, 'Swamp Barrier Ledge', obj=RoomObject(0x1F98A6, [0xBB, 0x43, 0xFA]))], + 0x35: [Pot(60, 6, PotItem.Key, 'Swamp Trench 2 Alcove', obj=RoomObject(0x1F979A, [0x7B, 0x33, 0xFA])), + Pot(20, 8, PotItem.FiveRupees, 'Swamp Big Key Ledge', obj=RoomObject(0x1F9725, [0x2B, 0x43, 0xFA])), + Pot(24, 8, PotItem.FiveRupees, 'Swamp Big Key Ledge', obj=RoomObject(0x1F9728, [0x33, 0x43, 0xFA])), + Pot(28, 8, PotItem.FiveRupees, 'Swamp Big Key Ledge', obj=RoomObject(0x1F972B, [0x3B, 0x43, 0xFA])), + Pot(32, 8, PotItem.FiveRupees, 'Swamp Big Key Ledge', obj=RoomObject(0x1F972E, [0x43, 0x43, 0xFA])), + Pot(36, 8, PotItem.FiveRupees, 'Swamp Big Key Ledge', obj=RoomObject(0x1F9731, [0x4B, 0x43, 0xFA])), + Pot(48, 20, PotItem.Heart, 'Swamp Trench 2 Departure', obj=RoomObject(0x1F9764, [0x63, 0xA3, 0xFA])), + Pot(76, 23, PotItem.Nothing, 'Swamp Trench 2 Pots', obj=RoomObject(0x1F976A, [0x9B, 0xBB, 0xFA])), + Pot(88, 23, PotItem.Nothing, 'Swamp Trench 2 Pots', obj=RoomObject(0x1F9776, [0xB3, 0xBB, 0xFA])), + Pot(100, 27, PotItem.Nothing, 'Swamp Trench 2 Pots', obj=RoomObject(0x1F9773, [0xCB, 0xDB, 0xFA])), + Pot(242, 28, PotItem.Nothing, 'Swamp Trench 2 Pots', obj=RoomObject(0x1F9779, [0xE7, 0xE7, 0xFA])), + Pot(240, 22, PotItem.Heart, 'Swamp Trench 2 Pots', obj=RoomObject(0x1F9770, [0xE3, 0xB7, 0xFA])), + Pot(76, 28, PotItem.Heart, 'Swamp Trench 2 Pots', obj=RoomObject(0x1F976D, [0x9B, 0xE3, 0xFA]))], + 0x36: [Pot(108, 4, PotItem.Bomb, 'Swamp Hub Dead Ledge', obj=RoomObject(0x1F9631, [0xDB, 0x23, 0xFA])), + Pot(112, 4, PotItem.FiveRupees, 'Swamp Hub Dead Ledge', obj=RoomObject(0x1F9634, [0xE3, 0x23, 0xFA])), + Pot(10, 16, PotItem.Heart, 'Swamp Hub Side Ledges', obj=RoomObject(0x1F9625, [0x17, 0x83, 0xFA])), + Pot(154, 15, PotItem.Nothing, 'Swamp Hub Side Ledges', obj=RoomObject(0x1F9628, [0x37, 0x7F, 0xFA])), + Pot(114, 16, PotItem.Key, 'Swamp Hub Side Ledges', obj=RoomObject(0x1F963A, [0xE7, 0x83, 0xFA])), + Pot(222, 15, PotItem.Nothing, 'Swamp Hub Side Ledges', obj=RoomObject(0x1F9637, [0xBF, 0x7F, 0xFA])), + Pot(188, 5, PotItem.Nothing, 'Swamp Hub North Ledge', obj=RoomObject(0x1F962B, [0x7B, 0x2F, 0xFA])), + Pot(192, 5, PotItem.Nothing, 'Swamp Hub North Ledge', obj=RoomObject(0x1F962E, [0x83, 0x2F, 0xFA]))], + 0x37: [Pot(60, 6, PotItem.Key, 'Swamp Trench 1 Alcove', obj=RoomObject(0x1F944A, [0x7B, 0x33, 0xFA])), + Pot(48, 20, PotItem.Nothing, 'Swamp Trench 1 Key Ledge', obj=RoomObject(0x1F9423, [0x63, 0xA3, 0xFA]))], + 0x38: [Pot(164, 12, PotItem.Bomb, 'Swamp Pot Row', obj=RoomObject(0x1F933A, [0x4B, 0x67, 0xFA])), + Pot(164, 13, PotItem.FiveRupees, 'Swamp Pot Row', obj=RoomObject(0x1F933D, [0x4B, 0x6F, 0xFA])), + Pot(164, 18, PotItem.Bomb, 'Swamp Pot Row', obj=RoomObject(0x1F9340, [0x4B, 0x97, 0xFA])), + Pot(164, 19, PotItem.Key, 'Swamp Pot Row', obj=RoomObject(0x1F9343, [0x4B, 0x9F, 0xFA]))], + 0x39: [Pot(12, 20, PotItem.Heart, 'Skull Spike Corner', obj=RoomObject(0x1FC14A, [0x1B, 0xA3, 0xFA])), + Pot(48, 28, PotItem.FiveArrows, 'Skull Spike Corner', obj=RoomObject(0x1FC153, [0x63, 0xE3, 0xFA])), + Pot(100, 22, PotItem.SmallMagic, 'Skull Final Drop', obj=RoomObject(0x1FC168, [0xCB, 0xB3, 0xFA])), + Pot(100, 26, PotItem.FiveArrows, 'Skull Final Drop', obj=RoomObject(0x1FC16B, [0xCB, 0xD3, 0xFA]))], + 0x3C: [Pot(24, 8, PotItem.SmallMagic, 'Hookshot Cave (Hook Islands)', obj=RoomObject(0x0A8979, [0x33, 0x43, 0xFA])), + Pot(64, 12, PotItem.FiveRupees, 'Hookshot Cave (Hook Islands)', obj=RoomObject(0x0A898E, [0x83, 0x63, 0xFA])), + Pot(20, 14, PotItem.OneRupee, 'Hookshot Cave (Hook Islands)', obj=RoomObject(0x0A897C, [0x2B, 0x73, 0xFA])), + Pot(20, 19, PotItem.Nothing, 'Hookshot Cave (Hook Islands)', obj=RoomObject(0x0A897F, [0x2B, 0x9B, 0xFA])), + Pot(68, 18, PotItem.FiveRupees, 'Hookshot Cave (Bonk Islands)', obj=RoomObject(0x0A8994, [0x8B, 0x93, 0xFA])), + Pot(96, 19, PotItem.Heart, 'Hookshot Cave (Front)', obj=RoomObject(0x0A8976, [0xC3, 0x9B, 0xFA])), + Pot(64, 20, PotItem.FiveRupees, 'Hookshot Cave (Bonk Islands)', obj=RoomObject(0x0A8997, [0x83, 0xA3, 0xFA])), + Pot(64, 26, PotItem.FiveRupees, 'Hookshot Cave (Bonk Islands)', obj=RoomObject(0x0A899A, [0x83, 0xD3, 0xFA]))], + 0x3D: [Pot(76, 12, PotItem.Bomb, 'GT Mini Helmasaur Room', obj=RoomObject(0x1FFC9B, [0x9B, 0x63, 0xFA])), + Pot(112, 12, PotItem.Bomb, 'GT Mini Helmasaur Room', obj=RoomObject(0x1FFC9E, [0xE3, 0x63, 0xFA])), + Pot(24, 22, PotItem.Heart, 'GT Crystal Inner Circle', obj=RoomObject(0x1FFCE3, [0x33, 0xB3, 0xFA])), + Pot(40, 22, PotItem.FiveArrows, 'GT Crystal Inner Circle', obj=RoomObject(0x1FFCE6, [0x53, 0xB3, 0xFA])), + Pot(32, 24, PotItem.Heart, 'GT Crystal Inner Circle', obj=RoomObject(0x1FFCF8, [0x43, 0xC3, 0xFA])), + Pot(20, 26, PotItem.FiveRupees, 'GT Crystal Inner Circle', obj=RoomObject(0x1FFCFB, [0x2B, 0xD3, 0xFA])), + Pot(36, 26, PotItem.BigMagic, 'GT Crystal Inner Circle', obj=RoomObject(0x1FFCFE, [0x4B, 0xD3, 0xFA]))], + 0x3E: [Pot(96, 6, PotItem.Bomb, 'Ice Stalfos Hint', obj=RoomObject(0x1FC41D, [0xC3, 0x33, 0xFA])), + Pot(100, 6, PotItem.SmallMagic, 'Ice Stalfos Hint', obj=RoomObject(0x1FC420, [0xCB, 0x33, 0xFA])), + Pot(88, 10, PotItem.Heart, 'Ice Stalfos Hint', obj=RoomObject(0x1FC429, [0xB3, 0x53, 0xFA])), + Pot(92, 10, PotItem.SmallMagic, 'Ice Stalfos Hint', obj=RoomObject(0x1FC42C, [0xBB, 0x53, 0xFA]))], + 0x3F: [Pot(12, 25, PotItem.OneRupee, 'Ice Hammer Block', obj=RoomObject(0x1FC4DC, [0x1B, 0xCB, 0xFA])), + Pot(20, 25, PotItem.OneRupee, 'Ice Hammer Block', obj=RoomObject(0x1FC4E5, [0x2B, 0xCB, 0xFA])), + Pot(12, 26, PotItem.Bomb, 'Ice Hammer Block', obj=RoomObject(0x1FC4DF, [0x1B, 0xD3, 0xFA])), + Pot(20, 26, PotItem.Bomb, 'Ice Hammer Block', obj=RoomObject(0x1FC4E8, [0x2B, 0xD3, 0xFA])), + Pot(12, 27, PotItem.Switch, 'Ice Hammer Block', obj=RoomObject(0x1FC4E2, [0x1B, 0xDB, 0xFA])), + Pot(20, 27, PotItem.Heart, 'Ice Hammer Block', obj=RoomObject(0x1FC4EB, [0x2B, 0xDB, 0xFA])), + Pot(28, 23, PotItem.Key, 'Ice Hammer Block', PotFlags.Block)], + 0x41: [Pot(100, 10, PotItem.Heart, 'Sewers Behind Tapestry', obj=RoomObject(0x0A8EDC, [0xCB, 0x53, 0xFA])), + Pot(52, 15, PotItem.OneRupee, 'Sewers Behind Tapestry', obj=RoomObject(0x0A8EDF, [0x6B, 0x7B, 0xFA])), + Pot(52, 16, PotItem.SmallMagic, 'Sewers Behind Tapestry', obj=RoomObject(0x0A8EE2, [0x6B, 0x83, 0xFA])), + Pot(148, 22, PotItem.SmallMagic, 'Sewers Behind Tapestry', obj=RoomObject(0x0A8EE5, [0x2B, 0xB7, 0xFA]))], + 0x43: [Pot(66, 4, PotItem.FiveArrows, 'Desert Wall Slide', obj=RoomObject(0x1F87BC, [0x87, 0x23, 0xFA])), + Pot(78, 4, PotItem.SmallMagic, 'Desert Wall Slide', obj=RoomObject(0x1F87BF, [0x9F, 0x23, 0xFA])), + Pot(66, 9, PotItem.Heart, 'Desert Wall Slide', obj=RoomObject(0x1F87C2, [0x87, 0x4B, 0xFA])), + Pot(78, 9, PotItem.Heart, 'Desert Wall Slide', obj=RoomObject(0x1F87C5, [0x9F, 0x4B, 0xFA])), + Pot(112, 28, PotItem.Nothing, 'Desert Tiles 2', obj=RoomObject(0x1F87DA, [0xE3, 0xE3, 0xFA])), + Pot(76, 28, PotItem.Nothing, 'Desert Tiles 2', obj=RoomObject(0x1F87D7, [0x9B, 0xE3, 0xFA])), + Pot(76, 20, PotItem.Nothing, 'Desert Tiles 2', obj=RoomObject(0x1F87D1, [0x9B, 0xA3, 0xFA])), + Pot(112, 20, PotItem.Key, 'Desert Tiles 2', obj=RoomObject(0x1F87D4, [0xE3, 0xA3, 0xFA]))], + 0x44: [Pot(204, 7, PotItem.Nothing, 'Thieves Conveyor Bridge', PotFlags.Block)], + 0x45: [Pot(12, 4, PotItem.FiveArrows, 'Thieves Basement Block', obj=RoomObject(0x1FDC2A, [0x1B, 0x23, 0xFA])), + Pot(48, 12, PotItem.FiveArrows, 'Thieves Basement Block', obj=RoomObject(0x1FDC2D, [0x63, 0x63, 0xFA])), + Pot(92, 11, PotItem.Nothing, "Thieves Blind's Cell Interior", obj=RoomObject(0x1FDC4E, [0xBB, 0x5B, 0xFA])), + Pot(108, 11, PotItem.Heart, "Thieves Blind's Cell Interior", obj=RoomObject(0x1FDC51, [0xDB, 0x5B, 0xFA])), + Pot(220, 16, PotItem.SmallMagic, "Thieves Blind's Cell Interior", obj=RoomObject(0x1FDC54, [0xBB, 0x87, 0xFA])), + Pot(236, 16, PotItem.Heart, "Thieves Blind's Cell Interior", obj=RoomObject(0x1FDC57, [0xDB, 0x87, 0xFA])), + Pot(0x9C, 7, PotItem.Nothing, 'Thieves Basement Block', PotFlags.Block)], + 0x46: [Pot(96, 5, PotItem.Heart, 'Swamp Donut Top', obj=RoomObject(0x1F9B91, [0xC3, 0x2B, 0xFA])), + Pot(28, 27, PotItem.Heart, 'Swamp Donut Bottom', obj=RoomObject(0x1F9B94, [0x3B, 0xDB, 0xFA]))], + 0x49: [Pot(104, 15, PotItem.SmallMagic, 'Skull Torch Room', obj=RoomObject(0x1FC102, [0xD3, 0x7B, 0xFA])), + Pot(104, 16, PotItem.SmallMagic, 'Skull Torch Room', obj=RoomObject(0x1FC105, [0xD3, 0x83, 0xFA])), + Pot(156, 27, PotItem.Nothing, 'Skull Star Pits', obj=RoomObject(0x1FC063, [0x3B, 0xDF, 0xFA])), + Pot(172, 24, PotItem.Nothing, 'Skull Star Pits', obj=RoomObject(0x1FC084, [0x5B, 0xC7, 0xFA])), + Pot(172, 23, PotItem.Nothing, 'Skull Star Pits', obj=RoomObject(0x1FC081, [0x5B, 0xBF, 0xFA])), + Pot(144, 20, PotItem.Nothing, 'Skull Star Pits', obj=RoomObject(0x1FC04E, [0x23, 0xA7, 0xFA])), + Pot(144, 19, PotItem.SmallMagic, 'Skull Star Pits', obj=RoomObject(0x1FC04B, [0x23, 0x9F, 0xFA])), + Pot(172, 20, PotItem.Heart, 'Skull Star Pits', obj=RoomObject(0x1FC07E, [0x5B, 0xA7, 0xFA])), + Pot(144, 27, PotItem.Heart, 'Skull Star Pits', obj=RoomObject(0x1FC051, [0x23, 0xDF, 0xFA])), + Pot(172, 28, PotItem.SmallMagic, 'Skull Star Pits', obj=RoomObject(0x1FC090, [0x5B, 0xE7, 0xFA])), + Pot(160, 27, PotItem.Nothing, 'Skull Star Pits', obj=RoomObject(0x1FC066, [0x43, 0xDF, 0xFA]))], + 0x4A: [Pot(14, 5, PotItem.Switch, 'PoD Left Cage', obj=RoomObject(0x1FA1C2, [0x1F, 0x2B, 0xFA])), + Pot(32, 5, PotItem.Bomb, 'PoD Left Cage', obj=RoomObject(0x1FA1C5, [0x43, 0x2B, 0xFA])), + Pot(14, 11, PotItem.Heart, 'PoD Left Cage', obj=RoomObject(0x1FA1C8, [0x1F, 0x5B, 0xFA])), + Pot(32, 11, PotItem.OneRupee, 'PoD Left Cage', obj=RoomObject(0x1FA1CB, [0x43, 0x5B, 0xFA])), + Pot(56, 8, PotItem.Bomb, 'PoD Middle Cage', obj=RoomObject(0x1FA1D1, [0x73, 0x43, 0xFA])), + Pot(68, 8, PotItem.Bomb, 'PoD Middle Cage', obj=RoomObject(0x1FA1D4, [0x8B, 0x43, 0xFA])), + Pot(92, 5, PotItem.Bomb, 'PoD Middle Cage', obj=RoomObject(0x1FA1DA, [0xBB, 0x2B, 0xFA])), + Pot(110, 5, PotItem.Switch, 'PoD Middle Cage', obj=RoomObject(0x1FA1DD, [0xDF, 0x2B, 0xFA])), + Pot(92, 11, PotItem.OneRupee, 'PoD Middle Cage', obj=RoomObject(0x1FA1E0, [0xBB, 0x5B, 0xFA])), + Pot(110, 11, PotItem.Heart, 'PoD Middle Cage', obj=RoomObject(0x1FA1E3, [0xDF, 0x5B, 0xFA]))], + 0x4B: [Pot(20, 6, PotItem.FiveArrows, 'PoD Mimics 1', obj=RoomObject(0x1FA83C, [0x2B, 0x33, 0xFA])), + Pot(40, 6, PotItem.Heart, 'PoD Mimics 1', obj=RoomObject(0x1FA83F, [0x53, 0x33, 0xFA]))], + 0x4E: [Pot(140, 7, PotItem.Nothing, 'Ice Bomb Jump Catwalk', obj=RoomObject(0x1FC57E, [0x1B, 0x3F, 0xFA])), + Pot(48, 10, PotItem.Nothing, 'Ice Bomb Jump Catwalk', obj=RoomObject(0x1FC587, [0x63, 0x53, 0xFA])), + Pot(140, 11, PotItem.Switch, 'Ice Bomb Jump Catwalk', obj=RoomObject(0x1FC581, [0x1B, 0x5F, 0xFA])), + Pot(28, 12, PotItem.Heart, 'Ice Bomb Jump Catwalk', obj=RoomObject(0x1FC584, [0x3B, 0x63, 0xFA])), + Pot(112, 12, PotItem.SmallMagic, 'Ice Narrow Corridor', obj=RoomObject(0x1FC58A, [0xE3, 0x63, 0xFA]))], + 0x50: [Pot(96, 0x6, PotItem.Heart, 'Hyrule Castle West Hall', PotFlags.LowerRegion, obj=RoomObject(0x0A9099, [0xC3, 0x33, 0xFA])), + Pot(100, 0x6, PotItem.Heart, 'Hyrule Castle West Hall', PotFlags.LowerRegion, obj=RoomObject(0x0A909C, [0xCB, 0x33, 0xFA]))], + 0x52: [Pot(138, 3, PotItem.Heart, 'Hyrule Castle East Hall', obj=RoomObject(0x0A91C7, [0x17, 0x1F, 0xFA])), + Pot(194, 26, PotItem.Heart, 'Hyrule Castle East Hall', obj=RoomObject(0x0A91CA, [0x87, 0xD7, 0xFA]))], + 0x53: [Pot(92, 11, PotItem.Heart, 'Desert Beamos Hall', obj=RoomObject(0x1F8844, [0xBB, 0x5B, 0xFA])), + Pot(96, 11, PotItem.SmallMagic, 'Desert Beamos Hall', obj=RoomObject(0x1F8847, [0xC3, 0x5B, 0xFA])), + Pot(100, 11, PotItem.Key, 'Desert Beamos Hall', obj=RoomObject(0x1F884A, [0xCB, 0x5B, 0xFA])), + Pot(104, 11, PotItem.Heart, 'Desert Beamos Hall', obj=RoomObject(0x1F884D, [0xD3, 0x5B, 0xFA]))], + 0x54: [Pot(186, 25, PotItem.FiveRupees, 'Swamp Attic', obj=RoomObject(0x1F9A28, [0x77, 0xCF, 0xFA])), + Pot(186, 26, PotItem.Heart, 'Swamp Attic', obj=RoomObject(0x1F9A2B, [0x77, 0xD7, 0xFA])), + Pot(186, 27, PotItem.Heart, 'Swamp Attic', obj=RoomObject(0x1F9A2E, [0x77, 0xDF, 0xFA])), + Pot(186, 28, PotItem.Heart, 'Swamp Attic', obj=RoomObject(0x1F9A31, [0x77, 0xE7, 0xFA]))], + 0x55: [Pot(230, 24, PotItem.SmallMagic, 'Hyrule Castle Secret Entrance', obj=RoomObject(0x0A8127, [0xCF, 0xC7, 0xFA])), + Pot(230, 25, PotItem.SmallMagic, 'Hyrule Castle Secret Entrance', obj=RoomObject(0x0A812A, [0xCF, 0xCF, 0xFA]))], + 0x56: [Pot(100, 6, PotItem.Nothing, 'Skull Back Drop', obj=RoomObject(0x1FBADC, [0xCB, 0x33, 0xFA])), + Pot(96, 10, PotItem.Nothing, 'Skull Back Drop', obj=RoomObject(0x1FBAEE, [0xC3, 0x53, 0xFA])), + Pot(92, 10, PotItem.Nothing, 'Skull Back Drop', obj=RoomObject(0x1FBAEB, [0xBB, 0x53, 0xFA])), + Pot(20, 6, PotItem.SmallMagic, 'Skull X Room', obj=RoomObject(0x1FBB1E, [0x2B, 0x33, 0xFA])), + Pot(40, 6, PotItem.SmallMagic, 'Skull X Room', obj=RoomObject(0x1FBB27, [0x53, 0x33, 0xFA])), + Pot(24, 7, PotItem.SmallMagic, 'Skull X Room', obj=RoomObject(0x1FBB21, [0x33, 0x3B, 0xFA])), + Pot(36, 7, PotItem.SmallMagic, 'Skull X Room', obj=RoomObject(0x1FBB24, [0x4B, 0x3B, 0xFA])), + Pot(12, 8, PotItem.Heart, 'Skull X Room', obj=RoomObject(0x1FBB12, [0x1B, 0x43, 0xFA])), + Pot(48, 8, PotItem.Heart, 'Skull X Room', obj=RoomObject(0x1FBB1B, [0x63, 0x43, 0xFA])), + Pot(24, 9, PotItem.SmallMagic, 'Skull X Room', obj=RoomObject(0x1FBB2A, [0x33, 0x4B, 0xFA])), + Pot(36, 9, PotItem.SmallMagic, 'Skull X Room', obj=RoomObject(0x1FBB30, [0x4B, 0x4B, 0xFA])), + Pot(20, 10, PotItem.FiveRupees, 'Skull X Room', obj=RoomObject(0x1FBB2D, [0x2B, 0x53, 0xFA])), + Pot(40, 10, PotItem.FiveRupees, 'Skull X Room', obj=RoomObject(0x1FBB33, [0x53, 0x53, 0xFA])), + Pot(12, 20, PotItem.Key, 'Skull 2 West Lobby', obj=RoomObject(0x1FBAC1, [0x1B, 0xA3, 0xFA])), + Pot(48, 20, PotItem.Nothing, 'Skull 2 West Lobby', obj=RoomObject(0x1FBAB8, [0x63, 0xA3, 0xFA]))], + 0x57: [Pot(92, 7, PotItem.BigMagic, 'Skull Lone Pot', obj=RoomObject(0x1FBB6F, [0xBB, 0x3B, 0xFA])), + Pot(32, 4, PotItem.Nothing, 'Skull Big Key', obj=RoomObject(0x1FBB72, [0x43, 0x23, 0xFA])), + Pot(92, 23, PotItem.Bomb, 'Skull Pot Prison', obj=RoomObject(0x1FBBB4, [0xBB, 0xBB, 0xFA])), + Pot(100, 23, PotItem.SmallMagic, 'Skull Pot Prison', obj=RoomObject(0x1FBBB7, [0xCB, 0xBB, 0xFA])), + Pot(84, 25, PotItem.FiveRupees, 'Skull Pot Prison', obj=RoomObject(0x1FBBB1, [0xAB, 0xCB, 0xFA])), + Pot(76, 27, PotItem.Heart, 'Skull Pot Prison', obj=RoomObject(0x1FBBAE, [0x9B, 0xDB, 0xFA])), + Pot(12, 20, PotItem.SmallMagic, 'Skull 2 East Lobby', obj=RoomObject(0x1FBB93, [0x1B, 0xA3, 0xFA])), + Pot(48, 20, PotItem.SmallMagic, 'Skull 2 East Lobby', obj=RoomObject(0x1FBB99, [0x63, 0xA3, 0xFA])), + Pot(30, 22, PotItem.Switch, 'Skull 2 East Lobby', obj=RoomObject(0x1FBB96, [0x3F, 0xB3, 0xFA]))], + 0x58: [Pot(12, 7, PotItem.SmallMagic, 'Skull Pull Switch', obj=RoomObject(0x1FBC4B, [0x1B, 0x3B, 0xFA])), + Pot(16, 7, PotItem.Nothing, 'Skull Pull Switch', obj=RoomObject(0x1FBC4E, [0x23, 0x3B, 0xFA])), + Pot(16, 8, PotItem.SmallMagic, 'Skull Pull Switch', obj=RoomObject(0x1FBC54, [0x23, 0x43, 0xFA])), + Pot(12, 12, PotItem.Nothing, 'Skull Pull Switch', obj=RoomObject(0x1FBC51, [0x1B, 0x63, 0xFA])), + Pot(96, 9, PotItem.Nothing, 'Skull Pot Circle', obj=RoomObject(0x1FBC90, [0xC3, 0x4B, 0xFA])), + Pot(92, 8, PotItem.Nothing, 'Skull Pot Circle', obj=RoomObject(0x1FBC8D, [0xBB, 0x43, 0xFA])), + Pot(108, 8, PotItem.Nothing, 'Skull Pot Circle', obj=RoomObject(0x1FBC9F, [0xDB, 0x43, 0xFA])), + Pot(108, 6, PotItem.Nothing, 'Skull Pot Circle', obj=RoomObject(0x1FBC99, [0xDB, 0x33, 0xFA])), + Pot(104, 5, PotItem.Nothing, 'Skull Pot Circle', obj=RoomObject(0x1FBC84, [0xD3, 0x2B, 0xFA])), + Pot(92, 6, PotItem.Nothing, 'Skull Pot Circle', obj=RoomObject(0x1FBC87, [0xBB, 0x33, 0xFA])), + Pot(96, 5, PotItem.Bomb, 'Skull Pot Circle', obj=RoomObject(0x1FBC7E, [0xC3, 0x2B, 0xFA])), + Pot(100, 5, PotItem.SmallMagic, 'Skull Pot Circle', obj=RoomObject(0x1FBC81, [0xCB, 0x2B, 0xFA])), + Pot(92, 7, PotItem.Heart, 'Skull Pot Circle', obj=RoomObject(0x1FBC8A, [0xBB, 0x3B, 0xFA])), + Pot(108, 7, PotItem.Heart, 'Skull Pot Circle', obj=RoomObject(0x1FBC9C, [0xDB, 0x3B, 0xFA])), + Pot(100, 9, PotItem.SmallMagic, 'Skull Pot Circle', obj=RoomObject(0x1FBC93, [0xCB, 0x4B, 0xFA])), + Pot(104, 9, PotItem.Bomb, 'Skull Pot Circle', obj=RoomObject(0x1FBC96, [0xD3, 0x4B, 0xFA]))], + 0x59: [Pot(26, 0xb, PotItem.Heart, 'Skull 3 Lobby', PotFlags.LowerRegion, obj=RoomObject(0x1FBFC0, [0x37, 0x5B, 0xFA])), + Pot(32, 8, PotItem.Nothing, 'Skull 3 Lobby', PotFlags.LowerRegion, obj=RoomObject(0x1FBFBD, [0x43, 0x43, 0xFA])), + Pot(76, 28, PotItem.Nothing, 'Skull East Bridge', obj=RoomObject(0x1FBF82, [0x9B, 0xE3, 0xFA])), + Pot(112, 28, PotItem.Nothing, 'Skull East Bridge', obj=RoomObject(0x1FBF85, [0xE3, 0xE3, 0xFA]))], + 0x5B: [Pot(218, 0x5, PotItem.Nothing, 'GT Hidden Spikes', PotFlags.LowerRegion, obj=RoomObject(0x1FF865, [0xB7, 0x2F, 0xFA])), + Pot(222, 0x5, PotItem.Switch, 'GT Hidden Spikes', PotFlags.LowerRegion, obj=RoomObject(0x1FF868, [0xBF, 0x2F, 0xFA])), + Pot(226, 0x5, PotItem.Nothing, 'GT Hidden Spikes', PotFlags.LowerRegion, obj=RoomObject(0x1FF86B, [0xC7, 0x2F, 0xFA]))], + 0x5C: [Pot(228, 25, PotItem.Nothing, 'GT Refill', obj=RoomObject(0x1FF964, [0xCB, 0xCF, 0xFA])), + Pot(104, 24, PotItem.Nothing, 'GT Refill', obj=RoomObject(0x1FF967, [0xD3, 0xC3, 0xFA])), + Pot(228, 22, PotItem.Nothing, 'GT Refill', obj=RoomObject(0x1FF96A, [0xCB, 0xB7, 0xFA])), + Pot(216, 25, PotItem.Nothing, 'GT Refill', obj=RoomObject(0x1FF95E, [0xB3, 0xCF, 0xFA])), + Pot(84, 24, PotItem.Nothing, 'GT Refill', obj=RoomObject(0x1FF95B, [0xAB, 0xC3, 0xFA])), + Pot(216, 22, PotItem.Nothing, 'GT Refill', obj=RoomObject(0x1FF958, [0xB3, 0xB7, 0xFA])), + Pot(94, 22, PotItem.Bomb, 'GT Refill', obj=RoomObject(0x1FF955, [0xBF, 0xB3, 0xFA])), + Pot(94, 26, PotItem.BigMagic, 'GT Refill', obj=RoomObject(0x1FF961, [0xBF, 0xD3, 0xFA]))], + 0x5D: [Pot(16, 5, PotItem.Bomb, 'GT Gauntlet 2', obj=RoomObject(0x1FF99F, [0x23, 0x2B, 0xFA])), + Pot(44, 5, PotItem.FiveRupees, 'GT Gauntlet 2', obj=RoomObject(0x1FF9A2, [0x5B, 0x2B, 0xFA])), + Pot(16, 11, PotItem.OneRupee, 'GT Gauntlet 2', obj=RoomObject(0x1FF9A5, [0x23, 0x5B, 0xFA])), + Pot(44, 11, PotItem.FiveArrows, 'GT Gauntlet 2', obj=RoomObject(0x1FF9A8, [0x5B, 0x5B, 0xFA])), + Pot(12, 20, PotItem.FiveArrows, 'GT Gauntlet 3', obj=RoomObject(0x1FF9C9, [0x1B, 0xA3, 0xFA])), + Pot(48, 20, PotItem.Bomb, 'GT Gauntlet 3', obj=RoomObject(0x1FF9CC, [0x63, 0xA3, 0xFA])), + Pot(12, 28, PotItem.SmallMagic, 'GT Gauntlet 3', obj=RoomObject(0x1FF9CF, [0x1B, 0xE3, 0xFA])), + Pot(48, 28, PotItem.Bomb, 'GT Gauntlet 3', obj=RoomObject(0x1FF9D2, [0x63, 0xE3, 0xFA]))], + 0x5E: [Pot(92, 4, PotItem.SmallMagic, 'Ice Falling Square', obj=RoomObject(0x1FC679, [0xBB, 0x23, 0xFA])), + Pot(96, 4, PotItem.SmallMagic, 'Ice Falling Square', obj=RoomObject(0x1FC67C, [0xC3, 0x23, 0xFA])), + Pot(76, 8, PotItem.Heart, 'Ice Falling Square', obj=RoomObject(0x1FC67F, [0x9B, 0x43, 0xFA])), + Pot(112, 8, PotItem.Heart, 'Ice Falling Square', obj=RoomObject(0x1FC688, [0xE3, 0x43, 0xFA]))], + 0x5F: [Pot(44, 27, PotItem.Switch, 'Ice Spike Room', obj=RoomObject(0x1FC6E8, [0x5B, 0xDB, 0xFA]))], + 0x60: [Pot(76, 4, PotItem.Heart, 'Hyrule Castle West Lobby', obj=RoomObject(0x0A92B2, [0x9B, 0x23, 0xFA])), + Pot(112, 4, PotItem.Heart, 'Hyrule Castle West Lobby', obj=RoomObject(0x0A92AF, [0xE3, 0x23, 0xFA]))], + 0x62: [Pot(208, 21, PotItem.Heart, 'Hyrule Castle East Lobby', obj=RoomObject(0x0A950E, [0xA3, 0xAF, 0xFA]))], + 0x63: [Pot(48, 4, PotItem.Nothing, 'Desert Tiles 1', obj=RoomObject(0x1F88C9, [0x63, 0x23, 0xFA])), + Pot(12, 4, PotItem.Nothing, 'Desert Tiles 1', obj=RoomObject(0x1F88C6, [0x1B, 0x23, 0xFA])), + Pot(12, 8, PotItem.Nothing, 'Desert Tiles 1', obj=RoomObject(0x1F88CC, [0x1B, 0x43, 0xFA])), + Pot(48, 12, PotItem.Nothing, 'Desert Tiles 1', obj=RoomObject(0x1F88D2, [0x63, 0x63, 0xFA])), + Pot(48, 8, PotItem.Heart, 'Desert Tiles 1', obj=RoomObject(0x1F88CF, [0x63, 0x43, 0xFA])), + Pot(12, 12, PotItem.Key, 'Desert Tiles 1', obj=RoomObject(0x1F88D5, [0x1B, 0x63, 0xFA]))], + 0x64: [Pot(12, 22, PotItem.Bomb, 'Thieves Attic Hint', PotFlags.SwitchLogicChange, obj=RoomObject(0x1FD9F9, [0x1B, 0xB3, 0xFA])), + Pot(16, 22, PotItem.Bomb, 'Thieves Attic Hint', PotFlags.SwitchLogicChange, obj=RoomObject(0x1FD9FC, [0x23, 0xB3, 0xFA])), + Pot(20, 22, PotItem.Bomb, 'Thieves Attic Hint', PotFlags.SwitchLogicChange, obj=RoomObject(0x1FD9FF, [0x2B, 0xB3, 0xFA])), + Pot(36, 28, PotItem.Bomb, 'Thieves Attic Switch', obj=RoomObject(0x1FDA1A, [0x4B, 0xE3, 0xFA])), + Pot(40, 28, PotItem.SmallMagic, 'Thieves Attic Switch', obj=RoomObject(0x1FDA1D, [0x53, 0xE3, 0xFA])), + Pot(44, 28, PotItem.SmallMagic, 'Thieves Attic Switch', obj=RoomObject(0x1FDA20, [0x5B, 0xE3, 0xFA])), + Pot(48, 28, PotItem.Switch, 'Thieves Attic Switch', obj=RoomObject(0x1FDA23, [0x63, 0xE3, 0xFA]))], + 0x65: [Pot(100, 28, PotItem.Bomb, 'Thieves Attic Window', obj=RoomObject(0x1FDA95, [0xCB, 0xE3, 0xFA])), + Pot(104, 28, PotItem.Bomb, 'Thieves Attic Window', obj=RoomObject(0x1FDA98, [0xD3, 0xE3, 0xFA]))], + 0x66: [Pot(48, 0x5, PotItem.FiveArrows, 'Swamp Refill', PotFlags.LowerRegion, obj=RoomObject(0x1F9F51, [0x63, 0x2B, 0xFA])), + Pot(52, 0x5, PotItem.Bomb, 'Swamp Refill', PotFlags.LowerRegion, obj=RoomObject(0x1F9F57, [0x6B, 0x2B, 0xFA])), + Pot(56, 0x5, PotItem.FiveRupees, 'Swamp Refill', PotFlags.LowerRegion, obj=RoomObject(0x1F9F5D, [0x73, 0x2B, 0xFA])), + Pot(48, 0x6, PotItem.FiveArrows, 'Swamp Refill', PotFlags.LowerRegion, RoomObject(0x1F9F54, [0x63, 0x33, 0xFA])), + Pot(52, 0x6, PotItem.Bomb, 'Swamp Refill', PotFlags.LowerRegion, obj=RoomObject(0x1F9F5A, [0x6B, 0x33, 0xFA])), + Pot(56, 0x6, PotItem.FiveRupees, 'Swamp Refill', PotFlags.LowerRegion, obj=RoomObject(0x1F9F60, [0x73, 0x33, 0xFA])), + Pot(84, 5, PotItem.Heart, 'Swamp Behind Waterfall', obj=RoomObject(0x1F9F07, [0xAB, 0x2B, 0xFA])), + Pot(104, 5, PotItem.FiveArrows, 'Swamp Behind Waterfall', obj=RoomObject(0x1F9F0D, [0xD3, 0x2B, 0xFA])), + Pot(84, 6, PotItem.Heart, 'Swamp Behind Waterfall', obj=RoomObject(0x1F9F0A, [0xAB, 0x33, 0xFA])), + Pot(104, 6, PotItem.Bomb, 'Swamp Behind Waterfall', obj=RoomObject(0x1F9F10, [0xD3, 0x33, 0xFA]))], + 0x67: [Pot(22, 26, PotItem.Nothing, 'Skull Left Drop', obj=RoomObject(0x1FBDDE, [0x2F, 0xD3, 0xFA])), + Pot(18, 22, PotItem.Nothing, 'Skull Left Drop', obj=RoomObject(0x1FBDD2, [0x27, 0xB3, 0xFA])), + Pot(12, 7, PotItem.FiveArrows, 'Skull Left Drop', obj=RoomObject(0x1FBDCC, [0x1B, 0x3B, 0xFA])), + Pot(48, 7, PotItem.SmallMagic, 'Skull Left Drop', obj=RoomObject(0x1FBDCF, [0x63, 0x3B, 0xFA])), + Pot(18, 23, PotItem.SmallMagic, 'Skull Left Drop', obj=RoomObject(0x1FBDD5, [0x27, 0xBB, 0xFA])), + Pot(18, 26, PotItem.Heart, 'Skull Left Drop', obj=RoomObject(0x1FBDDB, [0x27, 0xD3, 0xFA])), + Pot(96, 19, PotItem.Heart, 'Skull Compass Room', obj=RoomObject(0x1FBDE7, [0xC3, 0x9B, 0xFA])), + Pot(74, 20, PotItem.SmallMagic, 'Skull Compass Room', obj=RoomObject(0x1FBDEA, [0x97, 0xA3, 0xFA])), + Pot(92, 9, PotItem.Nothing, 'Skull Compass Room', obj=RoomObject(0x1FBDE1, [0xBB, 0x4B, 0xFA])), + Pot(84, 28, PotItem.Nothing, 'Skull Compass Room', obj=RoomObject(0x1FBDF0, [0xAB, 0xE3, 0xFA])), + Pot(104, 28, PotItem.Heart, 'Skull Compass Room', obj=RoomObject(0x1FBDF3, [0xD3, 0xE3, 0xFA]))], + 0x68: [Pot(84, 14, PotItem.Nothing, 'Skull Pinball', obj=RoomObject(0x1FBE8A, [0xAB, 0x73, 0xFA])), + Pot(84, 13, PotItem.Nothing, 'Skull Pinball', obj=RoomObject(0x1FBE87, [0xAB, 0x6B, 0xFA])), + Pot(88, 12, PotItem.Nothing, 'Skull Pinball', obj=RoomObject(0x1FBE84, [0xB3, 0x63, 0xFA])), + Pot(88, 6, PotItem.Nothing, 'Skull Pinball', obj=RoomObject(0x1FBE7E, [0xB3, 0x33, 0xFA])), + Pot(88, 5, PotItem.Nothing, 'Skull Pinball', obj=RoomObject(0x1FBE7B, [0xB3, 0x2B, 0xFA])), + Pot(88, 4, PotItem.Nothing, 'Skull Pinball', obj=RoomObject(0x1FBE78, [0xB3, 0x23, 0xFA])), + Pot(64, 17, PotItem.Nothing, 'Skull Pinball', obj=RoomObject(0x1FBEA5, [0x83, 0x8B, 0xFA])), + Pot(64, 15, PotItem.Nothing, 'Skull Pinball', obj=RoomObject(0x1FBE9F, [0x83, 0x7B, 0xFA])), + Pot(64, 7, PotItem.Heart, 'Skull Pinball', obj=RoomObject(0x1FBE75, [0x83, 0x3B, 0xFA])), + Pot(88, 7, PotItem.SmallMagic, 'Skull Pinball', obj=RoomObject(0x1FBE81, [0xB3, 0x3B, 0xFA])), + Pot(64, 16, PotItem.Heart, 'Skull Pinball', obj=RoomObject(0x1FBEA2, [0x83, 0x83, 0xFA])), + Pot(64, 24, PotItem.SmallMagic, 'Skull Pinball', obj=RoomObject(0x1FBEAB, [0x83, 0xC3, 0xFA])), + Pot(64, 25, PotItem.Heart, 'Skull Pinball', obj=RoomObject(0x1FBEAE, [0x83, 0xCB, 0xFA]))], + 0x6B: [Pot(28, 5, PotItem.Heart, 'GT Crystal Paths', obj=RoomObject(0x1FF7C1, [0x3B, 0x2B, 0xFA])), + Pot(44, 5, PotItem.Nothing, 'GT Crystal Paths', obj=RoomObject(0x1FF7C4, [0x5B, 0x2B, 0xFA])), + Pot(28, 8, PotItem.Nothing, 'GT Crystal Paths', obj=RoomObject(0x1FF7D0, [0x3B, 0x43, 0xFA])), + Pot(44, 8, PotItem.SmallMagic, 'GT Crystal Paths', obj=RoomObject(0x1FF7D3, [0x5B, 0x43, 0xFA])), + Pot(28, 11, PotItem.SmallMagic, 'GT Crystal Paths', obj=RoomObject(0x1FF7D6, [0x3B, 0x5B, 0xFA])), + Pot(44, 11, PotItem.Nothing, 'GT Crystal Paths', obj=RoomObject(0x1FF7D9, [0x5B, 0x5B, 0xFA])), + Pot(90, 25, PotItem.Nothing, 'GT Mimics 2', obj=RoomObject(0x1FF7FD, [0xB7, 0xCB, 0xFA])), + Pot(98, 25, PotItem.FiveArrows, 'GT Mimics 2', obj=RoomObject(0x1FF800, [0xC7, 0xCB, 0xFA]))], + 0x6C: [Pot(20, 6, PotItem.Heart, 'GT Quad Pot', obj=RoomObject(0x1FFA8E, [0x2B, 0x33, 0xFA])), + Pot(40, 6, PotItem.FiveArrows, 'GT Quad Pot', obj=RoomObject(0x1FFA91, [0x53, 0x33, 0xFA])), + Pot(20, 10, PotItem.Bomb, 'GT Quad Pot', obj=RoomObject(0x1FFA94, [0x2B, 0x53, 0xFA])), + Pot(40, 10, PotItem.SmallMagic, 'GT Quad Pot', obj=RoomObject(0x1FFA97, [0x53, 0x53, 0xFA]))], + 0x6D: [Pot(28, 26, PotItem.Heart, 'GT Gauntlet 5', obj=RoomObject(0x1FFA3C, [0x3B, 0xD3, 0xFA])), + Pot(32, 26, PotItem.Heart, 'GT Gauntlet 5', obj=RoomObject(0x1FFA3F, [0x43, 0xD3, 0xFA])), + Pot(28, 27, PotItem.SmallMagic, 'GT Gauntlet 5', obj=RoomObject(0x1FFA42, [0x3B, 0xDB, 0xFA])), + Pot(32, 27, PotItem.SmallMagic, 'GT Gauntlet 5', obj=RoomObject(0x1FFA45, [0x43, 0xDB, 0xFA]))], + 0x73: [Pot(154, 21, PotItem.FiveArrows, 'Desert Circle of Pots', obj=RoomObject(0x1F8933, [0x37, 0xAF, 0xFA])), + Pot(158, 21, PotItem.OneRupee, 'Desert Circle of Pots', obj=RoomObject(0x1F8936, [0x3F, 0xAF, 0xFA])), + Pot(20, 23, PotItem.Switch, 'Desert Circle of Pots', obj=RoomObject(0x1F8939, [0x2B, 0xBB, 0xFA])), + Pot(36, 23, PotItem.FiveRupees, 'Desert Circle of Pots', obj=RoomObject(0x1F894E, [0x4B, 0xBB, 0xFA])), + Pot(144, 24, PotItem.Heart, 'Desert Circle of Pots', obj=RoomObject(0x1F893C, [0x23, 0xC7, 0xFA])), + Pot(168, 24, PotItem.FiveArrows, 'Desert Circle of Pots', obj=RoomObject(0x1F894B, [0x53, 0xC7, 0xFA])), + Pot(20, 26, PotItem.SmallMagic, 'Desert Circle of Pots', obj=RoomObject(0x1F893F, [0x2B, 0xD3, 0xFA])), + Pot(36, 26, PotItem.Heart, 'Desert Circle of Pots', obj=RoomObject(0x1F8948, [0x4B, 0xD3, 0xFA])), + Pot(154, 27, PotItem.OneRupee, 'Desert Circle of Pots', obj=RoomObject(0x1F8942, [0x37, 0xDF, 0xFA])), + Pot(158, 27, PotItem.FiveRupees, 'Desert Circle of Pots', obj=RoomObject(0x1F8945, [0x3F, 0xDF, 0xFA]))], + 0x74: [Pot(30, 5, PotItem.SmallMagic, 'Desert Map Room', obj=RoomObject(0x1F8A39, [0x3F, 0x2B, 0xFA])), + Pot(62, 5, PotItem.Switch, 'Desert Map Room', obj=RoomObject(0x1F89FD, [0x7F, 0x2B, 0xFA])), + Pot(94, 5, PotItem.SmallMagic, 'Desert Map Room', obj=RoomObject(0x1F8A48, [0xBF, 0x2B, 0xFA])), + Pot(14, 11, PotItem.Heart, 'Desert Map Room', obj=RoomObject(0x1F8A3C, [0x1F, 0x5B, 0xFA])), + Pot(46, 11, PotItem.FiveArrows, 'Desert Map Room', obj=RoomObject(0x1F8A3F, [0x5F, 0x5B, 0xFA])), + Pot(78, 11, PotItem.FiveArrows, 'Desert Map Room', obj=RoomObject(0x1F8A42, [0x9F, 0x5B, 0xFA])), + Pot(110, 11, PotItem.Heart, 'Desert Map Room', obj=RoomObject(0x1F8A45, [0xDF, 0x5B, 0xFA]))], + 0x75: [Pot(148, 22, PotItem.SmallMagic, 'Desert Arrow Pot Corner', obj=RoomObject(0x1F8A89, [0x2B, 0xB7, 0xFA])), + Pot(160, 22, PotItem.FiveArrows, 'Desert Arrow Pot Corner', obj=RoomObject(0x1F8A8C, [0x43, 0xB7, 0xFA])), + Pot(172, 22, PotItem.Heart, 'Desert Arrow Pot Corner', obj=RoomObject(0x1F8A8F, [0x5B, 0xB7, 0xFA]))], + 0x76: [Pot(112, 12, PotItem.Heart, 'Swamp Drain Right', obj=RoomObject(0x1F9DCC, [0xE3, 0x63, 0xFA])), + Pot(84, 23, PotItem.Heart, 'Swamp Flooded Spot', obj=RoomObject(0x1F9DF3, [0xAB, 0xBB, 0xFA])), + Pot(96, 23, PotItem.Heart, 'Swamp Flooded Spot', obj=RoomObject(0x1F9DF6, [0xC3, 0xBB, 0xFA]))], + 0x7B: [Pot(48, 10, PotItem.Nothing, 'GT Conveyor Star Pits', obj=RoomObject(0x1FEF9B, [0x63, 0x53, 0xFA])), + Pot(88, 10, PotItem.Nothing, 'GT Conveyor Star Pits', obj=RoomObject(0x1FEFA1, [0xB3, 0x53, 0xFA])), + Pot(76, 7, PotItem.Nothing, 'GT Conveyor Star Pits', obj=RoomObject(0x1FEF9E, [0x9B, 0x3B, 0xFA])), + Pot(60, 4, PotItem.Heart, 'GT Conveyor Star Pits', obj=RoomObject(0x1FEFAD, [0x7B, 0x23, 0xFA])), + Pot(64, 4, PotItem.Key, 'GT Conveyor Star Pits', obj=RoomObject(0x1FEFB0, [0x83, 0x23, 0xFA]))], + 0x7C: [Pot(36, 21, PotItem.Nothing, 'GT Falling Bridge', obj=RoomObject(0x1FF0AA, [0x4B, 0xAB, 0xFA])), + Pot(24, 11, PotItem.Nothing, 'GT Falling Bridge', obj=RoomObject(0x1FF095, [0x33, 0x5B, 0xFA])), + Pot(28, 4, PotItem.Heart, 'GT Falling Bridge', obj=RoomObject(0x1FF08F, [0x3B, 0x23, 0xFA])), + Pot(32, 4, PotItem.Heart, 'GT Falling Bridge', obj=RoomObject(0x1FF092, [0x43, 0x23, 0xFA]))], + 0x7D: [Pot(44, 12, PotItem.Nothing, 'GT Firesnake Room', obj=RoomObject(0x1FF155, [0x5B, 0x63, 0xFA])), + Pot(44, 6, PotItem.Nothing, 'GT Firesnake Room', obj=RoomObject(0x1FF152, [0x5B, 0x33, 0xFA])), + Pot(112, 6, PotItem.Heart, 'GT Firesnake Room', obj=RoomObject(0x1FF16A, [0xE3, 0x33, 0xFA])), + Pot(108, 20, PotItem.FiveArrows, 'GT Warp Maze - Pot Rail', obj=RoomObject(0x1FF1EB, [0xDB, 0xA3, 0xFA])), + Pot(114, 20, PotItem.Bomb, 'GT Petting Zoo', obj=RoomObject(0x1FF1EE, [0xE7, 0xA3, 0xFA])), + Pot(76, 28, PotItem.Bomb, 'GT Petting Zoo', obj=RoomObject(0x1FF1F1, [0x9B, 0xE3, 0xFA]))], + 0x7E: [Pot(86, 15, PotItem.Heart, 'Ice Tall Hint', obj=RoomObject(0x1FC77E, [0xAF, 0x7B, 0xFA])), + Pot(82, 26, PotItem.SmallMagic, 'Ice Tall Hint', obj=RoomObject(0x1FC781, [0xA7, 0xD3, 0xFA])), + Pot(100, 26, PotItem.Switch, 'Ice Tall Hint', obj=RoomObject(0x1FC7A2, [0xCB, 0xD3, 0xFA])), + Pot(104, 26, PotItem.Nothing, 'Ice Tall Hint', obj=RoomObject(0x1FC7A5, [0xD3, 0xD3, 0xFA]))], + 0x80: [Pot(48, 4, PotItem.Heart, 'Hyrule Dungeon Cellblock', obj=RoomObject(0x0AA3CD, [0x63, 0x23, 0xFA])), + Pot(52, 4, PotItem.Heart, 'Hyrule Dungeon Cellblock', obj=RoomObject(0x0AA3D3, [0x6B, 0x23, 0xFA])), + Pot(56, 4, PotItem.Heart, 'Hyrule Dungeon Cellblock', obj=RoomObject(0x0AA3D6, [0x73, 0x23, 0xFA]))], + 0x82: [Pot(50, 0x5, PotItem.Nothing, 'Hyrule Dungeon South Abyss', PotFlags.LowerRegion, obj=RoomObject(0x0AA0D5, [0x67, 0x2B, 0xFA])), + Pot(50, 0xA, PotItem.Nothing, 'Hyrule Dungeon South Abyss', PotFlags.LowerRegion, obj=RoomObject(0x0AA0D8, [0x67, 0x53, 0xFA])), + Pot(76, 0x12, PotItem.Heart, 'Hyrule Dungeon South Abyss', PotFlags.LowerRegion, obj=RoomObject(0x0AA0D2, [0x9B, 0x93, 0xFA]))], + 0x83: [Pot(76, 4, PotItem.FiveArrows, 'Desert West Wing', obj=RoomObject(0x1F8B54, [0x9B, 0x23, 0xFA])), + Pot(80, 4, PotItem.OneRupee, 'Desert West Wing', obj=RoomObject(0x1F8B57, [0xA3, 0x23, 0xFA])), + Pot(76, 28, PotItem.FiveRupees, 'Desert West Wing', obj=RoomObject(0x1F8B5A, [0x9B, 0xE3, 0xFA])), + Pot(80, 28, PotItem.FiveArrows, 'Desert West Wing', obj=RoomObject(0x1F8B5D, [0xA3, 0xE3, 0xFA]))], + 0x84: [Pot(64, 17, PotItem.Nothing, 'Desert Main Lobby', obj=RoomObject(0x1F8C90, [0x83, 0x8B, 0xFA])), + Pot(60, 17, PotItem.Nothing, 'Desert Main Lobby', obj=RoomObject(0x1F8C8D, [0x7B, 0x8B, 0xFA])), + Pot(80, 14, PotItem.Nothing, 'Desert Main Lobby', obj=RoomObject(0x1F8C93, [0xA3, 0x73, 0xFA])), + Pot(44, 14, PotItem.Nothing, 'Desert Main Lobby', obj=RoomObject(0x1F8C96, [0x5B, 0x73, 0xFA])), + Pot(100, 6, PotItem.Nothing, 'Desert Main Lobby', obj=RoomObject(0x1F8C87, [0xCB, 0x33, 0xFA])), + Pot(24, 6, PotItem.Nothing, 'Desert Main Lobby', obj=RoomObject(0x1F8C81, [0x33, 0x33, 0xFA])), + Pot(24, 7, PotItem.FiveArrows, 'Desert Main Lobby', obj=RoomObject(0x1F8C84, [0x33, 0x3B, 0xFA])), + Pot(100, 7, PotItem.FiveArrows, 'Desert Main Lobby', obj=RoomObject(0x1F8C8A, [0xCB, 0x3B, 0xFA]))], + 0x85: [Pot(44, 28, PotItem.Heart, 'Desert East Wing', obj=RoomObject(0x1F8D59, [0x5B, 0xE3, 0xFA])), + Pot(48, 28, PotItem.FiveArrows, 'Desert East Wing', obj=RoomObject(0x1F8D5C, [0x63, 0xE3, 0xFA]))], + 0x87: [Pot(12, 11, PotItem.Nothing, 'Hera Tile Room', obj=RoomObject(0x1FD12A, [0x1B, 0x5B, 0xFA])), + Pot(16, 12, PotItem.Nothing, 'Hera Tile Room', obj=RoomObject(0x1FD130, [0x23, 0x63, 0xFA])), + Pot(40, 12, PotItem.Nothing, 'Hera Tile Room', obj=RoomObject(0x1FD139, [0x53, 0x63, 0xFA])), + Pot(32, 12, PotItem.Nothing, 'Hera Tile Room', obj=RoomObject(0x1FD136, [0x43, 0x63, 0xFA])), + Pot(24, 12, PotItem.Nothing, 'Hera Tile Room', obj=RoomObject(0x1FD133, [0x33, 0x63, 0xFA])), + Pot(16, 11, PotItem.Nothing, 'Hera Tile Room', obj=RoomObject(0x1FD12D, [0x23, 0x5B, 0xFA])), + Pot(76, 20, PotItem.SmallMagic, 'Hera Torches', obj=RoomObject(0x1FD18D, [0x9B, 0xA3, 0xFA])), + Pot(112, 20, PotItem.BigMagic, 'Hera Torches', obj=RoomObject(0x1FD190, [0xE3, 0xA3, 0xFA]))], + 0x8B: [Pot(76, 12, PotItem.Nothing, 'GT Conveyor Cross', obj=RoomObject(0x1FF2F7, [0x9B, 0x63, 0xFA])), + Pot(112, 12, PotItem.Key, 'GT Conveyor Cross', obj=RoomObject(0x1FF2FA, [0xE3, 0x63, 0xFA])), + Pot(32, 23, PotItem.Nothing, 'GT Hookshot South Platform', obj=RoomObject(0x1FF2C7, [0x43, 0xBB, 0xFA])), + Pot(28, 23, PotItem.Nothing, 'GT Hookshot South Platform', obj=RoomObject(0x1FF2C4, [0x3B, 0xBB, 0xFA])), + Pot(32, 9, PotItem.SmallMagic, 'GT Hookshot Mid Platform', obj=RoomObject(0x1FF2C1, [0x43, 0x4B, 0xFA])), + Pot(76, 20, PotItem.Nothing, 'GT Map Room', obj=RoomObject(0x1FF309, [0x9B, 0xA3, 0xFA])), + Pot(76, 28, PotItem.Heart, 'GT Map Room', obj=RoomObject(0x1FF30C, [0x9B, 0xE3, 0xFA]))], + 0x8C: [Pot(76, 12, PotItem.Switch, 'GT Hope Room', obj=RoomObject(0x1FF377, [0x9B, 0x63, 0xFA])), + Pot(112, 12, PotItem.SmallMagic, 'GT Hope Room', obj=RoomObject(0x1FF37A, [0xE3, 0x63, 0xFA])), + Pot(76, 20, PotItem.Bomb, "GT Bob's Room", obj=RoomObject(0x1FF3B9, [0x9B, 0xA3, 0xFA])), + Pot(92, 20, PotItem.Bomb, "GT Bob's Room", obj=RoomObject(0x1FF3BF, [0xBB, 0xA3, 0xFA])), + Pot(100, 21, PotItem.FiveArrows, "GT Bob's Room", obj=RoomObject(0x1FF3C2, [0xCB, 0xAB, 0xFA])), + Pot(104, 26, PotItem.Bomb, "GT Bob's Room", obj=RoomObject(0x1FF3E0, [0xD3, 0xD3, 0xFA])), + Pot(88, 27, PotItem.Bomb, "GT Bob's Room", obj=RoomObject(0x1FF3BC, [0xB3, 0xDB, 0xFA]))], + 0x8D: [Pot(204, 11, PotItem.Nothing, 'GT Speed Torch Upper', obj=RoomObject(0x1FF492, [0x9B, 0x5F, 0xFA])), + Pot(204, 14, PotItem.BigMagic, 'GT Speed Torch Upper', obj=RoomObject(0x1FF495, [0x9B, 0x77, 0xFA])), + Pot(28, 23, PotItem.Heart, 'GT Pots n Blocks', obj=RoomObject(0x1FF477, [0x3B, 0xBB, 0xFA])), + Pot(36, 23, PotItem.Heart, 'GT Pots n Blocks', obj=RoomObject(0x1FF47D, [0x4B, 0xBB, 0xFA])), + Pot(32, 24, PotItem.BigMagic, 'GT Pots n Blocks', obj=RoomObject(0x1FF47A, [0x43, 0xC3, 0xFA]))], + 0x8E: [Pot(80, 5, PotItem.FiveArrows, 'Ice Lonely Freezor', obj=RoomObject(0x1FC835, [0xA3, 0x2B, 0xFA])), + Pot(80, 6, PotItem.Nothing, 'Ice Lonely Freezor', obj=RoomObject(0x1FC838, [0xA3, 0x33, 0xFA]))], + 0x91: [Pot(84, 4, PotItem.Heart, 'Mire Falling Foes', obj=RoomObject(0x1FB9B0, [0xAB, 0x23, 0xFA])), + Pot(104, 4, PotItem.SmallMagic, 'Mire Falling Foes', obj=RoomObject(0x1FB9B3, [0xD3, 0x23, 0xFA]))], + 0x92: [Pot(86, 23, PotItem.Nothing, 'Mire Tall Dark and Roomy', obj=RoomObject(0x1FB966, [0xAF, 0xBB, 0xFA])), + Pot(92, 23, PotItem.Nothing, 'Mire Tall Dark and Roomy', obj=RoomObject(0x1FB969, [0xBB, 0xBB, 0xFA])), + Pot(98, 23, PotItem.Nothing, 'Mire Tall Dark and Roomy', obj=RoomObject(0x1FB96C, [0xC7, 0xBB, 0xFA])), + Pot(104, 23, PotItem.Nothing, 'Mire Tall Dark and Roomy', obj=RoomObject(0x1FB96F, [0xD3, 0xBB, 0xFA]))], + 0x93: [Pot(28, 7, PotItem.Switch, 'Mire Dark Shooters', obj=RoomObject(0x1FB85D, [0x3B, 0x3B, 0xFA])), + Pot(0x9C, 0x17, PotItem.Nothing, 'Mire Block X', PotFlags.Block), + Pot(96, 7, PotItem.Heart, 'Mire Dark Shooters', PotFlags.NoSwitch, obj=RoomObject(0x1FB860, [0xC3, 0x3B, 0xFA]))], + 0x96: [Pot(14, 18, PotItem.Nothing, 'GT Torch Cross', obj=RoomObject(0x1FFC69, [0x1F, 0x93, 0xFA])), + Pot(32, 5, PotItem.Nothing, 'GT Torch Cross', obj=RoomObject(0x1FFC5D, [0x43, 0x2B, 0xFA])), + Pot(46, 11, PotItem.Nothing, 'GT Torch Cross', obj=RoomObject(0x1FFC60, [0x5F, 0x5B, 0xFA])), + Pot(32, 17, PotItem.SmallMagic, 'GT Torch Cross', obj=RoomObject(0x1FFC63, [0x43, 0x8B, 0xFA])), + Pot(32, 24, PotItem.SmallMagic, 'GT Torch Cross', obj=RoomObject(0x1FFC6F, [0x43, 0xC3, 0xFA])), + Pot(14, 24, PotItem.Nothing, 'GT Torch Cross', obj=RoomObject(0x1FFC6C, [0x1F, 0xC3, 0xFA])), + Pot(76, 21, PotItem.Heart, 'GT Staredown', obj=RoomObject(0x1FFC0F, [0x9B, 0xAB, 0xFA])), + Pot(112, 21, PotItem.BigMagic, 'GT Staredown', obj=RoomObject(0x1FFC12, [0xE3, 0xAB, 0xFA]))], + 0x99: [Pot(40, 20, PotItem.SmallMagic, 'Eastern Darkness', obj=RoomObject(0x0A96F4, [0x53, 0xA3, 0xFA])), + Pot(84, 20, PotItem.Heart, 'Eastern Darkness', obj=RoomObject(0x0A96F7, [0xAB, 0xA3, 0xFA]))], + 0x9B: [Pot(48, 4, PotItem.SmallMagic, 'GT Double Switch Pot Corners', obj=RoomObject(0x1FF509, [0x63, 0x23, 0xFA])), + Pot(48, 12, PotItem.Key, 'GT Double Switch Pot Corners', obj=RoomObject(0x1FF50C, [0x63, 0x63, 0xFA])), + Pot(28, 24, PotItem.Nothing, 'GT Warp Maze - Mid Section', obj=RoomObject(0x1FF53C, [0x3B, 0xC3, 0xFA])), + Pot(32, 24, PotItem.Nothing, 'GT Warp Maze - Mid Section', obj=RoomObject(0x1FF53F, [0x43, 0xC3, 0xFA]))], + 0x9C: [Pot(56, 8, PotItem.SmallMagic, 'GT Invisible Catwalk', obj=RoomObject(0x1FF693, [0x73, 0x43, 0xFA])), + Pot(56, 9, PotItem.FiveArrows, 'GT Invisible Catwalk', obj=RoomObject(0x1FF696, [0x73, 0x4B, 0xFA]))], + 0x9D: [Pot(76, 4, PotItem.Bomb, 'GT Crystal Conveyor Left', obj=RoomObject(0x1FF6ED, [0x9B, 0x23, 0xFA])), + Pot(84, 4, PotItem.SmallMagic, 'GT Crystal Conveyor Left', obj=RoomObject(0x1FF6F0, [0xAB, 0x23, 0xFA])), + Pot(32, 7, PotItem.Nothing, 'GT Compass Room', obj=RoomObject(0x1FF6EA, [0x43, 0x3B, 0xFA])), + Pot(40, 9, PotItem.Nothing, 'GT Compass Room', obj=RoomObject(0x1FF6E7, [0x53, 0x4B, 0xFA]))], + 0x9F: [Pot(138, 20, PotItem.Nothing, 'Ice Many Pots', obj=RoomObject(0x1FC904, [0x17, 0xA7, 0xFA])), + Pot(138, 19, PotItem.Heart, 'Ice Many Pots', obj=RoomObject(0x1FC901, [0x17, 0x9F, 0xFA])), + Pot(178, 19, PotItem.Heart, 'Ice Many Pots', obj=RoomObject(0x1FC913, [0x67, 0x9F, 0xFA])), + Pot(40, 21, PotItem.Switch, 'Ice Many Pots', obj=RoomObject(0x1FC928, [0x53, 0xAB, 0xFA])), + Pot(138, 21, PotItem.Key, 'Ice Many Pots', obj=RoomObject(0x1FC907, [0x17, 0xAF, 0xFA])), + Pot(20, 27, PotItem.Heart, 'Ice Many Pots', obj=RoomObject(0x1FC92B, [0x2B, 0xDB, 0xFA])), + Pot(138, 27, PotItem.Heart, 'Ice Many Pots', obj=RoomObject(0x1FC90D, [0x17, 0xDF, 0xFA])), + Pot(178, 28, PotItem.Heart, 'Ice Many Pots', obj=RoomObject(0x1FC922, [0x67, 0xE7, 0xFA])), + Pot(178, 21, PotItem.Nothing, 'Ice Many Pots', obj=RoomObject(0x1FC919, [0x67, 0xAF, 0xFA])), + Pot(178, 20, PotItem.Nothing, 'Ice Many Pots', obj=RoomObject(0x1FC916, [0x67, 0xA7, 0xFA])), + Pot(40, 27, PotItem.Nothing, 'Ice Many Pots', obj=RoomObject(0x1FC92E, [0x53, 0xDB, 0xFA])), + Pot(178, 27, PotItem.Nothing, 'Ice Many Pots', obj=RoomObject(0x1FC91F, [0x67, 0xDF, 0xFA])), + Pot(178, 26, PotItem.Nothing, 'Ice Many Pots', obj=RoomObject(0x1FC91C, [0x67, 0xD7, 0xFA])), + Pot(138, 28, PotItem.Nothing, 'Ice Many Pots', obj=RoomObject(0x1FC910, [0x17, 0xE7, 0xFA])), + Pot(138, 26, PotItem.Nothing, 'Ice Many Pots', obj=RoomObject(0x1FC90A, [0x17, 0xD7, 0xFA])), + Pot(20, 21, PotItem.Nothing, 'Ice Many Pots', obj=RoomObject(0x1FC925, [0x2B, 0xAB, 0xFA]))], + 0xA1: [Pot(150, 6, PotItem.Key, 'Mire Fishbone', obj=RoomObject(0x1FB7F2, [0x2F, 0x37, 0xFA])), + Pot(100, 11, PotItem.SmallMagic, 'Mire Fishbone', obj=RoomObject(0x1FB80D, [0xCB, 0x5B, 0xFA])), + Pot(104, 12, PotItem.Heart, 'Mire Fishbone', obj=RoomObject(0x1FB810, [0xD3, 0x63, 0xFA])), + Pot(108, 13, PotItem.SmallMagic, 'Mire Fishbone', obj=RoomObject(0x1FB813, [0xDB, 0x6B, 0xFA])), + Pot(112, 14, PotItem.Heart, 'Mire Fishbone', obj=RoomObject(0x1FB816, [0xE3, 0x73, 0xFA])), + Pot(96, 27, PotItem.Nothing, 'Mire South Fish', obj=RoomObject(0x1FB804, [0xC3, 0xDB, 0xFA])), + Pot(92, 21, PotItem.Nothing, 'Mire South Fish', obj=RoomObject(0x1FB7FB, [0xBB, 0xAB, 0xFA])), + Pot(96, 23, PotItem.Heart, 'Mire South Fish', obj=RoomObject(0x1FB7FE, [0xC3, 0xBB, 0xFA])), + Pot(92, 25, PotItem.Nothing, 'Mire South Fish', obj=RoomObject(0x1FB801, [0xBB, 0xCB, 0xFA])), + Pot(76, 28, PotItem.Nothing, 'Mire South Fish', obj=RoomObject(0x1FB807, [0x9B, 0xE3, 0xFA])), + Pot(112, 28, PotItem.Nothing, 'Mire South Fish', obj=RoomObject(0x1FB80A, [0xE3, 0xE3, 0xFA]))], + 0xA2: [Pot(12, 28, PotItem.BigMagic, 'Mire Left Bridge', obj=RoomObject(0x1FB6B1, [0x1B, 0xE3, 0xFA]))], + 0xA8: [Pot(138, 28, PotItem.Nothing, 'Eastern Stalfos Spawn', obj=RoomObject(0x0A97BB, [0x17, 0xE7, 0xFA])), + Pot(178, 28, PotItem.Nothing, 'Eastern Stalfos Spawn', obj=RoomObject(0x0A97BE, [0x67, 0xE7, 0xFA])), + Pot(178, 19, PotItem.Nothing, 'Eastern Stalfos Spawn', obj=RoomObject(0x0A97B8, [0x67, 0x9F, 0xFA])), + Pot(138, 19, PotItem.Heart, 'Eastern Stalfos Spawn', obj=RoomObject(0x0A97B5, [0x17, 0x9F, 0xFA])), + Pot(30, 24, PotItem.OneRupee, 'Eastern Stalfos Spawn', obj=RoomObject(0x0A97C1, [0x3F, 0xC3, 0xFA]))], + 0xA9: [Pot(144, 0xB, PotItem.FiveArrows, 'Eastern Courtyard', PotFlags.LowerRegion, obj=RoomObject(0x0A9983, [0x23, 0x5F, 0xFA])), + Pot(236, 0xB, PotItem.FiveArrows, 'Eastern Courtyard', PotFlags.LowerRegion, obj=RoomObject(0x0A9989, [0xDB, 0x5F, 0xFA])), + Pot(144, 0xC, PotItem.FiveArrows, 'Eastern Courtyard', PotFlags.LowerRegion, obj=RoomObject(0x0A9986, [0x23, 0x67, 0xFA])), + Pot(236, 0xC, PotItem.Heart, 'Eastern Courtyard', PotFlags.LowerRegion, obj=RoomObject(0x0A998C, [0xDB, 0x67, 0xFA])), + Pot(12, 19, PotItem.Nothing, 'Eastern Courtyard Ledge', obj=RoomObject(0x0A994B, [0x1B, 0x9B, 0xFA])), + Pot(112, 19, PotItem.Nothing, 'Eastern Courtyard Ledge', obj=RoomObject(0x0A993C, [0xE3, 0x9B, 0xFA])), + Pot(16, 20, PotItem.Heart, 'Eastern Courtyard Ledge', obj=RoomObject(0x0A994E, [0x23, 0xA3, 0xFA])), + Pot(108, 20, PotItem.Heart, 'Eastern Courtyard Ledge', obj=RoomObject(0x0A9936, [0xDB, 0xA3, 0xFA]))], + 0xAA: [Pot(212, 10, PotItem.Nothing, 'Eastern Pot Switch', obj=RoomObject(0x0A9AA5, [0xAB, 0x57, 0xFA])), + Pot(232, 10, PotItem.Nothing, 'Eastern Pot Switch', obj=RoomObject(0x0A9AA8, [0xD3, 0x57, 0xFA])), + Pot(232, 5, PotItem.Nothing, 'Eastern Pot Switch', obj=RoomObject(0x0A9AA2, [0xD3, 0x2F, 0xFA])), + Pot(212, 5, PotItem.Heart, 'Eastern Pot Switch', obj=RoomObject(0x0A9A9F, [0xAB, 0x2F, 0xFA])), + Pot(94, 8, PotItem.Switch, 'Eastern Pot Switch', obj=RoomObject(0x0A9AAB, [0xBF, 0x43, 0xFA])), + Pot(108, 0x17, PotItem.Heart, 'Eastern Map Balcony', PotFlags.LowerRegion, obj=RoomObject(0x0A9AF5, [0xDB, 0xBB, 0xFA])), + Pot(108, 0x18, PotItem.Heart, 'Eastern Map Balcony', PotFlags.LowerRegion, obj=RoomObject(0x0A9AF8, [0xDB, 0xC3, 0xFA])), + Pot(108, 0x19, PotItem.Heart, 'Eastern Map Balcony', PotFlags.LowerRegion, obj=RoomObject(0x0A9AFB, [0xDB, 0xCB, 0xFA]))], + 0xAB: [Pot(20, 24, PotItem.Key, 'Thieves Spike Switch', obj=RoomObject(0x1FD99D, [0x2B, 0xC3, 0xFA]))], + 0xAE: [Pot(76, 12, PotItem.Switch, 'Iced T', obj=RoomObject(0x1FC95D, [0x9B, 0x63, 0xFA]))], + 0xB0: [Pot(20, 27, PotItem.Nothing, 'Tower Circle of Pots', obj=RoomObject(0x1F8F2C, [0x2B, 0xDB, 0xFA])), + Pot(24, 24, PotItem.Nothing, 'Tower Circle of Pots', obj=RoomObject(0x1F8F26, [0x33, 0xC3, 0xFA])), + Pot(44, 25, PotItem.Nothing, 'Tower Circle of Pots', obj=RoomObject(0x1F8F38, [0x5B, 0xCB, 0xFA])), + Pot(20, 21, PotItem.Bomb, 'Tower Circle of Pots', obj=RoomObject(0x1F8F14, [0x2B, 0xAB, 0xFA])), + Pot(28, 21, PotItem.OneRupee, 'Tower Circle of Pots', obj=RoomObject(0x1F8F17, [0x3B, 0xAB, 0xFA])), + Pot(32, 21, PotItem.FiveRupees, 'Tower Circle of Pots', obj=RoomObject(0x1F8F1A, [0x43, 0xAB, 0xFA])), + Pot(40, 21, PotItem.FiveArrows, 'Tower Circle of Pots', obj=RoomObject(0x1F8F1D, [0x53, 0xAB, 0xFA])), + Pot(16, 23, PotItem.FiveRupees, 'Tower Circle of Pots', obj=RoomObject(0x1F8F20, [0x23, 0xBB, 0xFA])), + Pot(44, 23, PotItem.OneRupee, 'Tower Circle of Pots', obj=RoomObject(0x1F8F3B, [0x5B, 0xBB, 0xFA])), + Pot(36, 24, PotItem.Heart, 'Tower Circle of Pots', obj=RoomObject(0x1F8F29, [0x4B, 0xC3, 0xFA])), + Pot(16, 25, PotItem.Heart, 'Tower Circle of Pots', obj=RoomObject(0x1F8F23, [0x23, 0xCB, 0xFA])), + Pot(28, 27, PotItem.FiveArrows, 'Tower Circle of Pots', obj=RoomObject(0x1F8F2F, [0x3B, 0xDB, 0xFA])), + Pot(40, 27, PotItem.Bomb, 'Tower Circle of Pots', obj=RoomObject(0x1F8F35, [0x53, 0xDB, 0xFA])), + Pot(32, 27, PotItem.Nothing, 'Tower Circle of Pots', obj=RoomObject(0x1F8F32, [0x43, 0xDB, 0xFA]))], + 0xB1: [Pot(76, 4, PotItem.Heart, 'Mire Spike Barrier', obj=RoomObject(0x1FB35A, [0x9B, 0x23, 0xFA])), + Pot(112, 4, PotItem.OneRupee, 'Mire Spike Barrier', obj=RoomObject(0x1FB35D, [0xE3, 0x23, 0xFA]))], + 0xB2: [Pot(48, 0x8, PotItem.OneRupee, 'Mire BK Door Room', PotFlags.LowerRegion, obj=RoomObject(0x1FB467, [0x63, 0x43, 0xFA])), + Pot(76, 0x8, PotItem.OneRupee, 'Mire BK Door Room', PotFlags.LowerRegion, obj=RoomObject(0x1FB470, [0x9B, 0x43, 0xFA])), + Pot(48, 0x9, PotItem.Nothing, 'Mire BK Door Room', PotFlags.LowerRegion, obj=RoomObject(0x1FB46A, [0x63, 0x4B, 0xFA])), + Pot(76, 0x9, PotItem.Heart, 'Mire BK Door Room', PotFlags.LowerRegion, obj=RoomObject(0x1FB473, [0x9B, 0x4B, 0xFA])), + Pot(48, 0xA, PotItem.Nothing, 'Mire BK Door Room', PotFlags.LowerRegion, obj=RoomObject(0x1FB46D, [0x63, 0x53, 0xFA])), + Pot(76, 0xA, PotItem.Nothing, 'Mire BK Door Room', PotFlags.LowerRegion, obj=RoomObject(0x1FB476, [0x9B, 0x53, 0xFA]))], + 0xB3: [Pot(12, 20, PotItem.Key, 'Mire Spikes', obj=RoomObject(0x1FB5A2, [0x1B, 0xA3, 0xFA])), + Pot(48, 20, PotItem.SmallMagic, 'Mire Spikes', obj=RoomObject(0x1FB5A5, [0x63, 0xA3, 0xFA])), + Pot(48, 28, PotItem.Switch, 'Mire Spikes', obj=RoomObject(0x1FB5C0, [0x63, 0xE3, 0xFA]))], + 0xB4: [Pot(44, 28, PotItem.BigMagic, 'TR Final Abyss Balcony', obj=RoomObject(0x1FE7B6, [0x5B, 0xE3, 0xFA])), + Pot(48, 28, PotItem.Heart, 'TR Final Abyss Balcony', obj=RoomObject(0x1FE7B9, [0x63, 0xE3, 0xFA]))], + 0xB5: [Pot(112, 4, PotItem.FiveRupees, 'TR Dark Ride Ledges', obj=RoomObject(0x1FEA78, [0xE3, 0x23, 0xFA])), + Pot(112, 15, PotItem.Heart, 'TR Dark Ride Ledges', obj=RoomObject(0x1FEAD5, [0xE3, 0x7B, 0xFA])), + Pot(76, 16, PotItem.Switch, 'TR Dark Ride Ledges', obj=RoomObject(0x1FEA93, [0x9B, 0x83, 0xFA])), + Pot(112, 16, PotItem.BigMagic, 'TR Dark Ride Ledges', obj=RoomObject(0x1FEAD8, [0xE3, 0x83, 0xFA])), + Pot(112, 17, PotItem.Heart, 'TR Dark Ride Ledges', obj=RoomObject(0x1FEADB, [0xE3, 0x8B, 0xFA])), + Pot(112, 28, PotItem.Bomb, 'TR Dark Ride Ledges', obj=RoomObject(0x1FEAF3, [0xE3, 0xE3, 0xFA]))], + 0xB6: [Pot(94, 9, PotItem.BigMagic, 'TR Refill', obj=RoomObject(0x1FDD29, [0xBF, 0x4B, 0xFA]))], + 0xB7: [Pot(30, 5, PotItem.SmallMagic, 'TR Roller Room', obj=RoomObject(0x1FDD76, [0x3F, 0x2B, 0xFA]))], + 0xB8: [Pot(96, 13, PotItem.Switch, 'Eastern Big Key', obj=RoomObject(0x0A9B66, [0xC3, 0x6B, 0xFA])), + Pot(88, 16, PotItem.Heart, 'Eastern Big Key', obj=RoomObject(0x0A9B60, [0xB3, 0x83, 0xFA])), + Pot(104, 16, PotItem.Heart, 'Eastern Big Key', obj=RoomObject(0x0A9B63, [0xD3, 0x83, 0xFA]))], + 0xB9: [Pot(92, 18, PotItem.OneRupee, 'Eastern Cannonball', obj=RoomObject(0x0A9C82, [0xBB, 0x93, 0xFA])), + Pot(96, 18, PotItem.FiveRupees, 'Eastern Cannonball', obj=RoomObject(0x0A9C85, [0xC3, 0x93, 0xFA])), + Pot(104, 18, PotItem.FiveRupees, 'Eastern Cannonball', obj=RoomObject(0x0A9C88, [0xD3, 0x93, 0xFA])), + Pot(108, 18, PotItem.OneRupee, 'Eastern Cannonball', obj=RoomObject(0x0A9C8B, [0xDB, 0x93, 0xFA]))], + 0xBA: [Pot(100, 8, PotItem.Nothing, 'Eastern Dark Pots', obj=RoomObject(0x0A9D3F, [0xCB, 0x43, 0xFA])), + Pot(88, 8, PotItem.Nothing, 'Eastern Dark Pots', obj=RoomObject(0x0A9D36, [0xB3, 0x43, 0xFA])), + Pot(94, 4, PotItem.OneRupee, 'Eastern Dark Pots', obj=RoomObject(0x0A9D39, [0xBF, 0x23, 0xFA])), + Pot(76, 6, PotItem.Heart, 'Eastern Dark Pots', obj=RoomObject(0x0A9D30, [0x9B, 0x33, 0xFA])), + Pot(112, 6, PotItem.Key, 'Eastern Dark Pots', obj=RoomObject(0x0A9D42, [0xE3, 0x33, 0xFA])), + Pot(76, 10, PotItem.Heart, 'Eastern Dark Pots', obj=RoomObject(0x0A9D33, [0x9B, 0x53, 0xFA])), + Pot(112, 10, PotItem.SmallMagic, 'Eastern Dark Pots', obj=RoomObject(0x0A9D45, [0xE3, 0x53, 0xFA])), + Pot(94, 12, PotItem.OneRupee, 'Eastern Dark Pots', obj=RoomObject(0x0A9D3C, [0xBF, 0x63, 0xFA]))], + 0xBC: [Pot(86, 4, PotItem.Heart, 'Thieves Hallway', obj=RoomObject(0x1FD941, [0xAF, 0x23, 0xFA])), + Pot(102, 4, PotItem.Key, 'Thieves Hallway', obj=RoomObject(0x1FD944, [0xCF, 0x23, 0xFA])), + Pot(138, 3, PotItem.Bomb, 'Thieves Conveyor Maze', obj=RoomObject(0x1FD8DB, [0x17, 0x1F, 0xFA])), + Pot(178, 3, PotItem.Switch, 'Thieves Conveyor Maze', obj=RoomObject(0x1FD8DE, [0x67, 0x1F, 0xFA])), + Pot(138, 12, PotItem.Heart, 'Thieves Conveyor Maze', obj=RoomObject(0x1FD8E1, [0x17, 0x67, 0xFA])), + Pot(178, 12, PotItem.Bomb, 'Thieves Conveyor Maze', obj=RoomObject(0x1FD8F3, [0x67, 0x67, 0xFA])), + Pot(12, 20, PotItem.Nothing, 'Thieves Pot Alcove Mid', obj=RoomObject(0x1FD8FF, [0x1B, 0xA3, 0xFA])), + Pot(48, 20, PotItem.Bomb, 'Thieves Pot Alcove Mid', obj=RoomObject(0x1FD902, [0x63, 0xA3, 0xFA])), + Pot(12, 28, PotItem.Bomb, 'Thieves Pot Alcove Mid', obj=RoomObject(0x1FD905, [0x1B, 0xE3, 0xFA])), + Pot(48, 28, PotItem.Bomb, 'Thieves Pot Alcove Mid', obj=RoomObject(0x1FD908, [0x63, 0xE3, 0xFA])), + Pot(28, 21, PotItem.FiveRupees, 'Thieves Pot Alcove Top', obj=RoomObject(0x1FD914, [0x3B, 0xAB, 0xFA])), + Pot(32, 21, PotItem.FiveRupees, 'Thieves Pot Alcove Top', obj=RoomObject(0x1FD917, [0x43, 0xAB, 0xFA])), + Pot(28, 27, PotItem.FiveRupees, 'Thieves Pot Alcove Bottom', obj=RoomObject(0x1FD923, [0x3B, 0xDB, 0xFA])), + Pot(32, 27, PotItem.FiveRupees, 'Thieves Pot Alcove Bottom', obj=RoomObject(0x1FD926, [0x43, 0xDB, 0xFA]))], + 0xBE: [Pot(92, 25, PotItem.Switch, 'Ice Switch Room', obj=RoomObject(0x1FC9E9, [0xBB, 0xCB, 0xFA]))], + 0xBF: [Pot(40, 20, PotItem.FiveArrows, 'Ice Refill', obj=RoomObject(0x1FCA56, [0x53, 0xA3, 0xFA])), + Pot(44, 20, PotItem.Heart, 'Ice Refill', obj=RoomObject(0x1FCA59, [0x5B, 0xA3, 0xFA])), + Pot(48, 20, PotItem.Bomb, 'Ice Refill', obj=RoomObject(0x1FCA5C, [0x63, 0xA3, 0xFA])), + Pot(40, 28, PotItem.SmallMagic, 'Ice Refill', obj=RoomObject(0x1FCA5F, [0x53, 0xE3, 0xFA])), + Pot(44, 28, PotItem.SmallMagic, 'Ice Refill', obj=RoomObject(0x1FCA62, [0x5B, 0xE3, 0xFA])), + Pot(48, 28, PotItem.SmallMagic, 'Ice Refill', obj=RoomObject(0x1FCA65, [0x63, 0xE3, 0xFA]))], + 0xC0: [Pot(48, 10, PotItem.Bomb, 'Tower Dark Pits', obj=RoomObject(0x1F8FE1, [0x63, 0x53, 0xFA])), + Pot(12, 14, PotItem.FiveRupees, 'Tower Dark Pits', obj=RoomObject(0x1F8FE7, [0x1B, 0x73, 0xFA])), + Pot(12, 26, PotItem.Heart, 'Tower Dark Pits', obj=RoomObject(0x1F8FF0, [0x1B, 0xD3, 0xFA])), + Pot(28, 27, PotItem.OneRupee, 'Tower Dark Pits', obj=RoomObject(0x1F8FF3, [0x3B, 0xDB, 0xFA]))], + 0xC2: [Pot(180, 7, PotItem.Switch, 'Mire Hub Switch', obj=RoomObject(0x1FB0C4, [0x6B, 0x3F, 0xFA])), + Pot(100, 0xE, PotItem.SmallMagic, 'Mire Hub Right', PotFlags.LowerRegion, RoomObject(0x1FB071, [0xCB, 0x73, 0xFA])), + Pot(68, 0x10, PotItem.OneRupee, 'Mire Hub', PotFlags.LowerRegion, RoomObject(0x1FB086, [0x8B, 0x83, 0xFA])), + Pot(64, 0x14, PotItem.FiveArrows, 'Mire Hub', PotFlags.LowerRegion, RoomObject(0x1FB089, [0x83, 0xA3, 0xFA]))], + 0xC4: [Pot(84, 9, PotItem.Bomb, 'TR Crystal Maze Interior', obj=RoomObject(0x1FEC06, [0xAB, 0x4B, 0xFA])), + Pot(24, 14, PotItem.Heart, 'TR Crystal Maze Interior', obj=RoomObject(0x1FEC09, [0x33, 0x73, 0xFA])), + Pot(56, 17, PotItem.FiveRupees, 'TR Crystal Maze Interior', obj=RoomObject(0x1FEC0C, [0x73, 0x8B, 0xFA])), + Pot(84, 17, PotItem.Bomb, 'TR Crystal Maze Interior', obj=RoomObject(0x1FEC0F, [0xAB, 0x8B, 0xFA])), + Pot(12, 21, PotItem.FiveArrows, 'TR Crystal Maze Interior', obj=RoomObject(0x1FEC15, [0x1B, 0xAB, 0xFA])), + Pot(76, 23, PotItem.OneRupee, 'TR Crystal Maze Interior', obj=RoomObject(0x1FEC12, [0x9B, 0xBB, 0xFA])), + Pot(48, 25, PotItem.SmallMagic, 'TR Crystal Maze Interior', obj=RoomObject(0x1FEC1B, [0x63, 0xCB, 0xFA])), + Pot(12, 26, PotItem.Heart, 'TR Crystal Maze Interior', obj=RoomObject(0x1FEC18, [0x1B, 0xD3, 0xFA]))], + 0xC6: [Pot(12, 7, PotItem.BigMagic, 'TR Hub Ledges', obj=RoomObject(0x1FDF50, [0x1B, 0x3B, 0xFA])), + Pot(12, 25, PotItem.Heart, 'TR Hub Ledges', obj=RoomObject(0x1FDF53, [0x1B, 0xCB, 0xFA]))], + 0xC7: [Pot(12, 10, PotItem.Heart, 'TR Torches', obj=RoomObject(0x1FE080, [0x1B, 0x53, 0xFA])), + Pot(12, 11, PotItem.BigMagic, 'TR Torches', obj=RoomObject(0x1FE083, [0x1B, 0x5B, 0xFA])), + Pot(12, 22, PotItem.SmallMagic, 'TR Torches Ledge', obj=RoomObject(0x1FE098, [0x1B, 0xB3, 0xFA])), + Pot(12, 28, PotItem.FiveArrows, 'TR Torches Ledge', obj=RoomObject(0x1FE095, [0x1B, 0xE3, 0xFA]))], + 0xC9: [Pot(30, 22, PotItem.OneRupee, 'Eastern Lobby', obj=RoomObject(0x0A9E30, [0x3F, 0xB3, 0xFA])), + Pot(94, 22, PotItem.OneRupee, 'Eastern Lobby', obj=RoomObject(0x0A9E36, [0xBF, 0xB3, 0xFA])), + Pot(60, 22, PotItem.Switch, 'Eastern Lobby', obj=RoomObject(0x0A9E33, [0x7B, 0xB3, 0xFA]))], + 0xCB: [Pot(80, 4, PotItem.Nothing, 'Thieves Ambush', obj=RoomObject(0x1FD59A, [0xA3, 0x23, 0xFA])), + Pot(80, 28, PotItem.Nothing, 'Thieves Ambush', obj=RoomObject(0x1FD5A3, [0xA3, 0xE3, 0xFA])), + Pot(88, 16, PotItem.Heart, 'Thieves Ambush', obj=RoomObject(0x1FD59D, [0xB3, 0x83, 0xFA])), + Pot(88, 28, PotItem.FiveRupees, 'Thieves Ambush', obj=RoomObject(0x1FD5A0, [0xB3, 0xE3, 0xFA]))], + 0xCC: [Pot(36, 4, PotItem.FiveRupees, 'Thieves Rail Ledge', obj=RoomObject(0x1FD6F0, [0x4B, 0x23, 0xFA])), + Pot(36, 28, PotItem.FiveRupees, 'Thieves Rail Ledge', obj=RoomObject(0x1FD6F3, [0x4B, 0xE3, 0xFA])), + Pot(112, 4, PotItem.Heart, 'Thieves BK Corner', obj=RoomObject(0x1FD6ED, [0xE3, 0x23, 0xFA])), + Pot(112, 28, PotItem.Bomb, 'Thieves BK Corner', obj=RoomObject(0x1FD6F6, [0xE3, 0xE3, 0xFA]))], + 0xCE: [Pot(76, 8, PotItem.SmallMagic, 'Ice Antechamber', obj=RoomObject(0x1FCAA7, [0x9B, 0x43, 0xFA])), + Pot(80, 8, PotItem.SmallMagic, 'Ice Antechamber', obj=RoomObject(0x1FCAAA, [0xA3, 0x43, 0xFA])), + Pot(108, 12, PotItem.Bomb, 'Ice Antechamber', obj=RoomObject(0x1FCAB9, [0xDB, 0x63, 0xFA])), + Pot(112, 12, PotItem.FiveArrows, 'Ice Antechamber', obj=RoomObject(0x1FCABC, [0xE3, 0x63, 0xFA])), + Pot(204, 11, PotItem.Hole, 'Ice Antechamber'), + Pot(0x6c, 8, PotItem.Nothing, 'Ice Antechamber', PotFlags.Block)], + 0xD0: [Pot(158, 5, PotItem.SmallMagic, 'Tower Dark Maze', obj=RoomObject(0x1F90A5, [0x3F, 0x2F, 0xFA])), + Pot(140, 11, PotItem.OneRupee, 'Tower Dark Maze', obj=RoomObject(0x1F90A8, [0x1B, 0x5F, 0xFA])), + Pot(42, 13, PotItem.SmallMagic, 'Tower Dark Maze', obj=RoomObject(0x1F90AE, [0x57, 0x6B, 0xFA])), + Pot(48, 16, PotItem.Heart, 'Tower Dark Maze', obj=RoomObject(0x1F90B1, [0x63, 0x83, 0xFA])), + Pot(176, 20, PotItem.OneRupee, 'Tower Dark Maze', obj=RoomObject(0x1F90B4, [0x63, 0xA7, 0xFA])), + Pot(146, 23, PotItem.FiveRupees, 'Tower Dark Maze', obj=RoomObject(0x1F90B7, [0x27, 0xBF, 0xFA])), + Pot(12, 28, PotItem.Heart, 'Tower Dark Maze', obj=RoomObject(0x1F90BA, [0x1B, 0xE3, 0xFA]))], + 0xD1: [Pot(48, 4, PotItem.BigMagic, 'Mire Conveyor Barrier', obj=RoomObject(0x1FB22C, [0x63, 0x23, 0xFA])), + Pot(168, 7, PotItem.OneRupee, 'Mire Conveyor Barrier', obj=RoomObject(0x1FB229, [0x53, 0x3F, 0xFA])), + Pot(76, 4, PotItem.OneRupee, 'Mire Neglected Room', obj=RoomObject(0x1FB23B, [0x9B, 0x23, 0xFA])), + Pot(112, 4, PotItem.FiveArrows, 'Mire Neglected Room', obj=RoomObject(0x1FB250, [0xE3, 0x23, 0xFA])), + Pot(76, 12, PotItem.Nothing, 'Mire Neglected Room', obj=RoomObject(0x1FB232, [0x9B, 0x63, 0xFA])), + Pot(112, 12, PotItem.OneRupee, 'Mire Neglected Room', obj=RoomObject(0x1FB235, [0xE3, 0x63, 0xFA]))], + 0xD6: [Pot(92, 22, PotItem.BigMagic, 'TR Main Lobby', obj=RoomObject(0x1FE0F9, [0xBB, 0xB3, 0xFA])), + Pot(96, 22, PotItem.Bomb, 'TR Main Lobby', obj=RoomObject(0x1FE0FC, [0xC3, 0xB3, 0xFA]))], + 0xD8: [Pot(202, 8, PotItem.Heart, 'Eastern Duo Eyegores', obj=RoomObject(0x0A95C0, [0x97, 0x47, 0xFA])), + Pot(242, 8, PotItem.FiveArrows, 'Eastern Duo Eyegores', obj=RoomObject(0x0A95CF, [0xE7, 0x47, 0xFA])), + Pot(202, 10, PotItem.FiveArrows, 'Eastern Duo Eyegores', obj=RoomObject(0x0A95C3, [0x97, 0x57, 0xFA])), + Pot(242, 10, PotItem.FiveArrows, 'Eastern Duo Eyegores', obj=RoomObject(0x0A95C9, [0xE7, 0x57, 0xFA])), + Pot(202, 12, PotItem.FiveArrows, 'Eastern Duo Eyegores', obj=RoomObject(0x0A95C6, [0x97, 0x67, 0xFA])), + Pot(242, 12, PotItem.Heart, 'Eastern Duo Eyegores', obj=RoomObject(0x0A95CC, [0xE7, 0x67, 0xFA])), + Pot(92, 24, PotItem.Heart, 'Eastern Single Eyegore', obj=RoomObject(0x0A95DB, [0xBB, 0xC3, 0xFA])), + Pot(96, 24, PotItem.FiveArrows, 'Eastern Single Eyegore', obj=RoomObject(0x0A95DE, [0xC3, 0xC3, 0xFA]))], + 0xD9: [Pot(92, 20, PotItem.FiveArrows, 'Eastern False Switches', obj=RoomObject(0x0A965A, [0xBB, 0xA3, 0xFA])), + Pot(92, 28, PotItem.Heart, 'Eastern False Switches', obj=RoomObject(0x0A965D, [0xBB, 0xE3, 0xFA]))], + 0xDA: [Pot(24, 23, PotItem.FiveArrows, 'Eastern Attic Start', obj=RoomObject(0x0A968B, [0x33, 0xBB, 0xFA])), + Pot(36, 23, PotItem.FiveArrows, 'Eastern Attic Start', obj=RoomObject(0x0A968E, [0x4B, 0xBB, 0xFA])), + Pot(24, 25, PotItem.Switch, 'Eastern Attic Start', obj=RoomObject(0x0A9691, [0x33, 0xCB, 0xFA])), + Pot(36, 25, PotItem.Heart, 'Eastern Attic Start', obj=RoomObject(0x0A9694, [0x4B, 0xCB, 0xFA]))], + 0xDB: [Pot(12, 4, PotItem.Nothing, 'Thieves Lobby', obj=RoomObject(0x1FD2C5, [0x1B, 0x23, 0xFA])), + Pot(62, 19, PotItem.Nothing, 'Thieves Lobby', PotFlags.LowerRegion, RoomObject(0x1FD300, [0x7F, 0x9B, 0xFA])), + Pot(112, 4, PotItem.FiveRupees, 'Thieves Lobby', obj=RoomObject(0x1FD2C8, [0xE3, 0x23, 0xFA])), + Pot(88, 16, PotItem.Heart, 'Thieves Lobby', obj=RoomObject(0x1FD2CB, [0xB3, 0x83, 0xFA]))], + 0xDC: [Pot(56, 4, PotItem.FiveRupees, 'Thieves Compass Room', obj=RoomObject(0x1FD447, [0x73, 0x23, 0xFA])), + Pot(112, 4, PotItem.Bomb, 'Thieves Compass Room', obj=RoomObject(0x1FD44A, [0xE3, 0x23, 0xFA])), + Pot(68, 16, PotItem.Heart, 'Thieves Compass Room', obj=RoomObject(0x1FD44D, [0x8B, 0x83, 0xFA])), + Pot(12, 28, PotItem.FiveArrows, 'Thieves Compass Room', obj=RoomObject(0x1FD453, [0x1B, 0xE3, 0xFA]))], + 0xE3: [Pot(88, 0x17, PotItem.Nothing, 'Bat Cave (right)', PotFlags.LowerRegion, RoomObject(0x0A82EE, [0xB3, 0xBB, 0xFA])), + Pot(100, 0x19, PotItem.OneRupee, 'Bat Cave (right)', PotFlags.LowerRegion, RoomObject(0x0A82F1, [0xCB, 0xCB, 0xFA]))], + 0xE4: [Pot(32, 9, PotItem.FiveRupees, 'Old Man House', obj=RoomObject(0x0AB42A, [0x43, 0x4B, 0xFA])), + Pot(112, 10, PotItem.OneRupee, 'Old Man House', obj=RoomObject(0x0AB46C, [0xE3, 0x53, 0xFA]))], + 0xE5: [Pot(48, 4, PotItem.OneRupee, 'Old Man House Back', obj=RoomObject(0x0AB545, [0x63, 0x23, 0xFA])), + Pot(76, 4, PotItem.OneRupee, 'Old Man House Back', obj=RoomObject(0x0AB548, [0x9B, 0x23, 0xFA])), + Pot(112, 16, PotItem.OneRupee, 'Old Man House Back', obj=RoomObject(0x0AB54B, [0xE3, 0x83, 0xFA])), + Pot(64, 18, PotItem.FiveRupees, 'Old Man House Back', obj=RoomObject(0x0AB54E, [0x83, 0x93, 0xFA]))], + 0xE6: [Pot(108, 12, PotItem.FiveArrows, 'Death Mountain Return Cave (left)', obj=RoomObject(0x0AB29D, [0xDB, 0x63, 0xFA])), + Pot(88, 16, PotItem.Heart, 'Death Mountain Return Cave (left)', obj=RoomObject(0x0AB2A0, [0xB3, 0x83, 0xFA])), + Pot(72, 20, PotItem.Nothing, 'Death Mountain Return Cave (left)', obj=RoomObject(0x0AB2A3, [0x93, 0xA3, 0xFA])), + Pot(56, 24, PotItem.OneRupee, 'Death Mountain Return Cave (left)', obj=RoomObject(0x0AB2A6, [0x73, 0xC3, 0xFA]))], + 0xE7: [Pot(68, 5, PotItem.OneRupee, 'Death Mountain Return Cave (right)', obj=RoomObject(0x0AB389, [0x8B, 0x2B, 0xFA])), + Pot(72, 5, PotItem.OneRupee, 'Death Mountain Return Cave (right)', obj=RoomObject(0x0AB38C, [0x93, 0x2B, 0xFA]))], + 0xE8: [Pot(96, 4, PotItem.Heart, 'Superbunny Cave (Bottom)', obj=RoomObject(0x0AA98E, [0xC3, 0x23, 0xFA]))], + 0xEB: [Pot(206, 8, PotItem.FiveRupees, 'Bumper Cave', obj=RoomObject(0x0AADE7, [0x9F, 0x47, 0xFA])), + Pot(210, 8, PotItem.FiveRupees, 'Bumper Cave', obj=RoomObject(0x0AADEA, [0xA7, 0x47, 0xFA])), + Pot(88, 14, PotItem.SmallMagic, 'Bumper Cave', obj=RoomObject(0x0AADED, [0xB3, 0x73, 0xFA])), + Pot(92, 14, PotItem.Heart, 'Bumper Cave', obj=RoomObject(0x0AADF0, [0xBB, 0x73, 0xFA])), + Pot(96, 14, PotItem.SmallMagic, 'Bumper Cave', obj=RoomObject(0x0AADF3, [0xC3, 0x73, 0xFA]))], + 0xF1: [Pot(64, 5, PotItem.Heart, 'Old Man Cave', obj=RoomObject(0x0AA6B2, [0x83, 0x2B, 0xFA]))], + 0xF3: [Pot(0x28, 0x14, PotItem.Nothing, 'Elder House', obj=RoomObject(0x0AA76F, [0x53, 0xA3, 0xFA])), + Pot(0x2C, 0x14, PotItem.Nothing, 'Elder House', obj=RoomObject(0x0AA772, [0x5B, 0xA3, 0xFA])), + Pot(0x30, 0x14, PotItem.Nothing, 'Elder House', obj=RoomObject(0x0AA775, [0x63, 0xA3, 0xFA]))], + 0xF8: [Pot(242, 13, PotItem.BigMagic, 'Superbunny Cave (Top)', obj=RoomObject(0x0AA8B9, [0xE7, 0x6F, 0xFA]))], + 0xFD: [Pot(88, 6, PotItem.FiveRupees, 'Fairy Ascension Cave (Top)', obj=RoomObject(0x0AAA51, [0xB3, 0x33, 0xFA])), + Pot(100, 6, PotItem.FiveRupees, 'Fairy Ascension Cave (Top)', obj=RoomObject(0x0AAA54, [0xCB, 0x33, 0xFA])), + Pot(84, 23, PotItem.FiveRupees, 'Fairy Ascension Cave (Bottom)', obj=RoomObject(0x0AAA57, [0xAB, 0xBB, 0xFA])), + Pot(84, 24, PotItem.FiveRupees, 'Fairy Ascension Cave (Bottom)', obj=RoomObject(0x0AAA5A, [0xAB, 0xC3, 0xFA]))], + 0xFF: [Pot(92, 8, PotItem.Heart, 'Paradox Cave Bomb Area', obj=RoomObject(0x0AAC5B, [0xBB, 0x43, 0xFA])), + Pot(96, 8, PotItem.Heart, 'Paradox Cave Bomb Area', obj=RoomObject(0x0AAC5E, [0xC3, 0x43, 0xFA])), + Pot(112, 28, PotItem.OneRupee, 'Paradox Cave Front', obj=RoomObject(0x0AAC6A, [0xE3, 0xE3, 0xFA]))], + 0x101: [Pot(12, 20, PotItem.Heart, 'Snitch Lady (East)', obj=RoomObject(0x03EBE5, [0x1B, 0xA3, 0xFA])), + Pot(224, 19, PotItem.Chicken, 'Snitch Lady (West)', obj=RoomObject(0x03EC1E, [0xC3, 0x9F, 0xFA])), + Pot(228, 19, PotItem.Heart, 'Snitch Lady (West)', obj=RoomObject(0x03EC21, [0xCB, 0x9F, 0xFA]))], + 0x102: [Pot(146, 19, PotItem.Heart, 'Sick Kids House', obj=RoomObject(0x03EC81, [0x27, 0x9F, 0xFA])), + Pot(150, 19, PotItem.Heart, 'Sick Kids House', obj=RoomObject(0x03EC84, [0x2F, 0x9F, 0xFA]))], + 0x103: [Pot(140, 7, PotItem.Chicken, 'Tavern', obj=RoomObject(0x03ECDA, [0x1B, 0x3F, 0xFA])), + Pot(140, 9, PotItem.Nothing, 'Tavern', obj=RoomObject(0x03ECDD, [0x1B, 0x4F, 0xFA])), + Pot(12, 12, PotItem.Heart, 'Tavern (Front)', obj=RoomObject(0x03ECE6, [0x1B, 0x63, 0xFA]))], + 0x104: [Pot(202, 21, PotItem.Heart, 'Links House', obj=RoomObject(0x0A8017, [0x97, 0xAF, 0xFA])), + Pot(202, 22, PotItem.Heart, 'Links House', obj=RoomObject(0x0A801A, [0x97, 0xB7, 0xFA])), + Pot(202, 23, PotItem.Heart, 'Links House', obj=RoomObject(0x0A801D, [0x97, 0xBF, 0xFA]))], + 0x105: [Pot(30, 20, PotItem.Heart, 'Sahasrahlas Hut', obj=RoomObject(0x03EDF2, [0x3F, 0xA3, 0xFA])), + Pot(28, 21, PotItem.Heart, 'Sahasrahlas Hut', obj=RoomObject(0x03EDEC, [0x3B, 0xAB, 0xFA])), + Pot(32, 21, PotItem.Heart, 'Sahasrahlas Hut', obj=RoomObject(0x03EDEF, [0x43, 0xAB, 0xFA]))], + 0x106: [Pot(0x6c, 0x1b, PotItem.Nothing, 'Brewery', obj=RoomObject(0x03EEB0, [0xDB, 0xDB, 0xFA]))], + 0x107: [Pot(214, 23, PotItem.Bomb, 'Light World Bomb Hut', obj=RoomObject(0x03EF49, [0xAF, 0xBF, 0xFA])), + Pot(222, 23, PotItem.FiveArrows, 'Light World Bomb Hut', obj=RoomObject(0x03EF4C, [0xBF, 0xBF, 0xFA])), + Pot(230, 23, PotItem.Bomb, 'Light World Bomb Hut', obj=RoomObject(0x03EF4F, [0xCF, 0xBF, 0xFA])), + Pot(214, 25, PotItem.OneRupee, 'Light World Bomb Hut', obj=RoomObject(0x03EF52, [0xAF, 0xCF, 0xFA])), + Pot(222, 25, PotItem.Nothing, 'Light World Bomb Hut', obj=RoomObject(0x03EF55, [0xBF, 0xCF, 0xFA])), + Pot(230, 25, PotItem.OneRupee, 'Light World Bomb Hut', obj=RoomObject(0x03EF58, [0xCF, 0xCF, 0xFA])), + Pot(214, 27, PotItem.Bomb, 'Light World Bomb Hut', obj=RoomObject(0x03EF5B, [0xAF, 0xDF, 0xFA])), + Pot(230, 27, PotItem.Bomb, 'Light World Bomb Hut', obj=RoomObject(0x03EF5E, [0xCF, 0xDF, 0xFA]))], + 0x108: [Pot(166, 19, PotItem.Chicken, 'Chicken House', obj=RoomObject(0x03EFA9, [0x4F, 0x9F, 0xFA]))], + 0x10C: [Pot(88, 14, PotItem.Heart, 'Hookshot Fairy', obj=RoomObject(0x03F329, [0xB3, 0x73, 0xFA]))], + # note: these addresses got moved thanks to waterfall fairy edit + 0x114: [Pot(92, 4, PotItem.Heart, 'Dark Desert Hint', obj=RoomObject(0x03F79A, [0xBB, 0x23, 0xFA])), + Pot(96, 4, PotItem.Heart, 'Dark Desert Hint', obj=RoomObject(0x03F79D, [0xC3, 0x23, 0xFA])), + Pot(92, 5, PotItem.Bomb, 'Dark Desert Hint', obj=RoomObject(0x03F7A0, [0xBB, 0x2B, 0xFA])), + Pot(96, 5, PotItem.Bomb, 'Dark Desert Hint', obj=RoomObject(0x03F7A3, [0xC3, 0x2B, 0xFA])), + Pot(92, 10, PotItem.FiveArrows, 'Dark Desert Hint', obj=RoomObject(0x03F7A6, [0xBB, 0x53, 0xFA])), + Pot(96, 10, PotItem.Heart, 'Dark Desert Hint', obj=RoomObject(0x03F7A9, [0xC3, 0x53, 0xFA]))], + 0x117: [Pot(138, 3, PotItem.Heart, 'Spike Cave', obj=RoomObject(0x03FCB2, [0x17, 0x1F, 0xFA])), # 0x38A -> 38A + Pot(142, 3, PotItem.Heart, 'Spike Cave', obj=RoomObject(0x03FCB8, [0x1F, 0x1F, 0xFA])), + Pot(166, 3, PotItem.Heart, 'Spike Cave', obj=RoomObject(0x03FCC1, [0x4F, 0x1F, 0xFA])), + Pot(170, 3, PotItem.Heart, 'Spike Cave', obj=RoomObject(0x03FCC7, [0x57, 0x1F, 0xFA])), + Pot(138, 4, PotItem.Heart, 'Spike Cave', obj=RoomObject(0x03FCB5, [0x17, 0x27, 0xFA])), + Pot(142, 4, PotItem.Heart, 'Spike Cave', obj=RoomObject(0x03FCBB, [0x1F, 0x27, 0xFA])), + Pot(166, 4, PotItem.Heart, 'Spike Cave', obj=RoomObject(0x03FCC4, [0x4F, 0x27, 0xFA])), + Pot(170, 4, PotItem.Heart, 'Spike Cave', obj=RoomObject(0x03FCCA, [0x57, 0x27, 0xFA])), + Pot(0x18, 0x8, PotItem.Nothing, 'Spike Cave', PotFlags.Block)], + 0x119: [Pot(44, 28, PotItem.Heart, 'Blinds Hideout', obj=RoomObject(0x03FDFB, [0x5B, 0xE3, 0xFA])), + Pot(48, 28, PotItem.OneRupee, 'Blinds Hideout', obj=RoomObject(0x03FDFE, [0x63, 0xE3, 0xFA])), + Pot(76, 28, PotItem.Heart, 'Blinds Hideout', obj=RoomObject(0x03FE01, [0x9B, 0xE3, 0xFA])), + Pot(80, 28, PotItem.Heart, 'Blinds Hideout', obj=RoomObject(0x03FE04, [0xA3, 0xE3, 0xFA]))], + 0x11A: [Pot(214, 10, PotItem.Heart, 'Palace of Darkness Hint', obj=RoomObject(0x03F91F, [0xAF, 0x57, 0xFA])), + Pot(218, 10, PotItem.Heart, 'Palace of Darkness Hint', obj=RoomObject(0x03F922, [0xB7, 0x57, 0xFA])), + Pot(226, 10, PotItem.Heart, 'Palace of Darkness Hint', obj=RoomObject(0x03F925, [0xC7, 0x57, 0xFA])), + Pot(230, 10, PotItem.Heart, 'Palace of Darkness Hint', obj=RoomObject(0x03F928, [0xCF, 0x57, 0xFA]))], + 0x11B: [Pot(24, 0x15, PotItem.Nothing, 'Cave 45', PotFlags.LowerRegion, RoomObject(0x03F416, [0x33, 0xAB, 0xFA])), + Pot(24, 0x16, PotItem.Heart, 'Cave 45', PotFlags.LowerRegion, obj=RoomObject(0x03F419, [0x33, 0xB3, 0xFA])), + Pot(32, 0x16, PotItem.Heart, 'Cave 45', PotFlags.LowerRegion, obj=RoomObject(0x03F422, [0x43, 0xB3, 0xFA])), + Pot(40, 0x16, PotItem.Heart, 'Cave 45', PotFlags.LowerRegion, obj=RoomObject(0x03F425, [0x53, 0xB3, 0xFA])), + Pot(24, 0x17, PotItem.Heart, 'Cave 45', PotFlags.LowerRegion, obj=RoomObject(0x03F41C, [0x33, 0xBB, 0xFA])), + Pot(28, 0x18, PotItem.Heart, 'Cave 45', PotFlags.LowerRegion, obj=RoomObject(0x03F41F, [0x3B, 0xC3, 0xFA])), + Pot(92, 22, PotItem.Bomb, 'Graveyard Cave', obj=RoomObject(0x03F3D8, [0xBB, 0xB3, 0xFA])), + Pot(96, 22, PotItem.Heart, 'Graveyard Cave', obj=RoomObject(0x03F3DB, [0xC3, 0xB3, 0xFA])), + Pot(92, 23, PotItem.Bomb, 'Graveyard Cave', obj=RoomObject(0x03F3DE, [0xBB, 0xBB, 0xFA])), + Pot(96, 23, PotItem.Heart, 'Graveyard Cave', obj=RoomObject(0x03F3E1, [0xC3, 0xBB, 0xFA])), + Pot(92, 24, PotItem.Bomb, 'Graveyard Cave', obj=RoomObject(0x03F3E4, [0xBB, 0xC3, 0xFA])), + Pot(96, 24, PotItem.Heart, 'Graveyard Cave', obj=RoomObject(0x03F3E7, [0xC3, 0xC3, 0xFA])), + Pot(92, 25, PotItem.Bomb, 'Graveyard Cave', obj=RoomObject(0x03F3EA, [0xBB, 0xCB, 0xFA])), + Pot(96, 25, PotItem.Heart, 'Graveyard Cave', obj=RoomObject(0x03F3ED, [0xC3, 0xCB, 0xFA]))], + 0x11D: [Pot(60, 6, PotItem.FiveRupees, 'Blinds Hideout (Top)', obj=RoomObject(0x03FE69, [0x7B, 0x33, 0xFA])), + Pot(64, 6, PotItem.FiveRupees, 'Blinds Hideout (Top)', obj=RoomObject(0x03FE6C, [0x83, 0x33, 0xFA])), + Pot(60, 7, PotItem.FiveRupees, 'Blinds Hideout (Top)', obj=RoomObject(0x03FE6F, [0x7B, 0x3B, 0xFA])), + Pot(64, 7, PotItem.FiveRupees, 'Blinds Hideout (Top)', obj=RoomObject(0x03FE72, [0x83, 0x3B, 0xFA])), + Pot(60, 8, PotItem.FiveRupees, 'Blinds Hideout (Top)', obj=RoomObject(0x03FE75, [0x7B, 0x43, 0xFA])), + Pot(64, 8, PotItem.FiveRupees, 'Blinds Hideout (Top)', obj=RoomObject(0x03FE78, [0x83, 0x43, 0xFA]))], + 0x11F: [Pot(174, 28, PotItem.Heart, 'Lumberjack House', obj=RoomObject(0x03FF8A, [0x5F, 0xE7, 0xFA])), + Pot(178, 28, PotItem.Heart, 'Lumberjack House', obj=RoomObject(0x03FF8D, [0x67, 0xE7, 0xFA]))], + 0x124: [Pot(20, 20, PotItem.FiveRupees, '50 Rupee Cave', obj=RoomObject(0x0AB5C2, [0x2B, 0xA3, 0xFA])), + Pot(40, 20, PotItem.FiveRupees, '50 Rupee Cave', obj=RoomObject(0x0AB5E3, [0x53, 0xA3, 0xFA])), + Pot(20, 21, PotItem.FiveRupees, '50 Rupee Cave', obj=RoomObject(0x0AB5C5, [0x2B, 0xAB, 0xFA])), + Pot(40, 21, PotItem.FiveRupees, '50 Rupee Cave', obj=RoomObject(0x0AB5E0, [0x53, 0xAB, 0xFA])), + Pot(20, 22, PotItem.FiveRupees, '50 Rupee Cave', obj=RoomObject(0x0AB5C8, [0x2B, 0xB3, 0xFA])), + Pot(40, 22, PotItem.FiveRupees, '50 Rupee Cave', obj=RoomObject(0x0AB5DD, [0x53, 0xB3, 0xFA])), + Pot(24, 24, PotItem.FiveRupees, '50 Rupee Cave', obj=RoomObject(0x0AB5CE, [0x33, 0xC3, 0xFA])), + Pot(28, 24, PotItem.FiveRupees, '50 Rupee Cave', obj=RoomObject(0x0AB5D1, [0x3B, 0xC3, 0xFA])), + Pot(32, 24, PotItem.FiveRupees, '50 Rupee Cave', obj=RoomObject(0x0AB5D4, [0x43, 0xC3, 0xFA])), + Pot(36, 24, PotItem.FiveRupees, '50 Rupee Cave', obj=RoomObject(0x0AB5D7, [0x4B, 0xC3, 0xFA]))], + 0x125: [Pot(24, 25, PotItem.FiveRupees, '20 Rupee Cave', obj=RoomObject(0x0AB618, [0x33, 0xCB, 0xFA])), + Pot(28, 25, PotItem.FiveRupees, '20 Rupee Cave', obj=RoomObject(0x0AB61B, [0x3B, 0xCB, 0xFA])), + Pot(32, 25, PotItem.FiveRupees, '20 Rupee Cave', obj=RoomObject(0x0AB61E, [0x43, 0xCB, 0xFA])), + Pot(36, 25, PotItem.FiveRupees, '20 Rupee Cave', obj=RoomObject(0x0AB621, [0x4B, 0xCB, 0xFA])), + Pot(88, 22, PotItem.Heart, 'Dark Lake Hylia Ledge Spike Cave', obj=RoomObject(0x0AB627, [0xB3, 0xB3, 0xFA])), + Pot(100, 22, PotItem.Heart, 'Dark Lake Hylia Ledge Spike Cave', obj=RoomObject(0x0AB62A, [0xCB, 0xB3, 0xFA])), + Pot(88, 28, PotItem.Heart, 'Dark Lake Hylia Ledge Spike Cave', obj=RoomObject(0x0AB633, [0xB3, 0xE3, 0xFA])), + Pot(100, 28, PotItem.Heart, 'Dark Lake Hylia Ledge Spike Cave', obj=RoomObject(0x0AB636, [0xCB, 0xE3, 0xFA]))], + 0x127: [Pot(24, 25, PotItem.Nothing, 'Dark World Hammer Peg Cave', obj=RoomObject(0x2A801A, [0x33, 0xCB, 0xFA])), + Pot(28, 25, PotItem.Nothing, 'Dark World Hammer Peg Cave', obj=RoomObject(0x2A801D, [0x3B, 0xCB, 0xFA])), + Pot(32, 25, PotItem.Nothing, 'Dark World Hammer Peg Cave', obj=RoomObject(0x2A8020, [0x43, 0xCB, 0xFA])), + Pot(36, 25, PotItem.Nothing, 'Dark World Hammer Peg Cave', obj=RoomObject(0x2A8023, [0x4B, 0xCB, 0xFA]))], } def shuffle_pots(world, player): import RaceRandom as random - new_pot_contents = {} + new_pot_contents = PotSecretTable() - for supertile in vanilla_pots: - old_pots = vanilla_pots[supertile] + for super_tile in vanilla_pots: + old_pots = vanilla_pots[super_tile] new_pots = [Pot(pot.x, pot.y, PotItem.Nothing, pot.room, pot.flags) for pot in old_pots] # sort in the order Hole, Switch, Key, Other, Nothing sort_order = {PotItem.Hole: 4, PotItem.Switch: 3, PotItem.Key: 2, PotItem.Nothing: 0} @@ -295,7 +879,7 @@ def shuffle_pots(world, player): elif old_pot.item == PotItem.Switch: available_pots = (pot for pot in new_pots if (pot.room == old_pot.room or pot.room in movable_switch_rooms[old_pot.room]) and not (pot.flags & PotFlags.NoSwitch)) elif old_pot.item == PotItem.Key: - if world.doorShuffle[player] == 'vanilla' and not world.retro[player] and not world.keydropshuffle[player] and world.logic[player] != 'nologic': + if world.doorShuffle[player] == 'vanilla' and not world.retro[player] and world.pottery[player] == 'none' and world.logic[player] != 'nologic': available_pots = (pot for pot in new_pots if pot.room not in invalid_key_rooms) else: available_pots = new_pots @@ -309,12 +893,14 @@ def shuffle_pots(world, player): if world.retro[player] and new_pot.item == PotItem.FiveArrows: new_pot.item = PotItem.FiveRupees - if new_pot.item == PotItem.Key and new_pot.room != old_pot.room: - # Move pot key to new room - key = next(location for location in world.get_region(old_pot.room, player).locations if location.name in key_drop_data) - world.get_region(old_pot.room, player).locations.remove(key) - world.get_region(new_pot.room, player).locations.append(key) - key.parent_region = world.get_region(new_pot.room, player) + if new_pot.item == PotItem.Key: + key = next(location for location in old_pot.get_region(world, player).locations if location.name in key_drop_data) + key.pot = new_pot + if new_pot.room != old_pot.room: + # Move pot key to new room + world.get_region(old_pot.room, player).locations.remove(key) + world.get_region(new_pot.room, player).locations.append(key) + key.parent_region = world.get_region(new_pot.room, player) elif new_pot.item == PotItem.Switch and (new_pot.flags & PotFlags.SwitchLogicChange): if new_pot.room == 'PoD Basement Ledge': basement = world.get_region(old_pot.room, player) @@ -328,8 +914,163 @@ def shuffle_pots(world, player): # Rule is created based on barrier world.get_door('Thieves Attic ES', player).barrier(CrystalBarrier.Orange) else: - raise Exception("Switch locattion in room %s requires logic change" % new_pot.room) + raise Exception("Switch location in room %s requires logic change" % new_pot.room) - new_pot_contents[supertile] = new_pots + new_pot_contents.room_map[super_tile] = new_pots world.pot_contents[player] = new_pot_contents + + +def shuffle_pot_switches(world, player): + import RaceRandom as random + + for super_tile in vanilla_pots: + new_pots = world.pot_contents[player].room_map[super_tile] + # sort in the order Hole, Switch, Key, Other, Nothing + sort_order = {PotItem.Hole: 4, PotItem.Switch: 3, PotItem.Key: 2, PotItem.Nothing: 0} + old_pots = sorted(new_pots, key=lambda pot: sort_order.get(pot.item, 1), reverse=True) + + for old_pot in old_pots: + if old_pot.item != PotItem.Switch: + break + else: + available_pots = [pot for pot in new_pots if (pot.room == old_pot.room or pot.room in movable_switch_rooms[old_pot.room]) and not (pot.flags & PotFlags.NoSwitch)] + + new_pot = random.choice(available_pots) + new_pot.item, old_pot.item = old_pot.item, new_pot.item + if world.retro[player] and new_pot.item == PotItem.FiveArrows: + new_pot.item = PotItem.FiveRupees + + if new_pot.item == PotItem.Switch and (new_pot.flags & PotFlags.SwitchLogicChange): + if new_pot.room == 'PoD Basement Ledge': + basement = world.get_region(old_pot.room, player) + ledge = world.get_region(new_pot.room, player) + ledge.locations.append(basement.locations.pop()) + elif new_pot.room == 'Swamp Push Statue': + from Rules import set_rule + set_rule(world.get_entrance('Swamp Push Statue NE', player), lambda state: state.has('Cane of Somaria', player)) + world.get_door('Swamp Push Statue NW', player).blocked = True + elif new_pot.room == 'Thieves Attic Hint': + # Rule is created based on barrier + world.get_door('Thieves Attic ES', player).barrier(CrystalBarrier.Orange) + else: + raise Exception("Switch location in room %s requires logic change" % new_pot.room) + for location in world.get_locations(): + if location.player == player and location.type == LocationType.Pot and location.pot.item == PotItem.Switch: + location.real = False + if location in world.dynamic_locations: + world.dynamic_locations.remove(location) + location.parent_region.locations.remove(location) + world.clear_location_cache() + + +def choose_pots(world, player): + pot_pool = set() + if world.pottery[player] == 'reduced': + dungeon_list = [] + for super_tile, pot_list in vanilla_pots.items(): + for pot in pot_list: + if pot.get_region(world, player).type == RegionType.Cave: + pot_pool.add(pot) + else: + dungeon_list.append(pot) + k = len(dungeon_list) // 4 + pot_pool.update(random.choices(dungeon_list, k=k)) + elif world.pottery[player] == 'clustered': + dungeon_map = defaultdict(list) + dungeon_count = 0 + for super_tile, pot_list in vanilla_pots.items(): + for pot in pot_list: + if pot.get_region(world, player).type == RegionType.Cave: + pot_pool.add(pot) + else: + dungeon_map[pot.room].append(pot) + dungeon_count += 1 + k = dungeon_count // 2 + options = sorted(list(dungeon_map.keys())) + chosen_amt = 0 + while chosen_amt < k: + chosen_section = random.choice(options) + pot_pool.update(dungeon_map[chosen_section]) + chosen_amt += len(dungeon_map[chosen_section]) + options.remove(chosen_section) + return pot_pool + + +key_drop_data = { + 'Hyrule Castle - Map Guard Key Drop': ['Drop', (0x09E20C, 0x72, 0), 'dropped in Hyrule Castle', 'Small Key (Escape)'], + 'Hyrule Castle - Boomerang Guard Key Drop': ['Drop', (0x09E204, 0x71, 1), 'dropped in Hyrule Castle', 'Small Key (Escape)'], + 'Hyrule Castle - Key Rat Key Drop': ['Drop', (0x09DB80, 0x21, 0), 'dropped in Hyrule Castle', 'Small Key (Escape)'], + 'Hyrule Castle - Big Key Drop': ['Drop', (0x09E327, 0x80, 2), 'dropped in Hyrule Castle', 'Big Key (Escape)'], + 'Eastern Palace - Dark Square Pot Key': ['Pot', 0xBA, 'in a pot in Eastern Palace', 'Small Key (Eastern Palace)'], + 'Eastern Palace - Dark Eyegore Key Drop': ['Drop', (0x09E4F8, 0x99, 3), 'dropped in Eastern Palace', 'Small Key (Eastern Palace)'], + 'Desert Palace - Desert Tiles 1 Pot Key': ['Pot', 0x63, 'in a pot in Desert Palace', 'Small Key (Desert Palace)'], + 'Desert Palace - Beamos Hall Pot Key': ['Pot', 0x53, 'in a pot in Desert Palace', 'Small Key (Desert Palace)'], + 'Desert Palace - Desert Tiles 2 Pot Key': ['Pot', 0x43, 'in a pot in Desert Palace', 'Small Key (Desert Palace)'], + 'Castle Tower - Dark Archer Key Drop': ['Drop', (0x09E7C9, 0xC0, 3), 'dropped in Castle Tower', 'Small Key (Agahnims Tower)'], + 'Castle Tower - Circle of Pots Key Drop': ['Drop', (0x09E688, 0xB0, 10), 'dropped in Castle Tower', 'Small Key (Agahnims Tower)'], + 'Swamp Palace - Pot Row Pot Key': ['Pot', 0x38, 'in a pot in Swamp Palace', 'Small Key (Swamp Palace)'], + 'Swamp Palace - Trench 1 Pot Key': ['Pot', 0x37, 'in a pot in Swamp Palace', 'Small Key (Swamp Palace)'], + 'Swamp Palace - Hookshot Pot Key': ['Pot', 0x36, 'in a pot in Swamp Palace', 'Small Key (Swamp Palace)'], + 'Swamp Palace - Trench 2 Pot Key': ['Pot', 0x35, 'in a pot in Swamp Palace', 'Small Key (Swamp Palace)'], + 'Swamp Palace - Waterway Pot Key': ['Pot', 0x16, 'in a pot in Swamp Palace', 'Small Key (Swamp Palace)'], + 'Skull Woods - West Lobby Pot Key': ['Pot', 0x56, 'in a pot in Skull Woods', 'Small Key (Skull Woods)'], + 'Skull Woods - Spike Corner Key Drop': ['Drop', (0x09DD74, 0x39, 1), 'dropped near Mothula', 'Small Key (Skull Woods)'], + "Thieves' Town - Hallway Pot Key": ['Pot', 0xBC, "in a pot in Thieves Town", 'Small Key (Thieves Town)'], + "Thieves' Town - Spike Switch Pot Key": ['Pot', 0xAB, "in a pot in Thieves Town", 'Small Key (Thieves Town)'], + 'Ice Palace - Jelly Key Drop': ['Drop', (0x09DA21, 0xE, 3), 'dropped in Ice Palace', 'Small Key (Ice Palace)'], + 'Ice Palace - Conveyor Key Drop': ['Drop', (0x09DE08, 0x3E, 8), 'dropped in Ice Palace', 'Small Key (Ice Palace)'], + 'Ice Palace - Hammer Block Key Drop': ['Pot', 0x3F, 'under a block in Ice Palace', 'Small Key (Ice Palace)'], + 'Ice Palace - Many Pots Pot Key': ['Pot', 0x9F, 'int a pot in Ice Palace', 'Small Key (Ice Palace)'], + 'Misery Mire - Spikes Pot Key': ['Pot', 0xB3, 'in a pot in Misery Mire', 'Small Key (Misery Mire)'], + 'Misery Mire - Fishbone Pot Key': ['Pot', 0xA1, 'in a pot in forgotten Mire', 'Small Key (Misery Mire)'], + 'Misery Mire - Conveyor Crystal Key Drop': ['Drop', (0x09E7FB, 0xC1, 9), 'dropped in Misery Mire', 'Small Key (Misery Mire)'], + 'Turtle Rock - Pokey 1 Key Drop': ['Drop', (0x09E70D, 0xB6, 5), 'dropped in Turtle Rock', 'Small Key (Turtle Rock)'], + 'Turtle Rock - Pokey 2 Key Drop': ['Drop', (0x09DA5D, 0x13, 6), 'dropped in Turtle Rock', 'Small Key (Turtle Rock)'], + 'Ganons Tower - Conveyor Cross Pot Key': ['Pot', 0x8B, "in a pot in Ganon's Tower", 'Small Key (Ganons Tower)'], + 'Ganons Tower - Double Switch Pot Key': ['Pot', 0x9B, "in a pot in Ganon's Tower", 'Small Key (Ganons Tower)'], + 'Ganons Tower - Conveyor Star Pits Pot Key': ['Pot', 0x7B, "in a pot in Ganon's Tower", 'Small Key (Ganons Tower)'], + 'Ganons Tower - Mini Helmasaur Key Drop': ['Drop', (0x09DDC4, 0x3D, 2), "dropped atop Ganon's Tower", 'Small Key (Ganons Tower)'] +} + + +class PotSecretTable(object): + def __init__(self): + self.room_map = defaultdict(list) + self.multiworld_count = 0 + + def write_pot_data_to_rom(self, rom, colorize): + pointer_address = 0x140000 # pots currently in bank 28 + pointer_offset = 0x128 * 2 + empty_address = (pointer_address + pointer_offset) + empty_pointer = pc_to_snes(empty_address) & 0xFFFF + data_pointer = pointer_address + pointer_offset + 2 + for room in range(0, 0x128): + if room in self.room_map: + list_idx = 0 + collection_rate_mask = 0x00 + data_address = pc_to_snes(data_pointer) & 0xFFFF + rom.write_bytes(pointer_address + room * 2, int16_as_bytes(data_address)) + for pot in self.room_map[room]: + rom.write_bytes(data_pointer + list_idx * 3, pot.pot_data()) + if pot.location is not None and not pot.location.forced_item: + collection_rate_mask |= 1 << (15 - list_idx) + if colorize and pot.obj_ref: + pot.obj_ref.change_type(Shuffled_Pot) + pot.obj_ref.write_to_rom(rom) + list_idx += 1 + rom.write_bytes(data_pointer + list_idx * 3, [0xFF, 0xFF]) + rom.write_bytes(snes_to_pc(0x28AA60) + room * 2, int16_as_bytes(collection_rate_mask)) + data_pointer += 3 * list_idx + 2 + else: + rom.write_bytes(pointer_address + room * 2, int16_as_bytes(empty_pointer)) + rom.write_bytes(empty_address, [0xFF, 0xFF]) + + def size(self): + size = 0x128 * 2 + for room in range(0, 0x128): + if room in self.room_map: + pot_list = [p for p in self.room_map[room]] + if pot_list: + size += len(pot_list) * 3 + 2 + return size diff --git a/README.md b/README.md index 6cb67700..c7ad7b42 100644 --- a/README.md +++ b/README.md @@ -4,15 +4,39 @@ This is a door randomizer for _The Legend of Zelda: A Link to the Past_ for the based on the Entrance Randomizer found at [KevinCathcart's Github Project.](https://github.com/KevinCathcart/ALttPEntranceRandomizer) See https://alttpr.com/ for more details on the normal randomizer. -# Known Issues +# Documentation +1. [Setup and Installation](#setup-and-installation) +2. [Commonly Missed Things](#commonly-missed-things) (** **Read This If New** **) +3. [Settings](#settings) + 1. [Dungeon Randomization](#dungeon-settings) + 1. [Dungeon Door Shuffle](#door-shuffle---doorshuffle) + 2. [Intensity Level](#intensity---intensity-number) + 3. [Key Drop Shuffle (Legacy)](#key-drop-shuffle-legacy---keydropshuffle) + 4. [Pottery](#pottery) + 5. [Shuffle Enemy Key Drops](#shuffle-enemy-key-drops---dropshuffle) + 6. [Experimental Features](#experimental-features) + 7. [Crossed Dungeon Specific Settings](#crossed-dungeon-specific-settings) + 2. [Item Randomization Changes](#item-randomization) + 1. [New "Items"](#new-items) + 2. [Shopsanity](#shopsanity) + 3. [Logic Level](#logic-level) + 4. [Goal](#goal) + 5. [Item Sorting](#item-sorting) + 6. [Forbidden Boss Items](#forbidden-boss-items) + 3. [Entrance Randomization](#entrance-randomization) + 1. [Shuffle Links House](#shuffle-links-house) + 2. [Overworld Map](#overworld-map) + 4. [Enemizer](#enemizer) + 5. [Game Options](#game-options) + 6. [Generation Setup & Miscellaneous](#generation-setup--miscellaneous) -[List of Known Issues and Their Status](https://docs.google.com/document/d/1Bk-m-QRvH5iF60ndptKYgyaV7P93D3TiG8xmdxp_bdQ/edit?usp=sharing) +## Setup and Installation -# Feedback and Bug Reports +### Feedback and Bug Reports Please just DM me on discord for now. I (Aerinon) can be found at the [ALTTP Randomizer discord](https://discordapp.com/invite/alttprandomizer). -# Installation +### Installation Click on @@ -22,7 +46,7 @@ Go down to Assets and find a build for your system (Windows, Mac, or Linux) Download and unzip. Find the DungeonRandomizer.exe or equivalent -# Installation from source +### Installation from source See these instructions. @@ -36,7 +60,8 @@ To use the CLI, run ```DungeonRandomizer.py```. Alternatively, run ```Gui.py``` for a simple graphical user interface. -# Commonly Missed Things and Differences from other Randomizers +# Commonly Missed Things +#### and Differences from other Randomizers Most of these apply only when the door shuffle is not vanilla. @@ -76,69 +101,111 @@ You start with a “Mirror Scroll”, a dumbed-down mirror that only works in du # Settings +## Dungeon Settings + Only extra settings are found here. All entrance randomizer settings are supported. See their [readme](https://github.com/KevinCathcart/ALttPEntranceRandomizer/blob/master/README.md) -## Door Shuffle (--doorShuffle) +### Door Shuffle (--doorShuffle) -### Basic +* Vanilla - Doors are not shuffled +* Basic - Doors are shuffled only within a single dungeon. +* Crossed - Doors are shuffled between dungeons as well. -Doors are shuffled only within a single dungeon. +### Intensity (--intensity number) -### Crossed +* Level 1 - Normal door and spiral staircases are shuffled +* Level 2 - Same as Level 1 plus open edges and both types of straight staircases are shuffled. +* Level 3 - Same as Level 2 plus Dungeon Lobbies are shuffled -Doors are shuffled between dungeons as well. - -### Vanilla - -Doors are not shuffled. - -## Intensity (--intensity number) - -#### Level 1 -Normal door and spiral staircases are shuffled -#### Level 2 -Same as Level 1 plus open edges and both types of straight staircases are shuffled. -#### Level 3 -Same as Level 2 plus Dungeon Lobbies are shuffled - -## KeyDropShuffle (--keydropshuffle) +### Key Drop Shuffle (Legacy) (--keydropshuffle) Adds 33 new locations to the randomization pool. The 32 small keys found under pots and dropped by enemies and the Big -Key drop location are added to the pool. The keys normally found there are added to the item pool. Retro adds -32 generic keys to the pool instead. +Key drop location are added to the pool. The keys normally found there are added to the item pool. Retro adds +32 generic keys to the pool instead. This has been can be controlled more granularly with the [Pottery](#pottery) and +[Shuffle Enemy Key Drops](#shuffle-enemy-key-drops---dropshuffle) -## Crossed Dungeon Specific Settings +### Pottery -### Mixed Travel (--mixed_travel value) +New pottery option that control which pots (and large blocks) are in the locations pool: -Due to Hammerjump, Hovering in PoD Arena, and the Mire Big Key Chest bomb jump two sections of a supertile that are +* None: No pots are in the pool, like normal randomizer +* Key Pots: The pots that have keys are in the pool. This is about half of the old keydropshuffle option +* Cave Pots: The pots that are not found in dungeons are in the pool. (Includes the large block in Spike Cave). Does + not include key pots. +* Cave + Keys Pots: Both non-dungeon pots and pots that used to have keys are in the pool. +* Reduced Dungeon Pots: Same as Cave+Keys but also roughly a quarter of dungeon pots are added to the location pool picked at random. This is a dynamic mode so pots in the pool will be colored. Pots out of the pool will have vanilla contents. +* Clustered Dungeon Pots: Like reduced but pots are grouped by logical sets and roughly 50% of pots are chosen from those groups. This is a dynamic mode like the above. +* Excludes Empty Pots: All pots that had some sort of objects under them are chosen to be in the location pool. This excludes most large blocks and some pots out of dungeons. +* Dungeon Pots: The pots that are in dungeons are in the pool. (Includes serveral large blocks) +* Lottery: All pots and large blocks are in the pool. + +By default, switches remain in their vanilla location (unless you turn on the legacy option below) + +CLI `--pottery