From 0faeeeb45fb7d76e095af0d5ea61985980fd9b95 Mon Sep 17 00:00:00 2001 From: aerinon Date: Fri, 19 Apr 2024 14:29:26 -0600 Subject: [PATCH 01/41] fix(enemizer): bonk fairies and standard tutorial guard fixed --- source/enemizer/Enemizer.py | 2 +- source/enemizer/OwEnemyList.py | 1 + source/rom/DataTables.py | 6 ++++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/source/enemizer/Enemizer.py b/source/enemizer/Enemizer.py index 65009fdd..021b7f03 100644 --- a/source/enemizer/Enemizer.py +++ b/source/enemizer/Enemizer.py @@ -260,7 +260,7 @@ def get_randomize_able_sprites_ow(area_id, data_tables): req = data_tables.sprite_requirements[key] if isinstance(req, dict): continue - if not req.static and req.can_randomize: + if not req.static and req.can_randomize and not sprite.static: sprite_table[idx] = sprite return sprite_table diff --git a/source/enemizer/OwEnemyList.py b/source/enemizer/OwEnemyList.py index ffbf79d4..3ca0f717 100644 --- a/source/enemizer/OwEnemyList.py +++ b/source/enemizer/OwEnemyList.py @@ -27,6 +27,7 @@ def init_vanilla_sprites_ow(): create_sprite(0x21b, EnemySprite.GreenKnifeGuard, 0x20, 0x1A, '', 0x09CB51) create_sprite(0x21b, EnemySprite.TutorialGuard, 0x2D, 0x25, '', 0x09CB54) create_sprite(0x21b, EnemySprite.TutorialGuard, 0x20, 0x29, '', 0x09CB57) + create_sprite(0x21b, EnemySprite.TutorialGuard, 0x3C, 0x2A, '', 0x09CB5B) create_sprite(0x21d, EnemySprite.Apple, 0x0B, 0x06, '', 0x09CB5B) create_sprite(0x22b, EnemySprite.TutorialGuard, 0x09, 0x1E, '', 0x09CB5F) create_sprite(0x22b, EnemySprite.TutorialGuard, 0x0B, 0x1E, '', 0x09CB62) diff --git a/source/rom/DataTables.py b/source/rom/DataTables.py index 9713ef80..72a1156d 100644 --- a/source/rom/DataTables.py +++ b/source/rom/DataTables.py @@ -134,9 +134,11 @@ class DataTables: # calculate how big this table is going to be? # bytes = sum(1+len(x)*3 for x in self.ow_enemy_table.values() if len(x) > 0)+1 # ending_byte = 0x09CB3B + bytes - max_per_state = {0: 0x40, 1: 0x90, 2: 0x8D} # dropped max on state 2 to steal space for a couple extra sprites (Murahdahla) + max_per_state = {0: 0x40, 1: 0x90, 2: 0x8B} # dropped max on state 2 to steal space for a couple extra sprites (Murahdahla, extra tutorial guard) + pointer_address = snes_to_pc(0x09C881) - data_pointer = snes_to_pc(0x09CB3B) # was originally 0x09CB41 - stealing space for a couple extra sprites (Murahdahla) + # currently borrowed 10 bytes, used 9 (2xMurah + TutorialGuard) + data_pointer = snes_to_pc(0x09CB38) # was originally 0x09CB41 - stealing space for a couple extra sprites (Murahdahla, extra tutorial guard) empty_pointer = pc_to_snes(data_pointer) & 0xFFFF rom.write_byte(data_pointer, 0xff) cached_dark_world = {} From 8461503c850a5a6552e0566c87967b1e84260bbc Mon Sep 17 00:00:00 2001 From: aerinon Date: Mon, 22 Apr 2024 08:33:59 -0600 Subject: [PATCH 02/41] fix(customizer): Fixed an issue with customizing prize packs --- Rom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rom.py b/Rom.py index d5106162..95327080 100644 --- a/Rom.py +++ b/Rom.py @@ -938,7 +938,7 @@ def patch_rom(world, rom, player, team, is_mystery=False): for player, drop_config in drops.items(): for pack_num in range(1, 8): if f'Pack {pack_num}' in drop_config: - for prize, idx in enumerate(drop_config[f'Pack {pack_num}']): + for idx, prize in enumerate(drop_config[f'Pack {pack_num}']): chosen = random.choice(uniform_prizes) if prize == 'Random' else possible_prizes[prize] prizes[(pack_num-1)*8 + idx] = chosen for tree_pull_tier in range(1, 4): From 20e60c8a1dcb2a2d62361a6056db9d4464ab6e9c Mon Sep 17 00:00:00 2001 From: aerinon Date: Mon, 22 Apr 2024 08:35:17 -0600 Subject: [PATCH 03/41] feat(spoiler): introducing custom spoiler levels --- BaseClasses.py | 191 ++++++++++-------- CLI.py | 5 +- Main.py | 16 +- Mystery.py | 4 +- Utils.py | 4 +- resources/app/cli/args.json | 16 +- resources/app/cli/lang/en.json | 10 +- resources/app/gui/lang/en.json | 7 +- .../gui/randomize/generation/checkboxes.json | 11 +- source/classes/constants.py | 2 +- source/gui/bottom.py | 1 - 11 files changed, 156 insertions(+), 111 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 64b33852..d2ddee2d 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -21,7 +21,7 @@ from source.dungeon.RoomObject import RoomObject class World(object): def __init__(self, players, shuffle, doorShuffle, logic, mode, swords, difficulty, difficulty_adjustments, - timer, progressive, goal, algorithm, accessibility, shuffle_ganon, custom, customitemarray, hints): + timer, progressive, goal, algorithm, accessibility, shuffle_ganon, custom, customitemarray, hints, spoiler_mode): self.players = players self.teams = 1 self.shuffle = shuffle.copy() @@ -68,6 +68,7 @@ class World(object): self.hints = hints.copy() self.dynamic_regions = [] self.dynamic_locations = [] + self.spoiler_mode = spoiler_mode self.spoiler = Spoiler(self) self.lamps_needed_for_dark_rooms = 1 self.doors = [] @@ -89,6 +90,7 @@ class World(object): self.data_tables = {} self.damage_table = {} + for player in range(1, players + 1): def set_player_attr(attr, val): self.__dict__.setdefault(attr, {})[player] = val @@ -2497,6 +2499,17 @@ class Spoiler(object): self.metadata = {} self.shops = [] self.bosses = OrderedDict() + if world.spoiler_mode == 'settings': + self.settings = {'settings'} + elif world.spoiler_mode == 'semi': + self.settings = {'settings', 'entrances', 'requirements', 'prizes'} + elif world.spoiler_mode == 'full': + self.settings = {'settings', 'entrances', 'requirements', 'prizes', 'shops', 'doors', 'items', 'bosses', 'misc'} + elif world.spoiler_mode == 'debug': + self.settings = {'settings', 'entrances', 'requirements', 'prizes', 'shops', 'doors', 'items', 'misc', 'bosses', 'debug'} + else: + self.settings = {} + def set_entrance(self, entrance, exit, direction, player): if self.world.players == 1: @@ -2609,27 +2622,29 @@ 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] + def include_item(item): + return ('items' in self.settings and not item.crystal) or ('prizes' in self.settings and item.crystal) self.locations = OrderedDict() listed_locations = set() - lw_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.type == RegionType.LightWorld] + lw_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.type == RegionType.LightWorld and include_item(loc.item)] self.locations['Light World'] = OrderedDict([(location.gen_name(), str(location.item) if location.item is not None else 'Nothing') for location in lw_locations]) listed_locations.update(lw_locations) - dw_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.type == RegionType.DarkWorld] + dw_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.type == RegionType.DarkWorld and include_item(loc.item)] self.locations['Dark World'] = OrderedDict([(location.gen_name(), str(location.item) if location.item is not None else 'Nothing') for location in dw_locations]) listed_locations.update(dw_locations) - cave_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.type == RegionType.Cave and not loc.skip] + cave_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.type == RegionType.Cave and not loc.skip and include_item(loc.item)] self.locations['Caves'] = OrderedDict([(location.gen_name(), str(location.item) if location.item is not None else 'Nothing') for location in cave_locations]) 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 and not loc.skip] + 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 and include_item(loc.item)] 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 and not loc.skip] + other_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and not loc.skip and include_item(loc.item)] 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) @@ -2721,65 +2736,68 @@ class Spoiler(object): outfile.write('ALttP Dungeon Randomizer Version %s - Seed: %s\n\n' % (self.metadata['version'], self.world.seed)) if self.metadata['user_notes']: outfile.write('User Notes: %s\n' % self.metadata['user_notes']) - outfile.write('Filling Algorithm: %s\n' % self.world.algorithm) + if 'settings' in self.settings: + 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))) - outfile.write(f'Settings Code: {self.metadata["code"][player]}\n') + if 'settings' in self.settings: + 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('Swords: %s\n' % self.metadata['weapons'][player]) - outfile.write('Goal: %s\n' % self.metadata['goal'][player]) - if self.metadata['goal'][player] in ['triforcehunt', 'trinity', 'ganonhunt']: - 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"Flute Mode: {self.metadata['flute_mode'][player]}\n") - outfile.write(f"Bow Mode: {self.metadata['bow_mode'][player]}\n") - 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]) - if self.metadata['shuffle'][player] != 'vanilla': - outfile.write(f"Link's House Shuffled: {yn(self.metadata['shufflelinks'][player])}\n") - outfile.write(f"Back of Tavern Shuffled: {yn(self.metadata['shuffletavern'][player])}\n") - outfile.write(f"GT/Ganon Shuffled: {yn(self.metadata['shuffleganon'])}\n") - outfile.write(f"Overworld Map: {self.metadata['overworld_map'][player]}\n") - outfile.write(f"Take Any Caves: {self.metadata['take_any'][player]}\n") - if self.metadata['goal'][player] != 'trinity': - outfile.write('Pyramid hole pre-opened: %s\n' % (self.metadata['open_pyramid'][player])) - outfile.write('Door Shuffle: %s\n' % self.metadata['door_shuffle'][player]) - if self.metadata['door_shuffle'][player] != 'vanilla': - outfile.write(f"Intensity: {self.metadata['intensity'][player]}\n") - outfile.write(f"Door Type Mode: {self.metadata['door_type_mode'][player]}\n") - outfile.write(f"Trap Door Mode: {self.metadata['trap_door_mode'][player]}\n") - outfile.write(f"Key Logic Algorithm: {self.metadata['key_logic'][player]}\n") - outfile.write(f"Decouple Doors: {yn(self.metadata['decoupledoors'][player])}\n") - outfile.write(f"Spiral Stairs can self-loop: {yn(self.metadata['door_self_loops'][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: {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(f"Small Key shuffle: {self.metadata['keyshuffle'][player]}\n") - outfile.write('Big Key shuffle: %s\n' % ('Yes' if self.metadata['bigkeyshuffle'][player] else 'No')) - outfile.write('Boss shuffle: %s\n' % self.metadata['boss_shuffle'][player]) - 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]) - if self.metadata['enemy_shuffle'][player] != 'none': - outfile.write(f"Enemy logic: {self.metadata['any_enemy_logic'][player]}\n") - outfile.write(f"Hints: {yn(self.metadata['hints'][player])}\n") - outfile.write('Race: %s\n' % ('Yes' if self.world.settings.world_rep['meta']['race'] else 'No')) + if 'settings' in self.settings: + outfile.write('Mode: %s\n' % self.metadata['mode'][player]) + outfile.write('Swords: %s\n' % self.metadata['weapons'][player]) + outfile.write('Goal: %s\n' % self.metadata['goal'][player]) + if self.metadata['goal'][player] in ['triforcehunt', 'trinity', 'ganonhunt']: + 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"Flute Mode: {self.metadata['flute_mode'][player]}\n") + outfile.write(f"Bow Mode: {self.metadata['bow_mode'][player]}\n") + 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]) + if self.metadata['shuffle'][player] != 'vanilla': + outfile.write(f"Link's House Shuffled: {yn(self.metadata['shufflelinks'][player])}\n") + outfile.write(f"Back of Tavern Shuffled: {yn(self.metadata['shuffletavern'][player])}\n") + outfile.write(f"GT/Ganon Shuffled: {yn(self.metadata['shuffleganon'])}\n") + outfile.write(f"Overworld Map: {self.metadata['overworld_map'][player]}\n") + outfile.write(f"Take Any Caves: {self.metadata['take_any'][player]}\n") + if self.metadata['goal'][player] != 'trinity': + outfile.write('Pyramid hole pre-opened: %s\n' % (self.metadata['open_pyramid'][player])) + outfile.write('Door Shuffle: %s\n' % self.metadata['door_shuffle'][player]) + if self.metadata['door_shuffle'][player] != 'vanilla': + outfile.write(f"Intensity: {self.metadata['intensity'][player]}\n") + outfile.write(f"Door Type Mode: {self.metadata['door_type_mode'][player]}\n") + outfile.write(f"Trap Door Mode: {self.metadata['trap_door_mode'][player]}\n") + outfile.write(f"Key Logic Algorithm: {self.metadata['key_logic'][player]}\n") + outfile.write(f"Decouple Doors: {yn(self.metadata['decoupledoors'][player])}\n") + outfile.write(f"Spiral Stairs can self-loop: {yn(self.metadata['door_self_loops'][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: {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(f"Small Key shuffle: {self.metadata['keyshuffle'][player]}\n") + outfile.write('Big Key shuffle: %s\n' % ('Yes' if self.metadata['bigkeyshuffle'][player] else 'No')) + outfile.write('Boss shuffle: %s\n' % self.metadata['boss_shuffle'][player]) + 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]) + if self.metadata['enemy_shuffle'][player] != 'none': + outfile.write(f"Enemy logic: {self.metadata['any_enemy_logic'][player]}\n") + outfile.write(f"Hints: {yn(self.metadata['hints'][player])}\n") + outfile.write('Race: %s\n' % ('Yes' if self.world.settings.world_rep['meta']['race'] else 'No')) if self.startinventory: outfile.write('Starting Inventory:'.ljust(line_width)) @@ -2817,26 +2835,29 @@ class Spoiler(object): 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 'requirements' in self.settings: + 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]))) - if self.entrances: + if 'misc' in self.settings: + outfile.write('\n\nBottle Refills:\n\n') + for fairy, bottle in self.bottles.items(): + outfile.write(f'{fairy}: {bottle}\n') + + if self.entrances and 'entrances' in self.settings: # 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: + if self.doors and 'doors' in self.settings: outfile.write('\n\nDoors:\n\n') outfile.write('\n'.join( ['%s%s %s %s %s' % ('Player {0}: '.format(entry['player']) if self.world.players > 1 else '', @@ -2845,33 +2866,37 @@ class Spoiler(object): self.world.fish.translate("meta", "doors", entry['exit']), '({0})'.format(entry['dname']) if self.world.doorShuffle[entry['player']] != 'basic' else '') for entry in self.doors.values()])) - if self.lobbies: + if self.lobbies and 'doors' in self.settings: outfile.write('\n\nDungeon Lobbies:\n\n') outfile.write('\n'.join( [f"{'Player {0}: '.format(entry['player']) if self.world.players > 1 else ''}{entry['lobby_name']}: {entry['door_name']}" for entry in self.lobbies.values()])) - if self.doorTypes: + if self.doorTypes and 'doors' in self.settings: # doorNames: For some reason these come in combined, somehow need to split on the thing to translate # 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()])) + # 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\nLocations:\n\n') - outfile.write('\n'.join(['%s: %s' % (self.world.fish.translate("meta", "locations", location), self.world.fish.translate("meta", "items", item)) for grouping in self.locations.values() for (location, item) in grouping.items()])) + outfile.write('\n'.join(['%s: %s' % (self.world.fish.translate("meta", "locations", location), self.world.fish.translate("meta", "items", item)) + for grouping in self.locations.values() for (location, item) in grouping.items()])) # 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\nShops:\n\n') - outfile.write('\n'.join("{} [{}]\n {}".format(self.world.fish.translate("meta", "locations", shop['location']), shop['type'], "\n ".join(self.world.fish.translate("meta", "items", item) for item in [shop.get('item_0', None), shop.get('item_1', None), shop.get('item_2', None)] if item)) for shop in self.shops)) + if 'shops' in self.settings: + outfile.write('\n\nShops:\n\n') + outfile.write('\n'.join("{} [{}]\n {}".format(self.world.fish.translate("meta", "locations", shop['location']), shop['type'], "\n ".join(self.world.fish.translate("meta", "items", item) for item in [shop.get('item_0', None), shop.get('item_1', None), shop.get('item_2', None)] if item)) for shop in self.shops)) - for player in range(1, self.world.players + 1): - if self.world.boss_shuffle[player] != 'none': - bossmap = self.bosses[str(player)] if self.world.players > 1 else self.bosses - 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']])) + if 'bosses' in self.settings: + for player in range(1, self.world.players + 1): + if self.world.boss_shuffle[player] != 'none': + bossmap = self.bosses[str(player)] if self.world.players > 1 else self.bosses + 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 extras(self, filename): # todo: conditional on enemy shuffle mode diff --git a/CLI.py b/CLI.py index 1066b845..1c1e0a30 100644 --- a/CLI.py +++ b/CLI.py @@ -224,7 +224,7 @@ def parse_settings(): 'mixed_travel': 'prevent', 'standardize_palettes': 'standardize', 'aga_randomness': True, - + "triforce_pool": 0, "triforce_goal": 0, "triforce_pool_min": 0, @@ -253,10 +253,9 @@ def parse_settings(): 'msu_resume': False, 'collection_rate': False, - # Spoiler defaults to TRUE + 'spoiler': 'full', # Playthrough defaults to TRUE # ROM defaults to TRUE - "create_spoiler": True, "calc_playthrough": True, "create_rom": True, "bps": False, diff --git a/Main.py b/Main.py index 8e49e3f0..4bdeae9b 100644 --- a/Main.py +++ b/Main.py @@ -85,7 +85,7 @@ def main(args, seed=None, fish=None): raise RuntimeError(BabelFish().translate("cli","cli","hybridglitches.door.shuffle")) world = World(args.multi, args.shuffle, args.door_shuffle, args.logic, args.mode, args.swords, args.difficulty, args.item_functionality, args.timer, args.progressive, args.goal, args.algorithm, - args.accessibility, args.shuffleganon, args.custom, args.customitemarray, args.hints) + args.accessibility, args.shuffleganon, args.custom, args.customitemarray, args.hints, args.spoiler) world.customizer = customized if customized else None logger = logging.getLogger('') if seed is None: @@ -202,7 +202,7 @@ def main(args, seed=None, fish=None): if item: world.push_precollected(item) - if args.create_spoiler and not args.jsonout: + if world.spoiler_mode != 'none' 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: @@ -406,12 +406,12 @@ def main(args, seed=None, fish=None): 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: + elif world.spoiler_mode != 'none' and not args.jsonout: world.spoiler.hashes_to_file(output_path(f'{outfilebase}_Spoiler.txt')) - if args.create_spoiler and not args.jsonout: + if world.spoiler_mode != 'none' and not args.jsonout: logger.info(world.fish.translate("cli", "cli", "patching.spoiler")) world.spoiler.to_file(output_path(f'{outfilebase}_Spoiler.txt')) - if args.loglevel == 'debug': + if 'debug' in world.spoiler.settings: world.spoiler.extras(output_path(f'{outfilebase}_Spoiler.txt')) if not args.skip_playthrough: @@ -420,7 +420,7 @@ def main(args, seed=None, fish=None): if args.jsonout: print(json.dumps({**jsonout, 'spoiler': world.spoiler.to_json()})) - elif args.create_spoiler: + elif world.spoiler_mode != 'none': logger.info(world.fish.translate("cli","cli","patching.spoiler")) if args.jsonout: with open(output_path('%s_Spoiler.json' % outfilebase), 'w') as outfile: @@ -435,7 +435,7 @@ def main(args, seed=None, fish=None): logger.info("") logger.info(world.fish.translate("cli","cli","made.rom") % (YES if (args.create_rom) else NO)) logger.info(world.fish.translate("cli","cli","made.playthrough") % (YES if (args.calc_playthrough) else NO)) - logger.info(world.fish.translate("cli","cli","made.spoiler") % (YES if (not args.jsonout and args.create_spoiler) else NO)) + logger.info(world.fish.translate("cli","cli","made.spoiler") % (YES if (not args.jsonout and world.spoiler_mode != 'none') else NO)) logger.info(world.fish.translate("cli","cli","seed") + ": %s", world.seed) logger.info(world.fish.translate("cli","cli","total.time"), time.perf_counter() - start) @@ -449,7 +449,7 @@ def copy_world(world): # ToDo: Not good yet ret = World(world.players, world.shuffle, world.doorShuffle, world.logic, world.mode, world.swords, world.difficulty, world.difficulty_adjustments, world.timer, world.progressive, world.goal, world.algorithm, - world.accessibility, world.shuffle_ganon, world.custom, world.customitemarray, world.hints) + world.accessibility, world.shuffle_ganon, world.custom, world.customitemarray, world.hints, world.spoiler_mode) ret.teams = world.teams ret.player_names = copy.deepcopy(world.player_names) ret.remote_items = world.remote_items.copy() diff --git a/Mystery.py b/Mystery.py index 170dd8bc..9d81b344 100644 --- a/Mystery.py +++ b/Mystery.py @@ -28,7 +28,7 @@ def main(): parser.add_argument('--multi', default=1, type=lambda value: min(max(int(value), 1), 255)) 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('--spoiler', default='none', choices=['none', 'settings', 'semi', 'full', 'debug']) parser.add_argument('--suppress_rom', action='store_true') parser.add_argument('--suppress_meta', action='store_true') parser.add_argument('--bps', action='store_true') @@ -63,7 +63,7 @@ def main(): erargs = parse_cli(['--multi', str(args.multi)]) erargs.seed = seed erargs.names = args.names - erargs.create_spoiler = args.create_spoiler + erargs.spoiler = args.spoiler erargs.suppress_rom = args.suppress_rom erargs.suppress_meta = args.suppress_meta erargs.bps = args.bps diff --git a/Utils.py b/Utils.py index 50ab1b84..6d52d5f0 100644 --- a/Utils.py +++ b/Utils.py @@ -342,11 +342,11 @@ def update_deprecated_args(args): # Don't do: Yes # Do: No if "suppress_spoiler" in argVars: - args.create_spoiler = not args.suppress_spoiler in truthy + args.spoiler = 'none' # Don't do: No # Do: Yes if "create_spoiler" in argVars: - args.suppress_spoiler = not args.create_spoiler in truthy + args.spoiler = 'full' # ROM defaults to TRUE # Don't do: Yes diff --git a/resources/app/cli/args.json b/resources/app/cli/args.json index 6aedcad8..c2d31606 100644 --- a/resources/app/cli/args.json +++ b/resources/app/cli/args.json @@ -4,14 +4,14 @@ "action": "store_true", "type": "bool" }, - "create_spoiler": { - "action": "store_false", - "dest": "suppress_spoiler", - "type": "bool", - "help": "suppress" - }, - "suppress_spoiler": { - "action": "store_true" + "spoiler": { + "choices": [ + "none", + "settings", + "semi", + "full", + "debug" + ] }, "mystery": { "action": "store_true", diff --git a/resources/app/cli/lang/en.json b/resources/app/cli/lang/en.json index 1f0defb9..b57508fe 100644 --- a/resources/app/cli/lang/en.json +++ b/resources/app/cli/lang/en.json @@ -60,7 +60,15 @@ }, "help": { "lang": [ "App Language, if available, defaults to English" ], - "create_spoiler": [ "Output a Spoiler File" ], + "spoiler": [ + "Spoiler File Options. (default: %(default)s)", + "None: No Spoiler", + "Meta: Meta information only about game. Intended for mystery settings", + "Settings: Only settings information", + "Semi: ", + "Full: Full spoiler generated", + "Debug: Includes debug information" + ], "bps": [ "Output BPS patches instead of ROMs"], "logic": [ "Select Enforcement of Item Requirements. (default: %(default)s)", diff --git a/resources/app/gui/lang/en.json b/resources/app/gui/lang/en.json index c14dee25..aa8be106 100644 --- a/resources/app/gui/lang/en.json +++ b/resources/app/gui/lang/en.json @@ -206,7 +206,12 @@ "randomizer.generation.bps": "Create BPS Patches", - "randomizer.generation.createspoiler": "Create Spoiler Log", + "randomizer.generation.spoiler": "Create Spoiler Log", + "randomizer.generation.spoiler.none": "None", + "randomizer.generation.spoiler.settings": "Settings Only", + "randomizer.generation.spoiler.semi": "Semi (Entrances and Prizes)", + "randomizer.generation.spoiler.full": "Full", + "randomizer.generation.spoiler.debug": "Debug", "randomizer.generation.createrom": "Create Patched ROM", "randomizer.generation.calcplaythrough": "Calculate Playthrough", "randomizer.generation.print_custom_yaml": "Print Customizer File", diff --git a/resources/app/gui/randomize/generation/checkboxes.json b/resources/app/gui/randomize/generation/checkboxes.json index 6e377027..3eeab78b 100644 --- a/resources/app/gui/randomize/generation/checkboxes.json +++ b/resources/app/gui/randomize/generation/checkboxes.json @@ -2,7 +2,16 @@ "checkboxes": { "createrom": { "type": "checkbox" }, "bps": { "type": "checkbox" }, - "createspoiler": { "type": "checkbox" }, + "spoiler": { + "type": "selectbox", + "options": [ + "none", + "settings", + "semi", + "full", + "debug" + ] + }, "calcplaythrough": { "type": "checkbox" }, "print_custom_yaml": { "type": "checkbox" } } diff --git a/source/classes/constants.py b/source/classes/constants.py index 15797e03..0950211e 100644 --- a/source/classes/constants.py +++ b/source/classes/constants.py @@ -134,7 +134,7 @@ SETTINGSTOPROCESS = { }, "generation": { "bps": "bps", - "createspoiler": "create_spoiler", + "spoiler": "spoiler", "createrom": "create_rom", "calcplaythrough": "calc_playthrough", "print_custom_yaml": "print_custom_yaml", diff --git a/source/gui/bottom.py b/source/gui/bottom.py index b9fdd9b2..5abb3de6 100644 --- a/source/gui/bottom.py +++ b/source/gui/bottom.py @@ -126,7 +126,6 @@ def bottom_frame(self, parent, args=None): made[k] = m.group(1) + m.group(2) + ' ' + m.group(4) successMsg += (made["rom"] % (YES if (guiargs.create_rom) else NO)) + "\n" successMsg += (made["playthrough"] % (YES if (guiargs.calc_playthrough) else NO)) + "\n" - successMsg += (made["spoiler"] % (YES if (not guiargs.jsonout and guiargs.create_spoiler) else NO)) + "\n" successMsg += (made["enemizer"] % (YES if needEnemizer else NO)) + "\n" # FIXME: English successMsg += ("Seed%s: %s" % ('s' if len(seeds) > 1 else "", ','.join(str(x) for x in seeds))) From fcc464ef2351e8737eeb620e82729e3b480a69c4 Mon Sep 17 00:00:00 2001 From: aerinon Date: Mon, 22 Apr 2024 11:49:03 -0600 Subject: [PATCH 04/41] fix(enemizer): messed up gfx on hyrule castle rain state --- source/enemizer/SpriteSheets.py | 6 +++++- source/rom/DataTables.py | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/source/enemizer/SpriteSheets.py b/source/enemizer/SpriteSheets.py index 2a935048..4496fb97 100644 --- a/source/enemizer/SpriteSheets.py +++ b/source/enemizer/SpriteSheets.py @@ -687,7 +687,11 @@ def setup_required_overworld_groups(sheets): sheets[4].add_sprite_to_sheet([None, None, None, None], {0xF, 0x9F}) # Waterfall of wishing (pre/post-Aga) sheets[3].add_sprite_to_sheet([None, None, None, 14], {0x14, 0xA4}) # Graveyard (pre/post-Aga) sheets[1].add_sprite_to_sheet([None, None, 76, 0x3F], {0x1B, 0xAB}) # Hyrule Castle (pre/post-Aga) - sheets[2].add_sprite_to_sheet([None, None, None, 0x3F], {}) # Hyrule Castle - rain state + ## group 0 set to 0x48 for tutortial guards + ## group 1 & 2 set for green knife guards (and probably normal green guard) + ## group 3 set for lightning gate + sheets[2].add_sprite_to_sheet([0x48, 0x49, 0x13, 0x3F], {}) # Hyrule Castle - rain state + # Smithy/Race/Kak (pre/post-Aga) sheets[6].add_sprite_to_sheet([0x4F, 0x49, 0x4A, 0x50], {0x18, 0x22, 0x28, 0xA8, 0xB2, 0xB8}) sheets[8].add_sprite_to_sheet([None, None, 18, None], {0x30, 0xC0}) # Desert (pre/post-Aga) diff --git a/source/rom/DataTables.py b/source/rom/DataTables.py index 72a1156d..752ec73e 100644 --- a/source/rom/DataTables.py +++ b/source/rom/DataTables.py @@ -94,6 +94,7 @@ class DataTables: # _00FA81 is LW normal # _00FAC1 is LW post-aga # _00FB01 is DW + # _00FA41 is rain state self.write_ow_sprite_data_to_rom(rom) for sprite, stats in self.enemy_stats.items(): # write health to rom From 292d49faed1cac09c0e483fd7a20f9da80249f13 Mon Sep 17 00:00:00 2001 From: aerinon Date: Tue, 23 Apr 2024 11:02:41 -0600 Subject: [PATCH 05/41] fix(enemizer): enenmy bans --- Main.py | 2 +- RELEASENOTES.md | 9 +++++++++ source/enemizer/enemy_deny.yaml | 3 +++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Main.py b/Main.py index 4bdeae9b..3a74ff8f 100644 --- a/Main.py +++ b/Main.py @@ -39,7 +39,7 @@ from source.enemizer.DamageTables import DamageTable from source.enemizer.Enemizer import randomize_enemies from source.rom.DataTables import init_data_tables -version_number = '1.4.1.10' +version_number = '1.4.1.11' version_branch = '-u' __version__ = f'{version_number}{version_branch}' diff --git a/RELEASENOTES.md b/RELEASENOTES.md index d37a2a0a..24398bb3 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -141,6 +141,15 @@ These are now independent of retro mode and have three options: None, Random, an # Patch Notes +* 1.4.1.11u + * New Feature: Several spoiler levels added: None, Settings-only, Semi, Full, Debug + * Semi includes only entrances, prizes, and medallions (potential new spoiler mode being worked on, definition may change) + * Standard: Re-added tutorial guard near large rock + * Enemizer + * Fixed the overwriting of bonk fairies + * Fixed broken graphics on hyrule castle + * Enemy bans + * Customizer: Fixed bug with customizing prize packs * 1.4.1.10u * Vanilla key logic: Fix for vanilla layout Misery Mire which allows more complex key logic. Locations blocked by crystal switch access are only locked by 2 keys thanks to that being the minimum in Mire to reach one of two crystal switches. * Autotracking: Fix for chest turn counter with chest containing multiworld items (Thanks Hiimcody) diff --git a/source/enemizer/enemy_deny.yaml b/source/enemizer/enemy_deny.yaml index d6dc6564..46b1176c 100644 --- a/source/enemizer/enemy_deny.yaml +++ b/source/enemizer/enemy_deny.yaml @@ -325,6 +325,7 @@ UwGeneralDeny: - [ 0x00c2, 5, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on pots - [ 0x00c5, 6, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Turtle Rock - Catwalk - Mini Helmasaur" - [ 0x00c5, 7, [ "Statue" ] ] #"Turtle Rock - Catwalk - Laser Eye (Left) 4" + - [0x00c6, 5, ["Bumper"]] - [ 0x00cb, 0, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on pots - [ 0x00cb, 3, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Thieves' Town - Grand Room NW - Zol 1" - [ 0x00cb, 5, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Thieves' Town - Grand Room NW - Zol 2" @@ -419,10 +420,12 @@ OwGeneralDeny: - [0x03, 10, ["Gibo"]] # OldMan eating Gibo - [0x05, 11, ["Bumper", "AntiFairyCircle"]] # Blocks path to portal - [0x1e, 3, ["Beamos", "Bumper", "BigSpike", "AntiFairyCircle"]] # forbid a beamos here + - [0x35, 8, ["RollerVerticalUp", "RollerVerticalDown"]] # blocks the dock - [0x40, 0, ["Beamos", "Bumper", "BigSpike", "AntiFairyCircle", "Thief"]] - [0x40, 7, ["Beamos", "Bumper", "BigSpike", "AntiFairyCircle", "Thief"]] - [0x40, 13, ["Beamos", "Bumper", "BigSpike", "AntiFairyCircle", "Thief"]] - [0x40, 14, ["Beamos", "Bumper", "BigSpike", "AntiFairyCircle", "Thief"]] + - [0x40, 16, ["RollerVerticalUp", "RollerVerticalDown"]] # Ropa near back hole is really large as a roller - [0x55, 6, ["BigSpike"]] - [0x57, 5, ["RollerVerticalUp", "RollerVerticalDown"]] - [0x5e, 0, ["Gibo"]] # kiki eating Gibo From 89645bb4b5f10a987e63667ec2e0eca9da805a9c Mon Sep 17 00:00:00 2001 From: aerinon Date: Tue, 23 Apr 2024 13:41:03 -0600 Subject: [PATCH 06/41] feat(entrances): lite/lean support enemy shuffle caves --- RELEASENOTES.md | 1 + source/overworld/EntranceShuffle2.py | 98 +++++++++++++++++++--------- 2 files changed, 69 insertions(+), 30 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 24398bb3..1f4720b4 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -144,6 +144,7 @@ These are now independent of retro mode and have three options: None, Random, an * 1.4.1.11u * New Feature: Several spoiler levels added: None, Settings-only, Semi, Full, Debug * Semi includes only entrances, prizes, and medallions (potential new spoiler mode being worked on, definition may change) + * Entrance: Lite/Lean support enemy drop shuffle * Standard: Re-added tutorial guard near large rock * Enemizer * Fixed the overwriting of bonk fairies diff --git a/source/overworld/EntranceShuffle2.py b/source/overworld/EntranceShuffle2.py index c15115b6..059aefbe 100644 --- a/source/overworld/EntranceShuffle2.py +++ b/source/overworld/EntranceShuffle2.py @@ -916,12 +916,18 @@ def do_limited_shuffle_exclude_drops(pool_def, avail, lw=True): def do_vanilla_connect(pool_def, avail): - if pool_def['condition'] == 'shopsanity': + if 'shopsanity' in pool_def['condition']: if avail.world.shopsanity[avail.player]: return - elif pool_def['condition'] == 'pottery': # this condition involves whether caves with pots are shuffled or not + if 'pottery' in pool_def['condition']: # this condition involves whether caves with pots are shuffled or not if avail.world.pottery[avail.player] not in ['none', 'keys', 'dungeon']: return + if 'dropshuffle' in pool_def['condition']: + if avail.world.dropshuffle[avail.player] not in ['none', 'keys']: + return + if 'enemy_drop' in pool_def['condition']: + if avail.world.dropshuffle[avail.player] not in ['none', 'keys'] or avail.world.enemy_shuffle[avail.player] != 'none': + return defaults = {**default_connections, **(inverted_default_connections if avail.inverted else open_default_connections)} if avail.inverted: if 'Dark Death Mountain Fairy' in pool_def['entrances']: @@ -1440,28 +1446,44 @@ modes = { 'special': 'vanilla', 'condition': '', 'entrances': ['Dark Death Mountain Fairy', 'Mire Fairy', 'Archery Game', - 'Fortune Teller (Dark)', 'Dark Sanctuary Hint', 'Bonk Fairy (Dark)', + 'Fortune Teller (Dark)', 'Dark Sanctuary Hint', 'Dark Lake Hylia Ledge Hint', 'Dark Lake Hylia Ledge Fairy', 'Dark Lake Hylia Fairy', - 'Dark Lake Hylia Shop', 'East Dark World Hint', 'Kakariko Gamble Game', 'Good Bee Cave', - 'Long Fairy Cave', 'Bush Covered House', 'Fortune Teller (Light)', 'Lost Woods Gamble', - 'Desert Fairy', 'Light Hype Fairy', 'Lake Hylia Fortune Teller', 'Lake Hylia Fairy', - 'Bonk Fairy (Light)'], + 'East Dark World Hint', 'Kakariko Gamble Game', + 'Bush Covered House', 'Fortune Teller (Light)', 'Lost Woods Gamble', + 'Desert Fairy', 'Light Hype Fairy', 'Lake Hylia Fortune Teller', 'Lake Hylia Fairy'], }, 'fixed_shops': { 'special': 'vanilla', 'condition': 'shopsanity', 'entrances': ['Dark Death Mountain Shop', 'Dark Potion Shop', 'Dark Lumberjack Shop', - 'Dark World Shop', 'Red Shield Shop', 'Kakariko Shop', 'Capacity Upgrade', - 'Lake Hylia Shop'], + 'Dark World Shop', 'Red Shield Shop', 'Kakariko Shop', 'Lake Hylia Shop', 'Dark Lake Hylia Shop'], }, 'fixed_pottery': { 'special': 'vanilla', 'condition': 'pottery', 'entrances': ['Lumberjack House', 'Snitch Lady (West)', 'Snitch Lady (East)', 'Tavern (Front)', - 'Light World Bomb Hut', '20 Rupee Cave', '50 Rupee Cave', 'Hookshot Fairy', - 'Palace of Darkness Hint', 'Dark Lake Hylia Ledge Spike Cave', - 'Mire Hint'] - + '20 Rupee Cave', '50 Rupee Cave', 'Palace of Darkness Hint', + 'Dark Lake Hylia Ledge Spike Cave', 'Mire Hint'] + }, + 'fixed_enemy_drops_fairies': { + 'special': 'vanilla', + 'condition': 'enemy_drop', + 'entrances': ['Bonk Fairy (Dark)', 'Good Bee Cave', 'Long Fairy Cave', 'Bonk Fairy (Light)'] + }, + 'fixed_pots_n_bones_fairies': { + 'special': 'vanilla', + 'condition': ['pottery', 'enemy_drop'], + 'entrances': ['Hookshot Fairy'] + }, + 'fixed_pots_n_bones': { + 'special': 'vanilla', + 'condition': ['pottery', 'dropshuffle'], + 'entrances': ['Light World Bomb Hut'] + }, + 'fixed_shop_n_bones': { + 'special': 'vanilla', + 'condition': ['shopsanity', 'enemy_drop'], + 'entrances': ['Capacity Upgrade'] }, 'item_caves': { # shuffles shops/pottery if they weren't fixed in the last steps 'entrances': ['Mimic Cave', 'Spike Cave', 'Mire Shed', 'Hammer Peg Cave', 'Chest Game', @@ -1469,14 +1491,14 @@ modes = { 'Ice Rod Cave', 'Dam', 'Bonk Rock Cave', 'Library', 'Potion Shop', 'Mini Moldorm Cave', 'Checkerboard Cave', 'Graveyard Cave', 'Cave 45', 'Sick Kids House', 'Blacksmiths Hut', 'Sahasrahlas Hut', 'Aginahs Cave', 'Chicken House', 'Kings Grave', 'Blinds Hideout', - 'Waterfall of Wishing', 'Dark Death Mountain Shop', + 'Waterfall of Wishing', 'Dark Death Mountain Shop', 'Dark Lake Hylia Shop', 'Dark Potion Shop', 'Dark Lumberjack Shop', 'Dark World Shop', 'Red Shield Shop', 'Kakariko Shop', 'Capacity Upgrade', 'Lake Hylia Shop', 'Lumberjack House', 'Snitch Lady (West)', 'Snitch Lady (East)', 'Tavern (Front)', 'Light World Bomb Hut', '20 Rupee Cave', '50 Rupee Cave', 'Hookshot Fairy', 'Palace of Darkness Hint', 'Dark Lake Hylia Ledge Spike Cave', - 'Mire Hint', - 'Links House', 'Tavern North'] + 'Bonk Fairy (Dark)', 'Good Bee Cave', 'Long Fairy Cave', 'Bonk Fairy (Light)', + 'Mire Hint', 'Links House', 'Tavern North'] }, 'old_man_cave': { # have to do old man cave first so lw dungeon don't use up everything 'special': 'old_man_cave_east', @@ -1516,28 +1538,44 @@ modes = { 'special': 'vanilla', 'condition': '', 'entrances': ['Dark Death Mountain Fairy', 'Mire Fairy', 'Archery Game', - 'Fortune Teller (Dark)', 'Dark Sanctuary Hint', 'Bonk Fairy (Dark)', + 'Fortune Teller (Dark)', 'Dark Sanctuary Hint', 'Dark Lake Hylia Ledge Hint', 'Dark Lake Hylia Ledge Fairy', 'Dark Lake Hylia Fairy', - 'Dark Lake Hylia Shop', 'East Dark World Hint', 'Kakariko Gamble Game', 'Good Bee Cave', - 'Long Fairy Cave', 'Bush Covered House', 'Fortune Teller (Light)', 'Lost Woods Gamble', - 'Desert Fairy', 'Light Hype Fairy', 'Lake Hylia Fortune Teller', 'Lake Hylia Fairy', - 'Bonk Fairy (Light)'], + 'East Dark World Hint', 'Kakariko Gamble Game', + 'Bush Covered House', 'Fortune Teller (Light)', 'Lost Woods Gamble', + 'Desert Fairy', 'Light Hype Fairy', 'Lake Hylia Fortune Teller', 'Lake Hylia Fairy'], }, 'fixed_shops': { 'special': 'vanilla', 'condition': 'shopsanity', 'entrances': ['Dark Death Mountain Shop', 'Dark Potion Shop', 'Dark Lumberjack Shop', - 'Dark World Shop', 'Red Shield Shop', 'Kakariko Shop', 'Capacity Upgrade', - 'Lake Hylia Shop'], + 'Dark World Shop', 'Red Shield Shop', 'Kakariko Shop', 'Lake Hylia Shop', 'Dark Lake Hylia Shop'], }, 'fixed_pottery': { 'special': 'vanilla', 'condition': 'pottery', 'entrances': ['Lumberjack House', 'Snitch Lady (West)', 'Snitch Lady (East)', 'Tavern (Front)', - 'Light World Bomb Hut', '20 Rupee Cave', '50 Rupee Cave', 'Hookshot Fairy', - 'Palace of Darkness Hint', 'Dark Lake Hylia Ledge Spike Cave', - 'Mire Hint'] - + '20 Rupee Cave', '50 Rupee Cave', 'Palace of Darkness Hint', + 'Dark Lake Hylia Ledge Spike Cave', 'Mire Hint'] + }, + 'fixed_enemy_drops_fairies': { + 'special': 'vanilla', + 'condition': 'enemy_drop', + 'entrances': ['Bonk Fairy (Dark)', 'Good Bee Cave', 'Long Fairy Cave', 'Bonk Fairy (Light)'] + }, + 'fixed_pots_n_bones_fairies': { + 'special': 'vanilla', + 'condition': ['pottery', 'enemy_drop'], + 'entrances': ['Hookshot Fairy'] + }, + 'fixed_pots_n_bones': { + 'special': 'vanilla', + 'condition': ['pottery', 'dropshuffle'], + 'entrances': ['Light World Bomb Hut'] + }, + 'fixed_shop_n_bones': { + 'special': 'vanilla', + 'condition': ['shopsanity', 'enemy_drop'], + 'entrances': ['Capacity Upgrade'] }, 'item_caves': { # shuffles shops/pottery if they weren't fixed in the last steps 'entrances': ['Mimic Cave', 'Spike Cave', 'Mire Shed', 'Hammer Peg Cave', 'Chest Game', @@ -1545,14 +1583,14 @@ modes = { 'Ice Rod Cave', 'Dam', 'Bonk Rock Cave', 'Library', 'Potion Shop', 'Mini Moldorm Cave', 'Checkerboard Cave', 'Graveyard Cave', 'Cave 45', 'Sick Kids House', 'Blacksmiths Hut', 'Sahasrahlas Hut', 'Aginahs Cave', 'Chicken House', 'Kings Grave', 'Blinds Hideout', - 'Waterfall of Wishing', 'Dark Death Mountain Shop', + 'Waterfall of Wishing', 'Dark Death Mountain Shop', 'Dark Lake Hylia Shop', 'Dark Potion Shop', 'Dark Lumberjack Shop', 'Dark World Shop', 'Red Shield Shop', 'Kakariko Shop', 'Capacity Upgrade', 'Lake Hylia Shop', 'Lumberjack House', 'Snitch Lady (West)', 'Snitch Lady (East)', 'Tavern (Front)', 'Light World Bomb Hut', '20 Rupee Cave', '50 Rupee Cave', 'Hookshot Fairy', 'Palace of Darkness Hint', 'Dark Lake Hylia Ledge Spike Cave', - 'Mire Hint', - 'Links House', 'Tavern North'] # inverted links house gets substituted + 'Bonk Fairy (Dark)', 'Good Bee Cave', 'Long Fairy Cave', 'Bonk Fairy (Light)', + 'Mire Hint', 'Links House', 'Tavern North'] # inverted links house gets substituted } } }, From 757f1568380fe0072f1b5bbad862ceea844ed95b Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 30 Apr 2024 15:37:39 -0500 Subject: [PATCH 07/41] Fixed TF Cutscene issue --- Rom.py | 2 +- data/base2current.bps | Bin 132318 -> 132319 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/Rom.py b/Rom.py index b09cb03d..9129d519 100644 --- a/Rom.py +++ b/Rom.py @@ -43,7 +43,7 @@ from source.enemizer.Enemizer import write_enemy_shuffle_settings JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '3147681c6f9f4f84bcf6d9ba1e06a85b' +RANDOMIZERBASEHASH = 'ddc8fd3a8c7265b8748c0df4382f8f0e' class JsonRom(object): diff --git a/data/base2current.bps b/data/base2current.bps index 37faa3b9cdaef64d772e5a0dbc9ec792202b9bd9..f62d66ee9e21ea0082797493c7b19f7d6ccae920 100644 GIT binary patch delta 3439 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 PY;DBv2c}<&e_j56?|D4L From 509b5ce64149460a36db4c5ce79c8d440197b623 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Fri, 3 May 2024 17:19:38 -0500 Subject: [PATCH 08/41] Fixed missing Ganon crystal rule for Trinity --- Rules.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Rules.py b/Rules.py index 27075c6f..1fbf8870 100644 --- a/Rules.py +++ b/Rules.py @@ -70,6 +70,8 @@ def set_rules(world, player): # require aga2 to beat ganon add_rule(world.get_location('Ganon', player), lambda state: state.has('Beat Agahnim 2', player)) elif world.goal[player] in ['triforcehunt', 'trinity']: + if world.goal[player] == 'trinity': + add_rule(world.get_location('Ganon', player), lambda state: state.has_crystals(world.crystals_needed_for_ganon[player], player)) for location in world.get_region('Hyrule Castle Courtyard', player).locations: if location.name == 'Murahdahla': add_rule(location, lambda state: state.item_count('Triforce Piece', player) + state.item_count('Power Star', player) >= int(state.world.treasure_hunt_count[player])) From 3f08cc2a7db01ce382d0bd1c3788bd85281b1b90 Mon Sep 17 00:00:00 2001 From: aerinon Date: Mon, 6 May 2024 15:57:21 -0600 Subject: [PATCH 09/41] fix: minor traversal error, was hiding other potential issues --- BaseClasses.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BaseClasses.py b/BaseClasses.py index d2ddee2d..958a6d18 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -635,7 +635,7 @@ class CollectionState(object): bc[conn] = door_crystal_state queue.append((conn, door_crystal_state)) elif door is None: - # note: no door in dungeon indicates what exactly? (always traversable)? + bc[conn] = new_crystal_state queue.append((conn, new_crystal_state)) else: new_crystal_state = CrystalBarrier.Orange From 26c2aa9fc5b973f82aa430ba590349012cf1be2b Mon Sep 17 00:00:00 2001 From: aerinon Date: Mon, 6 May 2024 15:58:24 -0600 Subject: [PATCH 10/41] change: shuffle back of tavern now default --- CLI.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CLI.py b/CLI.py index 1c1e0a30..18a1d72b 100644 --- a/CLI.py +++ b/CLI.py @@ -190,7 +190,7 @@ def parse_settings(): "shuffleganon": True, "shuffle": "vanilla", "shufflelinks": False, - "shuffletavern": False, + "shuffletavern": True, "overworld_map": "default", 'take_any': 'none', "pseudoboots": False, From bb5236d5d4506bbd744c5690f79b01664ad7b9dd Mon Sep 17 00:00:00 2001 From: aerinon Date: Mon, 6 May 2024 16:19:14 -0600 Subject: [PATCH 11/41] feat: promote new entrance shuffle algorithm out of experimental --- BaseClasses.py | 2 +- EntranceShuffle.py | 2786 ------------------------ ItemList.py | 2 +- Main.py | 8 +- Plando.py | 4 +- RELEASENOTES.md | 4 + Rom.py | 6 +- source/dungeon/EnemyList.py | 2 +- source/overworld/EntranceData.py | 159 ++ source/overworld/EntranceShuffle2.py | 161 +- test/dungeons/TestDungeon.py | 2 +- test/inverted/TestInverted.py | 5 +- test/inverted/TestInvertedBombRules.py | 3 +- test/inverted_owg/TestInvertedOWG.py | 4 +- test/owg/TestVanillaOWG.py | 4 +- test/stats/EntranceShuffleStats.py | 4 +- test/vanilla/TestVanilla.py | 4 +- 17 files changed, 190 insertions(+), 2970 deletions(-) delete mode 100644 EntranceShuffle.py create mode 100644 source/overworld/EntranceData.py diff --git a/BaseClasses.py b/BaseClasses.py index 958a6d18..a78a7bc2 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -11,11 +11,11 @@ except ImportError: from enum import IntFlag as FastEnum from source.classes.BabelFish import BabelFish -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 +from source.overworld.EntranceData import door_addresses, indirect_connections class World(object): diff --git a/EntranceShuffle.py b/EntranceShuffle.py deleted file mode 100644 index 84caaaf1..00000000 --- a/EntranceShuffle.py +++ /dev/null @@ -1,2786 +0,0 @@ -import RaceRandom as random - -# ToDo: With shuffle_ganon option, prevent gtower from linking to an exit only location through a 2 entrance cave. -from collections import defaultdict - - -def link_entrances(world, player): - invFlag = world.mode[player] == 'inverted' - - connect_exit(world, 'Chris Houlihan Room Exit', 'Links House', player) # should always match link's house, except for plandos - - Dungeon_Exits = Dungeon_Exits_Base.copy() - Cave_Exits = Cave_Exits_Base.copy() - Old_Man_House = Old_Man_House_Base.copy() - Cave_Three_Exits = Cave_Three_Exits_Base.copy() - - unbias_some_entrances(Dungeon_Exits, Cave_Exits, Old_Man_House, Cave_Three_Exits) - - # setup mandatory connections - for exitname, regionname in mandatory_connections: - connect_simple(world, exitname, regionname, player) - - connect_custom(world, player) - - # if we do not shuffle, set default connections - if world.shuffle[player] == 'vanilla': - for exitname, regionname in default_connections: - connect_simple(world, exitname, regionname, player) - for exitname, regionname in default_dungeon_connections: - connect_simple(world, exitname, regionname, player) - if not invFlag: - for exitname, regionname in open_default_connections: - connect_simple(world, exitname, regionname, player) - for exitname, regionname in open_default_dungeon_connections: - connect_simple(world, exitname, regionname, player) - else: - for exitname, regionname in inverted_default_connections: - connect_simple(world, exitname, regionname, player) - for exitname, regionname in inverted_default_dungeon_connections: - connect_simple(world, exitname, regionname, player) - elif world.shuffle[player] == 'dungeonssimple': - for exitname, regionname in default_connections: - connect_simple(world, exitname, regionname, player) - if not invFlag: - for exitname, regionname in open_default_connections: - connect_simple(world, exitname, regionname, player) - else: - for exitname, regionname in inverted_default_connections: - connect_simple(world, exitname, regionname, player) - - simple_shuffle_dungeons(world, player) - elif world.shuffle[player] == 'dungeonsfull': - for exitname, regionname in default_connections: - connect_simple(world, exitname, regionname, player) - if not invFlag: - for exitname, regionname in open_default_connections: - connect_simple(world, exitname, regionname, player) - else: - for exitname, regionname in inverted_default_connections: - connect_simple(world, exitname, regionname, player) - - skull_woods_shuffle(world, player) - - dungeon_exits = list(Dungeon_Exits) - lw_entrances = list(LW_Dungeon_Entrances) if not invFlag else list(Inverted_LW_Dungeon_Entrances) - dw_entrances = list(DW_Dungeon_Entrances) if not invFlag else list(Inverted_DW_Dungeon_Entrances) - - if invFlag: - lw_dungeon_entrances_must_exit = list(Inverted_LW_Dungeon_Entrances_Must_Exit) - - # randomize which desert ledge door is a must-exit - if random.randint(0, 1) == 0: - lw_dungeon_entrances_must_exit.append('Desert Palace Entrance (North)') - lw_entrances.append('Desert Palace Entrance (West)') - else: - lw_dungeon_entrances_must_exit.append('Desert Palace Entrance (West)') - lw_entrances.append('Desert Palace Entrance (North)') - dungeon_exits.append(('Hyrule Castle Exit (South)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')) - lw_entrances.append('Hyrule Castle Entrance (South)') - elif world.mode[player] == 'standard': - # must connect front of hyrule castle to do escape - connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player) - elif world.doorShuffle[player] != 'vanilla': - lw_entrances.append('Hyrule Castle Entrance (South)') - else: - dungeon_exits.append(('Hyrule Castle Exit (South)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')) - lw_entrances.append('Hyrule Castle Entrance (South)') - - if not world.shuffle_ganon: - connect_two_way(world, 'Agahnims Tower' if world.is_atgt_swapped(player) else 'Ganons Tower', 'Ganons Tower Exit', player) - hc_ledge_entrances = ['Hyrule Castle Entrance (West)', 'Hyrule Castle Entrance (East)'] - else: - if not invFlag: - dw_entrances.append('Ganons Tower') - else: - lw_entrances.append('Agahnims Tower') - dungeon_exits.append('Ganons Tower Exit') - hc_ledge_entrances = ['Hyrule Castle Entrance (West)', 'Hyrule Castle Entrance (East)', 'Agahnims Tower'] - - if invFlag: - # shuffle aga door first. If it's on HC ledge, remaining HC ledge door must be must-exit - all_entrances_aga = lw_entrances + dw_entrances - aga_doors = [i for i in all_entrances_aga] - random.shuffle(aga_doors) - aga_door = aga_doors.pop() - - if aga_door in hc_ledge_entrances: - lw_entrances.remove(aga_door) - hc_ledge_entrances.remove(aga_door) - - random.shuffle(hc_ledge_entrances) - hc_ledge_must_exit = hc_ledge_entrances.pop() - lw_entrances.remove(hc_ledge_must_exit) - lw_dungeon_entrances_must_exit.append(hc_ledge_must_exit) - - if aga_door in lw_entrances: - lw_entrances.remove(aga_door) - elif aga_door in dw_entrances: - dw_entrances.remove(aga_door) - - connect_two_way(world, aga_door, 'Agahnims Tower Exit', player) - dungeon_exits.remove('Agahnims Tower Exit') - - connect_mandatory_exits(world, lw_entrances, dungeon_exits, lw_dungeon_entrances_must_exit, player) - elif world.mode[player] == 'standard': - # rest of hyrule castle must be in light world, so it has to be the one connected to east exit of desert - hyrule_castle_exits = [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')] - 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) - elif world.doorShuffle[player] != 'vanilla': - # sanc is in light world, so must all of HC if door shuffle is on - 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) - - if not invFlag: - connect_mandatory_exits(world, dw_entrances, dungeon_exits, list(DW_Dungeon_Entrances_Must_Exit), player) - connect_caves(world, lw_entrances, dw_entrances, dungeon_exits, player) - elif world.shuffle[player] == 'simple': - simple_shuffle_dungeons(world, player) - - old_man_entrances = list(Old_Man_Entrances) if not invFlag else list(Inverted_Old_Man_Entrances) - caves = list(Cave_Exits) - three_exit_caves = list(Cave_Three_Exits) - - single_doors = list(Single_Cave_Doors) - bomb_shop_doors = list(Bomb_Shop_Single_Cave_Doors) if not invFlag else list(Inverted_Bomb_Shop_Single_Cave_Doors) - blacksmith_doors = list(Blacksmith_Single_Cave_Doors) + (['Links House'] if not invFlag else []) - door_targets = list(Single_Cave_Targets) if not invFlag else list(Inverted_Single_Cave_Targets) - - # we shuffle all 2 entrance caves as pairs as a start - # start with the ones that need to be directed - two_door_caves = list(Two_Door_Caves_Directional) if not invFlag else list(Inverted_Two_Door_Caves_Directional) - random.shuffle(two_door_caves) - random.shuffle(caves) - while two_door_caves: - entrance1, entrance2 = two_door_caves.pop() - exit1, exit2 = caves.pop() - connect_two_way(world, entrance1, exit1, player) - connect_two_way(world, entrance2, exit2, player) - - # now the remaining pairs - two_door_caves = list(Two_Door_Caves) if not invFlag else list(Inverted_Two_Door_Caves) - random.shuffle(two_door_caves) - while two_door_caves: - entrance1, entrance2 = two_door_caves.pop() - exit1, exit2 = caves.pop() - connect_two_way(world, entrance1, exit1, player) - connect_two_way(world, entrance2, exit2, player) - - # place links house - if world.mode[player] == 'standard' or not world.shufflelinks[player]: - links_house = 'Links House' if not invFlag else 'Big Bomb Shop' - else: - if not invFlag: - links_house_doors = [i for i in LW_Single_Cave_Doors if i not in Isolated_LH_Doors_Open] - else: - links_house_doors = [i for i in DW_Single_Cave_Doors if i not in Inverted_Dark_Sanctuary_Doors + Isolated_LH_Doors] - links_house = random.choice(links_house_doors) - connect_two_way(world, links_house, 'Links House Exit', player) - connect_exit(world, 'Chris Houlihan Room Exit', links_house, player) # should match link's house - if links_house in bomb_shop_doors: - bomb_shop_doors.remove(links_house) - if links_house in blacksmith_doors: - blacksmith_doors.remove(links_house) - if links_house in old_man_entrances: - old_man_entrances.remove(links_house) - if links_house in single_doors: - single_doors.remove(links_house) - - if invFlag: - # place dark sanc - sanc_doors = [door for door in Inverted_Dark_Sanctuary_Doors if door in bomb_shop_doors] - sanc_door = random.choice(sanc_doors) - bomb_shop_doors.remove(sanc_door) - - connect_entrance(world, sanc_door, 'Dark Sanctuary Hint', player) - world.get_entrance('Dark Sanctuary Hint Exit', player).connect(world.get_entrance(sanc_door, player).parent_region) - - # at this point only Light World death mountain entrances remain - # place old man, has limited options - random.shuffle(old_man_entrances) - old_man_exit = old_man_entrances.pop() - if not invFlag: - remaining_entrances = ['Old Man Cave (West)', 'Old Man House (Bottom)', 'Death Mountain Return Cave (West)', 'Paradox Cave (Bottom)', 'Paradox Cave (Middle)', 'Paradox Cave (Top)', - 'Fairy Ascension Cave (Bottom)', 'Fairy Ascension Cave (Top)', 'Spiral Cave', 'Spiral Cave (Bottom)'] - - remaining_entrances.extend(old_man_entrances) - random.shuffle(remaining_entrances) - old_man_entrance = remaining_entrances.pop() - connect_two_way(world, old_man_entrance, 'Old Man Cave Exit (West)', player) - else: - remaining_entrances = ['Paradox Cave (Bottom)', 'Paradox Cave (Middle)', 'Paradox Cave (Top)', 'Old Man House (Bottom)', - 'Fairy Ascension Cave (Bottom)', 'Fairy Ascension Cave (Top)', 'Spiral Cave (Bottom)', 'Old Man Cave (East)', - 'Death Mountain Return Cave (East)', 'Spiral Cave', 'Old Man House (Top)', 'Spectacle Rock Cave', - 'Spectacle Rock Cave Peak', 'Spectacle Rock Cave (Bottom)'] - - # place old man, bumper cave bottom to DDM entrances not in east bottom - connect_two_way(world, 'Bumper Cave (Bottom)', 'Old Man Cave Exit (West)', player) - connect_two_way(world, old_man_exit, 'Old Man Cave Exit (East)', player) - - if invFlag and old_man_exit == 'Spike Cave': - bomb_shop_doors.remove('Spike Cave') - bomb_shop_doors.extend(old_man_entrances) - - # add old man house to ensure it is always somewhere on light death mountain - caves.extend(list(Old_Man_House)) - caves.extend(list(three_exit_caves)) - - # connect rest - connect_caves(world, remaining_entrances, [], caves, player) - - # scramble holes - scramble_holes(world, player) - - # place blacksmith, has limited options - random.shuffle(blacksmith_doors) - blacksmith_hut = blacksmith_doors.pop() - connect_entrance(world, blacksmith_hut, 'Blacksmiths Hut', player) - bomb_shop_doors.extend(blacksmith_doors) - - # place bomb shop, has limited options - random.shuffle(bomb_shop_doors) - bomb_shop = bomb_shop_doors.pop() - connect_entrance(world, bomb_shop, 'Big Bomb Shop', player) - single_doors.extend(bomb_shop_doors) - - # tavern back door cannot be shuffled yet - connect_doors(world, ['Tavern North'], ['Tavern'], player) - - assert(len(single_doors) == len(door_targets)) - - # place remaining doors - connect_doors(world, single_doors, door_targets, player) - elif world.shuffle[player] == 'restricted': - simple_shuffle_dungeons(world, player) - - if not invFlag: - lw_entrances = list(LW_Entrances + LW_Single_Cave_Doors + Old_Man_Entrances) - dw_entrances = list(DW_Entrances + DW_Single_Cave_Doors) - dw_must_exits = list(DW_Entrances_Must_Exit) - old_man_entrances = list(Old_Man_Entrances) - caves = list(Cave_Exits + Cave_Three_Exits) - single_doors = list(Single_Cave_Doors) - bomb_shop_doors = list(Bomb_Shop_Single_Cave_Doors + Bomb_Shop_Multi_Cave_Doors) - blacksmith_doors = list(Blacksmith_Single_Cave_Doors + Blacksmith_Multi_Cave_Doors) - door_targets = list(Single_Cave_Targets) - else: - lw_entrances = list(Inverted_LW_Entrances + Inverted_LW_Single_Cave_Doors) - dw_entrances = list(Inverted_DW_Entrances + Inverted_DW_Single_Cave_Doors + Inverted_Old_Man_Entrances) - lw_must_exits = list(Inverted_LW_Entrances_Must_Exit) - old_man_entrances = list(Inverted_Old_Man_Entrances) - caves = list(Cave_Exits + Cave_Three_Exits + Old_Man_House) - single_doors = list(Single_Cave_Doors) - bomb_shop_doors = list(Inverted_Bomb_Shop_Single_Cave_Doors + Inverted_Bomb_Shop_Multi_Cave_Doors) - blacksmith_doors = list(Inverted_Blacksmith_Single_Cave_Doors + Inverted_Blacksmith_Multi_Cave_Doors) - door_targets = list(Inverted_Single_Cave_Targets) - - # place links house - if world.mode[player] == 'standard' or not world.shufflelinks[player]: - links_house = 'Links House' if not invFlag else 'Big Bomb Shop' - else: - if not invFlag: - links_house_doors = [i for i in lw_entrances if i not in Isolated_LH_Doors_Open] - else: - links_house_doors = [i for i in dw_entrances if i not in Inverted_Dark_Sanctuary_Doors + Isolated_LH_Doors] - links_house = random.choice(links_house_doors) - connect_two_way(world, links_house, 'Links House Exit', player) - connect_exit(world, 'Chris Houlihan Room Exit', links_house, player) # should match link's house - if not invFlag: - if links_house in lw_entrances: - lw_entrances.remove(links_house) - else: - if links_house in dw_entrances: - dw_entrances.remove(links_house) - - if invFlag: - # place dark sanc - sanc_doors = [door for door in Inverted_Dark_Sanctuary_Doors if door in dw_entrances] - sanc_door = random.choice(sanc_doors) - dw_entrances.remove(sanc_door) - connect_entrance(world, sanc_door, 'Dark Sanctuary Hint', player) - world.get_entrance('Dark Sanctuary Hint Exit', player).connect(world.get_entrance(sanc_door, player).parent_region) - - # tavern back door cannot be shuffled yet - connect_doors(world, ['Tavern North'], ['Tavern'], player) - - # in restricted, the only mandatory exits are in dark world (lw in inverted) - if not invFlag: - connect_mandatory_exits(world, dw_entrances, caves, dw_must_exits, player) - else: - connect_mandatory_exits(world, lw_entrances, caves, lw_must_exits, player) - - # place old man, has limited options - # exit has to come from specific set of doors, the entrance is free to move about - old_man_entrances = [door for door in old_man_entrances if door in (lw_entrances if not invFlag else dw_entrances)] - random.shuffle(old_man_entrances) - old_man_exit = old_man_entrances.pop() - connect_two_way(world, old_man_exit, 'Old Man Cave Exit (East)', player) - if not invFlag: - lw_entrances.remove(old_man_exit) - else: - dw_entrances.remove(old_man_exit) - - # place blacksmith, has limited options - all_entrances = lw_entrances + dw_entrances - # cannot place it anywhere already taken (or that are otherwise not eligible for placement) - blacksmith_doors = [door for door in blacksmith_doors if door in all_entrances] - random.shuffle(blacksmith_doors) - blacksmith_hut = blacksmith_doors.pop() - connect_entrance(world, blacksmith_hut, 'Blacksmiths Hut', player) - if blacksmith_hut in lw_entrances: - lw_entrances.remove(blacksmith_hut) - if blacksmith_hut in dw_entrances: - dw_entrances.remove(blacksmith_hut) - bomb_shop_doors.extend(blacksmith_doors) - - # place bomb shop, has limited options - all_entrances = lw_entrances + dw_entrances - # cannot place it anywhere already taken (or that are otherwise not eligible for placement) - bomb_shop_doors = [door for door in bomb_shop_doors if door in all_entrances] - random.shuffle(bomb_shop_doors) - bomb_shop = bomb_shop_doors.pop() - connect_entrance(world, bomb_shop, 'Big Bomb Shop', player) - if bomb_shop in lw_entrances: - lw_entrances.remove(bomb_shop) - if bomb_shop in dw_entrances: - dw_entrances.remove(bomb_shop) - - # standard mode cannot have Bonk Fairy Light be a connector in case of starting boots - # or boots are in links house, etc. - removed = False - if world.mode[player] == 'standard' and 'Bonk Fairy (Light)' in lw_entrances: - lw_entrances.remove('Bonk Fairy (Light)') - removed = True - - # place the old man cave's entrance somewhere in the light world - if not invFlag: - random.shuffle(lw_entrances) - old_man_entrance = lw_entrances.pop() - else: - random.shuffle(dw_entrances) - old_man_entrance = dw_entrances.pop() - connect_two_way(world, old_man_entrance, 'Old Man Cave Exit (West)', player) - - if not invFlag: - # place Old Man House in Light World - connect_caves(world, lw_entrances, [], list(Old_Man_House), player) #for multiple seeds - - # now scramble the rest - connect_caves(world, lw_entrances, dw_entrances, caves, player) - if removed: - lw_entrances.append('Bonk Fairy (Light)') - - # scramble holes - scramble_holes(world, player) - - doors = lw_entrances + dw_entrances - - # place remaining doors - connect_doors(world, doors, door_targets, player) - elif world.shuffle[player] == 'full': - skull_woods_shuffle(world, player) - - if not invFlag: - lw_entrances = list(LW_Entrances + LW_Dungeon_Entrances + LW_Single_Cave_Doors + Old_Man_Entrances) - dw_entrances = list(DW_Entrances + DW_Dungeon_Entrances + DW_Single_Cave_Doors) - dw_must_exits = list(DW_Entrances_Must_Exit + DW_Dungeon_Entrances_Must_Exit) - lw_must_exits = list(LW_Dungeon_Entrances_Must_Exit) - old_man_entrances = list(Old_Man_Entrances + ['Tower of Hera']) - caves = list(Cave_Exits + Dungeon_Exits + Cave_Three_Exits) # don't need to consider three exit caves, have one exit caves to avoid parity issues - bomb_shop_doors = list(Bomb_Shop_Single_Cave_Doors + Bomb_Shop_Multi_Cave_Doors) - blacksmith_doors = list(Blacksmith_Single_Cave_Doors + Blacksmith_Multi_Cave_Doors) - door_targets = list(Single_Cave_Targets) - old_man_house = list(Old_Man_House) - else: - lw_entrances = list(Inverted_LW_Entrances + Inverted_LW_Dungeon_Entrances + Inverted_LW_Single_Cave_Doors) - dw_entrances = list(Inverted_DW_Entrances + Inverted_DW_Dungeon_Entrances + Inverted_DW_Single_Cave_Doors + Inverted_Old_Man_Entrances) - lw_must_exits = list(Inverted_LW_Dungeon_Entrances_Must_Exit + Inverted_LW_Entrances_Must_Exit) - old_man_entrances = list(Inverted_Old_Man_Entrances + Old_Man_Entrances + ['Ganons Tower', 'Tower of Hera']) - caves = list(Cave_Exits + Dungeon_Exits + Cave_Three_Exits) # don't need to consider three exit caves, have one exit caves to avoid parity issues - bomb_shop_doors = list(Inverted_Bomb_Shop_Single_Cave_Doors + Inverted_Bomb_Shop_Multi_Cave_Doors) - blacksmith_doors = list(Inverted_Blacksmith_Single_Cave_Doors + Inverted_Blacksmith_Multi_Cave_Doors) - door_targets = list(Inverted_Single_Cave_Targets) - old_man_house = list(Old_Man_House) - - # randomize which desert ledge door is a must-exit - if random.randint(0, 1) == 0: - lw_must_exits.append('Desert Palace Entrance (North)') - lw_entrances.append('Desert Palace Entrance (West)') - else: - lw_must_exits.append('Desert Palace Entrance (West)') - lw_entrances.append('Desert Palace Entrance (North)') - - # tavern back door cannot be shuffled yet - connect_doors(world, ['Tavern North'], ['Tavern'], player) - - if world.mode[player] == 'standard': - # must connect front of hyrule castle to do escape - connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player) - elif not invFlag and world.doorShuffle[player] != 'vanilla': - lw_entrances.append('Hyrule Castle Entrance (South)') - else: - caves.append(tuple(random.sample(['Hyrule Castle Exit (South)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)'],3))) - lw_entrances.append('Hyrule Castle Entrance (South)') - - if not world.shuffle_ganon: - connect_two_way(world, 'Ganons Tower' if not world.is_atgt_swapped(player) else 'Agahnims Tower', 'Ganons Tower Exit', player) - hc_ledge_entrances = ['Hyrule Castle Entrance (West)', 'Hyrule Castle Entrance (East)'] - else: - if not invFlag: - dw_entrances.append('Ganons Tower') - else: - lw_entrances.append('Agahnims Tower') - caves.append('Ganons Tower Exit') - hc_ledge_entrances = ['Hyrule Castle Entrance (West)', 'Hyrule Castle Entrance (East)', 'Agahnims Tower'] - - if invFlag: - # shuffle aga door first. if it's on hc ledge, then one other hc ledge door has to be must_exit - all_entrances_aga = lw_entrances + dw_entrances - aga_doors = [i for i in all_entrances_aga if world.shufflelinks[player] or i != 'Big Bomb Shop'] - random.shuffle(aga_doors) - aga_door = aga_doors.pop() - - if aga_door in hc_ledge_entrances: - lw_entrances.remove(aga_door) - hc_ledge_entrances.remove(aga_door) - - random.shuffle(hc_ledge_entrances) - hc_ledge_must_exit = hc_ledge_entrances.pop() - lw_entrances.remove(hc_ledge_must_exit) - lw_must_exits.append(hc_ledge_must_exit) - if aga_door in lw_entrances: - lw_entrances.remove(aga_door) - elif aga_door in dw_entrances: - dw_entrances.remove(aga_door) - connect_two_way(world, aga_door, 'Agahnims Tower Exit', player) - caves.remove('Agahnims Tower Exit') - - # place links house - if world.mode[player] == 'standard' or not world.shufflelinks[player]: - links_house = 'Links House' if not invFlag else 'Big Bomb Shop' - else: - if not invFlag: - links_house_doors = [i for i in lw_entrances + lw_must_exits if i not in Isolated_LH_Doors_Open] - else: - links_house_doors = [i for i in dw_entrances if i not in Inverted_Dark_Sanctuary_Doors + Isolated_LH_Doors] - links_house = random.choice(links_house_doors) - connect_two_way(world, links_house, 'Links House Exit', player) - connect_exit(world, 'Chris Houlihan Room Exit', links_house, player) # should match link's house - if not invFlag: - if links_house in lw_entrances: - lw_entrances.remove(links_house) - if links_house in lw_must_exits: - lw_must_exits.remove(links_house) - else: - if links_house in dw_entrances: - dw_entrances.remove(links_house) - - # place dark sanc - sanc_doors = [door for door in Inverted_Dark_Sanctuary_Doors if door in dw_entrances] - sanc_door = random.choice(sanc_doors) - dw_entrances.remove(sanc_door) - connect_entrance(world, sanc_door, 'Dark Sanctuary Hint', player) - world.get_entrance('Dark Sanctuary Hint Exit', player).connect(world.get_entrance(sanc_door, player).parent_region) - - # we randomize which world requirements we fulfill first so we get better dungeon distribution - # we also places the Old Man House at this time to make sure he can be connected to the desert one way - # no dw must exits in inverted, but we randomize whether cave is in light or dark world - if random.randint(0, 1) == 0: - caves += old_man_house - connect_mandatory_exits(world, lw_entrances, caves, lw_must_exits, player) - try: - caves.remove(old_man_house[0]) - except ValueError: - pass - else: #if the cave wasn't placed we get here - connect_caves(world, lw_entrances, [], old_man_house, player) - if not invFlag: - connect_mandatory_exits(world, dw_entrances, caves, dw_must_exits, player) - else: - if not invFlag: - connect_mandatory_exits(world, dw_entrances, caves, dw_must_exits, player) - caves += old_man_house - connect_mandatory_exits(world, lw_entrances, caves, lw_must_exits, player) - try: - caves.remove(old_man_house[0]) - except ValueError: - pass - else: # if the cave wasn't placed we get here - connect_caves(world, lw_entrances, [], old_man_house, player) - else: - connect_caves(world, dw_entrances, [], old_man_house, player) - connect_mandatory_exits(world, lw_entrances, caves, lw_must_exits, player) - - if world.mode[player] == 'standard': - # rest of hyrule castle must be in light world - connect_caves(world, lw_entrances, [], [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')], player) - # in full, Sanc must be in light world, so must all of HC if door shuffle is on - elif not invFlag and world.doorShuffle[player] != 'vanilla': - connect_caves(world, lw_entrances, [], [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)', 'Hyrule Castle Exit (South)')], player) - - # place old man, has limited options - # exit has to come from specific set of doors, the entrance is free to move about - old_man_entrances = [door for door in old_man_entrances if door in lw_entrances + (dw_entrances if invFlag else [])] - random.shuffle(old_man_entrances) - old_man_exit = old_man_entrances.pop() - connect_two_way(world, old_man_exit, 'Old Man Cave Exit (East)', player) - if old_man_exit in dw_entrances: - dw_entrances.remove(old_man_exit) - old_man_world = 'dark' - elif old_man_exit in lw_entrances: - lw_entrances.remove(old_man_exit) - old_man_world = 'light' - - # place blacksmith, has limited options - all_entrances = lw_entrances + dw_entrances - # cannot place it anywhere already taken (or that are otherwise not eligible for placement) - blacksmith_doors = [door for door in blacksmith_doors if door in all_entrances] - random.shuffle(blacksmith_doors) - blacksmith_hut = blacksmith_doors.pop() - connect_entrance(world, blacksmith_hut, 'Blacksmiths Hut', player) - if blacksmith_hut in lw_entrances: - lw_entrances.remove(blacksmith_hut) - if blacksmith_hut in dw_entrances: - dw_entrances.remove(blacksmith_hut) - bomb_shop_doors.extend(blacksmith_doors) - - # place bomb shop, has limited options - all_entrances = lw_entrances + dw_entrances - # cannot place it anywhere already taken (or that are otherwise not eligible for placement) - bomb_shop_doors = [door for door in bomb_shop_doors if door in all_entrances] - random.shuffle(bomb_shop_doors) - bomb_shop = bomb_shop_doors.pop() - connect_entrance(world, bomb_shop, 'Big Bomb Shop', player) - if bomb_shop in lw_entrances: - lw_entrances.remove(bomb_shop) - if bomb_shop in dw_entrances: - dw_entrances.remove(bomb_shop) - - # standard mode cannot have Bonk Fairy Light be a connector in case of - # starting boots or boots are in links house, etc. - removed = False - if world.mode[player] == 'standard' and 'Bonk Fairy (Light)' in lw_entrances: - lw_entrances.remove('Bonk Fairy (Light)') - removed = True - - # place the old man cave's entrance somewhere in the same world he'll exit from - if old_man_world == 'light': - random.shuffle(lw_entrances) - old_man_entrance = lw_entrances.pop() - elif old_man_world == 'dark': - random.shuffle(dw_entrances) - old_man_entrance = dw_entrances.pop() - connect_two_way(world, old_man_entrance, 'Old Man Cave Exit (West)', player) - - # now scramble the rest - connect_caves(world, lw_entrances, dw_entrances, caves, player) - - if removed: - lw_entrances.append('Bonk Fairy (Light)') - - # scramble holes - scramble_holes(world, player) - - doors = lw_entrances + dw_entrances - - # place remaining doors - connect_doors(world, doors, door_targets, player) - elif world.shuffle[player] == 'crossed': - skull_woods_shuffle(world, player) - - if not invFlag: - entrances = list(LW_Entrances + LW_Dungeon_Entrances + LW_Single_Cave_Doors + Old_Man_Entrances + DW_Entrances + DW_Dungeon_Entrances + DW_Single_Cave_Doors) - must_exits = list(DW_Entrances_Must_Exit + DW_Dungeon_Entrances_Must_Exit + LW_Dungeon_Entrances_Must_Exit) - - old_man_entrances = list(Old_Man_Entrances + ['Tower of Hera']) - caves = list(Cave_Exits + Dungeon_Exits + Cave_Three_Exits + Old_Man_House) # don't need to consider three exit caves, have one exit caves to avoid parity issues - bomb_shop_doors = list(Bomb_Shop_Single_Cave_Doors + Bomb_Shop_Multi_Cave_Doors) - blacksmith_doors = list(Blacksmith_Single_Cave_Doors + Blacksmith_Multi_Cave_Doors) - door_targets = list(Single_Cave_Targets) - else: - entrances = list(Inverted_LW_Entrances + Inverted_LW_Dungeon_Entrances + Inverted_LW_Single_Cave_Doors + Inverted_Old_Man_Entrances + Inverted_DW_Entrances + Inverted_DW_Dungeon_Entrances + Inverted_DW_Single_Cave_Doors) - must_exits = list(Inverted_LW_Entrances_Must_Exit + Inverted_LW_Dungeon_Entrances_Must_Exit) - - old_man_entrances = list(Inverted_Old_Man_Entrances + Old_Man_Entrances + ['Ganons Tower', 'Tower of Hera']) - caves = list(Cave_Exits + Dungeon_Exits + Cave_Three_Exits + Old_Man_House) # don't need to consider three exit caves, have one exit caves to avoid parity issues - bomb_shop_doors = list(Inverted_Bomb_Shop_Single_Cave_Doors + Inverted_Bomb_Shop_Multi_Cave_Doors) - blacksmith_doors = list(Inverted_Blacksmith_Single_Cave_Doors + Inverted_Blacksmith_Multi_Cave_Doors) - door_targets = list(Inverted_Single_Cave_Targets) - - # randomize which desert ledge door is a must-exit - if random.randint(0, 1) == 0: - must_exits.append('Desert Palace Entrance (North)') - entrances.append('Desert Palace Entrance (West)') - else: - must_exits.append('Desert Palace Entrance (West)') - entrances.append('Desert Palace Entrance (North)') - - # tavern back door cannot be shuffled yet - connect_doors(world, ['Tavern North'], ['Tavern'], player) - - if world.mode[player] == 'standard': - # must connect front of hyrule castle to do escape - connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player) - else: - caves.append(tuple(random.sample(['Hyrule Castle Exit (South)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)'],3))) - entrances.append('Hyrule Castle Entrance (South)') - - if not world.shuffle_ganon: - connect_two_way(world, 'Ganons Tower' if not world.is_atgt_swapped(player) else 'Agahnims Tower', 'Ganons Tower Exit', player) - hc_ledge_entrances = ['Hyrule Castle Entrance (West)', 'Hyrule Castle Entrance (East)'] - else: - entrances.append('Ganons Tower' if not world.is_atgt_swapped(player) else 'Agahnims Tower') - caves.append('Ganons Tower Exit') - hc_ledge_entrances = ['Hyrule Castle Entrance (West)', 'Hyrule Castle Entrance (East)', 'Agahnims Tower'] - - if invFlag: - # shuffle aga door. if it's on hc ledge, then one other hc ledge door has to be must_exit - aga_choices = [x for x in entrances if world.shufflelinks[player] or x != 'Big Bomb Shop'] - aga_door = random.choice(aga_choices) - - if aga_door in hc_ledge_entrances: - hc_ledge_entrances.remove(aga_door) - - random.shuffle(hc_ledge_entrances) - hc_ledge_must_exit = hc_ledge_entrances.pop() - entrances.remove(hc_ledge_must_exit) - must_exits.append(hc_ledge_must_exit) - - entrances.remove(aga_door) - connect_two_way(world, aga_door, 'Agahnims Tower Exit', player) - caves.remove('Agahnims Tower Exit') - - # place links house - if world.mode[player] == 'standard' or not world.shufflelinks[player]: - links_house = 'Links House' if not invFlag else 'Big Bomb Shop' - else: - if not invFlag: - links_house_doors = [i for i in entrances + must_exits if i not in Isolated_LH_Doors_Open] - else: - links_house_doors = [i for i in entrances + must_exits if i not in Inverted_Dark_Sanctuary_Doors + Isolated_LH_Doors] - if not invFlag and world.doorShuffle[player] == 'crossed' and world.intensity[player] >= 3: - exclusions = DW_Entrances + DW_Dungeon_Entrances + DW_Single_Cave_Doors\ - + DW_Entrances_Must_Exit + DW_Dungeon_Entrances_Must_Exit + ['Ganons Tower'] - links_house_doors = [i for i in links_house_doors if i not in exclusions] - links_house = random.choice(list(links_house_doors)) - connect_two_way(world, links_house, 'Links House Exit', player) - connect_exit(world, 'Chris Houlihan Room Exit', links_house, player) # should match link's house - if links_house in entrances: - entrances.remove(links_house) - elif links_house in must_exits: - must_exits.remove(links_house) - - if invFlag: - # place dark sanc - sanc_doors = [door for door in Inverted_Dark_Sanctuary_Doors if door in entrances] - sanc_door = random.choice(sanc_doors) - entrances.remove(sanc_door) - connect_entrance(world, sanc_door, 'Dark Sanctuary Hint', player) - world.get_entrance('Dark Sanctuary Hint Exit', player).connect(world.get_entrance(sanc_door, player).parent_region) - - #place must-exit caves - connect_mandatory_exits(world, entrances, caves, must_exits, player) - - if world.mode[player] == 'standard': - # rest of hyrule castle must be dealt with - connect_caves(world, entrances, [], [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')], player) - - # place old man, has limited options - # exit has to come from specific set of doors, the entrance is free to move about - old_man_entrances = [door for door in old_man_entrances if door in entrances] - random.shuffle(old_man_entrances) - old_man_exit = old_man_entrances.pop() - connect_two_way(world, old_man_exit, 'Old Man Cave Exit (East)', player) - entrances.remove(old_man_exit) - - # place blacksmith, has limited options - # cannot place it anywhere already taken (or that are otherwise not eligible for placement) - blacksmith_doors = [door for door in blacksmith_doors if door in entrances] - random.shuffle(blacksmith_doors) - blacksmith_hut = blacksmith_doors.pop() - connect_entrance(world, blacksmith_hut, 'Blacksmiths Hut', player) - entrances.remove(blacksmith_hut) - if not invFlag: - bomb_shop_doors.extend(blacksmith_doors) - - # place bomb shop, has limited options - # cannot place it anywhere already taken (or that are otherwise not eligible for placement) - bomb_shop_doors = [door for door in bomb_shop_doors if door in entrances] - random.shuffle(bomb_shop_doors) - bomb_shop = bomb_shop_doors.pop() - connect_entrance(world, bomb_shop, 'Big Bomb Shop', player) - entrances.remove(bomb_shop) - - # standard mode cannot have Bonk Fairy Light be a connector in case of - # starting boots or boots are in links house, etc. - removed = False - if world.mode[player] == 'standard' and 'Bonk Fairy (Light)' in entrances: - entrances.remove('Bonk Fairy (Light)') - removed = True - - # place the old man cave's entrance somewhere - random.shuffle(entrances) - old_man_entrance = entrances.pop() - connect_two_way(world, old_man_entrance, 'Old Man Cave Exit (West)', player) - - # now scramble the rest - connect_caves(world, entrances, [], caves, player) - - if removed: - entrances.append('Bonk Fairy (Light)') - - # scramble holes - scramble_holes(world, player) - - # place remaining doors - connect_doors(world, entrances, door_targets, player) - elif world.shuffle[player] == 'insanity': - # beware ye who enter here - - if not invFlag: - entrances_must_exits = DW_Entrances_Must_Exit + DW_Dungeon_Entrances_Must_Exit + LW_Dungeon_Entrances_Must_Exit + ['Skull Woods Second Section Door (West)'] - - doors = LW_Entrances + LW_Dungeon_Entrances + LW_Dungeon_Entrances_Must_Exit + ['Kakariko Well Cave', 'Bat Cave Cave', 'North Fairy Cave', 'Sanctuary', 'Lost Woods Hideout Stump', 'Lumberjack Tree Cave'] + Old_Man_Entrances +\ - DW_Entrances + DW_Dungeon_Entrances + DW_Entrances_Must_Exit + DW_Dungeon_Entrances_Must_Exit + ['Skull Woods First Section Door', 'Skull Woods Second Section Door (East)', 'Skull Woods Second Section Door (West)'] +\ - LW_Single_Cave_Doors + DW_Single_Cave_Doors - exit_pool = list(doors) - - # TODO: there are other possible entrances we could support here by way of exiting from a connector, - # and rentering to find bomb shop. However appended list here is all those that we currently have - # bomb shop logic for. - # Specifically we could potentially add: 'Dark Death Mountain Ledge (East)' and doors associated with pits - bomb_shop_doors = list(Bomb_Shop_Single_Cave_Doors + Bomb_Shop_Multi_Cave_Doors+['Desert Palace Entrance (East)', 'Turtle Rock Isolated Ledge Entrance', 'Bumper Cave (Top)', 'Hookshot Cave Back Entrance']) - blacksmith_doors = list(Blacksmith_Single_Cave_Doors + Blacksmith_Multi_Cave_Doors) - door_targets = list(Single_Cave_Targets) - else: - entrances_must_exits = Inverted_LW_Entrances_Must_Exit + Inverted_LW_Dungeon_Entrances_Must_Exit - - doors = Inverted_LW_Entrances + Inverted_LW_Dungeon_Entrances + Inverted_LW_Entrances_Must_Exit + Inverted_LW_Dungeon_Entrances_Must_Exit + ['Kakariko Well Cave', 'Bat Cave Cave', 'North Fairy Cave', 'Sanctuary', 'Lost Woods Hideout Stump', 'Lumberjack Tree Cave', 'Hyrule Castle Secret Entrance Stairs'] + Inverted_Old_Man_Entrances +\ - Inverted_DW_Entrances + Inverted_DW_Dungeon_Entrances + ['Skull Woods First Section Door', 'Skull Woods Second Section Door (East)', 'Skull Woods Second Section Door (West)'] +\ - Inverted_LW_Single_Cave_Doors + Inverted_DW_Single_Cave_Doors + ['Desert Palace Entrance (West)', 'Desert Palace Entrance (North)'] - exit_pool = list(doors) - - # TODO: there are other possible entrances we could support here by way of exiting from a connector, - # and rentering to find bomb shop. However appended list here is all those that we currently have - # bomb shop logic for. - # Specifically we could potentially add: 'Dark Death Mountain Ledge (East)' and doors associated with pits - bomb_shop_doors = list(Inverted_Bomb_Shop_Single_Cave_Doors + Inverted_Bomb_Shop_Multi_Cave_Doors + ['Turtle Rock Isolated Ledge Entrance', 'Hookshot Cave Back Entrance']) - blacksmith_doors = list(Inverted_Blacksmith_Single_Cave_Doors + Inverted_Blacksmith_Multi_Cave_Doors) - door_targets = list(Inverted_Single_Cave_Targets) - - # randomize which desert ledge door is a must-exit - if random.randint(0, 1) == 0: - entrances_must_exits.append('Desert Palace Entrance (North)') - else: - entrances_must_exits.append('Desert Palace Entrance (West)') - - random.shuffle(doors) - - old_man_entrances = list(Old_Man_Entrances) + ['Tower of Hera'] - if invFlag: - old_man_entrances += list(Inverted_Old_Man_Entrances) + ['Ganons Tower'] - - caves = Cave_Exits + Dungeon_Exits + Cave_Three_Exits + ['Old Man House Exit (Bottom)', 'Old Man House Exit (Top)', 'Skull Woods First Section Exit', 'Skull Woods Second Section Exit (East)', 'Skull Woods Second Section Exit (West)', - 'Kakariko Well Exit', 'Bat Cave Exit', 'North Fairy Cave Exit', 'Lost Woods Hideout Exit', 'Lumberjack Tree Exit', 'Sanctuary Exit'] - - - # shuffle up holes - hole_entrances = ['Kakariko Well Drop', 'Bat Cave Drop', 'North Fairy Cave Drop', 'Lost Woods Hideout Drop', 'Lumberjack Tree Tree', 'Sanctuary Grave', - 'Skull Woods First Section Hole (East)', 'Skull Woods First Section Hole (West)', 'Skull Woods First Section Hole (North)', 'Skull Woods Second Section Hole'] - - hole_targets = ['Kakariko Well (top)', 'Bat Cave (right)', 'North Fairy Cave', 'Lost Woods Hideout (top)', 'Lumberjack Tree (top)', 'Sewer Drop', 'Skull Back Drop', - 'Skull Left Drop', 'Skull Pinball', 'Skull Pot Circle'] - - # tavern back door cannot be shuffled yet - connect_doors(world, ['Tavern North'], ['Tavern'], player) - - if world.mode[player] == 'standard': - # cannot move uncle cave - connect_entrance(world, 'Hyrule Castle Secret Entrance Drop', 'Hyrule Castle Secret Entrance', player) - connect_exit(world, 'Hyrule Castle Secret Entrance Exit', 'Hyrule Castle Secret Entrance Stairs', player) - connect_entrance(world, 'Hyrule Castle Secret Entrance Stairs', 'Hyrule Castle Secret Entrance Exit', player) - else: - hole_entrances.append('Hyrule Castle Secret Entrance Drop') - hole_targets.append('Hyrule Castle Secret Entrance') - caves.append('Hyrule Castle Secret Entrance Exit') - if not invFlag: - doors.append('Hyrule Castle Secret Entrance Stairs') - exit_pool.append('Hyrule Castle Secret Entrance Stairs') - - if not world.shuffle_ganon: - connect_two_way(world, 'Ganons Tower' if not world.is_atgt_swapped(player) else 'Agahnims Tower', 'Ganons Tower Exit', player) - connect_two_way(world, 'Pyramid Entrance' if not invFlag else 'Inverted Pyramid Entrance', 'Pyramid Exit', player) - connect_entrance(world, 'Pyramid Hole' if not invFlag else 'Inverted Pyramid Hole', 'Pyramid', player) - else: - caves.extend(['Ganons Tower Exit', 'Pyramid Exit']) - hole_entrances.append('Pyramid Hole' if not invFlag else 'Inverted Pyramid Hole') - hole_targets.append('Pyramid') - if not invFlag: - entrances_must_exits.append('Pyramid Entrance') - exit_pool.extend(['Ganons Tower', 'Pyramid Entrance'] if not invFlag else ['Agahnims Tower', 'Inverted Pyramid Entrance']) - doors.extend(['Ganons Tower', 'Pyramid Entrance'] if not invFlag else ['Agahnims Tower', 'Inverted Pyramid Entrance']) - - random.shuffle(hole_entrances) - random.shuffle(hole_targets) - random.shuffle(exit_pool) - - - # fill up holes - for hole in hole_entrances: - connect_entrance(world, hole, hole_targets.pop(), player) - - # hyrule castle handling - if world.mode[player] == 'standard': - # must connect front of hyrule castle to do escape - connect_entrance(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player) - connect_exit(world, 'Hyrule Castle Exit (South)', 'Hyrule Castle Entrance (South)', player) - caves.append(('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')) - else: - doors.append('Hyrule Castle Entrance (South)') - caves.append(('Hyrule Castle Exit (South)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')) - if not invFlag: - exit_pool.append('Hyrule Castle Entrance (South)') - random.shuffle(doors) - - # place links house - if world.mode[player] == 'standard' or not world.shufflelinks[player]: - links_house = 'Links House' if not invFlag else 'Big Bomb Shop' - else: - if not invFlag: - links_house_doors = [i for i in doors if i not in Isolated_LH_Doors_Open] - else: - links_house_doors = [i for i in doors if i not in Inverted_Dark_Sanctuary_Doors + Isolated_LH_Doors] - if not invFlag and world.doorShuffle[player] == 'crossed' and world.intensity[player] >= 3: - exclusions = DW_Entrances + DW_Dungeon_Entrances + DW_Single_Cave_Doors \ - + DW_Entrances_Must_Exit + DW_Dungeon_Entrances_Must_Exit + ['Ganons Tower'] - links_house_doors = [i for i in links_house_doors if i not in exclusions] - links_house = random.choice(links_house_doors) - connect_two_way(world, links_house, 'Links House Exit', player) - connect_exit(world, 'Chris Houlihan Room Exit', links_house, player) # should match link's house - exit_pool.remove(links_house) - doors.remove(links_house) - - if invFlag: - # place dark sanc - sanc_doors = [door for door in Inverted_Dark_Sanctuary_Doors if door in exit_pool] - sanc_door = random.choice(sanc_doors) - exit_pool.remove(sanc_door) - doors.remove(sanc_door) - connect_entrance(world, sanc_door, 'Dark Sanctuary Hint', player) - world.get_entrance('Dark Sanctuary Hint Exit', player).connect(world.get_entrance(sanc_door, player).parent_region) - - # now let's deal with mandatory reachable stuff - def extract_reachable_exit(cavelist): - random.shuffle(cavelist) - candidate = None - for cave in cavelist: - if isinstance(cave, tuple) and len(cave) > 1: - # special handling: TRock has two entries that we should consider entrance only - # ToDo this should be handled in a more sensible manner - if cave[0] in ['Turtle Rock Exit (Front)', 'Spectacle Rock Cave Exit (Peak)'] and len(cave) == 2: - continue - candidate = cave - break - if candidate is None: - raise RuntimeError('No suitable cave.') - cavelist.remove(candidate) - return candidate - - def connect_reachable_exit(entrance, caves, doors, exit_pool): - cave = extract_reachable_exit(caves) - - exit = cave[-1] - cave = cave[:-1] - connect_exit(world, exit, entrance, player) - connect_entrance(world, doors.pop(), exit, player) - # rest of cave now is forced to be in this world - exit_pool.remove(entrance) - caves.append(cave) - - # connect mandatory exits - for entrance in entrances_must_exits: - connect_reachable_exit(entrance, caves, doors, exit_pool) - - # place old man, has limited options - # exit has to come from specific set of doors, the entrance is free to move about - old_man_entrances = [entrance for entrance in old_man_entrances if entrance in exit_pool] - random.shuffle(old_man_entrances) - old_man_exit = old_man_entrances.pop() - exit_pool.remove(old_man_exit) - - connect_exit(world, 'Old Man Cave Exit (East)', old_man_exit, player) - connect_entrance(world, doors.pop(), 'Old Man Cave Exit (East)', player) - caves.append('Old Man Cave Exit (West)') - - # place blacksmith, has limited options - blacksmith_doors = [door for door in blacksmith_doors if door in doors] - random.shuffle(blacksmith_doors) - blacksmith_hut = blacksmith_doors.pop() - connect_entrance(world, blacksmith_hut, 'Blacksmiths Hut', player) - doors.remove(blacksmith_hut) - - # place dam and pyramid fairy, have limited options - bomb_shop_doors = [door for door in bomb_shop_doors if door in doors] - random.shuffle(bomb_shop_doors) - bomb_shop = bomb_shop_doors.pop() - connect_entrance(world, bomb_shop, 'Big Bomb Shop', player) - doors.remove(bomb_shop) - - # standard mode cannot have Bonk Fairy Light be a connector in case of - # starting boots or boots are in links house, etc. - removed = False - if world.mode[player] == 'standard' and 'Bonk Fairy (Light)' in doors: - doors.remove('Bonk Fairy (Light)') - removed = True - - # handle remaining caves - for cave in caves: - if isinstance(cave, str): - cave = (cave,) - - for exit in cave: - connect_exit(world, exit, exit_pool.pop(), player) - connect_entrance(world, doors.pop(), exit, player) - - if removed: - doors.append('Bonk Fairy (Light)') - - # place remaining doors - connect_doors(world, doors, door_targets, player) - else: - raise NotImplementedError('Shuffling not supported yet') - - # check for swamp palace fix - if world.get_entrance('Dam', player).connected_region.name != 'Dam' or world.get_entrance('Swamp Palace', player).connected_region.name != 'Swamp Portal': - world.swamp_patch_required[player] = True - - # check for potion shop location - if world.get_entrance('Potion Shop', player).connected_region.name != 'Potion Shop': - world.powder_patch_required[player] = True - - # check for ganon location - if world.get_entrance('Pyramid Hole' if not invFlag else 'Inverted Pyramid Hole', player).connected_region.name != 'Pyramid': - world.ganon_at_pyramid[player] = False - - # check for Ganon's Tower location - if world.get_entrance('Ganons Tower' if not world.is_atgt_swapped(player) else 'Agahnims Tower', player).connected_region.name != 'Ganons Tower Portal': - world.ganonstower_vanilla[player] = False - - -def connect_custom(world, player): - if hasattr(world, 'custom_entrances') and world.custom_entrances[player]: - for exit_name, region_name in world.custom_entrances[player]: - # doesn't actually change addresses - connect_simple(world, exit_name, region_name, player) - # this needs to remove custom connections from the pool - - -def connect_simple(world, exitname, regionname, player): - world.get_entrance(exitname, player).connect(world.get_region(regionname, player)) - - -def connect_entrance(world, entrancename, exitname, player): - entrance = world.get_entrance(entrancename, player) - # check if we got an entrance or a region to connect to - try: - region = world.get_region(exitname, player) - exit = None - except RuntimeError: - exit = world.get_entrance(exitname, player) - region = exit.parent_region - - # if this was already connected somewhere, remove the backreference - if entrance.connected_region is not None: - entrance.connected_region.entrances.remove(entrance) - - target = exit_ids[exit.name][0] if exit is not None else exit_ids.get(region.name, None) - addresses = door_addresses[entrance.name][0] - - entrance.connect(region, addresses, target) - world.spoiler.set_entrance(entrance.name, exit.name if exit is not None else region.name, 'entrance', player) - -def connect_exit(world, exitname, entrancename, player): - entrance = world.get_entrance(entrancename, player) - exit = world.get_entrance(exitname, player) - - # if this was already connected somewhere, remove the backreference - if exit.connected_region is not None: - exit.connected_region.entrances.remove(exit) - - exit.connect(entrance.parent_region, door_addresses[entrance.name][1], exit_ids[exit.name][1]) - world.spoiler.set_entrance(entrance.name, exit.name, 'exit', player) - - -def connect_two_way(world, entrancename, exitname, player): - entrance = world.get_entrance(entrancename, player) - exit = world.get_entrance(exitname, player) - - # if these were already connected somewhere, remove the backreference - if entrance.connected_region is not None: - entrance.connected_region.entrances.remove(entrance) - if exit.connected_region is not None: - exit.connected_region.entrances.remove(exit) - - entrance.connect(exit.parent_region, door_addresses[entrance.name][0], exit_ids[exit.name][0]) - exit.connect(entrance.parent_region, door_addresses[entrance.name][1], exit_ids[exit.name][1]) - world.spoiler.set_entrance(entrance.name, exit.name, 'both', player) - - -def scramble_holes(world, player): - hole_entrances = [('Kakariko Well Cave', 'Kakariko Well Drop'), - ('Bat Cave Cave', 'Bat Cave Drop'), - ('North Fairy Cave', 'North Fairy Cave Drop'), - ('Lost Woods Hideout Stump', 'Lost Woods Hideout Drop'), - ('Lumberjack Tree Cave', 'Lumberjack Tree Tree'), - ('Sanctuary', 'Sanctuary Grave')] - - hole_targets = [('Kakariko Well Exit', 'Kakariko Well (top)'), - ('Bat Cave Exit', 'Bat Cave (right)'), - ('North Fairy Cave Exit', 'North Fairy Cave'), - ('Lost Woods Hideout Exit', 'Lost Woods Hideout (top)'), - ('Lumberjack Tree Exit', 'Lumberjack Tree (top)')] - - if not world.shuffle_ganon: - if world.mode[player] == 'inverted': - connect_two_way(world, 'Inverted Pyramid Entrance', 'Pyramid Exit', player) - connect_entrance(world, 'Inverted Pyramid Hole', 'Pyramid', player) - else: - connect_two_way(world, 'Pyramid Entrance', 'Pyramid Exit', player) - connect_entrance(world, 'Pyramid Hole', 'Pyramid', player) - else: - hole_targets.append(('Pyramid Exit', 'Pyramid')) - - if world.mode[player] == 'standard': - # cannot move uncle cave - connect_two_way(world, 'Hyrule Castle Secret Entrance Stairs', 'Hyrule Castle Secret Entrance Exit', player) - connect_entrance(world, 'Hyrule Castle Secret Entrance Drop', 'Hyrule Castle Secret Entrance', player) - else: - hole_entrances.append(('Hyrule Castle Secret Entrance Stairs', 'Hyrule Castle Secret Entrance Drop')) - hole_targets.append(('Hyrule Castle Secret Entrance Exit', 'Hyrule Castle Secret Entrance')) - - # do not shuffle sanctuary into pyramid hole unless shuffle is crossed - if world.shuffle[player] == 'crossed': - hole_targets.append(('Sanctuary Exit', 'Sewer Drop')) - if world.shuffle_ganon: - random.shuffle(hole_targets) - exit, target = hole_targets.pop() - if world.mode[player] == 'inverted': - connect_two_way(world, 'Inverted Pyramid Entrance', exit, player) - connect_entrance(world, 'Inverted Pyramid Hole', target, player) - else: - connect_two_way(world, 'Pyramid Entrance', exit, player) - connect_entrance(world, 'Pyramid Hole', target, player) - if world.shuffle[player] != 'crossed': - hole_targets.append(('Sanctuary Exit', 'Sewer Drop')) - - random.shuffle(hole_targets) - for entrance, drop in hole_entrances: - exit, target = hole_targets.pop() - connect_two_way(world, entrance, exit, player) - connect_entrance(world, drop, target, player) - - -def connect_random(world, exitlist, targetlist, player, two_way=False): - targetlist = list(targetlist) - random.shuffle(targetlist) - - for exit, target in zip(exitlist, targetlist): - if two_way: - connect_two_way(world, exit, target, player) - else: - connect_entrance(world, exit, target, player) - - -def connect_mandatory_exits(world, entrances, caves, must_be_exits, player): - - # Keeps track of entrances that cannot be used to access each exit / cave - inverted = world.mode[player] == 'inverted' - if inverted: - invalid_connections = Inverted_Must_Exit_Invalid_Connections.copy() - else: - invalid_connections = Must_Exit_Invalid_Connections.copy() - invalid_cave_connections = defaultdict(set) - - if world.logic[player] in ['owglitches', 'hybridglitches', 'nologic']: - import OverworldGlitchRules - for entrance in OverworldGlitchRules.inverted_non_mandatory_exits if inverted else OverworldGlitchRules.open_non_mandatory_exits: - invalid_connections[entrance] = set() - if entrance in must_be_exits: - must_be_exits.remove(entrance) - entrances.append(entrance) - - """This works inplace""" - random.shuffle(entrances) - random.shuffle(caves) - - # Handle inverted Aga Tower - if it depends on connections, then so does Hyrule Castle Ledge - if inverted: - for entrance in invalid_connections: - if world.get_entrance(entrance, player).connected_region == world.get_region('Agahnims Tower Portal', player): - for exit in invalid_connections[entrance]: - invalid_connections[exit] = invalid_connections[exit].union({'Agahnims Tower', 'Hyrule Castle Entrance (West)', 'Hyrule Castle Entrance (East)'}) - break - - used_caves = [] - required_entrances = 0 # Number of entrances reserved for used_caves - while must_be_exits: - exit = must_be_exits.pop() - # find multi exit cave - cave = None - for candidate in caves: - if not isinstance(candidate, str) and (candidate in used_caves or len(candidate) < len(entrances) - required_entrances - 1): - cave = candidate - break - - if cave is None: - raise RuntimeError('No more caves left. Should not happen!') - - # all caves are sorted so that the last exit is always reachable - connect_two_way(world, exit, cave[-1], player) - if len(cave) == 2: - entrance = next(e for e in entrances[::-1] if e not in invalid_connections[exit] and e not in invalid_cave_connections[tuple(cave)]) - entrances.remove(entrance) - connect_two_way(world, entrance, cave[0], player) - if cave in used_caves: - required_entrances -= 2 - used_caves.remove(cave) - if entrance in invalid_connections: - for exit2 in invalid_connections[entrance]: - invalid_connections[exit2] = invalid_connections[exit2].union(invalid_connections[exit]).union(invalid_cave_connections[tuple(cave)]) - elif cave[-1] == 'Spectacle Rock Cave Exit': #Spectacle rock only has one exit - cave_entrances = [] - for cave_exit in cave[:-1]: - entrance = next(e for e in entrances[::-1] if e not in invalid_connections[exit]) - cave_entrances.append(entrance) - entrances.remove(entrance) - connect_two_way(world,entrance,cave_exit, player) - if entrance not in invalid_connections: - invalid_connections[exit] = set() - if all(entrance in invalid_connections for entrance in cave_entrances): - new_invalid_connections = invalid_connections[cave_entrances[0]].intersection(invalid_connections[cave_entrances[1]]) - for exit2 in new_invalid_connections: - invalid_connections[exit2] = invalid_connections[exit2].union(invalid_connections[exit]) - else:#save for later so we can connect to multiple exits - if cave in used_caves: - required_entrances -= 1 - used_caves.remove(cave) - else: - required_entrances += len(cave)-1 - caves.append(cave[0:-1]) - random.shuffle(caves) - used_caves.append(cave[0:-1]) - invalid_cave_connections[tuple(cave[0:-1])] = invalid_cave_connections[tuple(cave)].union(invalid_connections[exit]) - caves.remove(cave) - for cave in used_caves: - if cave in caves: #check if we placed multiple entrances from this 3 or 4 exit - for cave_exit in cave: - entrance = next(e for e in entrances[::-1] if e not in invalid_cave_connections[tuple(cave)]) - invalid_cave_connections[tuple(cave)] = set() - entrances.remove(entrance) - connect_two_way(world, entrance, cave_exit, player) - caves.remove(cave) - - -def connect_caves(world, lw_entrances, dw_entrances, caves, player): - """This works inplace""" - random.shuffle(lw_entrances) - random.shuffle(dw_entrances) - random.shuffle(caves) - while caves: - # connect highest exit count caves first, prevent issue where we have 2 or 3 exits accross worlds left to fill - cave_candidate = (None, 0) - for i, cave in enumerate(caves): - if isinstance(cave, str): - cave = (cave,) - if len(cave) > cave_candidate[1]: - cave_candidate = (i, len(cave)) - cave = caves.pop(cave_candidate[0]) - - target = lw_entrances if random.randint(0, 1) == 0 else dw_entrances - if isinstance(cave, str): - cave = (cave,) - - # check if we can still fit the cave into our target group - if len(target) < len(cave): - # need to use other set - target = lw_entrances if target is dw_entrances else dw_entrances - - for exit in cave: - connect_two_way(world, target.pop(), exit, player) - - -def connect_doors(world, doors, targets, player): - """This works inplace""" - random.shuffle(doors) - random.shuffle(targets) - placing = min(len(doors), len(targets)) - for door, target in zip(doors, targets): - connect_entrance(world, door, target, player) - doors[:] = doors[placing:] - targets[:] = targets[placing:] - - -def skull_woods_shuffle(world, player): - connect_random(world, ['Skull Woods First Section Hole (East)', 'Skull Woods First Section Hole (West)', 'Skull Woods First Section Hole (North)', 'Skull Woods Second Section Hole'], - ['Skull Left Drop', 'Skull Pinball', 'Skull Pot Circle', 'Skull Back Drop'], player) - connect_random(world, ['Skull Woods First Section Door', 'Skull Woods Second Section Door (East)', 'Skull Woods Second Section Door (West)'], - ['Skull Woods First Section Exit', 'Skull Woods Second Section Exit (East)', 'Skull Woods Second Section Exit (West)'], player, True) - - -def simple_shuffle_dungeons(world, player): - skull_woods_shuffle(world, player) - - dungeon_entrances = ['Eastern Palace', 'Tower of Hera', 'Thieves Town', 'Skull Woods Final Section', 'Palace of Darkness', 'Ice Palace', 'Misery Mire', 'Swamp Palace'] - dungeon_exits = ['Eastern Palace Exit', 'Tower of Hera Exit', 'Thieves Town Exit', 'Skull Woods Final Section Exit', 'Palace of Darkness Exit', 'Ice Palace Exit', 'Misery Mire Exit', 'Swamp Palace Exit'] - - # TODO: Consider letting inverted shuffle GT - if not world.is_atgt_swapped(player) and not world.shuffle_ganon: - connect_two_way(world, 'Ganons Tower', 'Ganons Tower Exit', player) - else: - dungeon_entrances.append('Ganons Tower') - dungeon_exits.append('Ganons Tower Exit') - - # shuffle up single entrance dungeons - connect_random(world, dungeon_entrances, dungeon_exits, player, True) - - # mix up 4 door dungeons - multi_dungeons = ['Desert', 'Turtle Rock'] - if world.mode[player] == 'open' or (world.is_atgt_swapped(player) and world.shuffle_ganon): - multi_dungeons.append('Hyrule Castle') - random.shuffle(multi_dungeons) - - dp_target = multi_dungeons[0] - tr_target = multi_dungeons[1] - if world.mode[player] not in ['open', 'inverted'] or (world.is_atgt_swapped(player) and world.shuffle_ganon is False): - # place hyrule castle as intended - hc_target = 'Hyrule Castle' - else: - hc_target = multi_dungeons[2] - - # door shuffle should restrict hyrule castle to the light world due to sanc being limited to the LW - if world.doorShuffle[player] != 'vanilla' and hc_target == 'Turtle Rock': - swap_w_dp = random.choice([True, False]) - if swap_w_dp: - hc_target, dp_target = dp_target, hc_target - else: - hc_target, tr_target = tr_target, hc_target - - # ToDo improve this? - if hc_target == 'Hyrule Castle': - connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player) - connect_two_way(world, 'Hyrule Castle Entrance (East)', 'Hyrule Castle Exit (East)', player) - connect_two_way(world, 'Hyrule Castle Entrance (West)', 'Hyrule Castle Exit (West)', player) - connect_two_way(world, 'Agahnims Tower', 'Agahnims Tower Exit', player) - elif hc_target == 'Desert': - connect_two_way(world, 'Desert Palace Entrance (South)', 'Hyrule Castle Exit (South)', player) - connect_two_way(world, 'Desert Palace Entrance (East)', 'Hyrule Castle Exit (East)', player) - connect_two_way(world, 'Desert Palace Entrance (West)', 'Hyrule Castle Exit (West)', player) - connect_two_way(world, 'Desert Palace Entrance (North)', 'Agahnims Tower Exit', player) - elif hc_target == 'Turtle Rock': - connect_two_way(world, 'Turtle Rock', 'Hyrule Castle Exit (South)', player) - connect_two_way(world, 'Turtle Rock Isolated Ledge Entrance', 'Hyrule Castle Exit (East)', player) - connect_two_way(world, 'Dark Death Mountain Ledge (West)', 'Hyrule Castle Exit (West)', player) - connect_two_way(world, 'Dark Death Mountain Ledge (East)', 'Agahnims Tower Exit', player) - - if dp_target == 'Hyrule Castle': - connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Desert Palace Exit (South)', player) - connect_two_way(world, 'Hyrule Castle Entrance (East)', 'Desert Palace Exit (East)', player) - connect_two_way(world, 'Hyrule Castle Entrance (West)', 'Desert Palace Exit (West)', player) - connect_two_way(world, 'Agahnims Tower', 'Desert Palace Exit (North)', player) - elif dp_target == 'Desert': - connect_two_way(world, 'Desert Palace Entrance (South)', 'Desert Palace Exit (South)', player) - connect_two_way(world, 'Desert Palace Entrance (East)', 'Desert Palace Exit (East)', player) - connect_two_way(world, 'Desert Palace Entrance (West)', 'Desert Palace Exit (West)', player) - connect_two_way(world, 'Desert Palace Entrance (North)', 'Desert Palace Exit (North)', player) - elif dp_target == 'Turtle Rock': - connect_two_way(world, 'Turtle Rock', 'Desert Palace Exit (South)', player) - connect_two_way(world, 'Turtle Rock Isolated Ledge Entrance', 'Desert Palace Exit (East)', player) - connect_two_way(world, 'Dark Death Mountain Ledge (West)', 'Desert Palace Exit (West)', player) - connect_two_way(world, 'Dark Death Mountain Ledge (East)', 'Desert Palace Exit (North)', player) - - if tr_target == 'Hyrule Castle': - connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Turtle Rock Exit (Front)', player) - connect_two_way(world, 'Hyrule Castle Entrance (East)', 'Turtle Rock Ledge Exit (East)', player) - connect_two_way(world, 'Hyrule Castle Entrance (West)', 'Turtle Rock Ledge Exit (West)', player) - connect_two_way(world, 'Agahnims Tower', 'Turtle Rock Isolated Ledge Exit', player) - elif tr_target == 'Desert': - connect_two_way(world, 'Desert Palace Entrance (South)', 'Turtle Rock Exit (Front)', player) - connect_two_way(world, 'Desert Palace Entrance (North)', 'Turtle Rock Ledge Exit (East)', player) - connect_two_way(world, 'Desert Palace Entrance (West)', 'Turtle Rock Ledge Exit (West)', player) - connect_two_way(world, 'Desert Palace Entrance (East)', 'Turtle Rock Isolated Ledge Exit', player) - elif tr_target == 'Turtle Rock': - connect_two_way(world, 'Turtle Rock', 'Turtle Rock Exit (Front)', player) - connect_two_way(world, 'Turtle Rock Isolated Ledge Entrance', 'Turtle Rock Isolated Ledge Exit', player) - connect_two_way(world, 'Dark Death Mountain Ledge (West)', 'Turtle Rock Ledge Exit (West)', player) - connect_two_way(world, 'Dark Death Mountain Ledge (East)', 'Turtle Rock Ledge Exit (East)', player) - -def unbias_some_entrances(Dungeon_Exits, Cave_Exits, Old_Man_House, Cave_Three_Exits): - def shuffle_lists_in_list(ls): - for i, item in enumerate(ls): - if isinstance(item, list): - ls[i] = random.sample(item, len(item)) - - def tuplize_lists_in_list(ls): - for i, item in enumerate(ls): - if isinstance(item, list): - ls[i] = tuple(item) - - shuffle_lists_in_list(Dungeon_Exits) - shuffle_lists_in_list(Cave_Exits) - shuffle_lists_in_list(Old_Man_House) - shuffle_lists_in_list(Cave_Three_Exits) - - # paradox fixup - if Cave_Three_Exits[1][0] == "Paradox Cave Exit (Bottom)": - i = random.randint(1,2) - Cave_Three_Exits[1][0] = Cave_Three_Exits[1][i] - Cave_Three_Exits[1][i] = "Paradox Cave Exit (Bottom)" - - # TR fixup - tr_fixup = False - for i, item in enumerate(Dungeon_Exits[-1]): - if 'Turtle Rock Ledge Exit (East)' == item: - tr_fixup = True - if 0 != i: - Dungeon_Exits[-1][i] = Dungeon_Exits[-1][0] - Dungeon_Exits[-1][0] = 'Turtle Rock Ledge Exit (East)' - break - - if not tr_fixup: raise RuntimeError("TR entrance shuffle fixup didn't happen") - - tuplize_lists_in_list(Dungeon_Exits) - tuplize_lists_in_list(Cave_Exits) - tuplize_lists_in_list(Old_Man_House) - tuplize_lists_in_list(Cave_Three_Exits) - - -LW_Dungeon_Entrances = ['Desert Palace Entrance (South)', - 'Desert Palace Entrance (West)', - 'Desert Palace Entrance (North)', - 'Eastern Palace', - 'Tower of Hera', - 'Hyrule Castle Entrance (West)', - 'Hyrule Castle Entrance (East)', - 'Agahnims Tower'] - -LW_Dungeon_Entrances_Must_Exit = ['Desert Palace Entrance (East)'] - -DW_Dungeon_Entrances = ['Thieves Town', - 'Skull Woods Final Section', - 'Ice Palace', - 'Misery Mire', - 'Palace of Darkness', - 'Swamp Palace', - 'Turtle Rock', - 'Dark Death Mountain Ledge (West)'] - -DW_Dungeon_Entrances_Must_Exit = ['Dark Death Mountain Ledge (East)', - 'Turtle Rock Isolated Ledge Entrance'] - -Dungeon_Exits_Base = [['Desert Palace Exit (South)', 'Desert Palace Exit (West)', 'Desert Palace Exit (East)'], - 'Desert Palace Exit (North)', - 'Eastern Palace Exit', - 'Tower of Hera Exit', - 'Thieves Town Exit', - 'Skull Woods Final Section Exit', - 'Ice Palace Exit', - 'Misery Mire Exit', - 'Palace of Darkness Exit', - 'Swamp Palace Exit', - 'Agahnims Tower Exit', - ['Turtle Rock Ledge Exit (East)', - 'Turtle Rock Exit (Front)', 'Turtle Rock Ledge Exit (West)', 'Turtle Rock Isolated Ledge Exit']] - -DW_Entrances_Must_Exit = ['Bumper Cave (Top)', 'Hookshot Cave Back Entrance'] - -Two_Door_Caves_Directional = [('Bumper Cave (Bottom)', 'Bumper Cave (Top)'), - ('Hookshot Cave', 'Hookshot Cave Back Entrance')] - -Two_Door_Caves = [('Elder House (East)', 'Elder House (West)'), - ('Two Brothers House (East)', 'Two Brothers House (West)'), - ('Superbunny Cave (Bottom)', 'Superbunny Cave (Top)')] - -Old_Man_Entrances = ['Old Man Cave (East)', - 'Old Man House (Top)', - 'Death Mountain Return Cave (East)', - 'Spectacle Rock Cave', - 'Spectacle Rock Cave Peak', - 'Spectacle Rock Cave (Bottom)'] - -Old_Man_House_Base = [['Old Man House Exit (Bottom)', 'Old Man House Exit (Top)']] - -Cave_Exits_Base = [['Elder House Exit (East)', 'Elder House Exit (West)'], - ['Two Brothers House Exit (East)', 'Two Brothers House Exit (West)'], - ['Death Mountain Return Cave Exit (West)', 'Death Mountain Return Cave Exit (East)'], - ['Fairy Ascension Cave Exit (Bottom)', 'Fairy Ascension Cave Exit (Top)'], - ['Bumper Cave Exit (Top)', 'Bumper Cave Exit (Bottom)'], - ['Hookshot Cave Back Exit', 'Hookshot Cave Front Exit']] - -Cave_Exits_Base += [('Superbunny Cave Exit (Bottom)', 'Superbunny Cave Exit (Top)'), - ('Spiral Cave Exit (Top)', 'Spiral Cave Exit')] - - -Cave_Three_Exits_Base = [('Spectacle Rock Cave Exit (Peak)', 'Spectacle Rock Cave Exit (Top)', - 'Spectacle Rock Cave Exit'), - ['Paradox Cave Exit (Top)', 'Paradox Cave Exit (Middle)','Paradox Cave Exit (Bottom)']] - - -LW_Entrances = ['Elder House (East)', - 'Elder House (West)', - 'Two Brothers House (East)', - 'Two Brothers House (West)', - 'Old Man Cave (West)', - 'Old Man House (Bottom)', - 'Death Mountain Return Cave (West)', - 'Paradox Cave (Bottom)', - 'Paradox Cave (Middle)', - 'Paradox Cave (Top)', - 'Fairy Ascension Cave (Bottom)', - 'Fairy Ascension Cave (Top)', - 'Spiral Cave', - 'Spiral Cave (Bottom)'] - -DW_Entrances = ['Bumper Cave (Bottom)', - 'Superbunny Cave (Top)', - 'Superbunny Cave (Bottom)', - 'Hookshot Cave'] - -Bomb_Shop_Multi_Cave_Doors = ['Hyrule Castle Entrance (South)', - 'Misery Mire', - 'Thieves Town', - 'Bumper Cave (Bottom)', - 'Swamp Palace', - 'Hyrule Castle Secret Entrance Stairs', - 'Skull Woods First Section Door', - 'Skull Woods Second Section Door (East)', - 'Skull Woods Second Section Door (West)', - 'Skull Woods Final Section', - 'Ice Palace', - 'Turtle Rock', - 'Dark Death Mountain Ledge (West)', - 'Dark Death Mountain Ledge (East)', - 'Superbunny Cave (Top)', - 'Superbunny Cave (Bottom)', - 'Hookshot Cave', - 'Ganons Tower', - 'Desert Palace Entrance (South)', - 'Tower of Hera', - 'Two Brothers House (West)', - 'Old Man Cave (East)', - 'Old Man House (Bottom)', - 'Old Man House (Top)', - 'Death Mountain Return Cave (East)', - 'Death Mountain Return Cave (West)', - 'Spectacle Rock Cave Peak', - 'Spectacle Rock Cave', - 'Spectacle Rock Cave (Bottom)', - 'Paradox Cave (Bottom)', - 'Paradox Cave (Middle)', - 'Paradox Cave (Top)', - 'Fairy Ascension Cave (Bottom)', - 'Fairy Ascension Cave (Top)', - 'Spiral Cave', - 'Spiral Cave (Bottom)', - 'Palace of Darkness', - 'Hyrule Castle Entrance (West)', - 'Hyrule Castle Entrance (East)', - 'Agahnims Tower', - 'Desert Palace Entrance (West)', - 'Desert Palace Entrance (North)' - # all entrances below this line would be possible for blacksmith_hut - # if it were not for dwarf checking multi-entrance caves - ] - -Blacksmith_Multi_Cave_Doors = ['Eastern Palace', - 'Elder House (East)', - 'Elder House (West)', - 'Two Brothers House (East)', - 'Old Man Cave (West)', - 'Sanctuary', - 'Lumberjack Tree Cave', - 'Lost Woods Hideout Stump', - 'North Fairy Cave', - 'Bat Cave Cave', - 'Kakariko Well Cave'] - -LW_Single_Cave_Doors = ['Blinds Hideout', - 'Lake Hylia Fairy', - 'Light Hype Fairy', - 'Desert Fairy', - 'Chicken House', - 'Aginahs Cave', - 'Sahasrahlas Hut', - 'Lake Hylia Shop', - 'Blacksmiths Hut', - 'Sick Kids House', - 'Lost Woods Gamble', - 'Fortune Teller (Light)', - 'Snitch Lady (East)', - 'Snitch Lady (West)', - 'Bush Covered House', - 'Tavern (Front)', - 'Light World Bomb Hut', - 'Kakariko Shop', - 'Mini Moldorm Cave', - 'Long Fairy Cave', - 'Good Bee Cave', - '20 Rupee Cave', - '50 Rupee Cave', - 'Ice Rod Cave', - 'Library', - 'Potion Shop', - 'Dam', - 'Lumberjack House', - 'Lake Hylia Fortune Teller', - 'Kakariko Gamble Game', - 'Waterfall of Wishing', - 'Capacity Upgrade', - 'Bonk Rock Cave', - 'Graveyard Cave', - 'Checkerboard Cave', - 'Cave 45', - 'Kings Grave', - 'Bonk Fairy (Light)', - 'Hookshot Fairy', - 'Mimic Cave', - 'Links House'] - -Isolated_LH_Doors_Open = ['Mimic Cave', - 'Kings Grave', - 'Waterfall of Wishing', - 'Desert Palace Entrance (South)', - 'Desert Palace Entrance (North)', - 'Capacity Upgrade', - 'Ice Palace', 'Dark World Shop', 'Dark Potion Shop', - 'Skull Woods Final Section', 'Skull Woods Second Section Door (West)', - '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', - 'Dark Lake Hylia Fairy', - 'C-Shaped House', - 'Big Bomb Shop', - 'Dark Death Mountain Fairy', - 'Dark Lake Hylia Shop', - 'Dark World Shop', - 'Red Shield Shop', - 'Mire Shed', - 'East Dark World Hint', - 'Mire Hint', - 'Spike Cave', - 'Palace of Darkness Hint', - 'Dark Lake Hylia Ledge Spike Cave', - 'Dark Death Mountain Shop', - 'Dark Potion Shop', - 'Pyramid Fairy', - 'Archery Game', - 'Dark Lumberjack Shop', - 'Hype Cave', - 'Brewery', - 'Dark Lake Hylia Ledge Hint', - 'Chest Game', - 'Mire Fairy', - 'Dark Lake Hylia Ledge Fairy', - 'Fortune Teller (Dark)', - 'Hammer Peg Cave'] - -Blacksmith_Single_Cave_Doors = ['Blinds Hideout', - 'Lake Hylia Fairy', - 'Light Hype Fairy', - 'Desert Fairy', - 'Chicken House', - 'Aginahs Cave', - 'Sahasrahlas Hut', - 'Lake Hylia Shop', - 'Blacksmiths Hut', - 'Sick Kids House', - 'Lost Woods Gamble', - 'Fortune Teller (Light)', - 'Snitch Lady (East)', - 'Snitch Lady (West)', - 'Bush Covered House', - 'Tavern (Front)', - 'Light World Bomb Hut', - 'Kakariko Shop', - 'Mini Moldorm Cave', - 'Long Fairy Cave', - 'Good Bee Cave', - '20 Rupee Cave', - '50 Rupee Cave', - 'Ice Rod Cave', - 'Library', - 'Potion Shop', - 'Dam', - 'Lumberjack House', - 'Lake Hylia Fortune Teller', - 'Kakariko Gamble Game'] - -Bomb_Shop_Single_Cave_Doors = ['Waterfall of Wishing', - 'Capacity Upgrade', - 'Bonk Rock Cave', - 'Graveyard Cave', - 'Checkerboard Cave', - 'Cave 45', - 'Kings Grave', - 'Bonk Fairy (Light)', - 'Hookshot Fairy', - 'East Dark World Hint', - 'Palace of Darkness Hint', - 'Dark Lake Hylia Fairy', - 'Dark Lake Hylia Ledge Fairy', - 'Dark Lake Hylia Ledge Spike Cave', - 'Dark Lake Hylia Ledge Hint', - 'Hype Cave', - 'Bonk Fairy (Dark)', - 'Brewery', - 'C-Shaped House', - 'Chest Game', - 'Hammer Peg Cave', - 'Red Shield Shop', - 'Dark Sanctuary Hint', - 'Fortune Teller (Dark)', - 'Dark World Shop', - 'Dark Lumberjack Shop', - 'Dark Potion Shop', - 'Archery Game', - 'Mire Shed', - 'Mire Hint', - 'Mire Fairy', - 'Spike Cave', - 'Dark Death Mountain Shop', - 'Dark Death Mountain Fairy', - 'Mimic Cave', - 'Big Bomb Shop', - 'Dark Lake Hylia Shop'] - -Single_Cave_Doors = ['Pyramid Fairy'] - -Single_Cave_Targets = ['Blinds Hideout', - 'Bonk Fairy (Light)', - 'Lake Hylia Healer Fairy', - 'Light Hype Fairy', - 'Desert Healer Fairy', - 'Kings Grave', - 'Chicken House', - 'Aginahs Cave', - 'Sahasrahlas Hut', - 'Lake Hylia Shop', - 'Sick Kids House', - 'Lost Woods Gamble', - 'Fortune Teller (Light)', - 'Snitch Lady (East)', - 'Snitch Lady (West)', - 'Bush Covered House', - 'Tavern (Front)', - 'Light World Bomb Hut', - 'Kakariko Shop', - 'Cave 45', - 'Graveyard Cave', - 'Checkerboard Cave', - 'Mini Moldorm Cave', - 'Long Fairy Cave', - 'Good Bee Cave', - '20 Rupee Cave', - '50 Rupee Cave', - 'Ice Rod Cave', - 'Bonk Rock Cave', - 'Library', - 'Potion Shop', - 'Hookshot Fairy', - 'Waterfall of Wishing', - 'Capacity Upgrade', - 'Pyramid Fairy', - 'East Dark World Hint', - 'Palace of Darkness Hint', - 'Dark Lake Hylia Healer Fairy', - 'Dark Lake Hylia Ledge Healer Fairy', - 'Dark Lake Hylia Ledge Spike Cave', - 'Dark Lake Hylia Ledge Hint', - 'Hype Cave', - 'Bonk Fairy (Dark)', - 'Brewery', - 'C-Shaped House', - 'Chest Game', - 'Hammer Peg Cave', - 'Red Shield Shop', - 'Dark Sanctuary Hint', - 'Fortune Teller (Dark)', - 'Village of Outcasts Shop', - 'Dark Lake Hylia Shop', - 'Dark Lumberjack Shop', - 'Archery Game', - 'Mire Shed', - 'Mire Hint', - 'Mire Healer Fairy', - 'Spike Cave', - 'Dark Death Mountain Shop', - 'Dark Death Mountain Healer Fairy', - 'Mimic Cave', - 'Dark Potion Shop', - 'Lumberjack House', - 'Lake Hylia Fortune Teller', - 'Kakariko Gamble Game', - 'Dam'] - -Inverted_LW_Dungeon_Entrances = ['Desert Palace Entrance (South)', - 'Eastern Palace', - 'Tower of Hera', - 'Hyrule Castle Entrance (West)', - 'Hyrule Castle Entrance (East)'] - -Inverted_DW_Dungeon_Entrances = ['Thieves Town', - 'Skull Woods Final Section', - 'Ice Palace', - 'Misery Mire', - 'Palace of Darkness', - 'Swamp Palace', - 'Turtle Rock', - 'Dark Death Mountain Ledge (West)', - 'Dark Death Mountain Ledge (East)', - 'Turtle Rock Isolated Ledge Entrance', - 'Ganons Tower'] - -Inverted_LW_Dungeon_Entrances_Must_Exit = ['Desert Palace Entrance (East)'] - -Inverted_Dungeon_Exits_Base = [['Desert Palace Exit (South)', 'Desert Palace Exit (West)', 'Desert Palace Exit (East)'], - 'Desert Palace Exit (North)', - 'Eastern Palace Exit', - 'Tower of Hera Exit', - 'Thieves Town Exit', - 'Skull Woods Final Section Exit', - 'Ice Palace Exit', - 'Misery Mire Exit', - 'Palace of Darkness Exit', - 'Swamp Palace Exit', - 'Agahnims Tower Exit', - ['Turtle Rock Ledge Exit (East)', - 'Turtle Rock Exit (Front)', 'Turtle Rock Ledge Exit (West)', 'Turtle Rock Isolated Ledge Exit']] - -Inverted_LW_Entrances_Must_Exit = ['Death Mountain Return Cave (West)', - 'Two Brothers House (West)'] - -Inverted_Two_Door_Caves_Directional = [('Old Man Cave (West)', 'Death Mountain Return Cave (West)'), - ('Two Brothers House (East)', 'Two Brothers House (West)')] - - -Inverted_Two_Door_Caves = [('Elder House (East)', 'Elder House (West)'), - ('Superbunny Cave (Bottom)', 'Superbunny Cave (Top)'), - ('Hookshot Cave', 'Hookshot Cave Back Entrance')] - - - -Inverted_Old_Man_Entrances = ['Dark Death Mountain Fairy', - 'Spike Cave'] - -Inverted_LW_Entrances = ['Elder House (East)', - 'Elder House (West)', - 'Two Brothers House (East)', - 'Old Man Cave (East)', - 'Old Man Cave (West)', - 'Old Man House (Bottom)', - 'Old Man House (Top)', - 'Death Mountain Return Cave (East)', - 'Paradox Cave (Bottom)', - 'Paradox Cave (Middle)', - 'Paradox Cave (Top)', - 'Spectacle Rock Cave', - 'Spectacle Rock Cave Peak', - 'Spectacle Rock Cave (Bottom)', - 'Fairy Ascension Cave (Bottom)', - 'Fairy Ascension Cave (Top)', - 'Spiral Cave', - 'Spiral Cave (Bottom)'] - - -Inverted_DW_Entrances = ['Bumper Cave (Bottom)', - 'Superbunny Cave (Top)', - 'Superbunny Cave (Bottom)', - 'Hookshot Cave', - 'Hookshot Cave Back Entrance'] - -Inverted_Bomb_Shop_Multi_Cave_Doors = ['Hyrule Castle Entrance (South)', - 'Misery Mire', - 'Thieves Town', - 'Bumper Cave (Bottom)', - 'Swamp Palace', - 'Hyrule Castle Secret Entrance Stairs', - 'Skull Woods First Section Door', - 'Skull Woods Second Section Door (East)', - 'Skull Woods Second Section Door (West)', - 'Skull Woods Final Section', - 'Ice Palace', - 'Turtle Rock', - 'Dark Death Mountain Ledge (West)', - 'Dark Death Mountain Ledge (East)', - 'Superbunny Cave (Top)', - 'Superbunny Cave (Bottom)', - 'Hookshot Cave', - 'Ganons Tower', - 'Desert Palace Entrance (South)', - 'Tower of Hera', - 'Two Brothers House (West)', - 'Old Man Cave (East)', - 'Old Man House (Bottom)', - 'Old Man House (Top)', - 'Death Mountain Return Cave (East)', - 'Death Mountain Return Cave (West)', - 'Spectacle Rock Cave Peak', - 'Paradox Cave (Bottom)', - 'Paradox Cave (Middle)', - 'Paradox Cave (Top)', - 'Fairy Ascension Cave (Bottom)', - 'Fairy Ascension Cave (Top)', - 'Spiral Cave', - 'Spiral Cave (Bottom)', - 'Palace of Darkness', - 'Hyrule Castle Entrance (West)', - 'Hyrule Castle Entrance (East)', - 'Agahnims Tower', - 'Desert Palace Entrance (West)', - 'Desert Palace Entrance (North)'] - -Inverted_Blacksmith_Multi_Cave_Doors = Blacksmith_Multi_Cave_Doors # same as non-inverted - -Inverted_LW_Single_Cave_Doors = [x for x in LW_Single_Cave_Doors] - -Inverted_DW_Single_Cave_Doors = ['Bonk Fairy (Dark)', - 'Dark Sanctuary Hint', - 'Big Bomb Shop', - 'Dark Lake Hylia Fairy', - 'C-Shaped House', - 'Bumper Cave (Top)', - 'Dark Lake Hylia Shop', - 'Dark World Shop', - 'Red Shield Shop', - 'Mire Shed', - 'East Dark World Hint', - 'Mire Hint', - 'Palace of Darkness Hint', - 'Dark Lake Hylia Ledge Spike Cave', - 'Dark Death Mountain Shop', - 'Dark Potion Shop', - 'Pyramid Fairy', - 'Archery Game', - 'Dark Lumberjack Shop', - 'Hype Cave', - 'Brewery', - 'Dark Lake Hylia Ledge Hint', - 'Chest Game', - 'Mire Fairy', - 'Dark Lake Hylia Ledge Fairy', - 'Fortune Teller (Dark)', - 'Hammer Peg Cave'] - - -Inverted_Bomb_Shop_Single_Cave_Doors = ['Waterfall of Wishing', - 'Capacity Upgrade', - 'Bonk Rock Cave', - 'Graveyard Cave', - 'Checkerboard Cave', - 'Cave 45', - 'Kings Grave', - 'Bonk Fairy (Light)', - 'Hookshot Fairy', - 'East Dark World Hint', - 'Palace of Darkness Hint', - 'Dark Lake Hylia Fairy', - 'Dark Lake Hylia Ledge Fairy', - 'Dark Lake Hylia Ledge Spike Cave', - 'Dark Lake Hylia Ledge Hint', - 'Hype Cave', - 'Bonk Fairy (Dark)', - 'Brewery', - 'C-Shaped House', - 'Chest Game', - 'Hammer Peg Cave', - 'Red Shield Shop', - 'Dark Sanctuary Hint', - 'Fortune Teller (Dark)', - 'Dark World Shop', - 'Dark Lumberjack Shop', - 'Dark Potion Shop', - 'Archery Game', - 'Mire Shed', - 'Mire Hint', - 'Mire Fairy', - 'Spike Cave', - 'Dark Death Mountain Shop', - 'Bumper Cave (Top)', - 'Mimic Cave', - 'Dark Lake Hylia Shop', - 'Big Bomb Shop', - 'Links House'] - -Inverted_Blacksmith_Single_Cave_Doors = ['Blinds Hideout', - 'Lake Hylia Fairy', - 'Light Hype Fairy', - 'Desert Fairy', - 'Chicken House', - 'Aginahs Cave', - 'Sahasrahlas Hut', - 'Lake Hylia Shop', - 'Blacksmiths Hut', - 'Sick Kids House', - 'Lost Woods Gamble', - 'Fortune Teller (Light)', - 'Snitch Lady (East)', - 'Snitch Lady (West)', - 'Bush Covered House', - 'Tavern (Front)', - 'Light World Bomb Hut', - 'Kakariko Shop', - 'Mini Moldorm Cave', - 'Long Fairy Cave', - 'Good Bee Cave', - '20 Rupee Cave', - '50 Rupee Cave', - 'Ice Rod Cave', - 'Library', - 'Potion Shop', - 'Dam', - 'Lumberjack House', - 'Lake Hylia Fortune Teller', - 'Kakariko Gamble Game', - 'Links House'] - -Inverted_Single_Cave_Targets = ['Blinds Hideout', - 'Bonk Fairy (Light)', - 'Lake Hylia Healer Fairy', - 'Light Hype Fairy', - 'Desert Healer Fairy', - 'Kings Grave', - 'Chicken House', - 'Aginahs Cave', - 'Sahasrahlas Hut', - 'Lake Hylia Shop', - 'Sick Kids House', - 'Lost Woods Gamble', - 'Fortune Teller (Light)', - 'Snitch Lady (East)', - 'Snitch Lady (West)', - 'Bush Covered House', - 'Tavern (Front)', - 'Light World Bomb Hut', - 'Kakariko Shop', - 'Cave 45', - 'Graveyard Cave', - 'Checkerboard Cave', - 'Mini Moldorm Cave', - 'Long Fairy Cave', - 'Good Bee Cave', - '20 Rupee Cave', - '50 Rupee Cave', - 'Ice Rod Cave', - 'Bonk Rock Cave', - 'Library', - 'Potion Shop', - 'Hookshot Fairy', - 'Waterfall of Wishing', - 'Capacity Upgrade', - 'Pyramid Fairy', - 'East Dark World Hint', - 'Palace of Darkness Hint', - 'Dark Lake Hylia Healer Fairy', - 'Dark Lake Hylia Ledge Healer Fairy', - 'Dark Lake Hylia Ledge Spike Cave', - 'Dark Lake Hylia Ledge Hint', - 'Hype Cave', - 'Bonk Fairy (Dark)', - 'Brewery', - 'C-Shaped House', - 'Chest Game', - 'Hammer Peg Cave', - 'Red Shield Shop', - 'Fortune Teller (Dark)', - 'Village of Outcasts Shop', - 'Dark Lake Hylia Shop', - 'Dark Lumberjack Shop', - 'Archery Game', - 'Mire Shed', - 'Mire Hint', - 'Mire Healer Fairy', - 'Spike Cave', - 'Dark Death Mountain Shop', - 'Dark Death Mountain Healer Fairy', - 'Mimic Cave', - 'Dark Potion Shop', - 'Lumberjack House', - 'Lake Hylia Fortune Teller', - 'Kakariko Gamble Game', - 'Dam'] - -# in inverted we put dark sanctuary in west dark world for now -Inverted_Dark_Sanctuary_Doors = ['Dark Sanctuary Hint', - 'Fortune Teller (Dark)', - 'Brewery', - 'C-Shaped House', - 'Chest Game', - 'Dark Lumberjack Shop', - 'Red Shield Shop', - 'Bumper Cave (Bottom)', - 'Bumper Cave (Top)', - 'Thieves Town'] - -Isolated_LH_Doors = ['Kings Grave', - 'Waterfall of Wishing', - 'Desert Palace Entrance (South)', - 'Desert Palace Entrance (North)', - 'Capacity Upgrade', - 'Ice Palace', 'Dark World Shop', 'Dark Potion Shop', - 'Skull Woods Final Section', 'Skull Woods Second Section Door (West)', - '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, { - 'Dark Death Mountain Ledge (East)': {'Dark Death Mountain Ledge (West)', 'Mimic Cave'}, - 'Dark Death Mountain Ledge (West)': {'Dark Death Mountain Ledge (East)', 'Mimic Cave'}, - 'Mimic Cave': {'Dark Death Mountain Ledge (West)', 'Dark Death Mountain Ledge (East)'}, - 'Bumper Cave (Top)': {'Death Mountain Return Cave (West)'}, - 'Death Mountain Return Cave (West)': {'Bumper Cave (Top)'}, - 'Skull Woods Second Section Door (West)': {'Skull Woods Final Section'}, - 'Skull Woods Final Section': {'Skull Woods Second Section Door (West)'}, -}) -Inverted_Must_Exit_Invalid_Connections = defaultdict(set, { - 'Bumper Cave (Top)': {'Death Mountain Return Cave (West)'}, - 'Death Mountain Return Cave (West)': {'Bumper Cave (Top)'}, - 'Desert Palace Entrance (North)': {'Desert Palace Entrance (West)'}, - 'Desert Palace Entrance (West)': {'Desert Palace Entrance (North)'}, - 'Agahnims Tower': {'Hyrule Castle Entrance (West)', 'Hyrule Castle Entrance (East)'}, - 'Hyrule Castle Entrance (West)': {'Hyrule Castle Entrance (East)', 'Agahnims Tower'}, - 'Hyrule Castle Entrance (East)': {'Hyrule Castle Entrance (West)', 'Agahnims Tower'}, -}) - - -# these are connections that cannot be shuffled and always exist. -# They link together separate parts of the world we need to divide into regions -mandatory_connections = [# underworld - ('Lost Woods Hideout (top to bottom)', 'Lost Woods Hideout (bottom)'), - ('Lumberjack Tree (top to bottom)', 'Lumberjack Tree (bottom)'), - ('Death Mountain Return Cave E', 'Death Mountain Return Cave (right)'), - ('Death Mountain Return Cave W', 'Death Mountain Return Cave (left)'), - ('Old Man Cave Dropdown', 'Old Man Cave (West)'), - ('Old Man Cave W', 'Old Man Cave (West)'), - ('Old Man Cave E', 'Old Man Cave (East)'), - ('Spectacle Rock Cave Drop', 'Spectacle Rock Cave Pool'), - ('Spectacle Rock Cave Peak Drop', 'Spectacle Rock Cave Pool'), - ('Spectacle Rock Cave West Edge', 'Spectacle Rock Cave (Bottom)'), - ('Spectacle Rock Cave East Edge', 'Spectacle Rock Cave Pool'), - ('Old Man House Front to Back', 'Old Man House Back'), - ('Old Man House Back to Front', 'Old Man House'), - ('Spiral Cave (top to bottom)', 'Spiral Cave (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'), - ('Paradox Shop', 'Paradox Shop'), - ('Fairy Ascension Cave Climb', 'Fairy Ascension Cave (Top)'), - ('Fairy Ascension Cave Pots', 'Fairy Ascension Cave (Bottom)'), - ('Fairy Ascension Cave Drop', 'Fairy Ascension Cave (Drop)'), - ('Sewer Drop', 'Sewers Rat Path'), - ('Kakariko Well (top to bottom)', 'Kakariko Well (bottom)'), - ('Kakariko Well (top to back)', 'Kakariko Well (back)'), - ('Blinds Hideout N', 'Blinds Hideout (Top)'), - ('Bat Cave Door', 'Bat Cave (left)'), - ('Good Bee Cave Front to Back', 'Good Bee Cave (back)'), - ('Good Bee Cave Back to Front', 'Good Bee Cave'), - ('Capacity Upgrade East', 'Capacity Fairy Pool'), - ('Capacity Fairy Pool West', 'Capacity Upgrade'), - ('Bonk Fairy (Dark) Pool', 'Bonk Fairy Pool'), - ('Bonk Fairy (Light) Pool', 'Bonk Fairy Pool'), - - ('Hookshot Cave Front to Middle', 'Hookshot Cave (Middle)'), - ('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 Back to Fairy', 'Hookshot Cave (Fairy Pool)'), - ('Hookshot Cave Fairy to Back', 'Hookshot Cave (Back)'), - ('Hookshot Cave Bonk Path', 'Hookshot Cave (Bonk Islands)'), - ('Hookshot Cave Hook Path', 'Hookshot Cave (Hook Islands)'), - ('Superbunny Cave Climb', 'Superbunny Cave (Top)'), - ('Bumper Cave Bottom to Top', 'Bumper Cave (top)'), - ('Bumper Cave Top To Bottom', 'Bumper Cave (bottom)'), - ('Ganon Drop', 'Bottom of Pyramid') - ] - -# non-shuffled entrance links -default_connections = [('Lost Woods Gamble', 'Lost Woods Gamble'), - ('Lost Woods Hideout Drop', 'Lost Woods Hideout (top)'), - ('Lost Woods Hideout Stump', 'Lost Woods Hideout (bottom)'), - ('Lost Woods Hideout Exit', 'Lost Woods East Area'), - ('Lumberjack House', 'Lumberjack House'), - ('Lumberjack Tree Tree', 'Lumberjack Tree (top)'), - ('Lumberjack Tree Cave', 'Lumberjack Tree (bottom)'), - ('Lumberjack Tree Exit', 'Lumberjack Area'), - ('Death Mountain Return Cave (East)', 'Death Mountain Return Cave (right)'), - ('Death Mountain Return Cave Exit (East)', 'West Death Mountain (Bottom)'), - ('Spectacle Rock Cave Peak', 'Spectacle Rock Cave (Peak)'), - ('Spectacle Rock Cave (Bottom)', 'Spectacle Rock Cave (Bottom)'), - ('Spectacle Rock Cave', 'Spectacle Rock Cave (Top)'), - ('Spectacle Rock Cave Exit', 'West Death Mountain (Bottom)'), - ('Spectacle Rock Cave Exit (Top)', 'West Death Mountain (Bottom)'), - ('Spectacle Rock Cave Exit (Peak)', 'West Death Mountain (Bottom)'), - ('Old Man House (Bottom)', 'Old Man House'), - ('Old Man House Exit (Bottom)', 'West Death Mountain (Bottom)'), - ('Old Man House (Top)', 'Old Man House Back'), - ('Old Man House Exit (Top)', 'West Death Mountain (Bottom)'), - ('Spiral Cave', 'Spiral Cave (Top)'), - ('Spiral Cave (Bottom)', 'Spiral Cave (Bottom)'), - ('Spiral Cave Exit', 'East Death Mountain (Bottom)'), - ('Spiral Cave Exit (Top)', 'Spiral Cave Ledge'), - ('Mimic Cave', 'Mimic Cave'), - ('Fairy Ascension Cave (Bottom)', 'Fairy Ascension Cave (Bottom)'), - ('Fairy Ascension Cave (Top)', 'Fairy Ascension Cave (Top)'), - ('Fairy Ascension Cave Exit (Bottom)', 'Fairy Ascension Plateau'), - ('Fairy Ascension Cave Exit (Top)', 'Fairy Ascension Ledge'), - ('Hookshot Fairy', 'Hookshot Fairy'), - ('Paradox Cave (Bottom)', 'Paradox Cave Front'), - ('Paradox Cave (Middle)', 'Paradox Cave'), - ('Paradox Cave (Top)', 'Paradox Cave'), - ('Paradox Cave Exit (Bottom)', 'East Death Mountain (Bottom)'), - ('Paradox Cave Exit (Middle)', 'East Death Mountain (Bottom)'), - ('Paradox Cave Exit (Top)', 'East Death Mountain (Top East)'), - ('Waterfall of Wishing', 'Waterfall of Wishing'), - ('Fortune Teller (Light)', 'Fortune Teller (Light)'), - ('Bonk Rock Cave', 'Bonk Rock Cave'), - ('Sanctuary', 'Sanctuary Portal'), - ('Sanctuary Exit', 'Sanctuary Area'), - ('Sanctuary Grave', 'Sewer Drop'), - ('Graveyard Cave', 'Graveyard Cave'), - ('Kings Grave', 'Kings Grave'), - ('North Fairy Cave Drop', 'North Fairy Cave'), - ('North Fairy Cave', 'North Fairy Cave'), - ('North Fairy Cave Exit', 'River Bend Area'), - ('Potion Shop', 'Potion Shop'), - ('Kakariko Well Drop', 'Kakariko Well (top)'), - ('Kakariko Well Cave', 'Kakariko Well (bottom)'), - ('Kakariko Well Exit', 'Kakariko Village'), - ('Blinds Hideout', 'Blinds Hideout'), - ('Elder House (West)', 'Elder House'), - ('Elder House (East)', 'Elder House'), - ('Elder House Exit (West)', 'Kakariko Village'), - ('Elder House Exit (East)', 'Kakariko Village'), - ('Snitch Lady (West)', 'Snitch Lady (West)'), - ('Snitch Lady (East)', 'Snitch Lady (East)'), - ('Bush Covered House', 'Bush Covered House'), - ('Chicken House', 'Chicken House'), - ('Sick Kids House', 'Sick Kids House'), - ('Light World Bomb Hut', 'Light World Bomb Hut'), - ('Kakariko Shop', 'Kakariko Shop'), - ('Tavern North', 'Tavern'), - ('Tavern (Front)', 'Tavern (Front)'), - ('Hyrule Castle Secret Entrance Drop', 'Hyrule Castle Secret Entrance'), - ('Hyrule Castle Secret Entrance Stairs', 'Hyrule Castle Secret Entrance'), - ('Hyrule Castle Secret Entrance Exit', 'Hyrule Castle Courtyard Northeast'), - ('Sahasrahlas Hut', 'Sahasrahlas Hut'), - ('Blacksmiths Hut', 'Blacksmiths Hut'), - ('Bat Cave Drop', 'Bat Cave (right)'), - ('Bat Cave Cave', 'Bat Cave (left)'), - ('Bat Cave Exit', 'Blacksmith Area'), - ('Two Brothers House (West)', 'Two Brothers House'), - ('Two Brothers House Exit (West)', 'Maze Race Ledge'), - ('Two Brothers House (East)', 'Two Brothers House'), - ('Two Brothers House Exit (East)', 'Kakariko Suburb Area'), - ('Library', 'Library'), - ('Kakariko Gamble Game', 'Kakariko Gamble Game'), - ('Bonk Fairy (Light)', 'Bonk Fairy (Light)'), - ('Lake Hylia Fairy', 'Lake Hylia Healer Fairy'), - ('Long Fairy Cave', 'Long Fairy Cave'), - ('Checkerboard Cave', 'Checkerboard Cave'), - ('Aginahs Cave', 'Aginahs Cave'), - ('Cave 45', 'Cave 45'), - ('Light Hype Fairy', 'Light Hype Fairy'), - ('Lake Hylia Fortune Teller', 'Lake Hylia Fortune Teller'), - ('Lake Hylia Shop', 'Lake Hylia Shop'), - ('Capacity Upgrade', 'Capacity Upgrade'), - ('Mini Moldorm Cave', 'Mini Moldorm Cave'), - ('Ice Rod Cave', 'Ice Rod Cave'), - ('Good Bee Cave', 'Good Bee Cave'), - ('20 Rupee Cave', '20 Rupee Cave'), - ('Desert Fairy', 'Desert Healer Fairy'), - ('50 Rupee Cave', '50 Rupee Cave'), - ('Dam', 'Dam'), - - ('Dark Lumberjack Shop', 'Dark Lumberjack Shop'), - ('Spike Cave', 'Spike Cave'), - ('Hookshot Cave Back Exit', 'Dark Death Mountain Floating Island'), - ('Hookshot Cave Back Entrance', 'Hookshot Cave (Back)'), - ('Hookshot Cave', 'Hookshot Cave (Front)'), - ('Hookshot Cave Front Exit', 'East Dark Death Mountain (Top)'), - ('Superbunny Cave (Top)', 'Superbunny Cave (Top)'), - ('Superbunny Cave Exit (Top)', 'East Dark Death Mountain (Top)'), - ('Superbunny Cave (Bottom)', 'Superbunny Cave (Bottom)'), - ('Superbunny Cave Exit (Bottom)', 'East Dark Death Mountain (Bottom)'), - ('Dark Death Mountain Shop', 'Dark Death Mountain Shop'), - ('Fortune Teller (Dark)', 'Fortune Teller (Dark)'), - ('Dark Sanctuary Hint', 'Dark Sanctuary Hint'), - ('Dark Potion Shop', 'Dark Potion Shop'), - ('Chest Game', 'Chest Game'), - ('C-Shaped House', 'C-Shaped House'), - ('Brewery', 'Brewery'), - ('Dark World Shop', 'Village of Outcasts Shop'), - ('Hammer Peg Cave', 'Hammer Peg Cave'), - ('Red Shield Shop', 'Red Shield Shop'), - ('Pyramid Fairy', 'Pyramid Fairy'), - ('Palace of Darkness Hint', 'Palace of Darkness Hint'), - ('Archery Game', 'Archery Game'), - ('Bonk Fairy (Dark)', 'Bonk Fairy (Dark)'), - ('Dark Lake Hylia Fairy', 'Dark Lake Hylia Healer Fairy'), - ('East Dark World Hint', 'East Dark World Hint'), - ('Mire Shed', 'Mire Shed'), - ('Mire Fairy', 'Mire Healer Fairy'), - ('Mire Hint', 'Mire Hint'), - ('Hype Cave', 'Hype Cave'), - ('Dark Lake Hylia Shop', 'Dark Lake Hylia Shop'), - ('Dark Lake Hylia Ledge Fairy', 'Dark Lake Hylia Ledge Healer Fairy'), - ('Dark Lake Hylia Ledge Hint', 'Dark Lake Hylia Ledge Hint'), - ('Dark Lake Hylia Ledge Spike Cave', 'Dark Lake Hylia Ledge Spike Cave') - ] - -open_default_connections = [('Links House', 'Links House'), - ('Links House Exit', 'Links House Area'), - ('Big Bomb Shop', 'Big Bomb Shop'), - ('Old Man Cave (West)', 'Old Man Cave Ledge'), - ('Old Man Cave (East)', 'Old Man Cave (East)'), - ('Old Man Cave Exit (West)', 'Mountain Pass Entry'), - ('Old Man Cave Exit (East)', 'West Death Mountain (Bottom)'), - ('Death Mountain Return Cave (West)', 'Death Mountain Return Cave (left)'), - ('Death Mountain Return Cave Exit (West)', 'Mountain Pass Ledge'), - ('Bumper Cave (Bottom)', 'Bumper Cave (bottom)'), - ('Bumper Cave (Top)', 'Bumper Cave (top)'), - ('Bumper Cave Exit (Top)', 'Bumper Cave Ledge'), - ('Bumper Cave Exit (Bottom)', 'Bumper Cave Entry'), - ('Dark Death Mountain Fairy', 'Dark Death Mountain Healer Fairy'), - ('Pyramid Hole', 'Pyramid'), - ('Pyramid Entrance', 'Bottom of Pyramid'), - ('Pyramid Exit', 'Pyramid Exit Ledge') - ] - -inverted_default_connections = [('Links House', 'Big Bomb Shop'), - ('Links House Exit', 'Big Bomb Shop Area'), - ('Big Bomb Shop', 'Links House'), - ('Dark Sanctuary Hint Exit', 'Dark Chapel Area'), - ('Old Man Cave (West)', 'Bumper Cave (bottom)'), - ('Old Man Cave (East)', 'Death Mountain Return Cave (left)'), - ('Old Man Cave Exit (West)', 'Bumper Cave Entry'), - ('Old Man Cave Exit (East)', 'West Dark Death Mountain (Bottom)'), - ('Death Mountain Return Cave (West)', 'Bumper Cave (top)'), - ('Death Mountain Return Cave Exit (West)', 'West Death Mountain (Bottom)'), - ('Bumper Cave (Bottom)', 'Old Man Cave Ledge'), - ('Bumper Cave (Top)', 'Dark Death Mountain Healer Fairy'), - ('Bumper Cave Exit (Top)', 'Mountain Pass Ledge'), - ('Bumper Cave Exit (Bottom)', 'Mountain Pass Entry'), - ('Dark Death Mountain Fairy', 'Old Man Cave (East)'), - ('Inverted Pyramid Hole', 'Pyramid'), - ('Inverted Pyramid Entrance', 'Bottom of Pyramid'), - ('Pyramid Exit', 'Hyrule Castle Courtyard') - ] - -# non shuffled dungeons -default_dungeon_connections = [('Hyrule Castle Entrance (South)', 'Hyrule Castle South Portal'), - ('Hyrule Castle Entrance (West)', 'Hyrule Castle West Portal'), - ('Hyrule Castle Entrance (East)', 'Hyrule Castle East Portal'), - ('Hyrule Castle Exit (South)', 'Hyrule Castle Courtyard'), - ('Hyrule Castle Exit (West)', 'Hyrule Castle Ledge'), - ('Hyrule Castle Exit (East)', 'Hyrule Castle Ledge'), - ('Desert Palace Entrance (South)', 'Desert South Portal'), - ('Desert Palace Entrance (West)', 'Desert West Portal'), - ('Desert Palace Entrance (North)', 'Desert Back Portal'), - ('Desert Palace Entrance (East)', 'Desert East Portal'), - ('Desert Palace Exit (South)', 'Desert Stairs'), - ('Desert Palace Exit (West)', 'Desert Ledge'), - ('Desert Palace Exit (East)', 'Desert Mouth'), - ('Desert Palace Exit (North)', 'Desert Ledge Keep'), - ('Eastern Palace', 'Eastern Portal'), - ('Eastern Palace Exit', 'Eastern Palace Area'), - ('Tower of Hera', 'Hera Portal'), - ('Tower of Hera Exit', 'West Death Mountain (Top)'), - - ('Palace of Darkness', 'Palace of Darkness Portal'), - ('Palace of Darkness Exit', 'Palace of Darkness Area'), - ('Swamp Palace', 'Swamp Portal'), # requires additional patch for flooding moat if moved - ('Swamp Palace Exit', 'Swamp Area'), - ('Skull Woods First Section Hole (East)', 'Skull Pinball'), - ('Skull Woods First Section Hole (West)', 'Skull Left Drop'), - ('Skull Woods First Section Hole (North)', 'Skull Pot Circle'), - ('Skull Woods First Section Door', 'Skull 1 Portal'), - ('Skull Woods First Section Exit', 'Skull Woods Forest'), - ('Skull Woods Second Section Hole', 'Skull Back Drop'), - ('Skull Woods Second Section Door (East)', 'Skull 2 East Portal'), - ('Skull Woods Second Section Door (West)', 'Skull 2 West Portal'), - ('Skull Woods Second Section Exit (East)', 'Skull Woods Forest'), - ('Skull Woods Second Section Exit (West)', 'Skull Woods Forest (West)'), - ('Skull Woods Final Section', 'Skull 3 Portal'), - ('Skull Woods Final Section Exit', 'Skull Woods Forest (West)'), - ('Thieves Town', 'Thieves Town Portal'), - ('Thieves Town Exit', 'Village of Outcasts'), - ('Ice Palace', 'Ice Portal'), - ('Ice Palace Exit', 'Ice Palace Area'), - ('Misery Mire', 'Mire Portal'), - ('Misery Mire Exit', 'Mire Area'), - ('Turtle Rock', 'Turtle Rock Main Portal'), - ('Turtle Rock Exit (Front)', 'Turtle Rock Area'), - ('Dark Death Mountain Ledge (West)', 'Turtle Rock Lazy Eyes Portal'), - ('Dark Death Mountain Ledge (East)', 'Turtle Rock Chest Portal'), - ('Turtle Rock Ledge Exit (West)', 'Dark Death Mountain Ledge'), - ('Turtle Rock Ledge Exit (East)', 'Dark Death Mountain Ledge'), - ('Turtle Rock Isolated Ledge Entrance', 'Turtle Rock Eye Bridge Portal'), - ('Turtle Rock Isolated Ledge Exit', 'Dark Death Mountain Isolated Ledge') - ] - -open_default_dungeon_connections = [('Agahnims Tower', 'Agahnims Tower Portal'), - ('Agahnims Tower Exit', 'Hyrule Castle Ledge'), - ('Ganons Tower', 'Ganons Tower Portal'), - ('Ganons Tower Exit', 'West Dark Death Mountain (Top)') - ] - -inverted_default_dungeon_connections = [('Agahnims Tower', 'Ganons Tower Portal'), - ('Agahnims Tower Exit', 'West Dark Death Mountain (Top)'), - ('Ganons Tower', 'Agahnims Tower Portal'), - ('Ganons Tower Exit', 'Hyrule Castle Ledge') - ] - -indirect_connections = { - 'Turtle Rock Ledge': 'Turtle Rock', - 'Pyramid Area': 'Pyramid Fairy', - 'Big Bomb Shop': 'Pyramid Fairy', - 'Mire Area': 'Pyramid Fairy', - #'West Dark World': 'Pyramid Fairy', - 'Big Bomb Shop Area': 'Pyramid Fairy', - #'Light World': 'Pyramid Fairy', - 'Old Man Cave (East)': 'Old Man S&Q' -} -# format: -# Key=Name -# addr = (door_index, exitdata) # multiexit -# | ([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 -door_addresses = {'Links House': (0x00, (0x0104, 0x2c, 0x0506, 0x0a9a, 0x0832, 0x0ae8, 0x08b8, 0x0b07, 0x08bf, 0x06, 0xfe, 0x0816, 0x0000)), - 'Desert Palace Entrance (South)': (0x08, (0x0084, 0x30, 0x0314, 0x0c56, 0x00a6, 0x0ca8, 0x0128, 0x0cc3, 0x0133, 0x0a, 0xfa, 0x0000, 0x0000)), - 'Desert Palace Entrance (West)': (0x0A, (0x0083, 0x30, 0x0280, 0x0c46, 0x0003, 0x0c98, 0x0088, 0x0cb3, 0x0090, 0x0a, 0xfd, 0x0000, 0x0000)), - 'Desert Palace Entrance (North)': (0x0B, (0x0063, 0x30, 0x0016, 0x0c00, 0x00a2, 0x0c28, 0x0128, 0x0c6d, 0x012f, 0x00, 0x0e, 0x0000, 0x0000)), - 'Desert Palace Entrance (East)': (0x09, (0x0085, 0x30, 0x02a8, 0x0c4a, 0x0142, 0x0c98, 0x01c8, 0x0cb7, 0x01cf, 0x06, 0xfe, 0x0000, 0x0000)), - 'Eastern Palace': (0x07, (0x00c9, 0x1e, 0x005a, 0x0600, 0x0ed6, 0x0618, 0x0f50, 0x066d, 0x0f5b, 0x00, 0xfa, 0x0000, 0x0000)), - 'Tower of Hera': (0x32, (0x0077, 0x03, 0x0050, 0x0014, 0x087c, 0x0068, 0x08f0, 0x0083, 0x08fb, 0x0a, 0xf4, 0x0000, 0x0000)), - 'Hyrule Castle Entrance (South)': (0x03, (0x0061, 0x1b, 0x0530, 0x0692, 0x0784, 0x06cc, 0x07f8, 0x06ff, 0x0803, 0x0e, 0xfa, 0x0000, 0x87be)), - 'Hyrule Castle Entrance (West)': (0x02, (0x0060, 0x1b, 0x0016, 0x0600, 0x06ae, 0x0604, 0x0728, 0x066d, 0x0733, 0x00, 0x02, 0x0000, 0x8124)), - 'Hyrule Castle Entrance (East)': (0x04, (0x0062, 0x1b, 0x004a, 0x0600, 0x0856, 0x0604, 0x08c8, 0x066d, 0x08d3, 0x00, 0xfa, 0x0000, 0x8158)), - 'Inverted Pyramid Entrance': (0x35, (0x0010, 0x1b, 0x0418, 0x0679, 0x06b4, 0x06c6, 0x0728, 0x06e6, 0x0733, 0x07, 0xf9, 0x0000, 0x0000)), - 'Agahnims Tower': (0x23, (0x00e0, 0x1b, 0x0032, 0x0600, 0x0784, 0x0634, 0x07f8, 0x066d, 0x0803, 0x00, 0x0a, 0x0000, 0x82be)), - 'Thieves Town': (0x33, (0x00db, 0x58, 0x0b2e, 0x075a, 0x0176, 0x07a8, 0x01f8, 0x07c7, 0x0203, 0x06, 0xfa, 0x0000, 0x0000)), - 'Skull Woods First Section Door': (0x29, (0x0058, 0x40, 0x0f4c, 0x01f6, 0x0262, 0x0248, 0x02e8, 0x0263, 0x02ef, 0x0a, 0xfe, 0x0000, 0x0000)), - 'Skull Woods Second Section Door (East)': (0x28, (0x0057, 0x40, 0x0eb8, 0x01e6, 0x01c2, 0x0238, 0x0248, 0x0253, 0x024f, 0x0a, 0xfe, 0x0000, 0x0000)), - 'Skull Woods Second Section Door (West)': (0x27, (0x0056, 0x40, 0x0c8e, 0x01a6, 0x0062, 0x01f8, 0x00e8, 0x0213, 0x00ef, 0x0a, 0x0e, 0x0000, 0x0000)), - 'Skull Woods Final Section': (0x2A, (0x0059, 0x40, 0x0282, 0x0066, 0x0016, 0x00b8, 0x0098, 0x00d3, 0x00a3, 0x0a, 0xfa, 0x0000, 0x0000)), - 'Ice Palace': (0x2C, (0x000e, 0x75, 0x0bc6, 0x0d6a, 0x0c3e, 0x0db8, 0x0cb8, 0x0dd7, 0x0cc3, 0x06, 0xf2, 0x0000, 0x0000)), - 'Misery Mire': (0x26, (0x0098, 0x70, 0x0414, 0x0c79, 0x00a6, 0x0cc7, 0x0128, 0x0ce6, 0x0133, 0x07, 0xfa, 0x0000, 0x0000)), - 'Palace of Darkness': (0x25, (0x004a, 0x5e, 0x005a, 0x0600, 0x0ed6, 0x0628, 0x0f50, 0x066d, 0x0f5b, 0x00, 0xfa, 0x0000, 0x0000)), - 'Swamp Palace': (0x24, (0x0028, 0x7b, 0x049e, 0x0e8c, 0x06f2, 0x0ed8, 0x0778, 0x0ef9, 0x077f, 0x04, 0xfe, 0x0000, 0x0000)), - 'Turtle Rock': (0x34, (0x00d6, 0x47, 0x0712, 0x00da, 0x0e96, 0x0128, 0x0f08, 0x0147, 0x0f13, 0x06, 0xfa, 0x0000, 0x0000)), - 'Dark Death Mountain Ledge (West)': (0x14, (0x0023, 0x45, 0x07ca, 0x0103, 0x0c46, 0x0157, 0x0cb8, 0x0172, 0x0cc3, 0x0b, 0x0a, 0x0000, 0x0000)), - 'Dark Death Mountain Ledge (East)': (0x18, (0x0024, 0x45, 0x07e0, 0x0103, 0x0d00, 0x0157, 0x0d78, 0x0172, 0x0d7d, 0x0b, 0x00, 0x0000, 0x0000)), - 'Turtle Rock Isolated Ledge Entrance': (0x17, (0x00d5, 0x45, 0x0ad4, 0x0164, 0x0ca6, 0x01b8, 0x0d18, 0x01d3, 0x0d23, 0x0a, 0xfa, 0x0000, 0x0000)), - 'Hyrule Castle Secret Entrance Stairs': (0x31, (0x0055, 0x1b, 0x044a, 0x067a, 0x0854, 0x06c8, 0x08c8, 0x06e7, 0x08d3, 0x06, 0xfa, 0x0000, 0x0000)), - 'Kakariko Well Cave': (0x38, (0x002f, 0x18, 0x0386, 0x0665, 0x0032, 0x06b7, 0x00b8, 0x06d2, 0x00bf, 0x0b, 0xfe, 0x0000, 0x0000)), - 'Bat Cave Cave': (0x10, (0x00e3, 0x22, 0x0412, 0x087a, 0x048e, 0x08c8, 0x0508, 0x08e7, 0x0513, 0x06, 0x02, 0x0000, 0x0000)), - 'Elder House (East)': (0x0D, (0x00f3, 0x18, 0x02c4, 0x064a, 0x0222, 0x0698, 0x02a8, 0x06b7, 0x02af, 0x06, 0xfe, 0x05d4, 0x0000)), - 'Elder House (West)': (0x0C, (0x00f2, 0x18, 0x02bc, 0x064c, 0x01e2, 0x0698, 0x0268, 0x06b9, 0x026f, 0x04, 0xfe, 0x05cc, 0x0000)), - 'North Fairy Cave': (0x37, (0x0008, 0x15, 0x0088, 0x0400, 0x0a36, 0x0448, 0x0aa8, 0x046f, 0x0ab3, 0x00, 0x0a, 0x0000, 0x0000)), - 'Lost Woods Hideout Stump': (0x2B, (0x00e1, 0x00, 0x0f4e, 0x01f6, 0x0262, 0x0248, 0x02e8, 0x0263, 0x02ef, 0x0a, 0x0e, 0x0000, 0x0000)), - 'Lumberjack Tree Cave': (0x11, (0x00e2, 0x02, 0x0118, 0x0015, 0x04c6, 0x0067, 0x0548, 0x0082, 0x0553, 0x0b, 0xfa, 0x0000, 0x0000)), - 'Two Brothers House (East)': (0x0F, (0x00f5, 0x29, 0x0880, 0x0b07, 0x0200, 0x0b58, 0x0238, 0x0b74, 0x028d, 0x09, 0x00, 0x0b86, 0x0000)), - 'Two Brothers House (West)': (0x0E, (0x00f4, 0x28, 0x08a0, 0x0b06, 0x0100, 0x0b58, 0x01b8, 0x0b73, 0x018d, 0x0a, 0x00, 0x0bb6, 0x0000)), - 'Sanctuary': (0x01, (0x0012, 0x13, 0x001c, 0x0400, 0x06de, 0x0414, 0x0758, 0x046d, 0x0763, 0x00, 0x02, 0x0000, 0x01aa)), - 'Old Man Cave (West)': (0x05, (0x00f0, 0x0a, 0x03a0, 0x0264, 0x0500, 0x02b8, 0x05a8, 0x02d3, 0x058d, 0x0a, 0x00, 0x0000, 0x0000)), - 'Old Man Cave (East)': (0x06, (0x00f1, 0x03, 0x1402, 0x0294, 0x0604, 0x02e8, 0x0678, 0x0303, 0x0683, 0x0a, 0xfc, 0x0000, 0x0000)), - 'Old Man House (Bottom)': (0x2F, (0x00e4, 0x03, 0x181a, 0x031e, 0x06b4, 0x03a7, 0x0728, 0x038d, 0x0733, 0x00, 0x0c, 0x0000, 0x0000)), - 'Old Man House (Top)': (0x30, (0x00e5, 0x03, 0x10c6, 0x0224, 0x0814, 0x0278, 0x0888, 0x0293, 0x0893, 0x0a, 0x0c, 0x0000, 0x0000)), - 'Death Mountain Return Cave (East)': (0x2E, (0x00e7, 0x03, 0x0d82, 0x01c4, 0x0600, 0x0218, 0x0648, 0x0233, 0x067f, 0x0a, 0x00, 0x0000, 0x0000)), - 'Death Mountain Return Cave (West)': (0x2D, (0x00e6, 0x0a, 0x00a0, 0x0205, 0x0500, 0x0257, 0x05b8, 0x0272, 0x058d, 0x0b, 0x00, 0x0000, 0x0000)), - 'Spectacle Rock Cave Peak': (0x22, (0x00ea, 0x03, 0x092c, 0x0133, 0x0754, 0x0187, 0x07c8, 0x01a2, 0x07d3, 0x0b, 0xfc, 0x0000, 0x0000)), - 'Spectacle Rock Cave': (0x21, (0x00fa, 0x03, 0x0eac, 0x01e3, 0x0754, 0x0237, 0x07c8, 0x0252, 0x07d3, 0x0b, 0xfc, 0x0000, 0x0000)), - 'Spectacle Rock Cave (Bottom)': (0x20, (0x00f9, 0x03, 0x0d9c, 0x01c3, 0x06d4, 0x0217, 0x0748, 0x0232, 0x0753, 0x0b, 0xfc, 0x0000, 0x0000)), - 'Paradox Cave (Bottom)': (0x1D, (0x00ff, 0x05, 0x0ee0, 0x01e3, 0x0d00, 0x0237, 0x0da8, 0x0252, 0x0d7d, 0x0b, 0x00, 0x0000, 0x0000)), - 'Paradox Cave (Middle)': (0x1E, (0x00ef, 0x05, 0x17e0, 0x0304, 0x0d00, 0x0358, 0x0dc8, 0x0373, 0x0d7d, 0x0a, 0x00, 0x0000, 0x0000)), - 'Paradox Cave (Top)': (0x1F, (0x00df, 0x05, 0x0460, 0x0093, 0x0d00, 0x00e7, 0x0db8, 0x0102, 0x0d7d, 0x0b, 0x00, 0x0000, 0x0000)), - 'Fairy Ascension Cave (Bottom)': (0x19, (0x00fd, 0x05, 0x0dd4, 0x01c4, 0x0ca6, 0x0218, 0x0d18, 0x0233, 0x0d23, 0x0a, 0xfa, 0x0000, 0x0000)), - 'Fairy Ascension Cave (Top)': (0x1A, (0x00ed, 0x05, 0x0ad4, 0x0163, 0x0ca6, 0x01b7, 0x0d18, 0x01d2, 0x0d23, 0x0b, 0xfa, 0x0000, 0x0000)), - 'Spiral Cave': (0x1C, (0x00ee, 0x05, 0x07c8, 0x0108, 0x0c46, 0x0158, 0x0cb8, 0x0177, 0x0cc3, 0x06, 0xfa, 0x0000, 0x0000)), - 'Spiral Cave (Bottom)': (0x1B, (0x00fe, 0x05, 0x0cca, 0x01a3, 0x0c56, 0x01f7, 0x0cc8, 0x0212, 0x0cd3, 0x0b, 0xfa, 0x0000, 0x0000)), - 'Bumper Cave (Bottom)': (0x15, (0x00fb, 0x4a, 0x03a0, 0x0263, 0x0500, 0x02b7, 0x05a8, 0x02d2, 0x058d, 0x0b, 0x00, 0x0000, 0x0000)), - 'Bumper Cave (Top)': (0x16, (0x00eb, 0x4a, 0x00a0, 0x020a, 0x0500, 0x0258, 0x05b8, 0x0277, 0x058d, 0x06, 0x00, 0x0000, 0x0000)), - 'Superbunny Cave (Top)': (0x13, (0x00e8, 0x45, 0x0460, 0x0093, 0x0d00, 0x00e7, 0x0db8, 0x0102, 0x0d7d, 0x0b, 0x00, 0x0000, 0x0000)), - 'Superbunny Cave (Bottom)': (0x12, (0x00f8, 0x45, 0x0ee0, 0x01e4, 0x0d00, 0x0238, 0x0d78, 0x0253, 0x0d7d, 0x0a, 0x00, 0x0000, 0x0000)), - 'Hookshot Cave': (0x39, (0x003c, 0x45, 0x04da, 0x00a3, 0x0cd6, 0x0107, 0x0d48, 0x0112, 0x0d53, 0x0b, 0xfa, 0x0000, 0x0000)), - 'Hookshot Cave Back Entrance': (0x3A, (0x002c, 0x45, 0x004c, 0x0000, 0x0c56, 0x0038, 0x0cc8, 0x006f, 0x0cd3, 0x00, 0x0a, 0x0000, 0x0000)), - 'Ganons Tower': (0x36, (0x000c, 0x43, 0x0052, 0x0000, 0x0884, 0x0028, 0x08f8, 0x006f, 0x0903, 0x00, 0xfc, 0x0000, 0x0000)), - 'Pyramid Entrance': (0x35, (0x0010, 0x5b, 0x0b0e, 0x075a, 0x0674, 0x07a8, 0x06e8, 0x07c7, 0x06f3, 0x06, 0xfa, 0x0000, 0x0000)), - 'Skull Woods First Section Hole (West)': ([0xDB84D, 0xDB84E], None), - 'Skull Woods First Section Hole (East)': ([0xDB84F, 0xDB850], None), - 'Skull Woods First Section Hole (North)': ([0xDB84C], None), - 'Skull Woods Second Section Hole': ([0xDB851, 0xDB852], None), - 'Pyramid Hole': ([0xDB854, 0xDB855, 0xDB856], None), - 'Inverted Pyramid Hole': ([0xDB854, 0xDB855, 0xDB856, 0x180340], None), - 'Waterfall of Wishing': (0x5B, (0x0114, 0x0f, 0x0080, 0x0200, 0x0e00, 0x0207, 0x0e60, 0x026f, 0x0e7d, 0x00, 0x00, 0x0000, 0x0000)), - 'Dam': (0x4D, (0x010b, 0x3b, 0x04a0, 0x0e8a, 0x06fa, 0x0ed8, 0x0778, 0x0ef7, 0x077f, 0x06, 0xfa, 0x0000, 0x0000)), - 'Blinds Hideout': (0x60, (0x0119, 0x18, 0x02b2, 0x064a, 0x0186, 0x0697, 0x0208, 0x06b7, 0x0213, 0x06, 0xfa, 0x0000, 0x0000)), - 'Hyrule Castle Secret Entrance Drop': ([0xDB858], None), - 'Bonk Fairy (Light)': (0x76, (0x0126, 0x2b, 0x00a0, 0x0a0a, 0x0700, 0x0a67, 0x0788, 0x0a77, 0x0785, 0x06, 0xfa, 0x0000, 0x0000)), - 'Lake Hylia Fairy': (0x5D, (0x0115, 0x2e, 0x0016, 0x0a00, 0x0cb6, 0x0a37, 0x0d28, 0x0a6d, 0x0d33, 0x00, 0x00, 0x0000, 0x0000)), - 'Light Hype Fairy': (0x6B, (0x0115, 0x34, 0x00a0, 0x0c04, 0x0900, 0x0c58, 0x0988, 0x0c73, 0x0985, 0x0a, 0xf6, 0x0000, 0x0000)), - 'Desert Fairy': (0x71, (0x0115, 0x3a, 0x0000, 0x0e00, 0x0400, 0x0e26, 0x0468, 0x0e6d, 0x0485, 0x00, 0x00, 0x0000, 0x0000)), - 'Kings Grave': (0x5A, (0x0113, 0x14, 0x0320, 0x0456, 0x0900, 0x04a6, 0x0998, 0x04c3, 0x097d, 0x0a, 0xf6, 0x0000, 0x0000)), - 'Tavern North': (0x42, (0x0103, 0x18, 0x1440, 0x08a7, 0x0206, 0x091b, 0x0288, 0x0914, 0x0293, 0xf7, 0x09, 0xFFFF, 0x0000)), - 'Chicken House': (0x4A, (0x0108, 0x18, 0x1120, 0x0837, 0x0106, 0x0888, 0x0188, 0x08a4, 0x0193, 0x07, 0xf9, 0x1530, 0x0000)), - 'Aginahs Cave': (0x70, (0x010a, 0x30, 0x0656, 0x0cc6, 0x02aa, 0x0d18, 0x0328, 0x0d33, 0x032f, 0x08, 0xf8, 0x0000, 0x0000)), - 'Sahasrahlas Hut': (0x44, (0x0105, 0x1e, 0x0610, 0x06d4, 0x0c76, 0x0727, 0x0cf0, 0x0743, 0x0cfb, 0x0a, 0xf6, 0x0000, 0x0000)), - 'Lake Hylia Shop': (0x57, (0x0112, 0x35, 0x0022, 0x0c00, 0x0b1a, 0x0c26, 0x0b98, 0x0c6d, 0x0b9f, 0x00, 0x00, 0x0000, 0x0000)), - 'Capacity Upgrade': (0x5C, (0x0115, 0x35, 0x0a46, 0x0d36, 0x0c2a, 0x0d88, 0x0ca8, 0x0da3, 0x0caf, 0x0a, 0xf6, 0x0000, 0x0000)), - 'Kakariko Well Drop': ([0xDB85C, 0xDB85D], None), - 'Blacksmiths Hut': (0x63, (0x0121, 0x22, 0x010c, 0x081a, 0x0466, 0x0868, 0x04d8, 0x0887, 0x04e3, 0x06, 0xfa, 0x041A, 0x0000)), - 'Bat Cave Drop': ([0xDB859, 0xDB85A], None), - 'Sick Kids House': (0x3F, (0x0102, 0x18, 0x10be, 0x0826, 0x01f6, 0x0877, 0x0278, 0x0893, 0x0283, 0x08, 0xf8, 0x14CE, 0x0000)), - 'North Fairy Cave Drop': ([0xDB857], None), - 'Lost Woods Gamble': (0x3B, (0x0100, 0x00, 0x004e, 0x0000, 0x0272, 0x0008, 0x02f0, 0x006f, 0x02f7, 0x00, 0x00, 0x0000, 0x0000)), - 'Fortune Teller (Light)': (0x64, (0x0122, 0x11, 0x060e, 0x04b4, 0x027d, 0x0508, 0x02f8, 0x0523, 0x0302, 0x0a, 0xf6, 0x0000, 0x0000)), - 'Snitch Lady (East)': (0x3D, (0x0101, 0x18, 0x0ad8, 0x074a, 0x02c6, 0x0798, 0x0348, 0x07b7, 0x0353, 0x06, 0xfa, 0x0DE8, 0x0000)), - 'Snitch Lady (West)': (0x3E, (0x0101, 0x18, 0x0788, 0x0706, 0x0046, 0x0758, 0x00c8, 0x0773, 0x00d3, 0x08, 0xf8, 0x0B98, 0x0000)), - 'Bush Covered House': (0x43, (0x0103, 0x18, 0x1156, 0x081a, 0x02b6, 0x0868, 0x0338, 0x0887, 0x0343, 0x06, 0xfa, 0x1466, 0x0000)), - 'Tavern (Front)': (0x41, (0x0103, 0x18, 0x1842, 0x0916, 0x0206, 0x0967, 0x0288, 0x0983, 0x0293, 0x08, 0xf8, 0x1C50, 0x0000)), - 'Light World Bomb Hut': (0x49, (0x0107, 0x18, 0x1800, 0x0916, 0x0000, 0x0967, 0x0068, 0x0983, 0x008d, 0x08, 0xf8, 0x9C0C, 0x0000)), - 'Kakariko Shop': (0x45, (0x011f, 0x18, 0x16a8, 0x08e7, 0x0136, 0x0937, 0x01b8, 0x0954, 0x01c3, 0x07, 0xf9, 0x1AB6, 0x0000)), - 'Lost Woods Hideout Drop': ([0xDB853], None), - 'Lumberjack Tree Tree': ([0xDB85B], None), - 'Cave 45': (0x50, (0x011b, 0x32, 0x0680, 0x0cc9, 0x0400, 0x0d16, 0x0438, 0x0d36, 0x0485, 0x07, 0xf9, 0x0000, 0x0000)), - 'Graveyard Cave': (0x51, (0x011b, 0x14, 0x0016, 0x0400, 0x08a2, 0x0446, 0x0918, 0x046d, 0x091f, 0x00, 0x00, 0x0000, 0x0000)), - 'Checkerboard Cave': (0x7D, (0x0126, 0x30, 0x00c8, 0x0c0a, 0x024a, 0x0c67, 0x02c8, 0x0c77, 0x02cf, 0x06, 0xfa, 0x0000, 0x0000)), - 'Mini Moldorm Cave': (0x7C, (0x0123, 0x35, 0x1480, 0x0e96, 0x0a00, 0x0ee8, 0x0a68, 0x0f03, 0x0a85, 0x08, 0xf8, 0x0000, 0x0000)), - 'Long Fairy Cave': (0x54, (0x011e, 0x2f, 0x06a0, 0x0aca, 0x0f00, 0x0b18, 0x0fa8, 0x0b37, 0x0f85, 0x06, 0xfa, 0x0000, 0x0000)), - 'Good Bee Cave': (0x6A, (0x0120, 0x37, 0x0084, 0x0c00, 0x0e26, 0x0c36, 0x0e98, 0x0c6f, 0x0ea3, 0x00, 0x00, 0x0000, 0x0000)), - '20 Rupee Cave': (0x7A, (0x0125, 0x37, 0x0200, 0x0c23, 0x0e00, 0x0c86, 0x0e68, 0x0c92, 0x0e7d, 0x0d, 0xf3, 0x0000, 0x0000)), - '50 Rupee Cave': (0x78, (0x0124, 0x3a, 0x0790, 0x0eea, 0x047a, 0x0f47, 0x04f8, 0x0f57, 0x04ff, 0x06, 0xfa, 0x0000, 0x0000)), - 'Ice Rod Cave': (0x7F, (0x0120, 0x37, 0x0080, 0x0c00, 0x0e00, 0x0c37, 0x0e48, 0x0c6f, 0x0e7d, 0x00, 0x00, 0x0000, 0x0000)), - 'Bonk Rock Cave': (0x79, (0x0124, 0x13, 0x0280, 0x044a, 0x0600, 0x04a7, 0x0638, 0x04b7, 0x067d, 0x06, 0xfa, 0x0000, 0x0000)), - 'Library': (0x48, (0x0107, 0x29, 0x0100, 0x0a14, 0x0200, 0x0a67, 0x0278, 0x0a83, 0x0285, 0x0a, 0xf6, 0x040E, 0x0000)), - 'Potion Shop': (0x4B, (0x0109, 0x16, 0x070a, 0x04e6, 0x0c56, 0x0538, 0x0cc8, 0x0553, 0x0cd3, 0x08, 0xf8, 0x0A98, 0x0000)), - 'Sanctuary Grave': ([0xDB85E], None), - 'Hookshot Fairy': (0x4F, (0x010c, 0x05, 0x0ee0, 0x01e3, 0x0d00, 0x0236, 0x0d78, 0x0252, 0x0d7d, 0x0b, 0xf5, 0x0000, 0x0000)), - 'Pyramid Fairy': (0x62, (0x0116, 0x5b, 0x0b1e, 0x0754, 0x06fa, 0x07a7, 0x0778, 0x07c3, 0x077f, 0x0a, 0xf6, 0x0000, 0x0000)), - 'East Dark World Hint': (0x68, (0x010e, 0x6f, 0x06a0, 0x0aca, 0x0f00, 0x0b18, 0x0fa8, 0x0b37, 0x0f85, 0x06, 0xfa, 0x0000, 0x0000)), - 'Palace of Darkness Hint': (0x67, (0x011a, 0x5e, 0x0c24, 0x0794, 0x0d12, 0x07e8, 0x0d90, 0x0803, 0x0d97, 0x0a, 0xf6, 0x0000, 0x0000)), - 'Dark Lake Hylia Fairy': (0x6C, (0x0115, 0x6e, 0x0016, 0x0a00, 0x0cb6, 0x0a36, 0x0d28, 0x0a6d, 0x0d33, 0x00, 0x00, 0x0000, 0x0000)), - 'Dark Lake Hylia Ledge Fairy': (0x80, (0x0115, 0x77, 0x0080, 0x0c00, 0x0e00, 0x0c37, 0x0e48, 0x0c6f, 0x0e7d, 0x00, 0x00, 0x0000, 0x0000)), - 'Dark Lake Hylia Ledge Spike Cave': (0x7B, (0x0125, 0x77, 0x0200, 0x0c27, 0x0e00, 0x0c86, 0x0e68, 0x0c96, 0x0e7d, 0x09, 0xf7, 0x0000, 0x0000)), - 'Dark Lake Hylia Ledge Hint': (0x69, (0x010e, 0x77, 0x0084, 0x0c00, 0x0e26, 0x0c36, 0x0e98, 0x0c6f, 0x0ea3, 0x00, 0x00, 0x0000, 0x0000)), - 'Hype Cave': (0x3C, (0x011e, 0x74, 0x00a0, 0x0c0a, 0x0900, 0x0c58, 0x0988, 0x0c77, 0x097d, 0x06, 0xfa, 0x0000, 0x0000)), - 'Bonk Fairy (Dark)': (0x77, (0x0126, 0x6b, 0x00a0, 0x0a05, 0x0700, 0x0a66, 0x0788, 0x0a72, 0x0785, 0x0b, 0xf5, 0x0000, 0x0000)), - 'Brewery': (0x47, (0x0106, 0x58, 0x16a8, 0x08e4, 0x013e, 0x0938, 0x01b8, 0x0953, 0x01c3, 0x0a, 0xf6, 0x1AB6, 0x0000)), - 'C-Shaped House': (0x53, (0x011c, 0x58, 0x09d8, 0x0744, 0x02ce, 0x0797, 0x0348, 0x07b3, 0x0353, 0x0a, 0xf6, 0x0DE8, 0x0000)), - 'Chest Game': (0x46, (0x0106, 0x58, 0x078a, 0x0705, 0x004e, 0x0758, 0x00c8, 0x0774, 0x00d3, 0x09, 0xf7, 0x0B98, 0x0000)), - 'Hammer Peg Cave': (0x7E, (0x0127, 0x62, 0x0894, 0x091e, 0x0492, 0x09a6, 0x0508, 0x098b, 0x050f, 0x00, 0x00, 0x0000, 0x0000)), - 'Red Shield Shop': (0x74, (0x0110, 0x5a, 0x079a, 0x06e8, 0x04d6, 0x0738, 0x0548, 0x0755, 0x0553, 0x08, 0xf8, 0x0AA8, 0x0000)), - 'Dark Sanctuary Hint': (0x59, (0x0112, 0x53, 0x001e, 0x0400, 0x06e2, 0x0446, 0x0758, 0x046d, 0x075f, 0x00, 0x00, 0x0000, 0x0000)), - 'Fortune Teller (Dark)': (0x65, (0x0122, 0x51, 0x0610, 0x04b4, 0x027e, 0x0507, 0x02f8, 0x0523, 0x0303, 0x0a, 0xf6, 0x091E, 0x0000)), - 'Dark World Shop': (0x5F, (0x010f, 0x58, 0x1058, 0x0814, 0x02be, 0x0868, 0x0338, 0x0883, 0x0343, 0x0a, 0xf6, 0x0000, 0x0000)), - 'Dark Lumberjack Shop': (0x56, (0x010f, 0x42, 0x041c, 0x0074, 0x04e2, 0x00c7, 0x0558, 0x00e3, 0x055f, 0x0a, 0xf6, 0x0000, 0x0000)), - 'Dark Potion Shop': (0x6E, (0x010f, 0x56, 0x080e, 0x04f4, 0x0c66, 0x0548, 0x0cd8, 0x0563, 0x0ce3, 0x0a, 0xf6, 0x0000, 0x0000)), - 'Archery Game': (0x58, (0x0111, 0x69, 0x069e, 0x0ac4, 0x02ea, 0x0b18, 0x0368, 0x0b33, 0x036f, 0x0a, 0xf6, 0x09AC, 0x0000)), - 'Mire Shed': (0x5E, (0x010d, 0x70, 0x0384, 0x0c69, 0x001e, 0x0cb6, 0x0098, 0x0cd6, 0x00a3, 0x07, 0xf9, 0x0000, 0x0000)), - 'Mire Hint': (0x61, (0x0114, 0x70, 0x0654, 0x0cc5, 0x02aa, 0x0d16, 0x0328, 0x0d32, 0x032f, 0x09, 0xf7, 0x0000, 0x0000)), - 'Mire Fairy': (0x55, (0x0115, 0x70, 0x03a8, 0x0c6a, 0x013a, 0x0cb7, 0x01b8, 0x0cd7, 0x01bf, 0x06, 0xfa, 0x0000, 0x0000)), - 'Spike Cave': (0x40, (0x0117, 0x43, 0x0ed4, 0x01e4, 0x08aa, 0x0236, 0x0928, 0x0253, 0x092f, 0x0a, 0xf6, 0x0000, 0x0000)), - 'Dark Death Mountain Shop': (0x6D, (0x0112, 0x45, 0x0ee0, 0x01e3, 0x0d00, 0x0236, 0x0daa, 0x0252, 0x0d7d, 0x0b, 0xf5, 0x0000, 0x0000)), - 'Dark Death Mountain Fairy': (0x6F, (0x0115, 0x43, 0x1400, 0x0294, 0x0600, 0x02e8, 0x0678, 0x0303, 0x0685, 0x0a, 0xf6, 0x0000, 0x0000)), - 'Mimic Cave': (0x4E, (0x010c, 0x05, 0x07e0, 0x0103, 0x0d00, 0x0156, 0x0d78, 0x0172, 0x0d7d, 0x0b, 0xf5, 0x0000, 0x0000)), - 'Big Bomb Shop': (0x52, (0x011c, 0x6c, 0x0506, 0x0a9a, 0x0832, 0x0ae7, 0x08b8, 0x0b07, 0x08bf, 0x06, 0xfa, 0x0816, 0x0000)), - 'Dark Lake Hylia Shop': (0x73, (0x010f, 0x75, 0x0380, 0x0c6a, 0x0a00, 0x0cb8, 0x0a58, 0x0cd7, 0x0a85, 0x06, 0xfa, 0x0000, 0x0000)), - 'Lumberjack House': (0x75, (0x011f, 0x02, 0x049c, 0x0088, 0x04e6, 0x00d8, 0x0558, 0x00f7, 0x0563, 0x08, 0xf8, 0x07AA, 0x0000)), - 'Lake Hylia Fortune Teller': (0x72, (0x0122, 0x35, 0x0380, 0x0c6a, 0x0a00, 0x0cb8, 0x0a58, 0x0cd7, 0x0a85, 0x06, 0xfa, 0x0000, 0x0000)), - 'Kakariko Gamble Game': (0x66, (0x0118, 0x29, 0x069e, 0x0ac4, 0x02ea, 0x0b18, 0x0368, 0x0b33, 0x036f, 0x0a, 0xf6, 0x09AC, 0x0000))} - -# format: -# Key=Name -# value = entrance # -# | (entrance #, exit #) -exit_ids = {'Links House Exit': (0x01, 0x00), - 'Chris Houlihan Room Exit': (None, 0x3D), - 'Desert Palace Exit (South)': (0x09, 0x0A), - 'Desert Palace Exit (West)': (0x0B, 0x0C), - 'Desert Palace Exit (East)': (0x0A, 0x0B), - 'Desert Palace Exit (North)': (0x0C, 0x0D), - 'Eastern Palace Exit': (0x08, 0x09), - 'Tower of Hera Exit': (0x33, 0x2D), - 'Hyrule Castle Exit (South)': (0x04, 0x03), - 'Hyrule Castle Exit (West)': (0x03, 0x02), - 'Hyrule Castle Exit (East)': (0x05, 0x04), - 'Agahnims Tower Exit': (0x24, 0x25), - 'Thieves Town Exit': (0x34, 0x35), - 'Skull Woods First Section Exit': (0x2A, 0x2B), - 'Skull Woods Second Section Exit (East)': (0x29, 0x2A), - 'Skull Woods Second Section Exit (West)': (0x28, 0x29), - 'Skull Woods Final Section Exit': (0x2B, 0x2C), - 'Ice Palace Exit': (0x2D, 0x2E), - 'Misery Mire Exit': (0x27, 0x28), - 'Palace of Darkness Exit': (0x26, 0x27), - 'Swamp Palace Exit': (0x25, 0x26), - 'Turtle Rock Exit (Front)': (0x35, 0x34), - 'Turtle Rock Ledge Exit (West)': (0x15, 0x16), - 'Turtle Rock Ledge Exit (East)': (0x19, 0x1A), - 'Turtle Rock Isolated Ledge Exit': (0x18, 0x19), - 'Hyrule Castle Secret Entrance Exit': (0x32, 0x33), - 'Kakariko Well Exit': (0x39, 0x3A), - 'Bat Cave Exit': (0x11, 0x12), - 'Elder House Exit (East)': (0x0E, 0x0F), - 'Elder House Exit (West)': (0x0D, 0x0E), - 'North Fairy Cave Exit': (0x38, 0x39), - 'Lost Woods Hideout Exit': (0x2C, 0x36), - 'Lumberjack Tree Exit': (0x12, 0x13), - 'Two Brothers House Exit (East)': (0x10, 0x11), - 'Two Brothers House Exit (West)': (0x0F, 0x10), - 'Sanctuary Exit': (0x02, 0x01), - 'Old Man Cave Exit (East)': (0x07, 0x08), - 'Old Man Cave Exit (West)': (0x06, 0x07), - 'Old Man House Exit (Bottom)': (0x30, 0x31), - 'Old Man House Exit (Top)': (0x31, 0x32), - 'Death Mountain Return Cave Exit (West)': (0x2E, 0x2F), - 'Death Mountain Return Cave Exit (East)': (0x2F, 0x30), - 'Spectacle Rock Cave Exit': (0x21, 0x22), - 'Spectacle Rock Cave Exit (Top)': (0x22, 0x23), - 'Spectacle Rock Cave Exit (Peak)': (0x23, 0x24), - 'Paradox Cave Exit (Bottom)': (0x1E, 0x1F), - 'Paradox Cave Exit (Middle)': (0x1F, 0x20), - 'Paradox Cave Exit (Top)': (0x20, 0x21), - 'Fairy Ascension Cave Exit (Bottom)': (0x1A, 0x1B), - 'Fairy Ascension Cave Exit (Top)': (0x1B, 0x1C), - 'Spiral Cave Exit': (0x1C, 0x1D), - 'Spiral Cave Exit (Top)': (0x1D, 0x1E), - 'Bumper Cave Exit (Top)': (0x17, 0x18), - 'Bumper Cave Exit (Bottom)': (0x16, 0x17), - 'Superbunny Cave Exit (Top)': (0x14, 0x15), - 'Superbunny Cave Exit (Bottom)': (0x13, 0x14), - 'Hookshot Cave Front Exit': (0x3A, 0x3B), - 'Hookshot Cave Back Exit': (0x3B, 0x3C), - 'Ganons Tower Exit': (0x37, 0x38), - 'Pyramid Exit': (0x36, 0x37), - 'Waterfall of Wishing': 0x5C, - 'Dam': 0x4E, - 'Blinds Hideout': 0x61, - 'Lumberjack House': 0x6B, - 'Bonk Fairy (Light)': 0x71, - 'Bonk Fairy (Dark)': 0x71, - 'Lake Hylia Healer Fairy': 0x5E, - 'Light Hype Fairy': 0x5E, - 'Desert Healer Fairy': 0x5E, - 'Dark Lake Hylia Healer Fairy': 0x5E, - 'Dark Lake Hylia Ledge Healer Fairy': 0x5E, - 'Mire Healer Fairy': 0x5E, - 'Dark Death Mountain Healer Fairy': 0x5E, - 'Fortune Teller (Light)': 0x65, - 'Lake Hylia Fortune Teller': 0x65, - 'Kings Grave': 0x5B, - 'Tavern': 0x43, - 'Chicken House': 0x4B, - 'Aginahs Cave': 0x4D, - 'Sahasrahlas Hut': 0x45, - 'Lake Hylia Shop': 0x58, - 'Dark Death Mountain Shop': 0x58, - 'Capacity Upgrade': 0x5D, - 'Blacksmiths Hut': 0x64, - 'Sick Kids House': 0x40, - 'Lost Woods Gamble': 0x3C, - 'Snitch Lady (East)': 0x3E, - 'Snitch Lady (West)': 0x3F, - 'Bush Covered House': 0x44, - 'Tavern (Front)': 0x42, - 'Light World Bomb Hut': 0x4A, - 'Kakariko Shop': 0x46, - 'Cave 45': 0x51, - 'Graveyard Cave': 0x52, - 'Checkerboard Cave': 0x72, - 'Mini Moldorm Cave': 0x6C, - 'Long Fairy Cave': 0x55, - 'Good Bee Cave': 0x56, - '20 Rupee Cave': 0x6F, - '50 Rupee Cave': 0x6D, - 'Ice Rod Cave': 0x84, - 'Bonk Rock Cave': 0x6E, - 'Library': 0x49, - 'Kakariko Gamble Game': 0x67, - 'Potion Shop': 0x4C, - 'Hookshot Fairy': 0x50, - 'Pyramid Fairy': 0x63, - 'East Dark World Hint': 0x69, - 'Palace of Darkness Hint': 0x68, - 'Big Bomb Shop': 0x53, - 'Village of Outcasts Shop': 0x60, - 'Dark Lake Hylia Shop': 0x60, - 'Dark Lumberjack Shop': 0x60, - 'Dark Potion Shop': 0x60, - 'Dark Lake Hylia Ledge Spike Cave': 0x70, - 'Dark Lake Hylia Ledge Hint': 0x6A, - 'Hype Cave': 0x3D, - 'Brewery': 0x48, - 'C-Shaped House': 0x54, - 'Chest Game': 0x47, - 'Hammer Peg Cave': 0x83, - 'Red Shield Shop': 0x57, - 'Dark Sanctuary Hint': 0x5A, - 'Fortune Teller (Dark)': 0x66, - 'Archery Game': 0x59, - 'Mire Shed': 0x5F, - 'Mire Hint': 0x62, - 'Spike Cave': 0x41, - 'Mimic Cave': 0x4F, - 'Kakariko Well (top)': 0x80, - 'Hyrule Castle Secret Entrance': 0x7D, - 'Bat Cave (right)': 0x7E, - 'North Fairy Cave': 0x7C, - 'Lost Woods Hideout (top)': 0x7A, - 'Lumberjack Tree (top)': 0x7F, - 'Sewer Drop': 0x81, - 'Skull Back Drop': 0x79, - 'Skull Left Drop': 0x77, - 'Skull Pinball': 0x78, - 'Skull Pot Circle': 0x76, - 'Pyramid': 0x7B} - -ow_prize_table = {'Links House': (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), - '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), - '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': (0x270, 0x900), - 'Chicken House': (0x120, 0x880), - 'Aginahs Cave': (0x2e0, 0xd00), - 'Sahasrahlas Hut': (0xcf0, 0x6c0), - 'Lake Hylia Shop': (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), - 'Hammer Peg Cave': (0x4c0, 0x940), - 'Red Shield Shop': (0x500, 0x680), - 'Dark Sanctuary Hint': (0x720, 0x4a0), - 'Fortune Teller (Dark)': (0x2c0, 0x4c0), - 'Dark World Shop': (0x2e0, 0x880), - 'Dark Lumberjack Shop': (0x4e0, 0x0d0), - 'Dark Potion Shop': (0xc80, 0x4c0), - 'Archery Game': (0x2f0, 0xaf0), - 'Mire Shed': (0x060, 0xc90), - 'Mire Hint': (0x2e0, 0xd00), - 'Mire Fairy': (0x1c0, 0xc90), - 'Spike Cave': (0x860, 0x180), - 'Dark Death Mountain Shop': (0xd80, 0x180), - 'Dark Death Mountain Fairy': (0x620, 0x2c0), - 'Mimic Cave': (0xc80, 0x180), - 'Big Bomb Shop': (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/ItemList.py b/ItemList.py index 5d7e3228..a0ecb729 100644 --- a/ItemList.py +++ b/ItemList.py @@ -4,13 +4,13 @@ import math import RaceRandom as random 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, valid_pot_location from Fill import FillError, fill_restrictive, get_dungeon_item_pool, track_dungeon_items, track_outside_keys from PotShuffle import vanilla_pots from Items import ItemFactory from source.dungeon.EnemyList import add_drop_contents +from source.overworld.EntranceShuffle2 import connect_entrance from source.item.FillUtil import trash_items, pot_items import source.classes.constants as CONST diff --git a/Main.py b/Main.py index 3a74ff8f..fec6996f 100644 --- a/Main.py +++ b/Main.py @@ -17,7 +17,6 @@ from OverworldGlitchRules import create_owg_connections from PotShuffle import shuffle_pots, shuffle_pot_switches from Regions import create_regions, create_shops, mark_light_dark_world_regions, create_dungeon_regions, adjust_locations from OverworldShuffle import link_overworld, create_dynamic_exits -from EntranceShuffle import link_entrances from Rom import patch_rom, patch_race_rom, apply_rom_settings, LocalRom, JsonRom, get_hash_string from Doors import create_doors from DoorShuffle import link_doors, connect_portal, link_doors_prep @@ -39,7 +38,7 @@ from source.enemizer.DamageTables import DamageTable from source.enemizer.Enemizer import randomize_enemies from source.rom.DataTables import init_data_tables -version_number = '1.4.1.11' +version_number = '1.4.1.12' version_branch = '-u' __version__ = f'{version_number}{version_branch}' @@ -249,10 +248,7 @@ def main(args, seed=None, fish=None): for player in range(1, world.players + 1): link_overworld(world, player) create_dynamic_exits(world, player) - if world.experimental[player] or world.shuffle[player] in ['lite', 'lean', 'swapped'] or world.shuffletavern[player] or (world.customizer and world.customizer.get_entrances()): - link_entrances_new(world, player) - else: - link_entrances(world, player) + link_entrances_new(world, player) if world.logic[player] in ('nologic', 'hybridglitches'): create_hybridmajor_connectors(world, player) diff --git a/Plando.py b/Plando.py index 1af62926..b2da5ba0 100755 --- a/Plando.py +++ b/Plando.py @@ -10,7 +10,7 @@ import sys from BaseClasses import World from Regions import create_regions from OverworldShuffle import link_overworld -from EntranceShuffle import link_entrances, connect_entrance, connect_two_way, connect_exit +from source.overworld.EntranceShuffle2 import link_entrances_new, connect_entrance, connect_two_way, connect_exit from Rom import patch_rom, LocalRom, write_string_to_rom, apply_rom_settings, get_sprite_from_name from Rules import set_rules from Dungeons import create_dungeons @@ -44,7 +44,7 @@ def main(args): create_dungeons(world, 1) link_overworld(world, 1) - link_entrances(world, 1) + link_entrances_new(world, 1) logger.info('Calculating Access Rules.') diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 1f4720b4..21905044 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -141,6 +141,10 @@ These are now independent of retro mode and have three options: None, Random, an # Patch Notes +* 1.4.1.12u + * New Entrance Shuffle Algorithm no longer experimental + * Back of Tavern Shuffle now on by default + * Fixed a small bug with traversal algorithm * 1.4.1.11u * New Feature: Several spoiler levels added: None, Settings-only, Semi, Full, Debug * Semi includes only entrances, prizes, and medallions (potential new spoiler mode being worked on, definition may change) diff --git a/Rom.py b/Rom.py index 95327080..9acb4ebd 100644 --- a/Rom.py +++ b/Rom.py @@ -10,7 +10,6 @@ import Items import RaceRandom as random import struct import sys -import subprocess try: import bps.apply import bps.io @@ -28,9 +27,10 @@ from Text import Triforce_texts, Blind_texts, BombShop2_texts, junk_texts from Text import KingsReturn_texts, Sanctuary_texts, Kakariko_texts, Blacksmiths_texts, DeathMountain_texts from Text import LostWoods_texts, WishingWell_texts, DesertPalace_texts, MountainTower_texts, LinksHouse_texts from Text import Lumberjacks_texts, SickKid_texts, FluteBoy_texts, Zora_texts, MagicShop_texts, Sahasrahla_names -from Utils import output_path, local_path, int16_as_bytes, int32_as_bytes, snes_to_pc +from Utils import local_path, int16_as_bytes, int32_as_bytes, snes_to_pc from Items import ItemFactory -from EntranceShuffle import door_addresses, exit_ids, ow_prize_table +from source.overworld.EntranceData import door_addresses +from source.overworld.EntranceShuffle2 import exit_ids, ow_prize_table from InitialSram import InitialSram from source.classes.SFX import randomize_sfx diff --git a/source/dungeon/EnemyList.py b/source/dungeon/EnemyList.py index 21ca04b6..8f168a8d 100644 --- a/source/dungeon/EnemyList.py +++ b/source/dungeon/EnemyList.py @@ -11,11 +11,11 @@ except ImportError: import RaceRandom as random from BaseClasses import Location, LocationType, RegionType -from EntranceShuffle import door_addresses from Items import ItemFactory from PotShuffle import key_drop_special from Utils import snes_to_pc, pc_to_snes, int16_as_bytes +from source.overworld.EntranceData import door_addresses class EnemyStats: def __init__(self, sprite, static, drop_flag=False, prize_pack: typing.Union[tuple, int] = 0, diff --git a/source/overworld/EntranceData.py b/source/overworld/EntranceData.py new file mode 100644 index 00000000..48759272 --- /dev/null +++ b/source/overworld/EntranceData.py @@ -0,0 +1,159 @@ +indirect_connections = { + 'Turtle Rock Ledge': 'Turtle Rock', + 'Pyramid Area': 'Pyramid Fairy', + 'Big Bomb Shop': 'Pyramid Fairy', + 'Mire Area': 'Pyramid Fairy', + #'West Dark World': 'Pyramid Fairy', + 'Big Bomb Shop Area': 'Pyramid Fairy', + #'Light World': 'Pyramid Fairy', + 'Old Man Cave (East)': 'Old Man S&Q' +} + +# format: +# Key=Name +# addr = (door_index, exitdata) # multiexit +# | ([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) + +door_addresses = {'Links House': (0x00, (0x0104, 0x2c, 0x0506, 0x0a9a, 0x0832, 0x0ae8, 0x08b8, 0x0b07, 0x08bf, 0x06, 0xfe, 0x0816, 0x0000)), + 'Desert Palace Entrance (South)': (0x08, (0x0084, 0x30, 0x0314, 0x0c56, 0x00a6, 0x0ca8, 0x0128, 0x0cc3, 0x0133, 0x0a, 0xfa, 0x0000, 0x0000)), + 'Desert Palace Entrance (West)': (0x0A, (0x0083, 0x30, 0x0280, 0x0c46, 0x0003, 0x0c98, 0x0088, 0x0cb3, 0x0090, 0x0a, 0xfd, 0x0000, 0x0000)), + 'Desert Palace Entrance (North)': (0x0B, (0x0063, 0x30, 0x0016, 0x0c00, 0x00a2, 0x0c28, 0x0128, 0x0c6d, 0x012f, 0x00, 0x0e, 0x0000, 0x0000)), + 'Desert Palace Entrance (East)': (0x09, (0x0085, 0x30, 0x02a8, 0x0c4a, 0x0142, 0x0c98, 0x01c8, 0x0cb7, 0x01cf, 0x06, 0xfe, 0x0000, 0x0000)), + 'Eastern Palace': (0x07, (0x00c9, 0x1e, 0x005a, 0x0600, 0x0ed6, 0x0618, 0x0f50, 0x066d, 0x0f5b, 0x00, 0xfa, 0x0000, 0x0000)), + 'Tower of Hera': (0x32, (0x0077, 0x03, 0x0050, 0x0014, 0x087c, 0x0068, 0x08f0, 0x0083, 0x08fb, 0x0a, 0xf4, 0x0000, 0x0000)), + 'Hyrule Castle Entrance (South)': (0x03, (0x0061, 0x1b, 0x0530, 0x0692, 0x0784, 0x06cc, 0x07f8, 0x06ff, 0x0803, 0x0e, 0xfa, 0x0000, 0x87be)), + 'Hyrule Castle Entrance (West)': (0x02, (0x0060, 0x1b, 0x0016, 0x0600, 0x06ae, 0x0604, 0x0728, 0x066d, 0x0733, 0x00, 0x02, 0x0000, 0x8124)), + 'Hyrule Castle Entrance (East)': (0x04, (0x0062, 0x1b, 0x004a, 0x0600, 0x0856, 0x0604, 0x08c8, 0x066d, 0x08d3, 0x00, 0xfa, 0x0000, 0x8158)), + 'Inverted Pyramid Entrance': (0x35, (0x0010, 0x1b, 0x0418, 0x0679, 0x06b4, 0x06c6, 0x0728, 0x06e6, 0x0733, 0x07, 0xf9, 0x0000, 0x0000)), + 'Agahnims Tower': (0x23, (0x00e0, 0x1b, 0x0032, 0x0600, 0x0784, 0x0634, 0x07f8, 0x066d, 0x0803, 0x00, 0x0a, 0x0000, 0x82be)), + 'Thieves Town': (0x33, (0x00db, 0x58, 0x0b2e, 0x075a, 0x0176, 0x07a8, 0x01f8, 0x07c7, 0x0203, 0x06, 0xfa, 0x0000, 0x0000)), + 'Skull Woods First Section Door': (0x29, (0x0058, 0x40, 0x0f4c, 0x01f6, 0x0262, 0x0248, 0x02e8, 0x0263, 0x02ef, 0x0a, 0xfe, 0x0000, 0x0000)), + 'Skull Woods Second Section Door (East)': (0x28, (0x0057, 0x40, 0x0eb8, 0x01e6, 0x01c2, 0x0238, 0x0248, 0x0253, 0x024f, 0x0a, 0xfe, 0x0000, 0x0000)), + 'Skull Woods Second Section Door (West)': (0x27, (0x0056, 0x40, 0x0c8e, 0x01a6, 0x0062, 0x01f8, 0x00e8, 0x0213, 0x00ef, 0x0a, 0x0e, 0x0000, 0x0000)), + 'Skull Woods Final Section': (0x2A, (0x0059, 0x40, 0x0282, 0x0066, 0x0016, 0x00b8, 0x0098, 0x00d3, 0x00a3, 0x0a, 0xfa, 0x0000, 0x0000)), + 'Ice Palace': (0x2C, (0x000e, 0x75, 0x0bc6, 0x0d6a, 0x0c3e, 0x0db8, 0x0cb8, 0x0dd7, 0x0cc3, 0x06, 0xf2, 0x0000, 0x0000)), + 'Misery Mire': (0x26, (0x0098, 0x70, 0x0414, 0x0c79, 0x00a6, 0x0cc7, 0x0128, 0x0ce6, 0x0133, 0x07, 0xfa, 0x0000, 0x0000)), + 'Palace of Darkness': (0x25, (0x004a, 0x5e, 0x005a, 0x0600, 0x0ed6, 0x0628, 0x0f50, 0x066d, 0x0f5b, 0x00, 0xfa, 0x0000, 0x0000)), + 'Swamp Palace': (0x24, (0x0028, 0x7b, 0x049e, 0x0e8c, 0x06f2, 0x0ed8, 0x0778, 0x0ef9, 0x077f, 0x04, 0xfe, 0x0000, 0x0000)), + 'Turtle Rock': (0x34, (0x00d6, 0x47, 0x0712, 0x00da, 0x0e96, 0x0128, 0x0f08, 0x0147, 0x0f13, 0x06, 0xfa, 0x0000, 0x0000)), + 'Dark Death Mountain Ledge (West)': (0x14, (0x0023, 0x45, 0x07ca, 0x0103, 0x0c46, 0x0157, 0x0cb8, 0x0172, 0x0cc3, 0x0b, 0x0a, 0x0000, 0x0000)), + 'Dark Death Mountain Ledge (East)': (0x18, (0x0024, 0x45, 0x07e0, 0x0103, 0x0d00, 0x0157, 0x0d78, 0x0172, 0x0d7d, 0x0b, 0x00, 0x0000, 0x0000)), + 'Turtle Rock Isolated Ledge Entrance': (0x17, (0x00d5, 0x45, 0x0ad4, 0x0164, 0x0ca6, 0x01b8, 0x0d18, 0x01d3, 0x0d23, 0x0a, 0xfa, 0x0000, 0x0000)), + 'Hyrule Castle Secret Entrance Stairs': (0x31, (0x0055, 0x1b, 0x044a, 0x067a, 0x0854, 0x06c8, 0x08c8, 0x06e7, 0x08d3, 0x06, 0xfa, 0x0000, 0x0000)), + 'Kakariko Well Cave': (0x38, (0x002f, 0x18, 0x0386, 0x0665, 0x0032, 0x06b7, 0x00b8, 0x06d2, 0x00bf, 0x0b, 0xfe, 0x0000, 0x0000)), + 'Bat Cave Cave': (0x10, (0x00e3, 0x22, 0x0412, 0x087a, 0x048e, 0x08c8, 0x0508, 0x08e7, 0x0513, 0x06, 0x02, 0x0000, 0x0000)), + 'Elder House (East)': (0x0D, (0x00f3, 0x18, 0x02c4, 0x064a, 0x0222, 0x0698, 0x02a8, 0x06b7, 0x02af, 0x06, 0xfe, 0x05d4, 0x0000)), + 'Elder House (West)': (0x0C, (0x00f2, 0x18, 0x02bc, 0x064c, 0x01e2, 0x0698, 0x0268, 0x06b9, 0x026f, 0x04, 0xfe, 0x05cc, 0x0000)), + 'North Fairy Cave': (0x37, (0x0008, 0x15, 0x0088, 0x0400, 0x0a36, 0x0448, 0x0aa8, 0x046f, 0x0ab3, 0x00, 0x0a, 0x0000, 0x0000)), + 'Lost Woods Hideout Stump': (0x2B, (0x00e1, 0x00, 0x0f4e, 0x01f6, 0x0262, 0x0248, 0x02e8, 0x0263, 0x02ef, 0x0a, 0x0e, 0x0000, 0x0000)), + 'Lumberjack Tree Cave': (0x11, (0x00e2, 0x02, 0x0118, 0x0015, 0x04c6, 0x0067, 0x0548, 0x0082, 0x0553, 0x0b, 0xfa, 0x0000, 0x0000)), + 'Two Brothers House (East)': (0x0F, (0x00f5, 0x29, 0x0880, 0x0b07, 0x0200, 0x0b58, 0x0238, 0x0b74, 0x028d, 0x09, 0x00, 0x0b86, 0x0000)), + 'Two Brothers House (West)': (0x0E, (0x00f4, 0x28, 0x08a0, 0x0b06, 0x0100, 0x0b58, 0x01b8, 0x0b73, 0x018d, 0x0a, 0x00, 0x0bb6, 0x0000)), + 'Sanctuary': (0x01, (0x0012, 0x13, 0x001c, 0x0400, 0x06de, 0x0414, 0x0758, 0x046d, 0x0763, 0x00, 0x02, 0x0000, 0x01aa)), + 'Old Man Cave (West)': (0x05, (0x00f0, 0x0a, 0x03a0, 0x0264, 0x0500, 0x02b8, 0x05a8, 0x02d3, 0x058d, 0x0a, 0x00, 0x0000, 0x0000)), + 'Old Man Cave (East)': (0x06, (0x00f1, 0x03, 0x1402, 0x0294, 0x0604, 0x02e8, 0x0678, 0x0303, 0x0683, 0x0a, 0xfc, 0x0000, 0x0000)), + 'Old Man House (Bottom)': (0x2F, (0x00e4, 0x03, 0x181a, 0x031e, 0x06b4, 0x03a7, 0x0728, 0x038d, 0x0733, 0x00, 0x0c, 0x0000, 0x0000)), + 'Old Man House (Top)': (0x30, (0x00e5, 0x03, 0x10c6, 0x0224, 0x0814, 0x0278, 0x0888, 0x0293, 0x0893, 0x0a, 0x0c, 0x0000, 0x0000)), + 'Death Mountain Return Cave (East)': (0x2E, (0x00e7, 0x03, 0x0d82, 0x01c4, 0x0600, 0x0218, 0x0648, 0x0233, 0x067f, 0x0a, 0x00, 0x0000, 0x0000)), + 'Death Mountain Return Cave (West)': (0x2D, (0x00e6, 0x0a, 0x00a0, 0x0205, 0x0500, 0x0257, 0x05b8, 0x0272, 0x058d, 0x0b, 0x00, 0x0000, 0x0000)), + 'Spectacle Rock Cave Peak': (0x22, (0x00ea, 0x03, 0x092c, 0x0133, 0x0754, 0x0187, 0x07c8, 0x01a2, 0x07d3, 0x0b, 0xfc, 0x0000, 0x0000)), + 'Spectacle Rock Cave': (0x21, (0x00fa, 0x03, 0x0eac, 0x01e3, 0x0754, 0x0237, 0x07c8, 0x0252, 0x07d3, 0x0b, 0xfc, 0x0000, 0x0000)), + 'Spectacle Rock Cave (Bottom)': (0x20, (0x00f9, 0x03, 0x0d9c, 0x01c3, 0x06d4, 0x0217, 0x0748, 0x0232, 0x0753, 0x0b, 0xfc, 0x0000, 0x0000)), + 'Paradox Cave (Bottom)': (0x1D, (0x00ff, 0x05, 0x0ee0, 0x01e3, 0x0d00, 0x0237, 0x0da8, 0x0252, 0x0d7d, 0x0b, 0x00, 0x0000, 0x0000)), + 'Paradox Cave (Middle)': (0x1E, (0x00ef, 0x05, 0x17e0, 0x0304, 0x0d00, 0x0358, 0x0dc8, 0x0373, 0x0d7d, 0x0a, 0x00, 0x0000, 0x0000)), + 'Paradox Cave (Top)': (0x1F, (0x00df, 0x05, 0x0460, 0x0093, 0x0d00, 0x00e7, 0x0db8, 0x0102, 0x0d7d, 0x0b, 0x00, 0x0000, 0x0000)), + 'Fairy Ascension Cave (Bottom)': (0x19, (0x00fd, 0x05, 0x0dd4, 0x01c4, 0x0ca6, 0x0218, 0x0d18, 0x0233, 0x0d23, 0x0a, 0xfa, 0x0000, 0x0000)), + 'Fairy Ascension Cave (Top)': (0x1A, (0x00ed, 0x05, 0x0ad4, 0x0163, 0x0ca6, 0x01b7, 0x0d18, 0x01d2, 0x0d23, 0x0b, 0xfa, 0x0000, 0x0000)), + 'Spiral Cave': (0x1C, (0x00ee, 0x05, 0x07c8, 0x0108, 0x0c46, 0x0158, 0x0cb8, 0x0177, 0x0cc3, 0x06, 0xfa, 0x0000, 0x0000)), + 'Spiral Cave (Bottom)': (0x1B, (0x00fe, 0x05, 0x0cca, 0x01a3, 0x0c56, 0x01f7, 0x0cc8, 0x0212, 0x0cd3, 0x0b, 0xfa, 0x0000, 0x0000)), + 'Bumper Cave (Bottom)': (0x15, (0x00fb, 0x4a, 0x03a0, 0x0263, 0x0500, 0x02b7, 0x05a8, 0x02d2, 0x058d, 0x0b, 0x00, 0x0000, 0x0000)), + 'Bumper Cave (Top)': (0x16, (0x00eb, 0x4a, 0x00a0, 0x020a, 0x0500, 0x0258, 0x05b8, 0x0277, 0x058d, 0x06, 0x00, 0x0000, 0x0000)), + 'Superbunny Cave (Top)': (0x13, (0x00e8, 0x45, 0x0460, 0x0093, 0x0d00, 0x00e7, 0x0db8, 0x0102, 0x0d7d, 0x0b, 0x00, 0x0000, 0x0000)), + 'Superbunny Cave (Bottom)': (0x12, (0x00f8, 0x45, 0x0ee0, 0x01e4, 0x0d00, 0x0238, 0x0d78, 0x0253, 0x0d7d, 0x0a, 0x00, 0x0000, 0x0000)), + 'Hookshot Cave': (0x39, (0x003c, 0x45, 0x04da, 0x00a3, 0x0cd6, 0x0107, 0x0d48, 0x0112, 0x0d53, 0x0b, 0xfa, 0x0000, 0x0000)), + 'Hookshot Cave Back Entrance': (0x3A, (0x002c, 0x45, 0x004c, 0x0000, 0x0c56, 0x0038, 0x0cc8, 0x006f, 0x0cd3, 0x00, 0x0a, 0x0000, 0x0000)), + 'Ganons Tower': (0x36, (0x000c, 0x43, 0x0052, 0x0000, 0x0884, 0x0028, 0x08f8, 0x006f, 0x0903, 0x00, 0xfc, 0x0000, 0x0000)), + 'Pyramid Entrance': (0x35, (0x0010, 0x5b, 0x0b0e, 0x075a, 0x0674, 0x07a8, 0x06e8, 0x07c7, 0x06f3, 0x06, 0xfa, 0x0000, 0x0000)), + 'Skull Woods First Section Hole (West)': ([0xDB84D, 0xDB84E], None), + 'Skull Woods First Section Hole (East)': ([0xDB84F, 0xDB850], None), + 'Skull Woods First Section Hole (North)': ([0xDB84C], None), + 'Skull Woods Second Section Hole': ([0xDB851, 0xDB852], None), + 'Pyramid Hole': ([0xDB854, 0xDB855, 0xDB856], None), + 'Inverted Pyramid Hole': ([0xDB854, 0xDB855, 0xDB856, 0x180340], None), + 'Waterfall of Wishing': (0x5B, (0x0114, 0x0f, 0x0080, 0x0200, 0x0e00, 0x0207, 0x0e60, 0x026f, 0x0e7d, 0x00, 0x00, 0x0000, 0x0000)), + 'Dam': (0x4D, (0x010b, 0x3b, 0x04a0, 0x0e8a, 0x06fa, 0x0ed8, 0x0778, 0x0ef7, 0x077f, 0x06, 0xfa, 0x0000, 0x0000)), + 'Blinds Hideout': (0x60, (0x0119, 0x18, 0x02b2, 0x064a, 0x0186, 0x0697, 0x0208, 0x06b7, 0x0213, 0x06, 0xfa, 0x0000, 0x0000)), + 'Hyrule Castle Secret Entrance Drop': ([0xDB858], None), + 'Bonk Fairy (Light)': (0x76, (0x0126, 0x2b, 0x00a0, 0x0a0a, 0x0700, 0x0a67, 0x0788, 0x0a77, 0x0785, 0x06, 0xfa, 0x0000, 0x0000)), + 'Lake Hylia Fairy': (0x5D, (0x0115, 0x2e, 0x0016, 0x0a00, 0x0cb6, 0x0a37, 0x0d28, 0x0a6d, 0x0d33, 0x00, 0x00, 0x0000, 0x0000)), + 'Light Hype Fairy': (0x6B, (0x0115, 0x34, 0x00a0, 0x0c04, 0x0900, 0x0c58, 0x0988, 0x0c73, 0x0985, 0x0a, 0xf6, 0x0000, 0x0000)), + 'Desert Fairy': (0x71, (0x0115, 0x3a, 0x0000, 0x0e00, 0x0400, 0x0e26, 0x0468, 0x0e6d, 0x0485, 0x00, 0x00, 0x0000, 0x0000)), + 'Kings Grave': (0x5A, (0x0113, 0x14, 0x0320, 0x0456, 0x0900, 0x04a6, 0x0998, 0x04c3, 0x097d, 0x0a, 0xf6, 0x0000, 0x0000)), + 'Tavern North': (0x42, (0x0103, 0x18, 0x1440, 0x08a7, 0x0206, 0x091b, 0x0288, 0x0914, 0x0293, 0xf7, 0x09, 0xFFFF, 0x0000)), + 'Chicken House': (0x4A, (0x0108, 0x18, 0x1120, 0x0837, 0x0106, 0x0888, 0x0188, 0x08a4, 0x0193, 0x07, 0xf9, 0x1530, 0x0000)), + 'Aginahs Cave': (0x70, (0x010a, 0x30, 0x0656, 0x0cc6, 0x02aa, 0x0d18, 0x0328, 0x0d33, 0x032f, 0x08, 0xf8, 0x0000, 0x0000)), + 'Sahasrahlas Hut': (0x44, (0x0105, 0x1e, 0x0610, 0x06d4, 0x0c76, 0x0727, 0x0cf0, 0x0743, 0x0cfb, 0x0a, 0xf6, 0x0000, 0x0000)), + 'Lake Hylia Shop': (0x57, (0x0112, 0x35, 0x0022, 0x0c00, 0x0b1a, 0x0c26, 0x0b98, 0x0c6d, 0x0b9f, 0x00, 0x00, 0x0000, 0x0000)), + 'Capacity Upgrade': (0x5C, (0x0115, 0x35, 0x0a46, 0x0d36, 0x0c2a, 0x0d88, 0x0ca8, 0x0da3, 0x0caf, 0x0a, 0xf6, 0x0000, 0x0000)), + 'Kakariko Well Drop': ([0xDB85C, 0xDB85D], None), + 'Blacksmiths Hut': (0x63, (0x0121, 0x22, 0x010c, 0x081a, 0x0466, 0x0868, 0x04d8, 0x0887, 0x04e3, 0x06, 0xfa, 0x041A, 0x0000)), + 'Bat Cave Drop': ([0xDB859, 0xDB85A], None), + 'Sick Kids House': (0x3F, (0x0102, 0x18, 0x10be, 0x0826, 0x01f6, 0x0877, 0x0278, 0x0893, 0x0283, 0x08, 0xf8, 0x14CE, 0x0000)), + 'North Fairy Cave Drop': ([0xDB857], None), + 'Lost Woods Gamble': (0x3B, (0x0100, 0x00, 0x004e, 0x0000, 0x0272, 0x0008, 0x02f0, 0x006f, 0x02f7, 0x00, 0x00, 0x0000, 0x0000)), + 'Fortune Teller (Light)': (0x64, (0x0122, 0x11, 0x060e, 0x04b4, 0x027d, 0x0508, 0x02f8, 0x0523, 0x0302, 0x0a, 0xf6, 0x0000, 0x0000)), + 'Snitch Lady (East)': (0x3D, (0x0101, 0x18, 0x0ad8, 0x074a, 0x02c6, 0x0798, 0x0348, 0x07b7, 0x0353, 0x06, 0xfa, 0x0DE8, 0x0000)), + 'Snitch Lady (West)': (0x3E, (0x0101, 0x18, 0x0788, 0x0706, 0x0046, 0x0758, 0x00c8, 0x0773, 0x00d3, 0x08, 0xf8, 0x0B98, 0x0000)), + 'Bush Covered House': (0x43, (0x0103, 0x18, 0x1156, 0x081a, 0x02b6, 0x0868, 0x0338, 0x0887, 0x0343, 0x06, 0xfa, 0x1466, 0x0000)), + 'Tavern (Front)': (0x41, (0x0103, 0x18, 0x1842, 0x0916, 0x0206, 0x0967, 0x0288, 0x0983, 0x0293, 0x08, 0xf8, 0x1C50, 0x0000)), + 'Light World Bomb Hut': (0x49, (0x0107, 0x18, 0x1800, 0x0916, 0x0000, 0x0967, 0x0068, 0x0983, 0x008d, 0x08, 0xf8, 0x9C0C, 0x0000)), + 'Kakariko Shop': (0x45, (0x011f, 0x18, 0x16a8, 0x08e7, 0x0136, 0x0937, 0x01b8, 0x0954, 0x01c3, 0x07, 0xf9, 0x1AB6, 0x0000)), + 'Lost Woods Hideout Drop': ([0xDB853], None), + 'Lumberjack Tree Tree': ([0xDB85B], None), + 'Cave 45': (0x50, (0x011b, 0x32, 0x0680, 0x0cc9, 0x0400, 0x0d16, 0x0438, 0x0d36, 0x0485, 0x07, 0xf9, 0x0000, 0x0000)), + 'Graveyard Cave': (0x51, (0x011b, 0x14, 0x0016, 0x0400, 0x08a2, 0x0446, 0x0918, 0x046d, 0x091f, 0x00, 0x00, 0x0000, 0x0000)), + 'Checkerboard Cave': (0x7D, (0x0126, 0x30, 0x00c8, 0x0c0a, 0x024a, 0x0c67, 0x02c8, 0x0c77, 0x02cf, 0x06, 0xfa, 0x0000, 0x0000)), + 'Mini Moldorm Cave': (0x7C, (0x0123, 0x35, 0x1480, 0x0e96, 0x0a00, 0x0ee8, 0x0a68, 0x0f03, 0x0a85, 0x08, 0xf8, 0x0000, 0x0000)), + 'Long Fairy Cave': (0x54, (0x011e, 0x2f, 0x06a0, 0x0aca, 0x0f00, 0x0b18, 0x0fa8, 0x0b37, 0x0f85, 0x06, 0xfa, 0x0000, 0x0000)), + 'Good Bee Cave': (0x6A, (0x0120, 0x37, 0x0084, 0x0c00, 0x0e26, 0x0c36, 0x0e98, 0x0c6f, 0x0ea3, 0x00, 0x00, 0x0000, 0x0000)), + '20 Rupee Cave': (0x7A, (0x0125, 0x37, 0x0200, 0x0c23, 0x0e00, 0x0c86, 0x0e68, 0x0c92, 0x0e7d, 0x0d, 0xf3, 0x0000, 0x0000)), + '50 Rupee Cave': (0x78, (0x0124, 0x3a, 0x0790, 0x0eea, 0x047a, 0x0f47, 0x04f8, 0x0f57, 0x04ff, 0x06, 0xfa, 0x0000, 0x0000)), + 'Ice Rod Cave': (0x7F, (0x0120, 0x37, 0x0080, 0x0c00, 0x0e00, 0x0c37, 0x0e48, 0x0c6f, 0x0e7d, 0x00, 0x00, 0x0000, 0x0000)), + 'Bonk Rock Cave': (0x79, (0x0124, 0x13, 0x0280, 0x044a, 0x0600, 0x04a7, 0x0638, 0x04b7, 0x067d, 0x06, 0xfa, 0x0000, 0x0000)), + 'Library': (0x48, (0x0107, 0x29, 0x0100, 0x0a14, 0x0200, 0x0a67, 0x0278, 0x0a83, 0x0285, 0x0a, 0xf6, 0x040E, 0x0000)), + 'Potion Shop': (0x4B, (0x0109, 0x16, 0x070a, 0x04e6, 0x0c56, 0x0538, 0x0cc8, 0x0553, 0x0cd3, 0x08, 0xf8, 0x0A98, 0x0000)), + 'Sanctuary Grave': ([0xDB85E], None), + 'Hookshot Fairy': (0x4F, (0x010c, 0x05, 0x0ee0, 0x01e3, 0x0d00, 0x0236, 0x0d78, 0x0252, 0x0d7d, 0x0b, 0xf5, 0x0000, 0x0000)), + 'Pyramid Fairy': (0x62, (0x0116, 0x5b, 0x0b1e, 0x0754, 0x06fa, 0x07a7, 0x0778, 0x07c3, 0x077f, 0x0a, 0xf6, 0x0000, 0x0000)), + 'East Dark World Hint': (0x68, (0x010e, 0x6f, 0x06a0, 0x0aca, 0x0f00, 0x0b18, 0x0fa8, 0x0b37, 0x0f85, 0x06, 0xfa, 0x0000, 0x0000)), + 'Palace of Darkness Hint': (0x67, (0x011a, 0x5e, 0x0c24, 0x0794, 0x0d12, 0x07e8, 0x0d90, 0x0803, 0x0d97, 0x0a, 0xf6, 0x0000, 0x0000)), + 'Dark Lake Hylia Fairy': (0x6C, (0x0115, 0x6e, 0x0016, 0x0a00, 0x0cb6, 0x0a36, 0x0d28, 0x0a6d, 0x0d33, 0x00, 0x00, 0x0000, 0x0000)), + 'Dark Lake Hylia Ledge Fairy': (0x80, (0x0115, 0x77, 0x0080, 0x0c00, 0x0e00, 0x0c37, 0x0e48, 0x0c6f, 0x0e7d, 0x00, 0x00, 0x0000, 0x0000)), + 'Dark Lake Hylia Ledge Spike Cave': (0x7B, (0x0125, 0x77, 0x0200, 0x0c27, 0x0e00, 0x0c86, 0x0e68, 0x0c96, 0x0e7d, 0x09, 0xf7, 0x0000, 0x0000)), + 'Dark Lake Hylia Ledge Hint': (0x69, (0x010e, 0x77, 0x0084, 0x0c00, 0x0e26, 0x0c36, 0x0e98, 0x0c6f, 0x0ea3, 0x00, 0x00, 0x0000, 0x0000)), + 'Hype Cave': (0x3C, (0x011e, 0x74, 0x00a0, 0x0c0a, 0x0900, 0x0c58, 0x0988, 0x0c77, 0x097d, 0x06, 0xfa, 0x0000, 0x0000)), + 'Bonk Fairy (Dark)': (0x77, (0x0126, 0x6b, 0x00a0, 0x0a05, 0x0700, 0x0a66, 0x0788, 0x0a72, 0x0785, 0x0b, 0xf5, 0x0000, 0x0000)), + 'Brewery': (0x47, (0x0106, 0x58, 0x16a8, 0x08e4, 0x013e, 0x0938, 0x01b8, 0x0953, 0x01c3, 0x0a, 0xf6, 0x1AB6, 0x0000)), + 'C-Shaped House': (0x53, (0x011c, 0x58, 0x09d8, 0x0744, 0x02ce, 0x0797, 0x0348, 0x07b3, 0x0353, 0x0a, 0xf6, 0x0DE8, 0x0000)), + 'Chest Game': (0x46, (0x0106, 0x58, 0x078a, 0x0705, 0x004e, 0x0758, 0x00c8, 0x0774, 0x00d3, 0x09, 0xf7, 0x0B98, 0x0000)), + 'Hammer Peg Cave': (0x7E, (0x0127, 0x62, 0x0894, 0x091e, 0x0492, 0x09a6, 0x0508, 0x098b, 0x050f, 0x00, 0x00, 0x0000, 0x0000)), + 'Red Shield Shop': (0x74, (0x0110, 0x5a, 0x079a, 0x06e8, 0x04d6, 0x0738, 0x0548, 0x0755, 0x0553, 0x08, 0xf8, 0x0AA8, 0x0000)), + 'Dark Sanctuary Hint': (0x59, (0x0112, 0x53, 0x001e, 0x0400, 0x06e2, 0x0446, 0x0758, 0x046d, 0x075f, 0x00, 0x00, 0x0000, 0x0000)), + 'Fortune Teller (Dark)': (0x65, (0x0122, 0x51, 0x0610, 0x04b4, 0x027e, 0x0507, 0x02f8, 0x0523, 0x0303, 0x0a, 0xf6, 0x091E, 0x0000)), + 'Dark World Shop': (0x5F, (0x010f, 0x58, 0x1058, 0x0814, 0x02be, 0x0868, 0x0338, 0x0883, 0x0343, 0x0a, 0xf6, 0x0000, 0x0000)), + 'Dark Lumberjack Shop': (0x56, (0x010f, 0x42, 0x041c, 0x0074, 0x04e2, 0x00c7, 0x0558, 0x00e3, 0x055f, 0x0a, 0xf6, 0x0000, 0x0000)), + 'Dark Potion Shop': (0x6E, (0x010f, 0x56, 0x080e, 0x04f4, 0x0c66, 0x0548, 0x0cd8, 0x0563, 0x0ce3, 0x0a, 0xf6, 0x0000, 0x0000)), + 'Archery Game': (0x58, (0x0111, 0x69, 0x069e, 0x0ac4, 0x02ea, 0x0b18, 0x0368, 0x0b33, 0x036f, 0x0a, 0xf6, 0x09AC, 0x0000)), + 'Mire Shed': (0x5E, (0x010d, 0x70, 0x0384, 0x0c69, 0x001e, 0x0cb6, 0x0098, 0x0cd6, 0x00a3, 0x07, 0xf9, 0x0000, 0x0000)), + 'Mire Hint': (0x61, (0x0114, 0x70, 0x0654, 0x0cc5, 0x02aa, 0x0d16, 0x0328, 0x0d32, 0x032f, 0x09, 0xf7, 0x0000, 0x0000)), + 'Mire Fairy': (0x55, (0x0115, 0x70, 0x03a8, 0x0c6a, 0x013a, 0x0cb7, 0x01b8, 0x0cd7, 0x01bf, 0x06, 0xfa, 0x0000, 0x0000)), + 'Spike Cave': (0x40, (0x0117, 0x43, 0x0ed4, 0x01e4, 0x08aa, 0x0236, 0x0928, 0x0253, 0x092f, 0x0a, 0xf6, 0x0000, 0x0000)), + 'Dark Death Mountain Shop': (0x6D, (0x0112, 0x45, 0x0ee0, 0x01e3, 0x0d00, 0x0236, 0x0daa, 0x0252, 0x0d7d, 0x0b, 0xf5, 0x0000, 0x0000)), + 'Dark Death Mountain Fairy': (0x6F, (0x0115, 0x43, 0x1400, 0x0294, 0x0600, 0x02e8, 0x0678, 0x0303, 0x0685, 0x0a, 0xf6, 0x0000, 0x0000)), + 'Mimic Cave': (0x4E, (0x010c, 0x05, 0x07e0, 0x0103, 0x0d00, 0x0156, 0x0d78, 0x0172, 0x0d7d, 0x0b, 0xf5, 0x0000, 0x0000)), + 'Big Bomb Shop': (0x52, (0x011c, 0x6c, 0x0506, 0x0a9a, 0x0832, 0x0ae7, 0x08b8, 0x0b07, 0x08bf, 0x06, 0xfa, 0x0816, 0x0000)), + 'Dark Lake Hylia Shop': (0x73, (0x010f, 0x75, 0x0380, 0x0c6a, 0x0a00, 0x0cb8, 0x0a58, 0x0cd7, 0x0a85, 0x06, 0xfa, 0x0000, 0x0000)), + 'Lumberjack House': (0x75, (0x011f, 0x02, 0x049c, 0x0088, 0x04e6, 0x00d8, 0x0558, 0x00f7, 0x0563, 0x08, 0xf8, 0x07AA, 0x0000)), + 'Lake Hylia Fortune Teller': (0x72, (0x0122, 0x35, 0x0380, 0x0c6a, 0x0a00, 0x0cb8, 0x0a58, 0x0cd7, 0x0a85, 0x06, 0xfa, 0x0000, 0x0000)), + 'Kakariko Gamble Game': (0x66, (0x0118, 0x29, 0x069e, 0x0ac4, 0x02ea, 0x0b18, 0x0368, 0x0b33, 0x036f, 0x0a, 0xf6, 0x09AC, 0x0000))} \ No newline at end of file diff --git a/source/overworld/EntranceShuffle2.py b/source/overworld/EntranceShuffle2.py index 059aefbe..cad11db6 100644 --- a/source/overworld/EntranceShuffle2.py +++ b/source/overworld/EntranceShuffle2.py @@ -5,6 +5,8 @@ import copy from collections import defaultdict from BaseClasses import RegionType +from source.overworld.EntranceData import door_addresses + class EntrancePool(object): def __init__(self, world, player): @@ -2443,165 +2445,6 @@ inverted_default_dungeon_connections = [('Agahnims Tower', 'Ganons Tower Portal' ('Ganons Tower Exit', 'Hyrule Castle Ledge') ] -indirect_connections = { - 'Turtle Rock Ledge': 'Turtle Rock', - 'Pyramid Area': 'Pyramid Fairy', - 'Big Bomb Shop': 'Pyramid Fairy', - 'Mire Area': 'Pyramid Fairy', - #'West Dark World': 'Pyramid Fairy', - 'Big Bomb Shop Area': 'Pyramid Fairy', - #'Light World': 'Pyramid Fairy', - 'Old Man Cave (East)': 'Old Man S&Q' -} -# format: -# Key=Name -# addr = (door_index, exitdata) # multiexit -# | ([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 -door_addresses = {'Links House': (0x00, (0x0104, 0x2c, 0x0506, 0x0a9a, 0x0832, 0x0ae8, 0x08b8, 0x0b07, 0x08bf, 0x06, 0xfe, 0x0816, 0x0000)), - 'Desert Palace Entrance (South)': (0x08, (0x0084, 0x30, 0x0314, 0x0c56, 0x00a6, 0x0ca8, 0x0128, 0x0cc3, 0x0133, 0x0a, 0xfa, 0x0000, 0x0000)), - 'Desert Palace Entrance (West)': (0x0A, (0x0083, 0x30, 0x0280, 0x0c46, 0x0003, 0x0c98, 0x0088, 0x0cb3, 0x0090, 0x0a, 0xfd, 0x0000, 0x0000)), - 'Desert Palace Entrance (North)': (0x0B, (0x0063, 0x30, 0x0016, 0x0c00, 0x00a2, 0x0c28, 0x0128, 0x0c6d, 0x012f, 0x00, 0x0e, 0x0000, 0x0000)), - 'Desert Palace Entrance (East)': (0x09, (0x0085, 0x30, 0x02a8, 0x0c4a, 0x0142, 0x0c98, 0x01c8, 0x0cb7, 0x01cf, 0x06, 0xfe, 0x0000, 0x0000)), - 'Eastern Palace': (0x07, (0x00c9, 0x1e, 0x005a, 0x0600, 0x0ed6, 0x0618, 0x0f50, 0x066d, 0x0f5b, 0x00, 0xfa, 0x0000, 0x0000)), - 'Tower of Hera': (0x32, (0x0077, 0x03, 0x0050, 0x0014, 0x087c, 0x0068, 0x08f0, 0x0083, 0x08fb, 0x0a, 0xf4, 0x0000, 0x0000)), - 'Hyrule Castle Entrance (South)': (0x03, (0x0061, 0x1b, 0x0530, 0x0692, 0x0784, 0x06cc, 0x07f8, 0x06ff, 0x0803, 0x0e, 0xfa, 0x0000, 0x87be)), - 'Hyrule Castle Entrance (West)': (0x02, (0x0060, 0x1b, 0x0016, 0x0600, 0x06ae, 0x0604, 0x0728, 0x066d, 0x0733, 0x00, 0x02, 0x0000, 0x8124)), - 'Hyrule Castle Entrance (East)': (0x04, (0x0062, 0x1b, 0x004a, 0x0600, 0x0856, 0x0604, 0x08c8, 0x066d, 0x08d3, 0x00, 0xfa, 0x0000, 0x8158)), - 'Inverted Pyramid Entrance': (0x35, (0x0010, 0x1b, 0x0418, 0x0679, 0x06b4, 0x06c6, 0x0728, 0x06e6, 0x0733, 0x07, 0xf9, 0x0000, 0x0000)), - 'Agahnims Tower': (0x23, (0x00e0, 0x1b, 0x0032, 0x0600, 0x0784, 0x0634, 0x07f8, 0x066d, 0x0803, 0x00, 0x0a, 0x0000, 0x82be)), - 'Thieves Town': (0x33, (0x00db, 0x58, 0x0b2e, 0x075a, 0x0176, 0x07a8, 0x01f8, 0x07c7, 0x0203, 0x06, 0xfa, 0x0000, 0x0000)), - 'Skull Woods First Section Door': (0x29, (0x0058, 0x40, 0x0f4c, 0x01f6, 0x0262, 0x0248, 0x02e8, 0x0263, 0x02ef, 0x0a, 0xfe, 0x0000, 0x0000)), - 'Skull Woods Second Section Door (East)': (0x28, (0x0057, 0x40, 0x0eb8, 0x01e6, 0x01c2, 0x0238, 0x0248, 0x0253, 0x024f, 0x0a, 0xfe, 0x0000, 0x0000)), - 'Skull Woods Second Section Door (West)': (0x27, (0x0056, 0x40, 0x0c8e, 0x01a6, 0x0062, 0x01f8, 0x00e8, 0x0213, 0x00ef, 0x0a, 0x0e, 0x0000, 0x0000)), - 'Skull Woods Final Section': (0x2A, (0x0059, 0x40, 0x0282, 0x0066, 0x0016, 0x00b8, 0x0098, 0x00d3, 0x00a3, 0x0a, 0xfa, 0x0000, 0x0000)), - 'Ice Palace': (0x2C, (0x000e, 0x75, 0x0bc6, 0x0d6a, 0x0c3e, 0x0db8, 0x0cb8, 0x0dd7, 0x0cc3, 0x06, 0xf2, 0x0000, 0x0000)), - 'Misery Mire': (0x26, (0x0098, 0x70, 0x0414, 0x0c79, 0x00a6, 0x0cc7, 0x0128, 0x0ce6, 0x0133, 0x07, 0xfa, 0x0000, 0x0000)), - 'Palace of Darkness': (0x25, (0x004a, 0x5e, 0x005a, 0x0600, 0x0ed6, 0x0628, 0x0f50, 0x066d, 0x0f5b, 0x00, 0xfa, 0x0000, 0x0000)), - 'Swamp Palace': (0x24, (0x0028, 0x7b, 0x049e, 0x0e8c, 0x06f2, 0x0ed8, 0x0778, 0x0ef9, 0x077f, 0x04, 0xfe, 0x0000, 0x0000)), - 'Turtle Rock': (0x34, (0x00d6, 0x47, 0x0712, 0x00da, 0x0e96, 0x0128, 0x0f08, 0x0147, 0x0f13, 0x06, 0xfa, 0x0000, 0x0000)), - 'Dark Death Mountain Ledge (West)': (0x14, (0x0023, 0x45, 0x07ca, 0x0103, 0x0c46, 0x0157, 0x0cb8, 0x0172, 0x0cc3, 0x0b, 0x0a, 0x0000, 0x0000)), - 'Dark Death Mountain Ledge (East)': (0x18, (0x0024, 0x45, 0x07e0, 0x0103, 0x0d00, 0x0157, 0x0d78, 0x0172, 0x0d7d, 0x0b, 0x00, 0x0000, 0x0000)), - 'Turtle Rock Isolated Ledge Entrance': (0x17, (0x00d5, 0x45, 0x0ad4, 0x0164, 0x0ca6, 0x01b8, 0x0d18, 0x01d3, 0x0d23, 0x0a, 0xfa, 0x0000, 0x0000)), - 'Hyrule Castle Secret Entrance Stairs': (0x31, (0x0055, 0x1b, 0x044a, 0x067a, 0x0854, 0x06c8, 0x08c8, 0x06e7, 0x08d3, 0x06, 0xfa, 0x0000, 0x0000)), - 'Kakariko Well Cave': (0x38, (0x002f, 0x18, 0x0386, 0x0665, 0x0032, 0x06b7, 0x00b8, 0x06d2, 0x00bf, 0x0b, 0xfe, 0x0000, 0x0000)), - 'Bat Cave Cave': (0x10, (0x00e3, 0x22, 0x0412, 0x087a, 0x048e, 0x08c8, 0x0508, 0x08e7, 0x0513, 0x06, 0x02, 0x0000, 0x0000)), - 'Elder House (East)': (0x0D, (0x00f3, 0x18, 0x02c4, 0x064a, 0x0222, 0x0698, 0x02a8, 0x06b7, 0x02af, 0x06, 0xfe, 0x05d4, 0x0000)), - 'Elder House (West)': (0x0C, (0x00f2, 0x18, 0x02bc, 0x064c, 0x01e2, 0x0698, 0x0268, 0x06b9, 0x026f, 0x04, 0xfe, 0x05cc, 0x0000)), - 'North Fairy Cave': (0x37, (0x0008, 0x15, 0x0088, 0x0400, 0x0a36, 0x0448, 0x0aa8, 0x046f, 0x0ab3, 0x00, 0x0a, 0x0000, 0x0000)), - 'Lost Woods Hideout Stump': (0x2B, (0x00e1, 0x00, 0x0f4e, 0x01f6, 0x0262, 0x0248, 0x02e8, 0x0263, 0x02ef, 0x0a, 0x0e, 0x0000, 0x0000)), - 'Lumberjack Tree Cave': (0x11, (0x00e2, 0x02, 0x0118, 0x0015, 0x04c6, 0x0067, 0x0548, 0x0082, 0x0553, 0x0b, 0xfa, 0x0000, 0x0000)), - 'Two Brothers House (East)': (0x0F, (0x00f5, 0x29, 0x0880, 0x0b07, 0x0200, 0x0b58, 0x0238, 0x0b74, 0x028d, 0x09, 0x00, 0x0b86, 0x0000)), - 'Two Brothers House (West)': (0x0E, (0x00f4, 0x28, 0x08a0, 0x0b06, 0x0100, 0x0b58, 0x01b8, 0x0b73, 0x018d, 0x0a, 0x00, 0x0bb6, 0x0000)), - 'Sanctuary': (0x01, (0x0012, 0x13, 0x001c, 0x0400, 0x06de, 0x0414, 0x0758, 0x046d, 0x0763, 0x00, 0x02, 0x0000, 0x01aa)), - 'Old Man Cave (West)': (0x05, (0x00f0, 0x0a, 0x03a0, 0x0264, 0x0500, 0x02b8, 0x05a8, 0x02d3, 0x058d, 0x0a, 0x00, 0x0000, 0x0000)), - 'Old Man Cave (East)': (0x06, (0x00f1, 0x03, 0x1402, 0x0294, 0x0604, 0x02e8, 0x0678, 0x0303, 0x0683, 0x0a, 0xfc, 0x0000, 0x0000)), - 'Old Man House (Bottom)': (0x2F, (0x00e4, 0x03, 0x181a, 0x031e, 0x06b4, 0x03a7, 0x0728, 0x038d, 0x0733, 0x00, 0x0c, 0x0000, 0x0000)), - 'Old Man House (Top)': (0x30, (0x00e5, 0x03, 0x10c6, 0x0224, 0x0814, 0x0278, 0x0888, 0x0293, 0x0893, 0x0a, 0x0c, 0x0000, 0x0000)), - 'Death Mountain Return Cave (East)': (0x2E, (0x00e7, 0x03, 0x0d82, 0x01c4, 0x0600, 0x0218, 0x0648, 0x0233, 0x067f, 0x0a, 0x00, 0x0000, 0x0000)), - 'Death Mountain Return Cave (West)': (0x2D, (0x00e6, 0x0a, 0x00a0, 0x0205, 0x0500, 0x0257, 0x05b8, 0x0272, 0x058d, 0x0b, 0x00, 0x0000, 0x0000)), - 'Spectacle Rock Cave Peak': (0x22, (0x00ea, 0x03, 0x092c, 0x0133, 0x0754, 0x0187, 0x07c8, 0x01a2, 0x07d3, 0x0b, 0xfc, 0x0000, 0x0000)), - 'Spectacle Rock Cave': (0x21, (0x00fa, 0x03, 0x0eac, 0x01e3, 0x0754, 0x0237, 0x07c8, 0x0252, 0x07d3, 0x0b, 0xfc, 0x0000, 0x0000)), - 'Spectacle Rock Cave (Bottom)': (0x20, (0x00f9, 0x03, 0x0d9c, 0x01c3, 0x06d4, 0x0217, 0x0748, 0x0232, 0x0753, 0x0b, 0xfc, 0x0000, 0x0000)), - 'Paradox Cave (Bottom)': (0x1D, (0x00ff, 0x05, 0x0ee0, 0x01e3, 0x0d00, 0x0237, 0x0da8, 0x0252, 0x0d7d, 0x0b, 0x00, 0x0000, 0x0000)), - 'Paradox Cave (Middle)': (0x1E, (0x00ef, 0x05, 0x17e0, 0x0304, 0x0d00, 0x0358, 0x0dc8, 0x0373, 0x0d7d, 0x0a, 0x00, 0x0000, 0x0000)), - 'Paradox Cave (Top)': (0x1F, (0x00df, 0x05, 0x0460, 0x0093, 0x0d00, 0x00e7, 0x0db8, 0x0102, 0x0d7d, 0x0b, 0x00, 0x0000, 0x0000)), - 'Fairy Ascension Cave (Bottom)': (0x19, (0x00fd, 0x05, 0x0dd4, 0x01c4, 0x0ca6, 0x0218, 0x0d18, 0x0233, 0x0d23, 0x0a, 0xfa, 0x0000, 0x0000)), - 'Fairy Ascension Cave (Top)': (0x1A, (0x00ed, 0x05, 0x0ad4, 0x0163, 0x0ca6, 0x01b7, 0x0d18, 0x01d2, 0x0d23, 0x0b, 0xfa, 0x0000, 0x0000)), - 'Spiral Cave': (0x1C, (0x00ee, 0x05, 0x07c8, 0x0108, 0x0c46, 0x0158, 0x0cb8, 0x0177, 0x0cc3, 0x06, 0xfa, 0x0000, 0x0000)), - 'Spiral Cave (Bottom)': (0x1B, (0x00fe, 0x05, 0x0cca, 0x01a3, 0x0c56, 0x01f7, 0x0cc8, 0x0212, 0x0cd3, 0x0b, 0xfa, 0x0000, 0x0000)), - 'Bumper Cave (Bottom)': (0x15, (0x00fb, 0x4a, 0x03a0, 0x0263, 0x0500, 0x02b7, 0x05a8, 0x02d2, 0x058d, 0x0b, 0x00, 0x0000, 0x0000)), - 'Bumper Cave (Top)': (0x16, (0x00eb, 0x4a, 0x00a0, 0x020a, 0x0500, 0x0258, 0x05b8, 0x0277, 0x058d, 0x06, 0x00, 0x0000, 0x0000)), - 'Superbunny Cave (Top)': (0x13, (0x00e8, 0x45, 0x0460, 0x0093, 0x0d00, 0x00e7, 0x0db8, 0x0102, 0x0d7d, 0x0b, 0x00, 0x0000, 0x0000)), - 'Superbunny Cave (Bottom)': (0x12, (0x00f8, 0x45, 0x0ee0, 0x01e4, 0x0d00, 0x0238, 0x0d78, 0x0253, 0x0d7d, 0x0a, 0x00, 0x0000, 0x0000)), - 'Hookshot Cave': (0x39, (0x003c, 0x45, 0x04da, 0x00a3, 0x0cd6, 0x0107, 0x0d48, 0x0112, 0x0d53, 0x0b, 0xfa, 0x0000, 0x0000)), - 'Hookshot Cave Back Entrance': (0x3A, (0x002c, 0x45, 0x004c, 0x0000, 0x0c56, 0x0038, 0x0cc8, 0x006f, 0x0cd3, 0x00, 0x0a, 0x0000, 0x0000)), - 'Ganons Tower': (0x36, (0x000c, 0x43, 0x0052, 0x0000, 0x0884, 0x0028, 0x08f8, 0x006f, 0x0903, 0x00, 0xfc, 0x0000, 0x0000)), - 'Pyramid Entrance': (0x35, (0x0010, 0x5b, 0x0b0e, 0x075a, 0x0674, 0x07a8, 0x06e8, 0x07c7, 0x06f3, 0x06, 0xfa, 0x0000, 0x0000)), - 'Skull Woods First Section Hole (West)': ([0xDB84D, 0xDB84E], None), - 'Skull Woods First Section Hole (East)': ([0xDB84F, 0xDB850], None), - 'Skull Woods First Section Hole (North)': ([0xDB84C], None), - 'Skull Woods Second Section Hole': ([0xDB851, 0xDB852], None), - 'Pyramid Hole': ([0xDB854, 0xDB855, 0xDB856], None), - 'Inverted Pyramid Hole': ([0xDB854, 0xDB855, 0xDB856, 0x180340], None), - 'Waterfall of Wishing': (0x5B, (0x0114, 0x0f, 0x0080, 0x0200, 0x0e00, 0x0207, 0x0e60, 0x026f, 0x0e7d, 0x00, 0x00, 0x0000, 0x0000)), - 'Dam': (0x4D, (0x010b, 0x3b, 0x04a0, 0x0e8a, 0x06fa, 0x0ed8, 0x0778, 0x0ef7, 0x077f, 0x06, 0xfa, 0x0000, 0x0000)), - 'Blinds Hideout': (0x60, (0x0119, 0x18, 0x02b2, 0x064a, 0x0186, 0x0697, 0x0208, 0x06b7, 0x0213, 0x06, 0xfa, 0x0000, 0x0000)), - 'Hyrule Castle Secret Entrance Drop': ([0xDB858], None), - 'Bonk Fairy (Light)': (0x76, (0x0126, 0x2b, 0x00a0, 0x0a0a, 0x0700, 0x0a67, 0x0788, 0x0a77, 0x0785, 0x06, 0xfa, 0x0000, 0x0000)), - 'Lake Hylia Fairy': (0x5D, (0x0115, 0x2e, 0x0016, 0x0a00, 0x0cb6, 0x0a37, 0x0d28, 0x0a6d, 0x0d33, 0x00, 0x00, 0x0000, 0x0000)), - 'Light Hype Fairy': (0x6B, (0x0115, 0x34, 0x00a0, 0x0c04, 0x0900, 0x0c58, 0x0988, 0x0c73, 0x0985, 0x0a, 0xf6, 0x0000, 0x0000)), - 'Desert Fairy': (0x71, (0x0115, 0x3a, 0x0000, 0x0e00, 0x0400, 0x0e26, 0x0468, 0x0e6d, 0x0485, 0x00, 0x00, 0x0000, 0x0000)), - 'Kings Grave': (0x5A, (0x0113, 0x14, 0x0320, 0x0456, 0x0900, 0x04a6, 0x0998, 0x04c3, 0x097d, 0x0a, 0xf6, 0x0000, 0x0000)), - 'Tavern North': (0x42, (0x0103, 0x18, 0x1440, 0x08a7, 0x0206, 0x091b, 0x0288, 0x0914, 0x0293, 0xf7, 0x09, 0xFFFF, 0x0000)), - 'Chicken House': (0x4A, (0x0108, 0x18, 0x1120, 0x0837, 0x0106, 0x0888, 0x0188, 0x08a4, 0x0193, 0x07, 0xf9, 0x1530, 0x0000)), - 'Aginahs Cave': (0x70, (0x010a, 0x30, 0x0656, 0x0cc6, 0x02aa, 0x0d18, 0x0328, 0x0d33, 0x032f, 0x08, 0xf8, 0x0000, 0x0000)), - 'Sahasrahlas Hut': (0x44, (0x0105, 0x1e, 0x0610, 0x06d4, 0x0c76, 0x0727, 0x0cf0, 0x0743, 0x0cfb, 0x0a, 0xf6, 0x0000, 0x0000)), - 'Lake Hylia Shop': (0x57, (0x0112, 0x35, 0x0022, 0x0c00, 0x0b1a, 0x0c26, 0x0b98, 0x0c6d, 0x0b9f, 0x00, 0x00, 0x0000, 0x0000)), - 'Capacity Upgrade': (0x5C, (0x0115, 0x35, 0x0a46, 0x0d36, 0x0c2a, 0x0d88, 0x0ca8, 0x0da3, 0x0caf, 0x0a, 0xf6, 0x0000, 0x0000)), - 'Kakariko Well Drop': ([0xDB85C, 0xDB85D], None), - 'Blacksmiths Hut': (0x63, (0x0121, 0x22, 0x010c, 0x081a, 0x0466, 0x0868, 0x04d8, 0x0887, 0x04e3, 0x06, 0xfa, 0x041A, 0x0000)), - 'Bat Cave Drop': ([0xDB859, 0xDB85A], None), - 'Sick Kids House': (0x3F, (0x0102, 0x18, 0x10be, 0x0826, 0x01f6, 0x0877, 0x0278, 0x0893, 0x0283, 0x08, 0xf8, 0x14CE, 0x0000)), - 'North Fairy Cave Drop': ([0xDB857], None), - 'Lost Woods Gamble': (0x3B, (0x0100, 0x00, 0x004e, 0x0000, 0x0272, 0x0008, 0x02f0, 0x006f, 0x02f7, 0x00, 0x00, 0x0000, 0x0000)), - 'Fortune Teller (Light)': (0x64, (0x0122, 0x11, 0x060e, 0x04b4, 0x027d, 0x0508, 0x02f8, 0x0523, 0x0302, 0x0a, 0xf6, 0x0000, 0x0000)), - 'Snitch Lady (East)': (0x3D, (0x0101, 0x18, 0x0ad8, 0x074a, 0x02c6, 0x0798, 0x0348, 0x07b7, 0x0353, 0x06, 0xfa, 0x0DE8, 0x0000)), - 'Snitch Lady (West)': (0x3E, (0x0101, 0x18, 0x0788, 0x0706, 0x0046, 0x0758, 0x00c8, 0x0773, 0x00d3, 0x08, 0xf8, 0x0B98, 0x0000)), - 'Bush Covered House': (0x43, (0x0103, 0x18, 0x1156, 0x081a, 0x02b6, 0x0868, 0x0338, 0x0887, 0x0343, 0x06, 0xfa, 0x1466, 0x0000)), - 'Tavern (Front)': (0x41, (0x0103, 0x18, 0x1842, 0x0916, 0x0206, 0x0967, 0x0288, 0x0983, 0x0293, 0x08, 0xf8, 0x1C50, 0x0000)), - 'Light World Bomb Hut': (0x49, (0x0107, 0x18, 0x1800, 0x0916, 0x0000, 0x0967, 0x0068, 0x0983, 0x008d, 0x08, 0xf8, 0x9C0C, 0x0000)), - 'Kakariko Shop': (0x45, (0x011f, 0x18, 0x16a8, 0x08e7, 0x0136, 0x0937, 0x01b8, 0x0954, 0x01c3, 0x07, 0xf9, 0x1AB6, 0x0000)), - 'Lost Woods Hideout Drop': ([0xDB853], None), - 'Lumberjack Tree Tree': ([0xDB85B], None), - 'Cave 45': (0x50, (0x011b, 0x32, 0x0680, 0x0cc9, 0x0400, 0x0d16, 0x0438, 0x0d36, 0x0485, 0x07, 0xf9, 0x0000, 0x0000)), - 'Graveyard Cave': (0x51, (0x011b, 0x14, 0x0016, 0x0400, 0x08a2, 0x0446, 0x0918, 0x046d, 0x091f, 0x00, 0x00, 0x0000, 0x0000)), - 'Checkerboard Cave': (0x7D, (0x0126, 0x30, 0x00c8, 0x0c0a, 0x024a, 0x0c67, 0x02c8, 0x0c77, 0x02cf, 0x06, 0xfa, 0x0000, 0x0000)), - 'Mini Moldorm Cave': (0x7C, (0x0123, 0x35, 0x1480, 0x0e96, 0x0a00, 0x0ee8, 0x0a68, 0x0f03, 0x0a85, 0x08, 0xf8, 0x0000, 0x0000)), - 'Long Fairy Cave': (0x54, (0x011e, 0x2f, 0x06a0, 0x0aca, 0x0f00, 0x0b18, 0x0fa8, 0x0b37, 0x0f85, 0x06, 0xfa, 0x0000, 0x0000)), - 'Good Bee Cave': (0x6A, (0x0120, 0x37, 0x0084, 0x0c00, 0x0e26, 0x0c36, 0x0e98, 0x0c6f, 0x0ea3, 0x00, 0x00, 0x0000, 0x0000)), - '20 Rupee Cave': (0x7A, (0x0125, 0x37, 0x0200, 0x0c23, 0x0e00, 0x0c86, 0x0e68, 0x0c92, 0x0e7d, 0x0d, 0xf3, 0x0000, 0x0000)), - '50 Rupee Cave': (0x78, (0x0124, 0x3a, 0x0790, 0x0eea, 0x047a, 0x0f47, 0x04f8, 0x0f57, 0x04ff, 0x06, 0xfa, 0x0000, 0x0000)), - 'Ice Rod Cave': (0x7F, (0x0120, 0x37, 0x0080, 0x0c00, 0x0e00, 0x0c37, 0x0e48, 0x0c6f, 0x0e7d, 0x00, 0x00, 0x0000, 0x0000)), - 'Bonk Rock Cave': (0x79, (0x0124, 0x13, 0x0280, 0x044a, 0x0600, 0x04a7, 0x0638, 0x04b7, 0x067d, 0x06, 0xfa, 0x0000, 0x0000)), - 'Library': (0x48, (0x0107, 0x29, 0x0100, 0x0a14, 0x0200, 0x0a67, 0x0278, 0x0a83, 0x0285, 0x0a, 0xf6, 0x040E, 0x0000)), - 'Potion Shop': (0x4B, (0x0109, 0x16, 0x070a, 0x04e6, 0x0c56, 0x0538, 0x0cc8, 0x0553, 0x0cd3, 0x08, 0xf8, 0x0A98, 0x0000)), - 'Sanctuary Grave': ([0xDB85E], None), - 'Hookshot Fairy': (0x4F, (0x010c, 0x05, 0x0ee0, 0x01e3, 0x0d00, 0x0236, 0x0d78, 0x0252, 0x0d7d, 0x0b, 0xf5, 0x0000, 0x0000)), - 'Pyramid Fairy': (0x62, (0x0116, 0x5b, 0x0b1e, 0x0754, 0x06fa, 0x07a7, 0x0778, 0x07c3, 0x077f, 0x0a, 0xf6, 0x0000, 0x0000)), - 'East Dark World Hint': (0x68, (0x010e, 0x6f, 0x06a0, 0x0aca, 0x0f00, 0x0b18, 0x0fa8, 0x0b37, 0x0f85, 0x06, 0xfa, 0x0000, 0x0000)), - 'Palace of Darkness Hint': (0x67, (0x011a, 0x5e, 0x0c24, 0x0794, 0x0d12, 0x07e8, 0x0d90, 0x0803, 0x0d97, 0x0a, 0xf6, 0x0000, 0x0000)), - 'Dark Lake Hylia Fairy': (0x6C, (0x0115, 0x6e, 0x0016, 0x0a00, 0x0cb6, 0x0a36, 0x0d28, 0x0a6d, 0x0d33, 0x00, 0x00, 0x0000, 0x0000)), - 'Dark Lake Hylia Ledge Fairy': (0x80, (0x0115, 0x77, 0x0080, 0x0c00, 0x0e00, 0x0c37, 0x0e48, 0x0c6f, 0x0e7d, 0x00, 0x00, 0x0000, 0x0000)), - 'Dark Lake Hylia Ledge Spike Cave': (0x7B, (0x0125, 0x77, 0x0200, 0x0c27, 0x0e00, 0x0c86, 0x0e68, 0x0c96, 0x0e7d, 0x09, 0xf7, 0x0000, 0x0000)), - 'Dark Lake Hylia Ledge Hint': (0x69, (0x010e, 0x77, 0x0084, 0x0c00, 0x0e26, 0x0c36, 0x0e98, 0x0c6f, 0x0ea3, 0x00, 0x00, 0x0000, 0x0000)), - 'Hype Cave': (0x3C, (0x011e, 0x74, 0x00a0, 0x0c0a, 0x0900, 0x0c58, 0x0988, 0x0c77, 0x097d, 0x06, 0xfa, 0x0000, 0x0000)), - 'Bonk Fairy (Dark)': (0x77, (0x0126, 0x6b, 0x00a0, 0x0a05, 0x0700, 0x0a66, 0x0788, 0x0a72, 0x0785, 0x0b, 0xf5, 0x0000, 0x0000)), - 'Brewery': (0x47, (0x0106, 0x58, 0x16a8, 0x08e4, 0x013e, 0x0938, 0x01b8, 0x0953, 0x01c3, 0x0a, 0xf6, 0x1AB6, 0x0000)), - 'C-Shaped House': (0x53, (0x011c, 0x58, 0x09d8, 0x0744, 0x02ce, 0x0797, 0x0348, 0x07b3, 0x0353, 0x0a, 0xf6, 0x0DE8, 0x0000)), - 'Chest Game': (0x46, (0x0106, 0x58, 0x078a, 0x0705, 0x004e, 0x0758, 0x00c8, 0x0774, 0x00d3, 0x09, 0xf7, 0x0B98, 0x0000)), - 'Hammer Peg Cave': (0x7E, (0x0127, 0x62, 0x0894, 0x091e, 0x0492, 0x09a6, 0x0508, 0x098b, 0x050f, 0x00, 0x00, 0x0000, 0x0000)), - 'Red Shield Shop': (0x74, (0x0110, 0x5a, 0x079a, 0x06e8, 0x04d6, 0x0738, 0x0548, 0x0755, 0x0553, 0x08, 0xf8, 0x0AA8, 0x0000)), - 'Dark Sanctuary Hint': (0x59, (0x0112, 0x53, 0x001e, 0x0400, 0x06e2, 0x0446, 0x0758, 0x046d, 0x075f, 0x00, 0x00, 0x0000, 0x0000)), - 'Fortune Teller (Dark)': (0x65, (0x0122, 0x51, 0x0610, 0x04b4, 0x027e, 0x0507, 0x02f8, 0x0523, 0x0303, 0x0a, 0xf6, 0x091E, 0x0000)), - 'Dark World Shop': (0x5F, (0x010f, 0x58, 0x1058, 0x0814, 0x02be, 0x0868, 0x0338, 0x0883, 0x0343, 0x0a, 0xf6, 0x0000, 0x0000)), - 'Dark Lumberjack Shop': (0x56, (0x010f, 0x42, 0x041c, 0x0074, 0x04e2, 0x00c7, 0x0558, 0x00e3, 0x055f, 0x0a, 0xf6, 0x0000, 0x0000)), - 'Dark Potion Shop': (0x6E, (0x010f, 0x56, 0x080e, 0x04f4, 0x0c66, 0x0548, 0x0cd8, 0x0563, 0x0ce3, 0x0a, 0xf6, 0x0000, 0x0000)), - 'Archery Game': (0x58, (0x0111, 0x69, 0x069e, 0x0ac4, 0x02ea, 0x0b18, 0x0368, 0x0b33, 0x036f, 0x0a, 0xf6, 0x09AC, 0x0000)), - 'Mire Shed': (0x5E, (0x010d, 0x70, 0x0384, 0x0c69, 0x001e, 0x0cb6, 0x0098, 0x0cd6, 0x00a3, 0x07, 0xf9, 0x0000, 0x0000)), - 'Mire Hint': (0x61, (0x0114, 0x70, 0x0654, 0x0cc5, 0x02aa, 0x0d16, 0x0328, 0x0d32, 0x032f, 0x09, 0xf7, 0x0000, 0x0000)), - 'Mire Fairy': (0x55, (0x0115, 0x70, 0x03a8, 0x0c6a, 0x013a, 0x0cb7, 0x01b8, 0x0cd7, 0x01bf, 0x06, 0xfa, 0x0000, 0x0000)), - 'Spike Cave': (0x40, (0x0117, 0x43, 0x0ed4, 0x01e4, 0x08aa, 0x0236, 0x0928, 0x0253, 0x092f, 0x0a, 0xf6, 0x0000, 0x0000)), - 'Dark Death Mountain Shop': (0x6D, (0x0112, 0x45, 0x0ee0, 0x01e3, 0x0d00, 0x0236, 0x0daa, 0x0252, 0x0d7d, 0x0b, 0xf5, 0x0000, 0x0000)), - 'Dark Death Mountain Fairy': (0x6F, (0x0115, 0x43, 0x1400, 0x0294, 0x0600, 0x02e8, 0x0678, 0x0303, 0x0685, 0x0a, 0xf6, 0x0000, 0x0000)), - 'Mimic Cave': (0x4E, (0x010c, 0x05, 0x07e0, 0x0103, 0x0d00, 0x0156, 0x0d78, 0x0172, 0x0d7d, 0x0b, 0xf5, 0x0000, 0x0000)), - 'Big Bomb Shop': (0x52, (0x011c, 0x6c, 0x0506, 0x0a9a, 0x0832, 0x0ae7, 0x08b8, 0x0b07, 0x08bf, 0x06, 0xfa, 0x0816, 0x0000)), - 'Dark Lake Hylia Shop': (0x73, (0x010f, 0x75, 0x0380, 0x0c6a, 0x0a00, 0x0cb8, 0x0a58, 0x0cd7, 0x0a85, 0x06, 0xfa, 0x0000, 0x0000)), - 'Lumberjack House': (0x75, (0x011f, 0x02, 0x049c, 0x0088, 0x04e6, 0x00d8, 0x0558, 0x00f7, 0x0563, 0x08, 0xf8, 0x07AA, 0x0000)), - 'Lake Hylia Fortune Teller': (0x72, (0x0122, 0x35, 0x0380, 0x0c6a, 0x0a00, 0x0cb8, 0x0a58, 0x0cd7, 0x0a85, 0x06, 0xfa, 0x0000, 0x0000)), - 'Kakariko Gamble Game': (0x66, (0x0118, 0x29, 0x069e, 0x0ac4, 0x02ea, 0x0b18, 0x0368, 0x0b33, 0x036f, 0x0a, 0xf6, 0x09AC, 0x0000))} # format: # Key=Name diff --git a/test/dungeons/TestDungeon.py b/test/dungeons/TestDungeon.py index 1eb3cc7b..daaccf33 100644 --- a/test/dungeons/TestDungeon.py +++ b/test/dungeons/TestDungeon.py @@ -2,11 +2,11 @@ import unittest from BaseClasses import World, CollectionState from Dungeons import create_dungeons, get_dungeon_item_pool -from EntranceShuffle import mandatory_connections, connect_simple from ItemList import difficulties, generate_itempool from Items import ItemFactory from Regions import create_regions from Rules import set_rules +from source.overworld.EntranceShuffle2 import mandatory_connections, connect_simple class TestDungeon(unittest.TestCase): diff --git a/test/inverted/TestInverted.py b/test/inverted/TestInverted.py index cc3eb68a..eee83292 100644 --- a/test/inverted/TestInverted.py +++ b/test/inverted/TestInverted.py @@ -3,12 +3,13 @@ from DoorShuffle import link_doors from Doors import create_doors from Dungeons import create_dungeons, get_dungeon_item_pool from OverworldShuffle import link_overworld -from EntranceShuffle import link_entrances from ItemList import generate_itempool, difficulties from Items import ItemFactory from Regions import create_regions, mark_light_dark_world_regions, create_dungeon_regions, create_shops from RoomData import create_rooms from Rules import set_rules + +from source.overworld.EntranceShuffle2 import link_entrances_new from test.TestBase import TestBase @@ -25,7 +26,7 @@ class TestInverted(TestBase): create_rooms(self.world, 1) create_dungeons(self.world, 1) link_overworld(self.world, 1) - link_entrances(self.world, 1) + link_entrances_new(self.world, 1) link_doors(self.world, 1) generate_itempool(self.world, 1) self.world.required_medallions[1] = ['Ether', 'Quake'] diff --git a/test/inverted/TestInvertedBombRules.py b/test/inverted/TestInvertedBombRules.py index 974ae797..c3a94ff8 100644 --- a/test/inverted/TestInvertedBombRules.py +++ b/test/inverted/TestInvertedBombRules.py @@ -2,7 +2,8 @@ import unittest from BaseClasses import World from Dungeons import create_dungeons -from EntranceShuffle import connect_entrance, Inverted_LW_Entrances, Inverted_LW_Dungeon_Entrances, Inverted_LW_Single_Cave_Doors, Inverted_Old_Man_Entrances, Inverted_DW_Entrances, Inverted_DW_Dungeon_Entrances, Inverted_DW_Single_Cave_Doors, \ +# todo: this test needs to be rewritten unfortunately +from source.overworld.EntranceShuffle2 import connect_entrance, Inverted_LW_Entrances, Inverted_LW_Dungeon_Entrances, Inverted_LW_Single_Cave_Doors, Inverted_Old_Man_Entrances, Inverted_DW_Entrances, Inverted_DW_Dungeon_Entrances, Inverted_DW_Single_Cave_Doors, \ Inverted_LW_Entrances_Must_Exit, Inverted_LW_Dungeon_Entrances_Must_Exit, Inverted_Bomb_Shop_Multi_Cave_Doors, Inverted_Bomb_Shop_Single_Cave_Doors, Inverted_Blacksmith_Single_Cave_Doors, Inverted_Blacksmith_Multi_Cave_Doors from Regions import create_regions from ItemList import difficulties diff --git a/test/inverted_owg/TestInvertedOWG.py b/test/inverted_owg/TestInvertedOWG.py index 9c584c25..640a3990 100644 --- a/test/inverted_owg/TestInvertedOWG.py +++ b/test/inverted_owg/TestInvertedOWG.py @@ -3,7 +3,7 @@ from DoorShuffle import link_doors from Doors import create_doors from Dungeons import create_dungeons, get_dungeon_item_pool from OverworldShuffle import link_overworld -from EntranceShuffle import link_entrances +from source.overworld.EntranceShuffle2 import link_entrances_new from ItemList import generate_itempool, difficulties from Items import ItemFactory from OverworldGlitchRules import create_owg_connections @@ -27,7 +27,7 @@ class TestInvertedOWG(TestBase): create_dungeons(self.world, 1) link_overworld(self.world, 1) create_owg_connections(self.world, 1) - link_entrances(self.world, 1) + link_entrances_new(self.world, 1) link_doors(self.world, 1) generate_itempool(self.world, 1) self.world.required_medallions[1] = ['Ether', 'Quake'] diff --git a/test/owg/TestVanillaOWG.py b/test/owg/TestVanillaOWG.py index 8c245125..e0e58e6d 100644 --- a/test/owg/TestVanillaOWG.py +++ b/test/owg/TestVanillaOWG.py @@ -3,7 +3,7 @@ from DoorShuffle import link_doors from Doors import create_doors from Dungeons import create_dungeons, get_dungeon_item_pool from OverworldShuffle import link_overworld -from EntranceShuffle import link_entrances +from source.overworld.EntranceShuffle2 import link_entrances_new from ItemList import difficulties, generate_itempool from Items import ItemFactory from OverworldGlitchRules import create_owg_connections @@ -26,7 +26,7 @@ class TestVanillaOWG(TestBase): create_rooms(self.world, 1) create_dungeons(self.world, 1) link_overworld(self.world, 1) - link_entrances(self.world, 1) + link_entrances_new(self.world, 1) link_doors(self.world, 1) create_owg_connections(self.world, 1) generate_itempool(self.world, 1) diff --git a/test/stats/EntranceShuffleStats.py b/test/stats/EntranceShuffleStats.py index addc1044..a948b265 100644 --- a/test/stats/EntranceShuffleStats.py +++ b/test/stats/EntranceShuffleStats.py @@ -7,11 +7,13 @@ import time from collections import Counter, defaultdict from source.overworld.EntranceShuffle2 import link_entrances_new -from EntranceShuffle import link_entrances +# from source.oEntranceShuffle import link_entrances_new from BaseClasses import World from Regions import create_regions, create_dungeon_regions +# probably deprecated + # tested: open + crossed (lh) Mar. 17 (made changes) # tested: open + simple (lh) Mar. 22 diff --git a/test/vanilla/TestVanilla.py b/test/vanilla/TestVanilla.py index f1bd23f2..8434221f 100644 --- a/test/vanilla/TestVanilla.py +++ b/test/vanilla/TestVanilla.py @@ -3,7 +3,7 @@ from DoorShuffle import link_doors from Doors import create_doors from Dungeons import create_dungeons, get_dungeon_item_pool from OverworldShuffle import link_overworld -from EntranceShuffle import link_entrances +from source.overworld.EntranceShuffle2 import link_entrances_new from ItemList import difficulties, generate_itempool from Items import ItemFactory from Regions import create_regions, create_dungeon_regions, create_shops, mark_light_dark_world_regions @@ -25,7 +25,7 @@ class TestVanilla(TestBase): create_rooms(self.world, 1) create_dungeons(self.world, 1) link_overworld(self.world, 1) - link_entrances(self.world, 1) + link_entrances_new(self.world, 1) link_doors(self.world, 1) generate_itempool(self.world, 1) self.world.required_medallions[1] = ['Ether', 'Quake'] From b3692c861d9f0c69549e045945d98fea6f5d7cc7 Mon Sep 17 00:00:00 2001 From: aerinon Date: Mon, 6 May 2024 16:49:24 -0600 Subject: [PATCH 12/41] fix: remove wallmasters from consideration from rooms with spiral staircases --- RELEASENOTES.md | 1 + source/enemizer/SpriteSheets.py | 86 ++++++++++++++++----------------- 2 files changed, 44 insertions(+), 43 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 21905044..b29e5821 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -144,6 +144,7 @@ These are now independent of retro mode and have three options: None, Random, an * 1.4.1.12u * New Entrance Shuffle Algorithm no longer experimental * Back of Tavern Shuffle now on by default + * Enemizer: Wallmasters banned from tiles where spiral staircases are. (Softlock issue) * Fixed a small bug with traversal algorithm * 1.4.1.11u * New Feature: Several spoiler levels added: None, Settings-only, Semi, Full, Debug diff --git a/source/enemizer/SpriteSheets.py b/source/enemizer/SpriteSheets.py index 4496fb97..108ac0b3 100644 --- a/source/enemizer/SpriteSheets.py +++ b/source/enemizer/SpriteSheets.py @@ -118,48 +118,48 @@ LenientTrapsForTesting = {0x16, 0x26, 0x3f, 0x40, 0x42, 0x46, 0x49, 0x4e, 0x57, 0x65, 0x6a, 0x74, 0x76, 0x7d, 0x98, 0x9e, 0xaf, 0xba, 0xc6, 0xcb, 0xce, 0xd2, 0xd5, 0xd8, 0xdf, 0xe4, 0xe7, 0xee, 0xfd, 0x10c} -# this will have to be dynamic if cave rooms are allowed in dungeons -WallmasterValidRooms = { - HC_NorthCorridor, HC_SwitchRoom, HoulihanRoom, TR_CrystalRollerRoom, - PalaceofDarkness0x09, PoD_StalfosTrapRoom, PoD_TurtleRoom, GT_EntranceRoom, Ice_EntranceRoom, - GanonEvacuationRoute, HC_BombableStockRoom, Sanctuary, TR_Hokku_BokkuKeyRoom2, TR_BigKeyRoom, TurtleRock0x15, - Swamp_SwimmingTreadmill, Hera_MoldormFallRoom, PoD_DarkMaze, PoD_BigChestRoom, PoD_Mimics_MovingWallRoom, - GT_IceArmos, GT_FinalHallway, Ice_BombFloor_BariRoom, Ice_Pengator_BigKeyRoom, Tower_Agahnim, HC_KeyRatRoom, - HC_SewerTextTriggerRoom, TR_WestExittoBalcony, TR_DoubleHokku_Bokku_BigchestRoom, Swamp_StatueRoom, Hera_BigChest, - Swamp_EntranceRoom, Skull_Mothula, PoD_BigHubRoom, PoD_MapChest_FairyRoom, Ice_CompassRoom, Hera_HardhatBeetlesRoom, - HC_SewerKeyChestRoom, Desert_Lanmolas, Swamp_PushBlockPuzzle_Pre_BigKeyRoom, Swamp_BigKey_BSRoom, - Swamp_BigChestRoom, Swamp_MapChest_WaterFillRoom, Swamp_KeyPotRoom, Skull_GibdoKey_MothulaHoleRoom, - PoD_BombableFloorRoom, PoD_SpikeBlock_ConveyorRoom, GT_TorchRoom2, Ice_StalfosKnights_ConveyorHellway, - Ice_MapChestRoom, Tower_FinalBridgeRoom, HC_FirstDarkRoom, HC_6RopesRoom, Desert_TorchPuzzle_MovingWallRoom, - TT_BigChestRoom, TT_JailCellsRoom, Swamp_CompassChestRoom, Skull_GibdoTorchPuzzleRoom, PoD_EntranceRoom, - PoD_Warps_SouthMimicsRoom, GT_Mini_HelmasaurConveyorRoom, GT_MoldormRoom, Ice_Bomb_JumpRoom, - IcePalaceCloneRoom_FairyRoom, HC_WestCorridor, HC_ThroneRoom, HC_EastCorridor, Desert_Popos2_BeamosHellwayRoom, - Swamp_UpstairsPitsRoom, CastleSecretEntrance_UncleDeathRoom, Skull_KeyPot_TrapRoom, Skull_BigKeyRoom, - Skull_BigChestRoom, Skull_FinalSectionEntranceRoom, PoD_HelmasaurKing, GT_SpikePitRoom, GT_Ganon_BallZ, - GT_Gauntlet1_2_3, Ice_LonelyFirebar, Ice_HiddenChest_SpikeFloorRoom, HC_WestEntranceRoom, HC_MainEntranceRoom, - HC_EastEntranceRoom, Desert_FinalSectionEntranceRoom, TT_WestAtticRoom, TT_EastAtticRoom, - Swamp_HiddenChest_HiddenDoorRoom, Skull_CompassChestRoom, Skull_KeyChest_TrapRoom, PoD_RupeeRoom, GT_MimicsRooms, - GT_LanmolasRoom, GT_Gauntlet4_5, Ice_PengatorsRoom, HC_SmallCorridortoJailCells, HC_BoomerangChestRoom, - HC_MapChestRoom, Desert_BigChestRoom, Desert_MapChestRoom, Desert_BigKeyChestRoom, Swamp_WaterDrainRoom, - Hera_EntranceRoom, GanonsTower, GT_EastSideCollapsingBridge_ExplodingWallRoom, GT_Winder_WarpMazeRoom, - Ice_HiddenChest_BombableFloorRoom, Ice_BigSpikeTrapsRoom, HC_JailCellRoom, HC_NextToChasmRoom, HC_BasementChasmRoom, - Desert_WestEntranceRoom, Desert_MainEntranceRoom, Desert_EastEntranceRoom, Hera_TileRoom, Eastern_FairyRoom, - GT_BlockPuzzle_SpikeSkip_MapChestRoom, GT_EastandWestDownstairs_BigChestRoom, GT_Tile_TorchPuzzleRoom, - IcePalace0x8E, Mire_Vitreous, Mire_FinalSwitchRoom, Mire_DarkBombWall_SwitchesRoom, - Mire_DarkCaneFloorSwitchPuzzleRoom, GT_FinalCollapsingBridgeRoom, GT_Torches1Room, Mire_TorchPuzzle_MovingWallRoom, - Mire_EntranceRoom, Eastern_EyegoreKeyRoom, GT_ManySpikes_WarpMazeRoom, GT_InvisibleFloorMazeRoom, - GT_CompassChest_InvisibleFloorRoom, Ice_BigChestRoom, IcePalace0x9F, Mire_Pre_VitreousRoom, Mire_FishRoom, - Mire_BridgeKeyChestRoom, MiseryMire0xA3, TR_Trinexx, GT_WizzrobesRooms, GT_MoldormFallRoom, Hera_FairyRoom, - Eastern_StalfosSpawnRoom, Eastern_BigChestRoom, Eastern_MapChestRoom, TT_MovingSpikes_KeyPotRoom, TT_BlindTheThief, - IcePalace0xAE, Ice_IceBridgeRoom, Tower_CircleofPots, Mire_HourglassRoom, Mire_SlugRoom, Mire_SpikeKeyChestRoom, - TR_Pre_TrinexxRoom, TR_DarkMaze, TR_ChainChompsRoom, TR_MapChest_KeyChest_RollerRoom, Eastern_BigKeyRoom, - Eastern_LobbyCannonballsRoom, Eastern_DarkAntifairy_KeyPotRoom, TT_Hellway, TT_ConveyorToilet, Ice_BlockPuzzleRoom, - IcePalaceCloneRoom_SwitchRoom, Tower_DarkBridgeRoom, Mire_CompassChest_TileRoom, Mire_BigHubRoom, Mire_BigChestRoom, - TR_FinalCrystalSwitchPuzzleRoom, TR_LaserBridge, TurtleRock0xC6, TR_TorchPuzzle, - Eastern_EntranceRoom, UnknownRoom, TT_NorthWestEntranceRoom, TT_NorthEastEntranceRoom, Ice_HoletoKholdstareRoom, - Tower_DarkMaze, Mire_ConveyorSlug_BigKeyRoom, Mire_Mire02_WizzrobesRoom, TR_LaserKeyRoom, TR_EntranceRoom, - Eastern_PreArmosKnightsRoom, Eastern_CanonballRoom, EasternPalace, TT_Main_SouthWestEntranceRoom, - TT_SouthEastEntranceRoom, Tower_EntranceRoom + +# wallmasters must not be on tiles near spiral staircases. Unknown if other stairs have issues +WallmasterInvalidRooms = { + HC_NorthCorridor, HC_SwitchRoom, TR_CrystalRollerRoom, + PalaceofDarkness0x09, PoD_StalfosTrapRoom, GT_EntranceRoom, Ice_EntranceRoom, + HC_BombableStockRoom, TurtleRock0x15, + Swamp_SwimmingTreadmill, Hera_MoldormFallRoom, PoD_BigChestRoom, + GT_IceArmos, GT_FinalHallway, Ice_BombFloor_BariRoom, + Swamp_StatueRoom, Hera_BigChest, + Swamp_EntranceRoom, Hera_HardhatBeetlesRoom, + Swamp_PushBlockPuzzle_Pre_BigKeyRoom, + Swamp_KeyPotRoom, + PoD_BombableFloorRoom, + Ice_MapChestRoom, Tower_FinalBridgeRoom, HC_FirstDarkRoom, HC_6RopesRoom, + TT_JailCellsRoom, PoD_EntranceRoom, + GT_Mini_HelmasaurConveyorRoom, GT_MoldormRoom, Ice_Bomb_JumpRoom, + Desert_Popos2_BeamosHellwayRoom, + GT_Ganon_BallZ, + GT_Gauntlet1_2_3, Ice_HiddenChest_SpikeFloorRoom, + Desert_FinalSectionEntranceRoom, TT_WestAtticRoom, + Swamp_HiddenChest_HiddenDoorRoom, PoD_RupeeRoom, GT_MimicsRooms, + GT_LanmolasRoom, Ice_PengatorsRoom, HC_SmallCorridortoJailCells, HC_BoomerangChestRoom, + HC_MapChestRoom, Swamp_WaterDrainRoom, + Hera_EntranceRoom, + Ice_BigSpikeTrapsRoom, HC_JailCellRoom, + Hera_TileRoom, + GT_EastandWestDownstairs_BigChestRoom, + IcePalace0x8E, Mire_FinalSwitchRoom, + Mire_DarkCaneFloorSwitchPuzzleRoom, Mire_TorchPuzzle_MovingWallRoom, + Mire_EntranceRoom, Eastern_EyegoreKeyRoom, + Ice_BigChestRoom, Mire_Pre_VitreousRoom, + Mire_BridgeKeyChestRoom, GT_WizzrobesRooms, GT_MoldormFallRoom, + TT_MovingSpikes_KeyPotRoom, + IcePalace0xAE, Tower_CircleofPots, + TR_DarkMaze, TR_ChainChompsRoom, + TT_ConveyorToilet, Ice_BlockPuzzleRoom, + Tower_DarkBridgeRoom, + UnknownRoom, + Tower_DarkMaze, Mire_ConveyorSlug_BigKeyRoom, Mire_Mire02_WizzrobesRoom, + EasternPalace, + Tower_EntranceRoom, Cave_BackwardsDeathMountainTopFloor, Cave0xE8, Cave_SpectacleRockHP, Cave0xEB, Cave0xED, + Cave_CrystalSwitch_5ChestsRoom, Cave0xF8, Cave0xFA, Cave0xFB, Cave0xFD, Cave0xFF } @@ -293,7 +293,7 @@ def init_sprite_requirements(): SpriteRequirement(EnemySprite.Terrorpin).sub_group(2, 0x2a).exclude({0x10c}), # probably fine in mimic now SpriteRequirement(EnemySprite.Blob).sub_group(1, 0x20), SpriteRequirement(EnemySprite.Wallmaster).immune().ow_skip().sub_group(2, 0x23) - .allow(WallmasterValidRooms), + .exclude(WallmasterInvalidRooms), SpriteRequirement(EnemySprite.StalfosKnight).sub_group(1, 0x20).exclude({0x10c}), SpriteRequirement(EnemySprite.HelmasaurKing).exalt().sub_group(2, 0x3a).sub_group(3, 0x3e), SpriteRequirement(EnemySprite.Bumper).immune().aquaphobia().sub_group(3, [0x52, 0x53]), From f1a996c2f8a5cb4de68262d19a5097ff8cf0ee66 Mon Sep 17 00:00:00 2001 From: aerinon Date: Mon, 6 May 2024 16:51:21 -0600 Subject: [PATCH 13/41] change: enable CI on unstable branch --- .github/workflows/ci.yml | 1 + RELEASENOTES.md | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ab6c8163..5d05b5e9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,7 @@ on: push: branches: - DoorDev + - DoorDevUnstable pull_request: branches: - DoorDev diff --git a/RELEASENOTES.md b/RELEASENOTES.md index b29e5821..f3aa76d0 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -145,6 +145,7 @@ These are now independent of retro mode and have three options: None, Random, an * New Entrance Shuffle Algorithm no longer experimental * Back of Tavern Shuffle now on by default * Enemizer: Wallmasters banned from tiles where spiral staircases are. (Softlock issue) + * Packaged build of unstable now available * Fixed a small bug with traversal algorithm * 1.4.1.11u * New Feature: Several spoiler levels added: None, Settings-only, Semi, Full, Debug From 9056a32e8d403e18af497159fbdd6dd8095467cf Mon Sep 17 00:00:00 2001 From: codemann8 Date: Mon, 6 May 2024 13:50:34 -0500 Subject: [PATCH 14/41] Added new logical boss defeated items for AD --- DoorShuffle.py | 14 ++++++ Doors.py | 10 +++++ Dungeons.py | 20 ++++----- ItemList.py | 117 ++++++++++++++++++++----------------------------- Items.py | 1 + Regions.py | 45 ++++++++++++++----- Rules.py | 40 +++++++---------- 7 files changed, 132 insertions(+), 115 deletions(-) diff --git a/DoorShuffle.py b/DoorShuffle.py index 9ca2227a..fbbabef3 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -3630,13 +3630,17 @@ logical_connections = [ ('Hyrule Castle Throne Room Tapestry', 'Hyrule Castle Behind Tapestry'), ('Hyrule Castle Tapestry Backwards', 'Hyrule Castle Throne Room'), ('Sewers Secret Room Push Block', 'Sewers Secret Room Blocked Path'), + ('Eastern Hint Tile Push Block', 'Eastern Hint Tile'), ('Eastern Map Balcony Hook Path', 'Eastern Map Room'), ('Eastern Map Room Drop Down', 'Eastern Map Balcony'), + ('Eastern Palace Boss', 'Eastern Boss Spoils'), + ('Desert Main Lobby Left Path', 'Desert Left Alcove'), ('Desert Main Lobby Right Path', 'Desert Right Alcove'), ('Desert Left Alcove Path', 'Desert Main Lobby'), ('Desert Right Alcove Path', 'Desert Main Lobby'), + ('Desert Palace Boss', 'Desert Boss Spoils'), ('Hera Lobby to Front Barrier - Blue', 'Hera Front'), ('Hera Front to Lobby Barrier - Blue', 'Hera Lobby'), @@ -3666,6 +3670,7 @@ logical_connections = [ ('Hera Big Chest Hook Path', 'Hera Big Chest Landing'), ('Hera Big Chest Landing Exit', 'Hera 4F'), ('Hera 5F Orange Path', 'Hera 5F Pot Block'), + ('Tower of Hera Boss', 'Hera Boss Spoils'), ('PoD Pit Room Block Path N', 'PoD Pit Room Blocked'), ('PoD Pit Room Block Path S', 'PoD Pit Room'), @@ -3717,6 +3722,8 @@ logical_connections = [ ('PoD Dark Pegs Middle Ranged Crystal Exit', 'PoD Dark Pegs Middle'), ('PoD Dark Pegs Middle to Left Bypass', 'PoD Dark Pegs Left'), ('PoD Dark Pegs Left Ranged Crystal Exit', 'PoD Dark Pegs Left'), + ('Palace of Darkness Boss', 'PoD Boss Spoils'), + ('Swamp Lobby Moat', 'Swamp Entrance'), ('Swamp Entrance Moat', 'Swamp Lobby'), ('Swamp Trench 1 Approach Dry', 'Swamp Trench 1 Nexus'), @@ -3759,12 +3766,15 @@ logical_connections = [ ('Swamp Drain Right Switch', 'Swamp Drain Left'), ('Swamp Flooded Spot Ladder', 'Swamp Flooded Room'), ('Swamp Flooded Room Ladder', 'Swamp Flooded Spot'), + ('Swamp Palace Boss', 'Swamp Boss Spoils'), ('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'), + ('Skull Woods Boss', 'Skull Boss Spoils'), + ('Thieves Rail Ledge Drop Down', 'Thieves BK Corner'), ('Thieves Hellway Orange Barrier', 'Thieves Hellway S Crystal'), ('Thieves Hellway Crystal Orange Barrier', 'Thieves Hellway'), @@ -3780,6 +3790,7 @@ logical_connections = [ ('Thieves Conveyor Block Path', 'Thieves Conveyor Bridge'), ("Thieves Blind's Cell Door", "Thieves Blind's Cell Interior"), ("Thieves Blind's Cell Exit", "Thieves Blind's Cell"), + ('Thieves Town Boss', 'Thieves Boss Spoils'), ('Ice Cross Bottom Push Block Left', 'Ice Floor Switch'), ('Ice Cross Right Push Block Top', 'Ice Bomb Drop'), @@ -3800,6 +3811,7 @@ logical_connections = [ ('Ice Big Chest Landing Push Blocks', 'Ice Big Chest View'), ('Ice Refill to Crystal', 'Ice Refill - Crystal'), ('Ice Refill Crystal Exit', 'Ice Refill'), + ('Ice Palace Boss', 'Ice Boss Spoils'), ('Mire Lobby Gap', 'Mire Post-Gap'), ('Mire Post-Gap Gap', 'Mire Lobby'), @@ -3835,6 +3847,7 @@ logical_connections = [ ('Mire South Fish Blue Barrier', 'Mire Fishbone'), ('Mire Fishbone Blue Barrier', 'Mire South Fish'), ('Mire Fishbone Blue Barrier Bypass', 'Mire South Fish'), + ('Misery Mire Boss', 'Mire Boss Spoils'), ('TR Main Lobby Gap', 'TR Lobby Ledge'), ('TR Lobby Ledge Gap', 'TR Main Lobby'), @@ -3882,6 +3895,7 @@ logical_connections = [ ('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'), + ('Turtle Rock Boss', 'TR Boss Spoils'), ('GT Blocked Stairs Block Path', 'GT Big Chest'), ('GT Speed Torch South Path', 'GT Speed Torch'), diff --git a/Doors.py b/Doors.py index 4c9611fc..defdcbf0 100644 --- a/Doors.py +++ b/Doors.py @@ -188,6 +188,7 @@ def create_doors(world, player): create_door(player, 'Eastern Duo Eyegores SE', Intr).dir(So, 0xd8, Right, High).pos(1), create_door(player, 'Eastern Duo Eyegores NE', Nrml).dir(No, 0xd8, Right, High).trap(0x4).pos(0), create_door(player, 'Eastern Boss SE', Nrml).dir(So, 0xc8, Right, High).no_exit().trap(0x4).pos(0), + create_door(player, 'Eastern Palace Boss', Lgcl), # Desert Palace create_door(player, 'Desert Main Lobby S', Nrml).dir(So, 0x84, Mid, High).pos(0).portal(Z, 0x22), @@ -251,6 +252,7 @@ def create_doors(world, player): create_door(player, 'Desert Wall Slide SE', Intr).dir(So, 0x43, Right, High).small_key().pos(1), create_door(player, 'Desert Wall Slide NW', Nrml).dir(No, 0x43, Left, High).big_key().pos(0).no_entrance(), create_door(player, 'Desert Boss SW', Nrml).dir(So, 0x33, Left, High).no_exit().trap(0x4).pos(0).portal(Z, 0x00), + create_door(player, 'Desert Palace Boss', Lgcl), # Hera create_door(player, 'Hera Lobby S', Nrml).dir(So, 0x77, Mid, Low).pos(0).portal(Z, 0x22, 1), @@ -316,6 +318,7 @@ def create_doors(world, player): 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), create_door(player, 'Hera Boss Inner Hole', Hole), + create_door(player, 'Tower of Hera Boss', Lgcl), # Castle Tower create_door(player, 'Tower Lobby S', Nrml).dir(So, 0xe0, Left, High).pos(3).portal(Z, 0x00), @@ -475,6 +478,7 @@ def create_doors(world, player): create_door(player, 'PoD Callback WS', Intr).dir(We, 0x0b, Mid, High).pos(1), create_door(player, 'PoD Callback Warp', Warp), create_door(player, 'PoD Boss SE', Nrml).dir(So, 0x5a, Right, High).no_exit().trap(0x4).pos(0), + create_door(player, 'Palace of Darkness Boss', Lgcl), create_door(player, 'Swamp Lobby S', Nrml).dir(So, 0x28, Mid, High).pos(1).portal(Z, 0x22), create_door(player, 'Swamp Lobby Moat', Lgcl), @@ -589,6 +593,7 @@ def create_doors(world, player): create_door(player, 'Swamp T SW', Intr).dir(So, 0x16, Left, High).small_key().pos(1), create_door(player, 'Swamp T NW', Nrml).dir(No, 0x16, Left, High).pos(3), create_door(player, 'Swamp Boss SW', Nrml).dir(So, 0x06, Left, High).no_exit().trap(0x4).pos(0), + create_door(player, 'Swamp Palace Boss', Lgcl), create_door(player, 'Skull 1 Lobby S', Nrml).dir(So, 0x58, Left, High).pos(4).portal(Z, 0x00), create_door(player, 'Skull 1 Lobby WS', Nrml).dir(We, 0x58, Bot, High).small_key().pos(1), @@ -640,6 +645,7 @@ def create_doors(world, player): create_door(player, 'Skull Spike Corner ES', Intr).dir(Ea, 0x39, Bot, High).small_key().pos(1), create_door(player, 'Skull Final Drop WS', Intr).dir(We, 0x39, Bot, High).small_key().pos(1), create_door(player, 'Skull Final Drop Hole', Hole), + create_door(player, 'Skull Woods Boss', Lgcl), create_door(player, 'Thieves Lobby S', Nrml).dir(So, 0xdb, Mid, High).pos(1).portal(Z, 0x22), create_door(player, 'Thieves Lobby N Edge', Open).dir(No, 0xdb, None, Low).edge(7, A, 0x10), @@ -721,6 +727,7 @@ def create_doors(world, player): create_door(player, 'Thieves Big Chest Room ES', Intr).dir(Ea, 0x44, Bot, High).small_key().pos(1), create_door(player, 'Thieves Conveyor Block WN', Intr).dir(We, 0x44, Top, High).pos(0), create_door(player, 'Thieves Trap EN', Intr).dir(Ea, 0x44, Left, Top).pos(0), + create_door(player, 'Thieves Town Boss', Lgcl), create_door(player, 'Ice Lobby SE', Nrml).dir(So, 0x0e, Right, High).pos(2).portal(X, 0x00), create_door(player, 'Ice Lobby WS', Intr).dir(We, 0x0e, Bot, High).pos(1), @@ -826,6 +833,7 @@ def create_doors(world, player): create_door(player, 'Ice Fairy Warp', Warp), create_door(player, 'Ice Antechamber NE', Nrml).dir(No, 0xce, Right, High).trap(0x4).pos(0), create_door(player, 'Ice Antechamber Hole', Hole), + create_door(player, 'Ice Palace Boss', Lgcl), create_door(player, 'Mire Lobby S', Nrml).dir(So, 0x98, Left, High).pos(0).portal(Z, 0x20), create_door(player, 'Mire Lobby Gap', Lgcl), @@ -962,6 +970,7 @@ def create_doors(world, player): create_door(player, 'Mire Antechamber Orange Barrier', Lgcl), create_door(player, 'Mire Antechamber NW', Nrml).dir(No, 0xa0, Left, High).big_key().pos(0), create_door(player, 'Mire Boss SW', Nrml).dir(So, 0x90, Left, High).no_exit().trap(0x4).pos(0), + create_door(player, 'Misery Mire Boss', Lgcl), create_door(player, 'TR Main Lobby SE', Nrml).dir(So, 0xd6, Right, High).pos(1).portal(X, 0x02), create_door(player, 'TR Lobby Ledge NE', Nrml).dir(No, 0xd6, Right, High).pos(2), @@ -1075,6 +1084,7 @@ def create_doors(world, player): 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), + create_door(player, 'Turtle Rock Boss', Lgcl), create_door(player, 'GT Lobby S', Nrml).dir(So, 0x0c, Mid, High).pos(0).portal(Z, 0x22), create_door(player, 'GT Lobby Left Down Stairs', Sprl).dir(Dn, 0x0c, 1, HTL).ss(A, 0x0f, 0x80), diff --git a/Dungeons.py b/Dungeons.py index 3284617e..7ffe518c 100644 --- a/Dungeons.py +++ b/Dungeons.py @@ -65,7 +65,7 @@ eastern_regions = [ 'Eastern Compass Room', 'Eastern Hint Tile', 'Eastern Hint Tile Blocked Path', 'Eastern Courtyard', 'Eastern Fairies', 'Eastern Map Valley', 'Eastern Dark Square', 'Eastern Dark Pots', 'Eastern Big Key', 'Eastern Darkness', 'Eastern Rupees', 'Eastern Attic Start', 'Eastern False Switches', 'Eastern Cannonball Hell', - 'Eastern Single Eyegore', 'Eastern Duo Eyegores', 'Eastern Boss', 'Eastern Portal' + 'Eastern Single Eyegore', 'Eastern Duo Eyegores', 'Eastern Boss', 'Eastern Boss Spoils', 'Eastern Portal' ] desert_regions = [ @@ -74,7 +74,7 @@ desert_regions = [ 'Desert North Hall', 'Desert Map Room', 'Desert Sandworm Corner', 'Desert Bonk Torch', 'Desert Circle of Pots', 'Desert Big Chest Room', 'Desert West Wing', 'Desert West Lobby', 'Desert Fairy Fountain', 'Desert Back Lobby', 'Desert Tiles 1', 'Desert Bridge', 'Desert Four Statues', 'Desert Beamos Hall', 'Desert Tiles 2', - 'Desert Wall Slide', 'Desert Boss', 'Desert West Portal', 'Desert South Portal', 'Desert East Portal', + 'Desert Wall Slide', 'Desert Boss', 'Desert Boss Spoils', 'Desert West Portal', 'Desert South Portal', 'Desert East Portal', 'Desert Back Portal' ] @@ -84,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 5F Pot Block', 'Hera Fairies', 'Hera Boss', 'Hera Portal' + 'Hera 5F Pot Block', 'Hera Fairies', 'Hera Boss', 'Hera Boss Spoils', 'Hera Portal' ] tower_regions = [ @@ -105,7 +105,7 @@ pod_regions = [ 'PoD Bow Statue Left', 'PoD Bow Statue Left - Crystal', 'PoD Bow Statue Right', 'PoD Bow Statue Right - Ranged Crystal', 'PoD Dark Pegs Landing', 'PoD Dark Pegs Right', 'PoD Dark Pegs Middle', 'PoD Dark Pegs Left', 'PoD Dark Pegs Landing - Ranged Crystal', 'PoD Dark Pegs Middle - Ranged Crystal', 'PoD Dark Pegs Left - Ranged Crystal', 'PoD Lonely Turtle', 'PoD Turtle Party', - 'PoD Dark Alley', 'PoD Callback', 'PoD Boss', 'Palace of Darkness Portal' + 'PoD Dark Alley', 'PoD Callback', 'PoD Boss', 'PoD Boss Spoils', 'Palace of Darkness Portal' ] swamp_regions = [ @@ -119,7 +119,7 @@ swamp_regions = [ '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 C', 'Swamp Waterway', 'Swamp I', 'Swamp T', 'Swamp Boss', 'Swamp Boss Spoils', 'Swamp Portal' ] skull_regions = [ @@ -127,7 +127,7 @@ skull_regions = [ '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 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 Torch Room', 'Skull Vines', 'Skull Spike Corner', 'Skull Final Drop', 'Skull Boss', 'Skull Boss Spoils', 'Skull 1 Portal', 'Skull 2 East Portal', 'Skull 2 West Portal', 'Skull 3 Portal' ] @@ -139,7 +139,7 @@ thieves_regions = [ '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' + 'Thieves Conveyor Block', 'Thieves Big Chest Room', 'Thieves Trap', 'Thieves Boss Spoils', 'Thieves Town Portal' ] ice_regions = [ @@ -152,7 +152,7 @@ ice_regions = [ 'Ice Hookshot Balcony', 'Ice Spikeball', 'Ice Lonely Freezor', 'Iced T', 'Ice Catwalk', 'Ice Many Pots', 'Ice Crystal Right', 'Ice Crystal Left', 'Ice Crystal Block', 'Ice Big Chest View', 'Ice Big Chest Landing', 'Ice Backwards Room', 'Ice Anti-Fairy', 'Ice Switch Room', 'Ice Refill', 'Ice Refill - Crystal', - 'Ice Fairy', 'Ice Antechamber', 'Ice Boss', 'Ice Portal' + 'Ice Fairy', 'Ice Antechamber', 'Ice Boss', 'Ice Boss Spoils', 'Ice Portal' ] mire_regions = [ @@ -168,7 +168,7 @@ mire_regions = [ 'Mire Key Rupees', 'Mire Block X', 'Mire Tall Dark and Roomy', 'Mire Tall Dark and Roomy - Ranged Crystal', 'Mire Crystal Right', 'Mire Crystal Mid', 'Mire Crystal Left', 'Mire Crystal Top', 'Mire Shooter Rupees', 'Mire Falling Foes', 'Mire Firesnake Skip', 'Mire Antechamber', - 'Mire Boss', 'Mire Portal' + 'Mire Boss', 'Mire Boss Spoils', 'Mire Portal' ] tr_regions = [ @@ -182,7 +182,7 @@ tr_regions = [ '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 Balcony', 'TR Final Abyss Ledge', 'TR Boss', + 'TR Crystal Maze End - Ranged Crystal', 'TR Final Abyss Balcony', 'TR Final Abyss Ledge', 'TR Boss', 'TR Boss Spoils', 'Turtle Rock Main Portal', 'Turtle Rock Lazy Eyes Portal', 'Turtle Rock Chest Portal', 'Turtle Rock Eye Bridge Portal' ] diff --git a/ItemList.py b/ItemList.py index 16130fc9..926dd7e4 100644 --- a/ItemList.py +++ b/ItemList.py @@ -194,10 +194,17 @@ def generate_itempool(world, player): if world.timer in ['ohko', 'timed-ohko']: world.can_take_damage = False + def set_event_item(location_name, item_name=None): + location = world.get_location(location_name, player) + if item_name: + world.push_item(location, ItemFactory(item_name, player), False) + location.event = True + location.locked = True + if world.goal[player] in ['pedestal', 'triforcehunt']: - world.push_item(world.get_location('Ganon', player), ItemFactory('Nothing', player), False) + set_event_item('Ganon', 'Nothing') else: - world.push_item(world.get_location('Ganon', player), ItemFactory('Triforce', player), False) + set_event_item('Ganon', 'Triforce') if world.goal[player] in ['triforcehunt', 'trinity']: region = world.get_region('Hyrule Castle Courtyard', player) @@ -233,75 +240,45 @@ def generate_itempool(world, player): old_man.forced_item = old_man.item old_man.skip = True - world.get_location('Ganon', player).event = True - world.get_location('Ganon', player).locked = True - world.push_item(world.get_location('Agahnim 1', player), ItemFactory('Beat Agahnim 1', player), False) - world.get_location('Agahnim 1', player).event = True - world.get_location('Agahnim 1', player).locked = True - world.push_item(world.get_location('Agahnim 2', player), ItemFactory('Beat Agahnim 2', player), False) - world.get_location('Agahnim 2', player).event = True - world.get_location('Agahnim 2', player).locked = True - world.push_item(world.get_location('Lost Old Man', player), ItemFactory('Escort Old Man', player), False) - world.get_location('Lost Old Man', player).event = True - world.get_location('Lost Old Man', player).locked = True - world.push_item(world.get_location('Old Man Drop Off', player), ItemFactory('Return Old Man', player), False) - world.get_location('Old Man Drop Off', player).event = True - world.get_location('Old Man Drop Off', player).locked = True - world.push_item(world.get_location('Dark Blacksmith Ruins', player), ItemFactory('Pick Up Purple Chest', player), False) - world.get_location('Dark Blacksmith Ruins', player).event = True - world.get_location('Dark Blacksmith Ruins', player).locked = True - world.push_item(world.get_location('Middle Aged Man', player), ItemFactory('Deliver Purple Chest', player), False) - world.get_location('Middle Aged Man', player).event = True - world.get_location('Middle Aged Man', player).locked = True - world.push_item(world.get_location('Frog', player), ItemFactory('Get Frog', player), False) - world.get_location('Frog', player).event = True - world.get_location('Frog', player).locked = True - world.push_item(world.get_location('Missing Smith', player), ItemFactory('Return Smith', player), False) - world.get_location('Missing Smith', player).event = True - world.get_location('Missing Smith', player).locked = True - world.push_item(world.get_location('Floodgate', player), ItemFactory('Open Floodgate', player), False) - world.get_location('Floodgate', player).event = True - world.get_location('Floodgate', player).locked = True - world.push_item(world.get_location('Big Bomb', player), ItemFactory('Pick Up Big Bomb', player), False) - world.get_location('Big Bomb', player).event = True - world.get_location('Big Bomb', player).locked = True - world.push_item(world.get_location('Pyramid Crack', player), ItemFactory('Detonate Big Bomb', player), False) - world.get_location('Pyramid Crack', player).event = True - world.get_location('Pyramid Crack', player).locked = True - world.push_item(world.get_location('Trench 1 Switch', player), ItemFactory('Trench 1 Filled', player), False) - world.get_location('Trench 1 Switch', player).event = True - world.get_location('Trench 1 Switch', player).locked = True - world.push_item(world.get_location('Trench 2 Switch', player), ItemFactory('Trench 2 Filled', player), False) - world.get_location('Trench 2 Switch', player).event = True - world.get_location('Trench 2 Switch', player).locked = True - world.push_item(world.get_location('Swamp Drain', player), ItemFactory('Drained Swamp', player), False) - world.get_location('Swamp Drain', player).event = True - world.get_location('Swamp Drain', player).locked = True - world.push_item(world.get_location('Turtle Medallion Pad', player), ItemFactory('Turtle Opened', player), False) - world.get_location('Turtle Medallion Pad', player).event = True - world.get_location('Turtle Medallion Pad', player).locked = True - world.push_item(world.get_location('Attic Cracked Floor', player), ItemFactory('Shining Light', player), False) - world.get_location('Attic Cracked Floor', player).event = True - world.get_location('Attic Cracked Floor', player).locked = True - world.push_item(world.get_location('Suspicious Maiden', player), ItemFactory('Maiden Rescued', player), False) - world.get_location('Suspicious Maiden', player).event = True - world.get_location('Suspicious Maiden', player).locked = True - world.push_item(world.get_location('Revealing Light', player), ItemFactory('Maiden Unmasked', player), False) - world.get_location('Revealing Light', player).event = True - world.get_location('Revealing Light', player).locked = True - 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 + event_items = { + 'Agahnim 1': 'Beat Agahnim 1', + 'Agahnim 2': 'Beat Agahnim 2', + 'Eastern Palace - Boss Kill': 'Beat Boss', + 'Desert Palace - Boss Kill': 'Beat Boss', + 'Tower of Hera - Boss Kill': 'Beat Boss', + 'Palace of Darkness - Boss Kill': 'Beat Boss', + 'Swamp Palace - Boss Kill': 'Beat Boss', + 'Skull Woods - Boss Kill': 'Beat Boss', + 'Thieves\' Town - Boss Kill': 'Beat Boss', + 'Ice Palace - Boss Kill': 'Beat Boss', + 'Misery Mire - Boss Kill': 'Beat Boss', + 'Turtle Rock - Boss Kill': 'Beat Boss', + 'Lost Old Man': 'Escort Old Man', + 'Old Man Drop Off': 'Return Old Man', + 'Floodgate': 'Open Floodgate', + 'Big Bomb': 'Pick Up Big Bomb', + 'Pyramid Crack': 'Detonate Big Bomb', + 'Frog': 'Get Frog', + 'Missing Smith': 'Return Smith', + 'Dark Blacksmith Ruins': 'Pick Up Purple Chest', + 'Middle Aged Man': 'Deliver Purple Chest', + 'Trench 1 Switch': 'Trench 1 Filled', + 'Trench 2 Switch': 'Trench 2 Filled', + 'Swamp Drain': 'Drained Swamp', + 'Turtle Medallion Pad': 'Turtle Opened', + 'Attic Cracked Floor': 'Shining Light', + 'Suspicious Maiden': 'Maiden Rescued', + 'Revealing Light': 'Maiden Unmasked', + 'Ice Block Drop': 'Convenient Block', + 'Skull Star Tile': 'Hidden Pits' + } + + for loc, item in event_items.items(): + set_event_item(loc, item) + 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 - world.get_location('Zelda Pickup', player).locked = True - world.push_item(world.get_location('Zelda Drop Off', player), ItemFactory('Zelda Delivered', player), False) - world.get_location('Zelda Drop Off', player).event = True - world.get_location('Zelda Drop Off', player).locked = True + set_event_item('Zelda Pickup', 'Zelda Herself') + set_event_item('Zelda Drop Off', 'Zelda Delivered') # set up item pool skip_pool_adjustments = False diff --git a/Items.py b/Items.py index e9a056b5..8a4f9c14 100644 --- a/Items.py +++ b/Items.py @@ -177,6 +177,7 @@ item_table = {'Bow': (True, False, None, 0x0B, 200, 'You have\nchosen the\narche 'Small Heart': (False, False, None, 0x42, 10, 'Just a little\npiece of love!', 'and the heart', 'the life-giving kid', 'little love for sale', 'fungus for life', 'life boy feels some love again', 'a heart'), 'Apples': (False, False, None, 0xD1, 30, 'Just a few pieces of fruit!', 'and the juicy fruit', 'the fruity kid', 'the fruit stand', 'expired fruit', 'bottle boy has fruit again', 'an apple hoard'), 'Fairy': (False, False, None, 0xD2, 50, 'Just a pixie!', 'and the pixie', 'the pixie kid', 'pixie for sale', 'pixie fungus', 'bottle boy has pixie again', 'a pixie'), + 'Beat Boss': (True, False, 'Event', 999, None, None, None, None, None, None, None, None), 'Beat Agahnim 1': (True, False, 'Event', 999, None, None, None, None, None, None, None, None), 'Beat Agahnim 2': (True, False, 'Event', 999, None, None, None, None, None, None, None, None), 'Get Frog': (True, False, 'Event', 999, None, None, None, None, None, None, None, None), diff --git a/Regions.py b/Regions.py index db9ad299..d828104e 100644 --- a/Regions.py +++ b/Regions.py @@ -489,7 +489,8 @@ def create_dungeon_regions(world, player): create_dungeon_region(player, 'Eastern Cannonball Hell', 'Eastern Palace', None, ['Eastern Cannonball Hell ES', 'Eastern Cannonball Hell WS']), create_dungeon_region(player, 'Eastern Single Eyegore', 'Eastern Palace', None, ['Eastern Single Eyegore ES', 'Eastern Single Eyegore NE']), create_dungeon_region(player, 'Eastern Duo Eyegores', 'Eastern Palace', None, ['Eastern Duo Eyegores SE', 'Eastern Duo Eyegores NE']), - create_dungeon_region(player, 'Eastern Boss', 'Eastern Palace', ['Eastern Palace - Boss', 'Eastern Palace - Prize'], ['Eastern Boss SE']), + create_dungeon_region(player, 'Eastern Boss', 'Eastern Palace', None, ['Eastern Boss SE', 'Eastern Palace Boss']), + create_dungeon_region(player, 'Eastern Boss Spoils', 'Eastern Palace', ['Eastern Palace - Boss', 'Eastern Palace - Prize', 'Eastern Palace - Boss Kill']), # Desert Palace create_dungeon_region(player, 'Desert Main Lobby', 'Desert Palace', None, ['Desert Main Lobby S', 'Desert Main Lobby N Edge', 'Desert Main Lobby Left Path', 'Desert Main Lobby Right Path']), @@ -518,7 +519,8 @@ def create_dungeon_regions(world, player): create_dungeon_region(player, 'Desert Beamos Hall', 'Desert Palace', ['Desert Palace - Beamos Hall Pot Key'], ['Desert Beamos Hall WS', 'Desert Beamos Hall NE']), create_dungeon_region(player, 'Desert Tiles 2', 'Desert Palace', ['Desert Palace - Desert Tiles 2 Pot Key'], ['Desert Tiles 2 SE', 'Desert Tiles 2 NE']), create_dungeon_region(player, 'Desert Wall Slide', 'Desert Palace', None, ['Desert Wall Slide SE', 'Desert Wall Slide NW']), - create_dungeon_region(player, 'Desert Boss', 'Desert Palace', ['Desert Palace - Boss', 'Desert Palace - Prize'], ['Desert Boss SW']), + create_dungeon_region(player, 'Desert Boss', 'Desert Palace', None, ['Desert Boss SW', 'Desert Palace Boss']), + create_dungeon_region(player, 'Desert Boss Spoils', 'Desert Palace', ['Desert Palace - Boss', 'Desert Palace - Prize', 'Desert Palace - Boss Kill']), # Hera create_dungeon_region(player, 'Hera Lobby', 'Tower of Hera', None, ['Hera Lobby S', 'Hera Lobby to Crystal', 'Hera Lobby to Front Barrier - Blue']), @@ -546,7 +548,8 @@ def create_dungeon_regions(world, player): create_dungeon_region(player, 'Hera 5F', 'Tower of Hera', None, ['Hera 5F Down Stairs', 'Hera 5F Up Stairs', 'Hera 5F Star Hole', 'Hera 5F Pothole Chain', 'Hera 5F Normal Holes', 'Hera 5F Orange Path']), create_dungeon_region(player, 'Hera 5F Pot Block', 'Tower of Hera', None), create_dungeon_region(player, 'Hera Fairies', 'Tower of Hera', None, ['Hera Fairies\' Warp']), - create_dungeon_region(player, 'Hera Boss', 'Tower of Hera', ['Tower of Hera - Boss', 'Tower of Hera - Prize'], ['Hera Boss Down Stairs', 'Hera Boss Outer Hole', 'Hera Boss Inner Hole']), + create_dungeon_region(player, 'Hera Boss', 'Tower of Hera', None, ['Hera Boss Down Stairs', 'Hera Boss Outer Hole', 'Hera Boss Inner Hole', 'Tower of Hera Boss']), + create_dungeon_region(player, 'Hera Boss Spoils', 'Tower of Hera', ['Tower of Hera - Boss', 'Tower of Hera - Prize', 'Tower of Hera - Boss Kill']), # AgaTower create_dungeon_region(player, 'Tower Lobby', 'Castle Tower', None, ['Tower Lobby NW', 'Tower Lobby S']), @@ -621,7 +624,8 @@ def create_dungeon_regions(world, player): create_dungeon_region(player, 'PoD Turtle Party', 'Palace of Darkness', None, ['PoD Turtle Party ES', 'PoD Turtle Party NW']), create_dungeon_region(player, 'PoD Dark Alley', 'Palace of Darkness', None, ['PoD Dark Alley NE']), create_dungeon_region(player, 'PoD Callback', 'Palace of Darkness', None, ['PoD Callback WS', 'PoD Callback Warp']), - create_dungeon_region(player, 'PoD Boss', 'Palace of Darkness', ['Palace of Darkness - Boss', 'Palace of Darkness - Prize'], ['PoD Boss SE']), + create_dungeon_region(player, 'PoD Boss', 'Palace of Darkness', None, ['PoD Boss SE', 'Palace of Darkness Boss']), + create_dungeon_region(player, 'PoD Boss Spoils', 'Palace of Darkness', ['Palace of Darkness - Boss', 'Palace of Darkness - Prize', 'Palace of Darkness - Boss Kill']), # swamp create_dungeon_region(player, 'Swamp Lobby', 'Swamp Palace', None, ['Swamp Lobby S', 'Swamp Lobby Moat']), @@ -674,7 +678,8 @@ def create_dungeon_regions(world, player): create_dungeon_region(player, 'Swamp Waterway', 'Swamp Palace', ['Swamp Palace - Waterway Pot Key'], ['Swamp Waterway NE', 'Swamp Waterway N', 'Swamp Waterway NW']), create_dungeon_region(player, 'Swamp I', 'Swamp Palace', None, ['Swamp I S']), create_dungeon_region(player, 'Swamp T', 'Swamp Palace', None, ['Swamp T SW', 'Swamp T NW']), - create_dungeon_region(player, 'Swamp Boss', 'Swamp Palace', ['Swamp Palace - Boss', 'Swamp Palace - Prize'], ['Swamp Boss SW']), + create_dungeon_region(player, 'Swamp Boss', 'Swamp Palace', None, ['Swamp Boss SW', 'Swamp Palace Boss']), + create_dungeon_region(player, 'Swamp Boss Spoils', 'Swamp Palace', ['Swamp Palace - Boss', 'Swamp Palace - Prize', 'Swamp Palace - Boss Kill']), # sw create_dungeon_region(player, 'Skull 1 Lobby', 'Skull Woods', None, ['Skull 1 Lobby S', 'Skull 1 Lobby WS', 'Skull 1 Lobby ES']), @@ -702,7 +707,8 @@ def create_dungeon_regions(world, player): create_dungeon_region(player, 'Skull Vines', 'Skull Woods', None, ['Skull Vines EN', 'Skull Vines NW']), create_dungeon_region(player, 'Skull Spike Corner', 'Skull Woods', ['Skull Woods - Spike Corner Key Drop'], ['Skull Spike Corner SW', 'Skull Spike Corner ES']), create_dungeon_region(player, 'Skull Final Drop', 'Skull Woods', None, ['Skull Final Drop WS', 'Skull Final Drop Hole']), - create_dungeon_region(player, 'Skull Boss', 'Skull Woods', ['Skull Woods - Boss', 'Skull Woods - Prize']), + create_dungeon_region(player, 'Skull Boss', 'Skull Woods', None, ['Skull Woods Boss']), + create_dungeon_region(player, 'Skull Boss Spoils', 'Skull Woods', ['Skull Woods - Boss', 'Skull Woods - Prize', 'Skull Woods - Boss Kill']), # tt create_dungeon_region(player, 'Thieves Lobby', 'Thieves\' Town', ['Thieves\' Town - Map Chest'], ['Thieves Lobby S', 'Thieves Lobby N Edge', 'Thieves Lobby NE Edge', 'Thieves Lobby E']), @@ -712,8 +718,9 @@ def create_dungeon_regions(world, player): create_dungeon_region(player, 'Thieves Compass Room', 'Thieves\' Town', ['Thieves\' Town - Compass Chest'], ['Thieves Compass Room NW Edge', 'Thieves Compass Room N Edge', 'Thieves Compass Room WS Edge', 'Thieves Compass Room W']), create_dungeon_region(player, 'Thieves Big Chest Nook', 'Thieves\' Town', ['Thieves\' Town - Big Key Chest'], ['Thieves Big Chest Nook ES Edge']), create_dungeon_region(player, 'Thieves Hallway', 'Thieves\' Town', ['Thieves\' Town - Hallway Pot Key'], ['Thieves Hallway SE', 'Thieves Hallway NE', 'Thieves Hallway WN', 'Thieves Hallway WS']), - create_dungeon_region(player, 'Thieves Boss', 'Thieves\' Town', ['Revealing Light', 'Thieves\' Town - Boss', 'Thieves\' Town - Prize'], ['Thieves Boss SE']), #create_dungeon_region(player, 'Thieves Boss', 'Thieves\' Town', ['Thieves\' Town - Boss', 'Thieves\' Town - Prize'], ['Revealing Light', 'Thieves Boss SE']), + create_dungeon_region(player, 'Thieves Boss', 'Thieves\' Town', ['Revealing Light'], ['Thieves Boss SE', 'Thieves Town Boss']), + create_dungeon_region(player, 'Thieves Boss Spoils', 'Thieves\' Town', ['Thieves\' Town - Boss', 'Thieves\' Town - Prize', 'Thieves\' Town - Boss Kill']), #create_dungeon_region(player, 'Thieves Revealing Light', 'Thieves\' Town', ['Revealing Light'], ['Thieves Boss Room']), create_dungeon_region(player, 'Thieves Pot Alcove Mid', 'Thieves\' Town', None, ['Thieves Pot Alcove Mid ES', 'Thieves Pot Alcove Mid WS']), create_dungeon_region(player, 'Thieves Pot Alcove Bottom', 'Thieves\' Town', None, ['Thieves Pot Alcove Bottom SW']), @@ -791,7 +798,8 @@ def create_dungeon_regions(world, player): create_dungeon_region(player, 'Ice Refill - Crystal', 'Ice Palace', None, ['Ice Refill Crystal Exit']), create_dungeon_region(player, 'Ice Fairy', 'Ice Palace', None, ['Ice Fairy Warp']), create_dungeon_region(player, 'Ice Antechamber', 'Ice Palace', None, ['Ice Antechamber NE', 'Ice Antechamber Hole']), - create_dungeon_region(player, 'Ice Boss', 'Ice Palace', ['Ice Palace - Boss', 'Ice Palace - Prize']), + create_dungeon_region(player, 'Ice Boss', 'Ice Palace', None, ['Ice Palace Boss']), + create_dungeon_region(player, 'Ice Boss Spoils', 'Ice Palace', ['Ice Palace - Boss', 'Ice Palace - Prize', 'Ice Palace - Boss Kill']), # mire create_dungeon_region(player, 'Mire Lobby', 'Misery Mire', None, ['Mire Lobby S', 'Mire Lobby Gap']), @@ -855,7 +863,8 @@ def create_dungeon_regions(world, player): create_dungeon_region(player, 'Mire Falling Foes', 'Misery Mire', None, ['Mire Falling Foes ES', 'Mire Falling Foes Up Stairs']), create_dungeon_region(player, 'Mire Firesnake Skip', 'Misery Mire', None, ['Mire Firesnake Skip Down Stairs', 'Mire Firesnake Skip Orange Barrier']), create_dungeon_region(player, 'Mire Antechamber', 'Misery Mire', None, ['Mire Antechamber Orange Barrier', 'Mire Antechamber NW']), - create_dungeon_region(player, 'Mire Boss', 'Misery Mire', ['Misery Mire - Boss', 'Misery Mire - Prize'], ['Mire Boss SW']), + create_dungeon_region(player, 'Mire Boss', 'Misery Mire', None, ['Mire Boss SW', 'Misery Mire Boss']), + create_dungeon_region(player, 'Mire Boss Spoils', 'Misery Mire', ['Misery Mire - Boss', 'Misery Mire - Prize', 'Misery Mire - Boss Kill']), # tr create_dungeon_region(player, 'TR Main Lobby', 'Turtle Rock', None, ['TR Main Lobby Gap', 'TR Main Lobby SE']), @@ -912,7 +921,8 @@ def create_dungeon_regions(world, player): create_dungeon_region(player, 'TR Crystal Maze End - Ranged Crystal', 'Turtle Rock', None, ['TR Crystal Maze End Ranged Crystal Exit']), create_dungeon_region(player, 'TR Final Abyss Balcony', 'Turtle Rock', None, ['TR Final Abyss South Stairs', 'TR Final Abyss Balcony Path']), create_dungeon_region(player, 'TR Final Abyss Ledge', 'Turtle Rock', None, ['TR Final Abyss NW', 'TR Final Abyss Ledge Path']), - create_dungeon_region(player, 'TR Boss', 'Turtle Rock', ['Turtle Rock - Boss', 'Turtle Rock - Prize'], ['TR Boss SW']), + create_dungeon_region(player, 'TR Boss', 'Turtle Rock', None, ['TR Boss SW', 'Turtle Rock Boss']), + create_dungeon_region(player, 'TR Boss Spoils', 'Turtle Rock', ['Turtle Rock - Boss', 'Turtle Rock - Prize', 'Turtle Rock - Boss Kill']), # gt create_dungeon_region(player, 'GT Lobby', 'Ganon\'s Tower', None, ['GT Lobby Left Down Stairs', 'GT Lobby Up Stairs', 'GT Lobby Right Down Stairs', 'GT Lobby S']), @@ -1235,7 +1245,10 @@ def adjust_locations(world, player): for l in ['Ganon', 'Agahnim 1', 'Agahnim 2', 'Frog', 'Missing Smith', 'Dark Blacksmith Ruins', 'Middle Aged Man', 'Floodgate', 'Trench 1 Switch', 'Trench 2 Switch', 'Swamp Drain', 'Turtle Medallion Pad', 'Attic Cracked Floor', 'Suspicious Maiden', 'Revealing Light', 'Big Bomb', 'Pyramid Crack', - 'Ice Block Drop', 'Lost Old Man', 'Old Man Drop Off', 'Zelda Pickup', 'Zelda Drop Off', 'Skull Star Tile']: + 'Ice Block Drop', 'Lost Old Man', 'Old Man Drop Off', 'Zelda Pickup', 'Zelda Drop Off', 'Skull Star Tile', + 'Eastern Palace - Boss Kill', 'Desert Palace - Boss Kill', 'Tower of Hera - Boss Kill', + 'Palace of Darkness - Boss Kill', 'Swamp Palace - Boss Kill', 'Skull Woods - Boss Kill', + 'Thieves\' Town - Boss Kill', 'Ice Palace - Boss Kill', 'Misery Mire - Boss Kill', 'Turtle Rock - Boss Kill']: location = world.get_location_unsafe(l, player) if location: location.type = LocationType.Logical @@ -1623,6 +1636,16 @@ location_table = {'Mushroom': (0x180013, 0x186df8, False, 'in the woods'), 'Ganon': (None, None, False, 'from me'), 'Agahnim 1': (None, None, False, 'from Ganon\'s wizardry form'), 'Agahnim 2': (None, None, False, 'from Ganon\'s wizardry form'), + 'Eastern Palace - Boss Kill': (None, None, False, None), + 'Desert Palace - Boss Kill': (None, None, False, None), + 'Tower of Hera - Boss Kill': (None, None, False, None), + 'Palace of Darkness - Boss Kill': (None, None, False, None), + 'Swamp Palace - Boss Kill': (None, None, False, None), + 'Thieves\' Town - Boss Kill': (None, None, False, None), + 'Skull Woods - Boss Kill': (None, None, False, None), + 'Ice Palace - Boss Kill': (None, None, False, None), + 'Misery Mire - Boss Kill': (None, None, False, None), + 'Turtle Rock - Boss Kill': (None, None, False, None), 'Lost Old Man': (None, None, False, None), 'Old Man Drop Off': (None, None, False, None), 'Floodgate': (None, None, False, None), diff --git a/Rules.py b/Rules.py index 1fbf8870..5106e2b5 100644 --- a/Rules.py +++ b/Rules.py @@ -63,7 +63,7 @@ def set_rules(world, player): if world.goal[player] == 'dungeons': # require all dungeons to beat ganon - add_rule(world.get_location('Ganon', player), lambda state: state.can_reach('Master Sword Pedestal', 'Location', player) and state.has_beaten_aga(player) and state.has('Beat Agahnim 2', player) and state.has_crystals(7, player)) + add_rule(world.get_location('Ganon', player), lambda state: state.has_beaten_aga(player) and state.has('Beat Agahnim 2', player) and state.has('Beat Boss', player, 10)) elif world.goal[player] in ['crystals', 'ganon']: add_rule(world.get_location('Ganon', player), lambda state: state.has_crystals(world.crystals_needed_for_ganon[player], player)) if world.goal[player] == 'ganon': @@ -148,9 +148,11 @@ def set_rule(spot, rule): spot.access_rule = rule -def set_defeat_dungeon_boss_rule(location): - # Lambda required to defer evaluation of dungeon.boss since it will change later if boos shuffle is used - set_rule(location, lambda state: location.parent_region.dungeon.boss.can_defeat(state)) +def set_defeat_dungeon_boss_rule(entrance): + # Lambda required to defer evaluation of dungeon.boss since it will change later if boss shuffle is used + set_rule(entrance, lambda state: entrance.parent_region.dungeon.boss.can_defeat(state)) + if entrance.parent_region.dungeon.name == 'Thieves Town': + add_rule(entrance, lambda state: entrance.parent_region.dungeon.boss.name != 'Blind' or state.has('Maiden Unmasked', entrance.player)) def set_always_allow(spot, rule): @@ -424,20 +426,17 @@ def global_rules(world, player): set_rule(world.get_entrance('Eastern Map Balcony Hook Path', player), lambda state: state.has('Hookshot', player)) # Boss rules. Same as below but no BK or arrow requirement. - set_defeat_dungeon_boss_rule(world.get_location('Eastern Palace - Prize', player)) - set_defeat_dungeon_boss_rule(world.get_location('Eastern Palace - Boss', player)) + set_defeat_dungeon_boss_rule(world.get_entrance('Eastern Palace Boss', player)) # Desert set_rule(world.get_location('Desert Palace - Torch', player), lambda state: state.has_Boots(player)) set_rule(world.get_entrance('Desert Wall Slide NW', player), lambda state: state.has_fire_source(player)) - set_defeat_dungeon_boss_rule(world.get_location('Desert Palace - Prize', player)) - set_defeat_dungeon_boss_rule(world.get_location('Desert Palace - Boss', player)) + set_defeat_dungeon_boss_rule(world.get_entrance('Desert Palace Boss', player)) # Tower of Hera set_rule(world.get_location('Tower of Hera - Big Key Chest', player), lambda state: state.has_fire_source(player)) set_rule(world.get_entrance('Hera Big Chest Hook Path', player), lambda state: state.has('Hookshot', player)) - set_defeat_dungeon_boss_rule(world.get_location('Tower of Hera - Boss', player)) - set_defeat_dungeon_boss_rule(world.get_location('Tower of Hera - Prize', player)) + set_defeat_dungeon_boss_rule(world.get_entrance('Tower of Hera Boss', player)) # Castle Tower set_rule(world.get_entrance('Tower Altar NW', player), lambda state: state.has_sword(player)) @@ -452,8 +451,7 @@ def global_rules(world, player): set_rule(world.get_entrance('PoD Dark Pegs Right to Landing', player), lambda state: state.has('Hammer', player)) set_rule(world.get_entrance('PoD Turtle Party NW', player), lambda state: state.has('Hammer', player)) set_rule(world.get_entrance('PoD Turtle Party ES', player), lambda state: state.has('Hammer', player)) - set_defeat_dungeon_boss_rule(world.get_location('Palace of Darkness - Boss', player)) - set_defeat_dungeon_boss_rule(world.get_location('Palace of Darkness - Prize', player)) + set_defeat_dungeon_boss_rule(world.get_entrance('Palace of Darkness Boss', player)) set_rule(world.get_entrance('Swamp Lobby Moat', player), lambda state: state.has('Flippers', player) and state.has('Open Floodgate', player)) set_rule(world.get_entrance('Swamp Entrance Moat', player), lambda state: state.has('Flippers', player) and state.has('Open Floodgate', player)) @@ -489,8 +487,7 @@ def global_rules(world, player): set_rule(world.get_entrance('Swamp Waterway N', player), lambda state: state.has('Flippers', player)) set_rule(world.get_entrance('Swamp Waterway NE', player), lambda state: state.has('Flippers', player)) set_rule(world.get_location('Swamp Palace - Waterway Pot Key', player), lambda state: state.has('Flippers', player)) - set_defeat_dungeon_boss_rule(world.get_location('Swamp Palace - Boss', player)) - set_defeat_dungeon_boss_rule(world.get_location('Swamp Palace - Prize', player)) + set_defeat_dungeon_boss_rule(world.get_entrance('Swamp Palace Boss', player)) set_rule(world.get_entrance('Skull Big Chest Hookpath', player), lambda state: state.has('Hookshot', player)) set_rule(world.get_entrance('Skull Torch Room WN', player), lambda state: state.has('Fire Rod', player)) @@ -513,8 +510,7 @@ def global_rules(world, player): set_rule(world.get_entrance('Skull 2 West Lobby Pits', player), lambda state: state.has_Boots(player) or hidden_pits_rule(state)) set_rule(world.get_entrance('Skull 2 West Lobby Ledge Pits', player), hidden_pits_rule) - set_defeat_dungeon_boss_rule(world.get_location('Skull Woods - Boss', player)) - set_defeat_dungeon_boss_rule(world.get_location('Skull Woods - Prize', player)) + set_defeat_dungeon_boss_rule(world.get_entrance('Skull Woods Boss', player)) # blind can't have the small key? - not necessarily true anymore - but likely still @@ -529,8 +525,7 @@ def global_rules(world, player): # for location in ['Suspicious Maiden', 'Thieves\' Town - Blind\'s Cell']: # set_rule(world.get_location(location, player), lambda state: state.has('Big Key (Thieves Town)', player)) set_rule(world.get_location('Revealing Light', player), lambda state: state.has('Shining Light', player) and state.has('Maiden Rescued', player)) - set_rule(world.get_location('Thieves\' Town - Boss', player), lambda state: state.has('Maiden Unmasked', player) and world.get_location('Thieves\' Town - Boss', player).parent_region.dungeon.boss.can_defeat(state)) - set_rule(world.get_location('Thieves\' Town - Prize', player), lambda state: state.has('Maiden Unmasked', player) and world.get_location('Thieves\' Town - Prize', player).parent_region.dungeon.boss.can_defeat(state)) + set_defeat_dungeon_boss_rule(world.get_entrance('Thieves Town Boss', player)) set_rule(world.get_entrance('Ice Lobby WS', player), lambda state: state.can_melt_things(player)) if is_trapped('Ice Lobby SE'): @@ -556,8 +551,7 @@ def global_rules(world, player): if is_trapped('Ice Switch Room NE'): set_rule(world.get_entrance('Ice Switch Room NE', player), lambda state: state.has('Cane of Somaria', player) or state.has('Convenient Block', player)) - set_defeat_dungeon_boss_rule(world.get_location('Ice Palace - Boss', player)) - set_defeat_dungeon_boss_rule(world.get_location('Ice Palace - Prize', player)) + set_defeat_dungeon_boss_rule(world.get_entrance('Ice Palace Boss', player)) set_rule(world.get_entrance('Mire Lobby Gap', player), lambda state: state.has_Boots(player) or state.has('Hookshot', player)) set_rule(world.get_entrance('Mire Post-Gap Gap', player), lambda state: state.has_Boots(player) or state.has('Hookshot', player)) @@ -587,8 +581,7 @@ def global_rules(world, player): # set_rule(world.get_entrance('Mire Dark Shooters SE', player), # lambda state: state.has('Cane of Somaria', player)) - set_defeat_dungeon_boss_rule(world.get_location('Misery Mire - Boss', player)) - set_defeat_dungeon_boss_rule(world.get_location('Misery Mire - Prize', player)) + set_defeat_dungeon_boss_rule(world.get_entrance('Misery Mire Boss', player)) set_rule(world.get_entrance('TR Main Lobby Gap', player), lambda state: state.has('Cane of Somaria', player)) set_rule(world.get_entrance('TR Lobby Ledge Gap', player), lambda state: state.has('Cane of Somaria', player)) @@ -618,8 +611,7 @@ def global_rules(world, player): set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Right', player), lambda state: state.can_avoid_lasers(player)) set_rule(world.get_location('Turtle Rock - Eye Bridge - Top Left', player), lambda state: state.can_avoid_lasers(player)) set_rule(world.get_location('Turtle Rock - Eye Bridge - Top Right', player), lambda state: state.can_avoid_lasers(player)) - set_defeat_dungeon_boss_rule(world.get_location('Turtle Rock - Boss', player)) - set_defeat_dungeon_boss_rule(world.get_location('Turtle Rock - Prize', player)) + set_defeat_dungeon_boss_rule(world.get_entrance('Turtle Rock Boss', player)) set_rule(world.get_location('Ganons Tower - Bob\'s Torch', player), lambda state: state.has_Boots(player)) set_rule(world.get_entrance('GT Hope Room EN', player), lambda state: state.has('Cane of Somaria', player)) From f80e8bbfd58b5217a83b2ad628c890384281b51b Mon Sep 17 00:00:00 2001 From: aerinon Date: Wed, 8 May 2024 15:13:38 -0600 Subject: [PATCH 15/41] fix: hc big key drop on basic doors --- RELEASENOTES.md | 1 + Rom.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index f3aa76d0..4767942f 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -146,6 +146,7 @@ These are now independent of retro mode and have three options: None, Random, an * Back of Tavern Shuffle now on by default * Enemizer: Wallmasters banned from tiles where spiral staircases are. (Softlock issue) * Packaged build of unstable now available + * Fix: HC Big Key drop doesn't count on Basic Doors * Fixed a small bug with traversal algorithm * 1.4.1.11u * New Feature: Several spoiler levels added: None, Settings-only, Semi, Full, Debug diff --git a/Rom.py b/Rom.py index 9acb4ebd..05dbd74f 100644 --- a/Rom.py +++ b/Rom.py @@ -585,8 +585,8 @@ def patch_rom(world, rom, player, team, is_mystery=False): valid_loc_by_dungeon = valid_dungeon_locations(valid_locations) # fix hc big key problems (map and compass too) - if (world.doorShuffle[player] not in ['vanilla', 'basic'] or world.dropshuffle[player] != 'none' - or world.pottery[player] not in ['none', 'cave']): + if (world.doorShuffle[player] != 'vanilla' or world.dropshuffle[player] != 'none' + or world.pottery[player] not in ['none', 'cave']): rom.write_byte(0x151f1, 2) rom.write_byte(0x15270, 2) sanctuary = world.get_region('Sanctuary', player) From ae35096f168c121f5d789f6147abdeaa83a01b43 Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 9 May 2024 14:35:50 -0600 Subject: [PATCH 16/41] fix: hera basement + small key for this dungeon --- RELEASENOTES.md | 3 ++- Rom.py | 2 +- data/base2current.bps | Bin 117405 -> 117396 bytes 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 4767942f..c083f29d 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -146,7 +146,8 @@ These are now independent of retro mode and have three options: None, Random, an * Back of Tavern Shuffle now on by default * Enemizer: Wallmasters banned from tiles where spiral staircases are. (Softlock issue) * Packaged build of unstable now available - * Fix: HC Big Key drop doesn't count on Basic Doors + * Fix: HC Big Key drop doesn't count on Basic Doors + * Fix: Small Key for this dungeon in Hera Basement doesn't count twice for the key counter * Fixed a small bug with traversal algorithm * 1.4.1.11u * New Feature: Several spoiler levels added: None, Settings-only, Semi, Full, Debug diff --git a/Rom.py b/Rom.py index 05dbd74f..a31fdddc 100644 --- a/Rom.py +++ b/Rom.py @@ -42,7 +42,7 @@ from source.enemizer.Enemizer import write_enemy_shuffle_settings JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '9eadc8aa7df986d90bbb3779e700aa77' +RANDOMIZERBASEHASH = '2cbc343d4d554d38d3b0c9c348ca6601' class JsonRom(object): diff --git a/data/base2current.bps b/data/base2current.bps index 63c3f0d2aa0f167229ab815a4b53899bc13cc818..b1a1f947ad9281e461a4051ac7df5f4631502e3c 100644 GIT binary patch delta 2826 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 kn?Ly?8fUprK(v&OV From d792560c9a02d2da63c838ec4b32c21a9c4d97a1 Mon Sep 17 00:00:00 2001 From: Catobat <69204835+Catobat@users.noreply.github.com> Date: Sat, 11 May 2024 02:08:00 +0200 Subject: [PATCH 17/41] Customizer option to always allow flipping Sanctuary --- BaseClasses.py | 3 ++ DoorShuffle.py | 4 +-- EntranceShuffle.py | 18 +++++----- OverworldShuffle.py | 21 ++++++------ Rom.py | 49 ++++++++++++++-------------- source/overworld/EntranceShuffle2.py | 10 +++--- 6 files changed, 54 insertions(+), 51 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index c85156d3..23005d5c 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -318,6 +318,9 @@ class World(object): def is_bombshop_start(self, player): return self.is_tile_swapped(0x2c, player) + def is_dark_chapel_start(self, player): + return self.is_tile_swapped(0x13, player) + def is_pyramid_open(self, player): if self.open_pyramid[player] == 'yes': return True diff --git a/DoorShuffle.py b/DoorShuffle.py index fbbabef3..85fac485 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -3351,7 +3351,7 @@ def remove_pair_type_if_present(door, world, player): def find_inaccessible_regions(world, player): world.inaccessible_regions[player] = [] start_regions = ['Links House' if not world.is_bombshop_start(player) else 'Big Bomb Shop'] - start_regions.append('Sanctuary' if world.mode[player] != 'inverted' else 'Dark Sanctuary Hint') + start_regions.append('Sanctuary' if not world.is_dark_chapel_start(player) else 'Dark Sanctuary Hint') regs = convert_regions(start_regions, world, player) if all(all(not e.connected_region for e in r.exits) for r in regs): # if attempting to find inaccessible regions before any connections made above, assume eventual access to Pyramid S&Q @@ -3397,7 +3397,7 @@ def find_accessible_entrances(world, player, builder): start_regions = ['Hyrule Castle Courtyard'] else: start_regions = ['Links House' if not world.is_bombshop_start(player) else 'Big Bomb Shop'] - start_regions.append('Sanctuary' if world.mode[player] != 'inverted' else 'Dark Sanctuary Hint') + start_regions.append('Sanctuary' if not world.is_dark_chapel_start(player) else 'Dark Sanctuary Hint') start_regions.append('Pyramid Area' if not world.is_tile_swapped(0x1b, player) else 'Hyrule Castle Ledge') regs = convert_regions(start_regions, world, player) diff --git a/EntranceShuffle.py b/EntranceShuffle.py index c374ec15..f0152b2e 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -67,7 +67,7 @@ def link_entrances(world, player): connect_logical(world, entrancename, exitname, player, exitname.endswith(' Exit')) for entrancename, exitname in default_connector_connections + dropexit_connections: connect_logical(world, entrancename, exitname, player, True) - if invFlag: + if world.is_dark_chapel_start(player): world.get_entrance('Dark Sanctuary Hint Exit', player).connect(world.get_entrance('Dark Sanctuary Hint', player).parent_region) if world.is_bombshop_start(player): world.get_entrance('Big Bomb Shop Exit', player).connect(world.get_entrance('Big Bomb Shop', player).parent_region) @@ -191,8 +191,8 @@ def link_entrances(world, player): connect_caves(world, lw_wdm_entrances, [], caves[0:c], player) connect_caves(world, lw_edm_entrances, [], caves[c:], player) - if invFlag: - # place dark sanc + # place dark sanc + if world.is_dark_chapel_start(player): place_dark_sanc(world, player) # place links house @@ -225,7 +225,7 @@ def link_entrances(world, player): scramble_holes(world, player) # place dark sanc - if invFlag: + if world.is_dark_chapel_start(player): place_dark_sanc(world, player) # place links house @@ -288,7 +288,7 @@ def link_entrances(world, player): caves.append('Ganons Tower Exit') # place dark sanc - if invFlag: + if world.is_dark_chapel_start(player): place_dark_sanc(world, player, list(zip(*drop_connections + dropexit_connections))[0]) # place links house @@ -342,7 +342,7 @@ def link_entrances(world, player): ([] if world.pottery[player] not in ['none', 'keys', 'dungeon'] else default_pot_connections) + ([] if world.take_any[player] == 'fixed' else default_takeany_connections)): connect_logical(world, entrancename, exitname, player, False) - if invFlag: + if world.is_dark_chapel_start(player): world.get_entrance('Dark Sanctuary Hint Exit', player).connect(world.get_entrance('Dark Sanctuary Hint', player).parent_region) suppress_spoiler = False @@ -437,7 +437,7 @@ def link_entrances(world, player): ([] if world.pottery[player] not in ['none', 'keys', 'dungeon'] else default_pot_connections) + ([] if world.take_any[player] == 'fixed' else default_takeany_connections)): connect_logical(world, entrancename, exitname, player, False) - if invFlag: + if world.is_dark_chapel_start(player): world.get_entrance('Dark Sanctuary Hint Exit', player).connect(world.get_entrance('Dark Sanctuary Hint', player).parent_region) suppress_spoiler = False @@ -512,7 +512,7 @@ def link_entrances(world, player): scramble_holes(world, player) # place dark sanc - if invFlag: + if world.is_dark_chapel_start(player): place_dark_sanc(world, player) # place links house @@ -589,7 +589,7 @@ def link_entrances(world, player): connect_entrance(world, hole, hole_targets.pop(), player) # place dark sanc - if invFlag: + if world.is_dark_chapel_start(player): place_dark_sanc(world, player) # place links house diff --git a/OverworldShuffle.py b/OverworldShuffle.py index fa5e536c..b47119c6 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -180,7 +180,7 @@ def link_overworld(world, player): else: connect_simple(world, 'Links House S&Q', 'Big Bomb Shop', player) - if not world.mode[player] == 'inverted': + if not world.is_dark_chapel_start(player): connect_simple(world, 'Sanctuary S&Q', 'Sanctuary', player) else: connect_simple(world, 'Sanctuary S&Q', 'Dark Sanctuary Hint', player) @@ -885,6 +885,7 @@ def connect_two_way(world, edgename1, edgename2, player, connected_edges=None): def determine_forced_flips(world, tile_ow_groups, do_grouped, player): undefined_chance = 50 + allow_flip_sanc = do_grouped flipped_groups = list() nonflipped_groups = list() merged_owids = list() @@ -899,6 +900,8 @@ def determine_forced_flips(world, tile_ow_groups, do_grouped, player): forced_nonflips = list() if 'undefined_chance' in custom_flips: undefined_chance = custom_flips['undefined_chance'] + if not do_grouped and 'always_allow_flipped_sanctuary' in custom_flips: + allow_flip_sanc = custom_flips['always_allow_flipped_sanctuary'] in [1, True, "True", "true"] if 'force_flip' in custom_flips: forced_flips = custom_flips['force_flip'] if 'force_no_flip' in custom_flips: @@ -974,7 +977,7 @@ def determine_forced_flips(world, tile_ow_groups, do_grouped, player): # Check if there are any groups that appear in both sets if any(group in flipped_groups for group in nonflipped_groups): raise GenerationException('Conflict found when flipping tiles') - return flipped_groups, nonflipped_groups, undefined_chance, merged_owids + return flipped_groups, nonflipped_groups, undefined_chance, allow_flip_sanc, merged_owids def shuffle_tiles(world, groups, result_list, do_grouped, forced_flips, player): (flipped_groups, nonflipped_groups, undefined_chance) = forced_flips @@ -1124,9 +1127,9 @@ def define_tile_groups(world, do_grouped, player): return False # sanctuary/chapel should not be flipped if S+Q guaranteed to output on that screen - if 0x13 in group and ((world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'district'] \ - and (world.mode[player] in ['standard', 'inverted'] or world.doorShuffle[player] != 'crossed' or world.intensity[player] < 3)) \ - or (world.shuffle[player] in ['lite', 'lean'] and world.mode[player] == 'inverted')): + if 0x13 in group and not allow_flip_sanc and ((world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'district'] \ + and (world.mode[player] in ['standard', 'inverted'] or world.doorShuffle[player] not in ['partitioned', 'crossed'] \ + or world.intensity[player] < 3)) or (world.shuffle[player] in ['lite', 'lean'] and world.mode[player] == 'inverted')): return False return True @@ -1167,7 +1170,7 @@ def define_tile_groups(world, do_grouped, player): merge_groups([[0x0f, 0x35], [0x12, 0x15, 0x33, 0x3f]]) # customizer adjustments - flipped_groups, nonflipped_groups, undefined_chance, merged_owids = determine_forced_flips(world, groups, do_grouped, player) + flipped_groups, nonflipped_groups, undefined_chance, allow_flip_sanc, merged_owids = determine_forced_flips(world, groups, do_grouped, player) for owids in merged_owids: merge_groups([owids]) @@ -1367,8 +1370,6 @@ def update_world_regions(world, player): def can_reach_smith(world, player): from Items import ItemFactory from BaseClasses import CollectionState - - invFlag = world.mode[player] == 'inverted' def explore_region(region_name, region=None): nonlocal found @@ -1407,7 +1408,7 @@ def can_reach_smith(world, player): start_region = 'Big Bomb Shop' explore_region(start_region) if not found: - if not invFlag: + if not world.is_dark_chapel_start(player): if world.intensity[player] >= 3 and world.doorShuffle[player] != 'vanilla': sanc_mirror = world.get_entrance('Sanctuary Mirror Route', player) explore_region(sanc_mirror.connected_region.name, sanc_mirror.connected_region) @@ -1586,7 +1587,7 @@ def validate_layout(world, player): start_region = 'Big Bomb Shop Area' explore_region(start_region) - if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'lite', 'lean'] and world.mode[player] == 'inverted': + if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'lite', 'lean'] and world.is_dark_chapel_start(player): start_region = 'Dark Chapel Area' explore_region(start_region) diff --git a/Rom.py b/Rom.py index 9129d519..db416798 100644 --- a/Rom.py +++ b/Rom.py @@ -685,8 +685,6 @@ def patch_rom(world, rom, player, team, is_mystery=False): rom.write_byte(0xDBB73 + exit.addresses, exit.target) if exit.name == 'Tavern North': rom.write_byte(0x157D0, exit.target) - if world.mode[player] == 'inverted': - patch_shuffled_dark_sanc(world, rom, player) # setup dr option flags based on experimental, etc. dr_flags = DROptions.Eternal_Mini_Bosses if world.doorShuffle[player] == 'vanilla' else DROptions.Town_Portal @@ -745,7 +743,7 @@ def patch_rom(world, rom, player, team, is_mystery=False): return region.is_light_world and not region.is_dark_world # dark world spawns - sanc_name = 'Sanctuary' if world.mode[player] != 'inverted' else 'Dark Sanctuary Hint' + sanc_name = 'Sanctuary' if not world.is_dark_chapel_start(player) else 'Dark Sanctuary Hint' sanc_region = world.get_region(sanc_name, player) if should_be_bunny(sanc_region, world.mode[player]): rom.write_bytes(0x13fff2, [0x12, 0x00 if sanc_name == 'Sanctuary' else 0x01]) @@ -2050,7 +2048,7 @@ def write_strings(rom, world, player, team): entrances_to_hint.update(ItemEntrances) entrances_to_hint.update(ShopEntrances) entrances_to_hint.update(OtherEntrances) - if world.mode[player] != 'inverted': + if not world.is_dark_chapel_start(player): entrances_to_hint.update({'Dark Sanctuary Hint': 'The dark sanctuary cave'}) if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'lite', 'lean', 'district']: if world.shufflelinks[player]: @@ -2368,10 +2366,10 @@ def write_strings(rom, world, player, team): # inverted spawn menu changes lh_text = "House" - if world.is_tile_swapped(0x2c, player): + if world.is_bombshop_start(player): lh_text = "Bomb Shop" sanc_text = "Sanctuary" - if world.mode[player] == 'inverted': + if world.is_dark_chapel_start(player): sanc_text = "Dark Chapel" tt['menu_start_2'] = "{MENU}\n{SPEED0}\n≥@'s " + lh_text + "\n " + sanc_text + "\n{CHOICE3}" tt['menu_start_3'] = "{MENU}\n{SPEED0}\n≥@'s " + lh_text + "\n " + sanc_text + "\n Mountain Cave\n{CHOICE2}" @@ -2468,6 +2466,8 @@ def set_inverted_mode(world, player, rom, inverted_buffer): rom.write_byte(snes_to_pc(0x0ABFBB), 0x90) # move mirror portal indicator to correct map (0xB0 normally) rom.write_byte(snes_to_pc(0x0280A6), 0xD0) # use starting point prompt instead of start at pyramid + if world.is_dark_chapel_start(player): + patch_shuffled_dark_sanc(world, rom, player) write_int16(rom, snes_to_pc(0x02D8D4), 0x112) # change sanctuary spawn point to dark sanc rom.write_bytes(snes_to_pc(0x02D8E8), [0x22, 0x22, 0x22, 0x23, 0x04, 0x04, 0x04, 0x05]) write_int16(rom, snes_to_pc(0x02D91A), 0x0400) @@ -2610,26 +2610,25 @@ def set_inverted_mode(world, player, rom, inverted_buffer): del world.data_tables[player].ow_enemy_table[0xab][5] # remove castle gate warp if world.is_tile_swapped(0x29, player): rom.write_bytes(snes_to_pc(0x06B2AB), [0xF0, 0xE1, 0x05]) # frog pickup on contact - if world.is_tile_swapped(0x2c, player): - if world.is_bombshop_start(player): - rom.write_bytes(snes_to_pc(0x03F484), [0xFD, 0x4B, 0x68]) # place bed in bomb shop - - # spawn in bomb shop - patch_shuffled_bomb_shop(world, rom, player) - rom.write_byte(snes_to_pc(0x02D8D2), 0x1C) - rom.write_bytes(snes_to_pc(0x02D8E0), [0x23, 0x22, 0x23, 0x23, 0x18, 0x18, 0x18, 0x19]) - rom.write_byte(snes_to_pc(0x02D919), 0x18) - rom.write_byte(snes_to_pc(0x02D927), 0x23) - write_int16(rom, snes_to_pc(0x02D934), 0x2398) - rom.write_byte(snes_to_pc(0x02D943), 0x18) - write_int16(rom, snes_to_pc(0x02D950), 0x0087) - write_int16(rom, snes_to_pc(0x02D95E), 0x0081) - rom.write_byte(snes_to_pc(0x02D9A4), 0x53) + if world.is_bombshop_start(player): + rom.write_bytes(snes_to_pc(0x03F484), [0xFD, 0x4B, 0x68]) # place bed in bomb shop + + # spawn in bomb shop + patch_shuffled_bomb_shop(world, rom, player) + rom.write_byte(snes_to_pc(0x02D8D2), 0x1C) + rom.write_bytes(snes_to_pc(0x02D8E0), [0x23, 0x22, 0x23, 0x23, 0x18, 0x18, 0x18, 0x19]) + rom.write_byte(snes_to_pc(0x02D919), 0x18) + rom.write_byte(snes_to_pc(0x02D927), 0x23) + write_int16(rom, snes_to_pc(0x02D934), 0x2398) + rom.write_byte(snes_to_pc(0x02D943), 0x18) + write_int16(rom, snes_to_pc(0x02D950), 0x0087) + write_int16(rom, snes_to_pc(0x02D95E), 0x0081) + rom.write_byte(snes_to_pc(0x02D9A4), 0x53) - # disable custom exit on links house exit - rom.write_byte(snes_to_pc(0x02E225), 0x1C) - rom.write_byte(snes_to_pc(0x02DAEE), 0x1C) - rom.write_byte(snes_to_pc(0x02DB8C), 0x6C) + # disable custom exit on links house exit + rom.write_byte(snes_to_pc(0x02E225), 0x1C) + rom.write_byte(snes_to_pc(0x02DAEE), 0x1C) + rom.write_byte(snes_to_pc(0x02DB8C), 0x6C) if world.is_tile_swapped(0x2f, player): rom.write_bytes(snes_to_pc(0x1BC80D), [0xB2, 0x0B, 0x82]) # add warp under rock rom.write_byte(snes_to_pc(0x1BC590), 0x00) # remove secret portal diff --git a/source/overworld/EntranceShuffle2.py b/source/overworld/EntranceShuffle2.py index cc000c82..16803f88 100644 --- a/source/overworld/EntranceShuffle2.py +++ b/source/overworld/EntranceShuffle2.py @@ -439,11 +439,11 @@ def do_blacksmith(entrances, exits, avail): links_region = links_region.name blacksmith_options = list(get_accessible_entrances(links_region, avail, assumed_inventory, False, True, True)) - if avail.inverted: + if avail.world.is_dark_chapel_start(avail.player): dark_sanc = avail.world.get_entrance('Dark Sanctuary Hint Exit', avail.player).connected_region.name blacksmith_options = list(OrderedDict.fromkeys(blacksmith_options + list(get_accessible_entrances(dark_sanc, avail, assumed_inventory, False, True, True)))) elif avail.is_sanc_forced_in_hc(): - sanc_region = avail.world.get_entrance('Sanctuary Exit',avail. player).connected_region + sanc_region = avail.world.get_entrance('Sanctuary Exit', avail.player).connected_region if sanc_region: blacksmith_options = list(OrderedDict.fromkeys(blacksmith_options + list(get_accessible_entrances(sanc_region.name, avail, assumed_inventory, False, True, True)))) else: @@ -582,7 +582,7 @@ def do_holes_and_linked_drops(entrances, exits, avail, cross_world): def do_dark_sanc(entrances, exits, avail): - if avail.inverted: + if avail.world.is_dark_chapel_start(avail.player): ext = avail.world.get_entrance('Dark Sanctuary Hint Exit', avail.player) if 'Dark Sanctuary Hint' in exits: forbidden = list(Isolated_LH_Doors) @@ -630,7 +630,7 @@ def do_links_house(entrances, exits, avail, cross_world): forbidden.append('Mimic Cave') if avail.world.is_bombshop_start(avail.player) and (avail.inverted == avail.world.is_tile_swapped(0x03, avail.player)): forbidden.extend(['Spectacle Rock Cave', 'Spectacle Rock Cave (Bottom)']) - if avail.inverted and avail.world.shuffle[avail.player] != 'district': + if avail.world.is_dark_chapel_start(avail.player) and avail.world.shuffle[avail.player] != 'district': dark_sanc_region = avail.world.get_entrance('Dark Sanctuary Hint Exit', avail.player).connected_region.name forbidden.extend(get_nearby_entrances(avail, dark_sanc_region)) else: @@ -669,7 +669,7 @@ def do_links_house(entrances, exits, avail, cross_world): avail.links_on_mountain = True # lobby shuffle means you ought to keep links house in the same world - sanc_spawn_can_be_dark = (not avail.inverted and avail.world.doorShuffle[avail.player] in ['partitioned', 'crossed'] + sanc_spawn_can_be_dark = (not avail.world.is_dark_chapel_start(avail.player) and avail.world.doorShuffle[avail.player] in ['partitioned', 'crossed'] and avail.world.intensity[avail.player] >= 3) if (cross_world and not sanc_spawn_can_be_dark) or avail.world.shuffle[avail.player] == 'district': From 71ab2b96fddcf988c38a6b07734049190faa5e42 Mon Sep 17 00:00:00 2001 From: Catobat <69204835+Catobat@users.noreply.github.com> Date: Sat, 11 May 2024 03:10:17 +0200 Subject: [PATCH 18/41] Fix oversight with some modes --- BaseClasses.py | 6 +++++- Main.py | 2 ++ OverworldShuffle.py | 7 ++++--- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 23005d5c..f0177fbd 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -88,6 +88,7 @@ class World(object): self.owwhirlpools = {} self.owflutespots = {} self.owsectors = {} + self.allow_flip_sanc = {} self.doors = [] self._door_cache = {} self.paired_doors = {} @@ -118,6 +119,7 @@ class World(object): set_player_attr('owcrossededges', []) set_player_attr('owwhirlpools', []) set_player_attr('owsectors', None) + set_player_attr('allow_flip_sanc', False) set_player_attr('remote_items', False) set_player_attr('required_medallions', ['Ether', 'Quake']) set_player_attr('bottle_refills', ['Bottle (Green Potion)', 'Bottle (Green Potion)']) @@ -319,7 +321,9 @@ class World(object): return self.is_tile_swapped(0x2c, player) def is_dark_chapel_start(self, player): - return self.is_tile_swapped(0x13, player) + if self.allow_flip_sanc[player]: + return self.is_tile_swapped(0x13, player) + return self.mode[player] == 'inverted' def is_pyramid_open(self, player): if self.open_pyramid[player] == 'yes': diff --git a/Main.py b/Main.py index 8f67ff4c..7c1b9aea 100644 --- a/Main.py +++ b/Main.py @@ -593,6 +593,7 @@ def copy_world(world): ret.standardize_palettes = world.standardize_palettes.copy() ret.owswaps = world.owswaps.copy() ret.owflutespots = world.owflutespots.copy() + ret.allow_flip_sanc = world.allow_flip_sanc.copy() ret.prizes = world.prizes.copy() ret.restrict_boss_items = world.restrict_boss_items.copy() ret.inaccessible_regions = world.inaccessible_regions.copy() @@ -788,6 +789,7 @@ def copy_world_premature(world, player): ret.standardize_palettes = world.standardize_palettes.copy() ret.owswaps = world.owswaps.copy() ret.owflutespots = world.owflutespots.copy() + ret.allow_flip_sanc = world.allow_flip_sanc.copy() ret.prizes = world.prizes.copy() ret.restrict_boss_items = world.restrict_boss_items.copy() ret.inaccessible_regions = world.inaccessible_regions.copy() diff --git a/OverworldShuffle.py b/OverworldShuffle.py index b47119c6..8e47e09a 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -151,7 +151,8 @@ def link_overworld(world, player): # tile shuffle logging.getLogger('').debug('Flipping overworld tiles') if world.owMixed[player]: - tile_groups, force_flipped, force_nonflipped, undefined_chance = define_tile_groups(world, False, player) + tile_groups, force_flipped, force_nonflipped, undefined_chance, allow_flip_sanc = define_tile_groups(world, False, player) + world.allow_flip_sanc[player] = allow_flip_sanc swapped_edges = shuffle_tiles(world, tile_groups, world.owswaps[player], False, (force_flipped, force_nonflipped, undefined_chance), player) update_world_regions(world, player) @@ -241,7 +242,7 @@ def link_overworld(world, player): # the idea is to XOR the new flips with the ones from Mixed so that non-parallel edges still work # Polar corresponds to Grouped with no flips in ow_crossed_tiles_mask ow_crossed_tiles_mask = [[],[],[]] - tile_groups, force_flipped, force_nonflipped, undefined_chance = define_tile_groups(world, True, player) + tile_groups, force_flipped, force_nonflipped, undefined_chance, _ = define_tile_groups(world, True, player) world.owcrossededges[player] = shuffle_tiles(world, tile_groups, ow_crossed_tiles_mask, True, (force_flipped, force_nonflipped, undefined_chance), player) ow_crossed_tiles = [i for i in range(0x82) if (i in world.owswaps[player][0]) != (i in ow_crossed_tiles_mask[0])] @@ -1184,7 +1185,7 @@ def define_tile_groups(world, do_grouped, player): tile_groups.append((group, lw_regions, dw_regions)) random.shuffle(tile_groups) - return tile_groups, flipped_groups, nonflipped_groups, undefined_chance + return tile_groups, flipped_groups, nonflipped_groups, undefined_chance, allow_flip_sanc def remove_reserved(world, groupedlist, connected_edges, player): new_grouping = {} From cabde21a2cdbe512f031f822164db97e51c3e274 Mon Sep 17 00:00:00 2001 From: aerinon Date: Sat, 11 May 2024 06:10:30 -0600 Subject: [PATCH 19/41] fix: starting inventory flute + flute_mode active --- Main.py | 4 +++- RELEASENOTES.md | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Main.py b/Main.py index fec6996f..93f5b048 100644 --- a/Main.py +++ b/Main.py @@ -228,7 +228,9 @@ def main(args, seed=None, fish=None): for p, inv_list in world.customizer.get_start_inventory().items(): if inv_list: for inv_item in inv_list: - item = ItemFactory(inv_item.strip(), p) + name = inv_item.strip() + name = name if name != 'Ocarina' or world.flute_mode[player] != 'active' else 'Ocarina (Activated)' + item = ItemFactory(name, p) if item: world.push_precollected(item) if args.print_custom_yaml: diff --git a/RELEASENOTES.md b/RELEASENOTES.md index c083f29d..b1ee524f 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -146,6 +146,7 @@ These are now independent of retro mode and have three options: None, Random, an * Back of Tavern Shuffle now on by default * Enemizer: Wallmasters banned from tiles where spiral staircases are. (Softlock issue) * Packaged build of unstable now available + * Customizer: Fixed an issue with starting with `Ocarina` and flute_mode is active * Fix: HC Big Key drop doesn't count on Basic Doors * Fix: Small Key for this dungeon in Hera Basement doesn't count twice for the key counter * Fixed a small bug with traversal algorithm From 1f85bc5a8d1f03b2d130046f4f121b67e6eb27eb Mon Sep 17 00:00:00 2001 From: aerinon Date: Sat, 11 May 2024 06:30:56 -0600 Subject: [PATCH 20/41] fix: typo --- source/item/FillUtil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/item/FillUtil.py b/source/item/FillUtil.py index 3fbdfe86..0e1db73c 100644 --- a/source/item/FillUtil.py +++ b/source/item/FillUtil.py @@ -760,7 +760,7 @@ mode_grouping = { 'Castle Tower - Dark Archer Key Drop', 'Castle Tower - Circle of Pots Key Drop', 'Skull Woods - Spike Corner Key Drop', 'Ice Palace - Jelly Key Drop', 'Ice Palace - Conveyor Key Drop', 'Misery Mire - Conveyor Crystal Key Drop', 'Turtle Rock - Pokey 1 Key Drop', - 'Turtle Rock - Pokey 2 Key Drop', 'Ganons Tower - Mini Helmasuar Key Drop', + 'Turtle Rock - Pokey 2 Key Drop', 'Ganons Tower - Mini Helmasaur Key Drop', ], 'Pot Keys': [ 'Eastern Palace - Dark Square Pot Key', 'Desert Palace - Desert Tiles 1 Pot Key', From 8e1f2911cd66fa9cbae36a51ae4e08cdce5f37b3 Mon Sep 17 00:00:00 2001 From: aerinon Date: Sat, 11 May 2024 07:28:07 -0600 Subject: [PATCH 21/41] feat: PreferredLocationGroup support updated --- Fill.py | 6 +++++- ItemList.py | 16 +++++++++++----- RELEASENOTES.md | 1 + docs/Customizer.md | 8 ++++++-- 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/Fill.py b/Fill.py index 6ac6b3bb..f41f845c 100644 --- a/Fill.py +++ b/Fill.py @@ -493,7 +493,7 @@ def config_sort(world): if world.item_pool_config.verify: config_sort_helper(world, world.item_pool_config.verify) elif world.item_pool_config.preferred: - config_sort_helper(world, world.item_pool_config.preferred) + config_sort_helper_random(world, world.item_pool_config.preferred) def config_sort_helper(world, sort_dict): @@ -503,6 +503,10 @@ def config_sort_helper(world, sort_dict): if (i.name, i.player) in sort_dict else 0) +def config_sort_helper_random(world, sort_dict): + world.itempool.sort(key=lambda i: 1 if (i.name, i.player) in sort_dict else 0) + + def calc_trash_locations(world, player): total_count, gt_count = 0, 0 for loc in world.get_locations(): diff --git a/ItemList.py b/ItemList.py index a0ecb729..bba6edec 100644 --- a/ItemList.py +++ b/ItemList.py @@ -1468,11 +1468,17 @@ def fill_specific_items(world): item_name = item_parts[0] world.item_pool_config.restricted[(item_name, item_player)] = placement['locations'] elif placement['type'] == 'PreferredLocationGroup': - item = placement['item'] - item_parts = item.split('#') - item_player = player if len(item_parts) < 2 else int(item_parts[1]) - item_name = item_parts[0] - world.item_pool_config.preferred[(item_name, item_player)] = placement['locations'] + items = [] + if 'item' in placement: + items.append(placement['item']) + elif 'items' in placement: + items.extend(placement['items']) + for item in items: + item_parts = item.split('#') + item_player = player if len(item_parts) < 2 else int(item_parts[1]) + item_name = item_parts[0] + world.item_pool_config.preferred[(item_name, item_player)] = placement['locations'] + world.item_pool_config.reserved_locations[player].update(placement['locations']) elif placement['type'] == 'Verification': item = placement['item'] item_parts = item.split('#') diff --git a/RELEASENOTES.md b/RELEASENOTES.md index b1ee524f..4f76e525 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -146,6 +146,7 @@ These are now independent of retro mode and have three options: None, Random, an * Back of Tavern Shuffle now on by default * Enemizer: Wallmasters banned from tiles where spiral staircases are. (Softlock issue) * Packaged build of unstable now available + * Customizer: New PreferredLocationGroup for putting a set of items in a set of locations. See customizer docs. * Customizer: Fixed an issue with starting with `Ocarina` and flute_mode is active * Fix: HC Big Key drop doesn't count on Basic Doors * Fix: Small Key for this dungeon in Hera Basement doesn't count twice for the key counter diff --git a/docs/Customizer.md b/docs/Customizer.md index b95081bd..11cea9fc 100644 --- a/docs/Customizer.md +++ b/docs/Customizer.md @@ -77,7 +77,7 @@ You may list each location for a player and the item you wish to place there. A This must be defined by player. Each player number should be listed with the appropriate section. Each section is a list of placement rules. Each placement rule has a specific type. -Supported Types: PlacementGroup, NotPlacmentGroup +Supported Types: PlacementGroup, NotPlacmentGroup, PreferredLocationGroup #### PlacementGroup @@ -85,7 +85,11 @@ You may define an item, and a list of locations. The locations may be weighted i #### NotPlacementGroup -You may define an item and a list of locations that an item should not be placed at. This will apply to all items of that type. The logic is considered for this. If it is otherwise impossible, the item will be considered for the listed locations. This is important for small key layouts mostly, but it will try other locations first. +You may define an item and a list of locations that an item should not be placed at. This will apply to all items of that type. The logic is considered for this. If it is otherwise impossible, the item will be considered for the listed locations. This is important for small key layouts mostly, but it will try other locations first. + +#### PreferredPlacementGroup + +You may define a list of items and a list of locations. Those items will be considered first for placements and the logic will attempt to place those items in those locations first. If there are more item than locations or vice versa, the leftover items or location will be treated normally. (Although, the leftover items will be placed earlier by the algorithm than those not listed) ### entrances From 1f66aa87feeb6e3577ffc15fb50b0fa217c5667b Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sat, 11 May 2024 13:34:42 -0500 Subject: [PATCH 22/41] Customizer option to always allow flipping Sanctuary --- DoorShuffle.py | 2 +- EntranceShuffle.py | 11 ++++------- ItemList.py | 2 +- OverworldShuffle.py | 4 ++-- Regions.py | 1 - source/overworld/EntranceShuffle2.py | 5 ++++- 6 files changed, 12 insertions(+), 13 deletions(-) diff --git a/DoorShuffle.py b/DoorShuffle.py index 85fac485..8e703b60 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -3363,7 +3363,7 @@ def find_inaccessible_regions(world, player): while len(queue) > 0: next_region = queue.popleft() visited_regions.add(next_region) - if world.mode[player] == 'inverted' and next_region.name == 'Dark Sanctuary Hint': # special spawn point in cave + if world.is_dark_chapel_start(player) and next_region.name == 'Dark Sanctuary Hint': # special spawn point in cave for ent in next_region.entrances: parent = ent.parent_region if parent and parent.type is not RegionType.Dungeon and parent not in queue and parent not in visited_regions: diff --git a/EntranceShuffle.py b/EntranceShuffle.py index f0152b2e..bac2fc69 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -1348,17 +1348,16 @@ def full_shuffle_dungeons(world, Dungeon_Exits, player): def place_links_house(world, player, ignore_list=[]): - invFlag = world.mode[player] == 'inverted' if world.mode[player] == 'standard' or not world.shufflelinks[player]: links_house = 'Links House' if not world.is_bombshop_start(player) else 'Big Bomb Shop' else: - if invFlag: + if world.is_dark_chapel_start(player): for dark_sanc in world.get_entrance('Dark Sanctuary Hint Exit', player).connected_region.exits: if dark_sanc.connected_region and dark_sanc.connected_region.name == 'Dark Sanctuary Hint': dark_sanc = dark_sanc.name break - if invFlag and isinstance(dark_sanc, str): + if world.is_dark_chapel_start(player) and isinstance(dark_sanc, str): links_house_doors = [i for i in get_distant_entrances(world, dark_sanc, player) if i in entrance_pool] else: links_house_doors = [i for i in get_starting_entrances(world, player, world.shuffle[player] != 'insanity') if i in entrance_pool] @@ -1399,16 +1398,14 @@ def place_dark_sanc(world, player, ignore_list=[]): def place_blacksmith(world, links_house, player): - invFlag = world.mode[player] == 'inverted' - assumed_inventory = list() - if world.logic[player] in ['noglitches', 'minorglitches'] and (world.is_tile_swapped(0x29, player) == invFlag): + if world.logic[player] in ['noglitches', 'minorglitches'] and (world.is_tile_swapped(0x29, player) == world.is_dark_chapel_start(player)): assumed_inventory.append('Titans Mitts') links_region = world.get_entrance(links_house, player).parent_region.name blacksmith_doors = list(build_accessible_entrance_list(world, links_region, player, assumed_inventory, False, True, True)) - if invFlag: + if world.is_dark_chapel_start(player): dark_sanc = world.get_entrance('Dark Sanctuary Hint Exit', player).connected_region.name blacksmith_doors = list(OrderedDict.fromkeys(blacksmith_doors + list(build_accessible_entrance_list(world, dark_sanc, player, assumed_inventory, False, True, True)))) elif world.doorShuffle[player] == 'vanilla' or world.intensity[player] < 3: diff --git a/ItemList.py b/ItemList.py index 926dd7e4..107b4b46 100644 --- a/ItemList.py +++ b/ItemList.py @@ -517,7 +517,7 @@ fixed_take_anys = [ def set_up_take_anys(world, player, skip_adjustments=False): - if world.mode[player] == 'inverted': + if world.is_dark_chapel_start(player): if 'Dark Sanctuary Hint' in take_any_locations: take_any_locations.remove('Dark Sanctuary Hint') if world.is_tile_swapped(0x29, player): diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 8e47e09a..5430e175 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -1076,7 +1076,7 @@ def shuffle_tiles(world, groups, result_list, do_grouped, forced_flips, player): attempts -= 1 continue # ensure sanc can be placed in LW in certain modes - if not do_grouped and world.shuffle[player] in ['simple', 'restricted', 'full', 'district'] and world.mode[player] != 'inverted' and (world.doorShuffle[player] != 'crossed' or world.intensity[player] < 3 or world.mode[player] == 'standard'): + if not do_grouped and world.shuffle[player] in ['simple', 'restricted', 'full', 'district'] and not world.is_dark_chapel_start(player) and (world.doorShuffle[player] != 'crossed' or world.intensity[player] < 3 or world.mode[player] == 'standard'): free_dw_drops = parity[5] + (1 if world.shuffle_ganon[player] else 0) free_drops = 6 + (1 if world.mode[player] != 'standard' else 0) + (1 if world.shuffle_ganon[player] else 0) if free_dw_drops == free_drops: @@ -1130,7 +1130,7 @@ def define_tile_groups(world, do_grouped, player): # sanctuary/chapel should not be flipped if S+Q guaranteed to output on that screen if 0x13 in group and not allow_flip_sanc and ((world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'district'] \ and (world.mode[player] in ['standard', 'inverted'] or world.doorShuffle[player] not in ['partitioned', 'crossed'] \ - or world.intensity[player] < 3)) or (world.shuffle[player] in ['lite', 'lean'] and world.mode[player] == 'inverted')): + or world.intensity[player] < 3)) or (world.shuffle[player] in ['lite', 'lean'] and world.is_dark_chapel_start(player))): return False return True diff --git a/Regions.py b/Regions.py index d828104e..9d5a3b30 100644 --- a/Regions.py +++ b/Regions.py @@ -383,7 +383,6 @@ def create_regions(world, player): def create_dungeon_regions(world, player): std_flag = world.mode[player] == 'standard' - inv_flag = world.mode[player] == 'inverted' world.regions += [ create_dungeon_region(player, 'Sanctuary Portal', 'Hyrule Castle', None, ['Sanctuary Exit', 'Enter HC (Sanc)']), create_dungeon_region(player, 'Hyrule Castle West Portal', 'Hyrule Castle', None, ['Hyrule Castle Exit (West)', 'Enter HC (West)']), diff --git a/source/overworld/EntranceShuffle2.py b/source/overworld/EntranceShuffle2.py index 16803f88..0f6190f5 100644 --- a/source/overworld/EntranceShuffle2.py +++ b/source/overworld/EntranceShuffle2.py @@ -200,7 +200,10 @@ def do_vanilla_connections(avail_pool): connect_vanilla_two_way(ent, avail_pool.default_map[ent], avail_pool) if ent in avail_pool.one_way_map and avail_pool.one_way_map[ent] in avail_pool.exits: connect_vanilla(ent, avail_pool.one_way_map[ent], avail_pool) - if avail_pool.inverted: + if avail_pool.world.is_bombshop_start(avail_pool.player): + ext = avail_pool.world.get_entrance('Big Bomb Shop Exit', avail_pool.player) + ext.connect(avail_pool.world.get_region('Big Bomb Shop Area', avail_pool.player)) + if avail_pool.world.is_dark_chapel_start(avail_pool.player): ext = avail_pool.world.get_entrance('Dark Sanctuary Hint Exit', avail_pool.player) ext.connect(avail_pool.world.get_region('Dark Chapel Area', avail_pool.player)) From 8b295a74ad8879dc076197b45ad9e811319ad03d Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sat, 11 May 2024 14:39:05 -0500 Subject: [PATCH 23/41] Some random Mixed OWR fixes --- DoorShuffle.py | 2 +- EntranceShuffle.py | 2 +- Regions.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/DoorShuffle.py b/DoorShuffle.py index 8e703b60..19483bd9 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -1537,7 +1537,7 @@ def check_entrance_fixes(world, player): 'Turtle Rock': 'tr', 'Ganons Tower': 'gt', } - if world.mode[player] == 'inverted': + if world.is_atgt_swapped(player): del checks['Ganons Tower'] for ent_name, key in checks.items(): entrance = world.get_entrance(ent_name, player) diff --git a/EntranceShuffle.py b/EntranceShuffle.py index bac2fc69..e49aa18e 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -1142,7 +1142,7 @@ def simple_shuffle_dungeons(world, player): # shuffle multi-entrance dungeons multi_dungeons = ['Desert Palace', 'Turtle Rock'] - if world.mode[player] == 'standard' or (world.mode[player] == 'inverted' and not world.shuffle_ganon): + if world.mode[player] == 'standard' or (world.is_atgt_swapped(player) and not world.shuffle_ganon): hc_target = 'Hyrule Castle' random.shuffle(multi_dungeons) else: diff --git a/Regions.py b/Regions.py index 9d5a3b30..6101d1ba 100644 --- a/Regions.py +++ b/Regions.py @@ -1162,8 +1162,8 @@ def create_shops(world, player): world.shops[player] = [] for region_name, (room_id, type, shopkeeper, custom, locked, inventory, sram) in shop_table.items(): if world.mode[player] == 'inverted': - if (0x35 not in world.owswaps[player][0] and region_name == 'Dark Lake Hylia Shop') \ - or (0x35 in world.owswaps[player][0] and region_name == 'Lake Hylia Shop'): + if (not world.is_tile_swapped(0x35, player) and region_name == 'Dark Lake Hylia Shop') \ + or (not world.is_tile_swapped(0x35, player) and region_name == 'Lake Hylia Shop'): locked = True inventory = [('Blue Potion', 160), ('Blue Shield', 50), ('Bombs (10)', 50)] custom = True From 56add15970c5faac782d9b41866ac47c1d425f47 Mon Sep 17 00:00:00 2001 From: aerinon Date: Tue, 14 May 2024 08:36:43 -0600 Subject: [PATCH 24/41] fix: all cross-dungeon modes should add the mc_rule --- RELEASENOTES.md | 1 + Rules.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 4f76e525..177754af 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -150,6 +150,7 @@ These are now independent of retro mode and have three options: None, Random, an * Customizer: Fixed an issue with starting with `Ocarina` and flute_mode is active * Fix: HC Big Key drop doesn't count on Basic Doors * Fix: Small Key for this dungeon in Hera Basement doesn't count twice for the key counter + * Fix: All cross-dungeon modes with restrict boss items should require map/compass for the boss * Fixed a small bug with traversal algorithm * 1.4.1.11u * New Feature: Several spoiler levels added: None, Settings-only, Semi, Full, Debug diff --git a/Rules.py b/Rules.py index ce6eb08a..131c2e40 100644 --- a/Rules.py +++ b/Rules.py @@ -819,7 +819,7 @@ def global_rules(world, player): d_name = "Thieves' Town" if dungeon.startswith('Thieves') else dungeon for loc in [info.prize, f'{d_name} - Boss']: add_mc_rule(loc) - if world.doorShuffle[player] == 'crossed': + if world.doorShuffle[player] not in ['vanilla', 'basic']: add_mc_rule('Agahnim 1') add_mc_rule('Agahnim 2') From a15a12d2401ec368e60b393cea3aa69c874313ac Mon Sep 17 00:00:00 2001 From: aerinon Date: Tue, 14 May 2024 10:03:54 -0600 Subject: [PATCH 25/41] fix: couple of enemy bans --- RELEASENOTES.md | 1 + source/enemizer/enemy_deny.yaml | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 177754af..22048ed7 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -152,6 +152,7 @@ These are now independent of retro mode and have three options: None, Random, an * Fix: Small Key for this dungeon in Hera Basement doesn't count twice for the key counter * Fix: All cross-dungeon modes with restrict boss items should require map/compass for the boss * Fixed a small bug with traversal algorithm + * Enemizer: Enemy bans+ * 1.4.1.11u * New Feature: Several spoiler levels added: None, Settings-only, Semi, Full, Debug * Semi includes only entrances, prizes, and medallions (potential new spoiler mode being worked on, definition may change) diff --git a/source/enemizer/enemy_deny.yaml b/source/enemizer/enemy_deny.yaml index 46b1176c..65a96ab1 100644 --- a/source/enemizer/enemy_deny.yaml +++ b/source/enemizer/enemy_deny.yaml @@ -109,7 +109,7 @@ UwGeneralDeny: - [ 0x0044, 6, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "BigSpike" ] ] #"Thieves' Town - Joke Room - Red Bari" - [ 0x0044, 8, [ "Statue", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "SpikeBlock", "Bumper" ] ] #"Thieves' Town - Joke Room - Blue Bari 4" - [ 0x0045, 1, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Thieves' Town - Basement Block Totems - Red Zazak" - - [ 0x0045, 4, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on pots + - [ 0x0045, 4, [ "Wizzrobe", "Statue", "Lynel" ] ] # Wizzrobes can't spawn on pots - [ 0x0045, 7, [ "AntiFairyCircle", "Bumper" ] ] #"Thieves' Town - Cells - Blue Zazak 4" - [ 0x0045, 8, [ "RollerHorizontalRight" ] ] #"Thieves' Town - Cells - Zol" - [ 0x0046, 0, [ "RollerVerticalUp", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "Bumper", "Statue" ] ] #"Swamp Palace - Big O Top - Hover 1" @@ -166,7 +166,7 @@ UwGeneralDeny: - [ 0x005e, 4, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Ice Palace - Pit Trap - Fire Bar (Clockwise)" - [ 0x005f, 0, [ "RollerVerticalDown", "RollerHorizontalLeft" ] ] #"Ice Palace - Bari University - Blue Bari 1" - [ 0x005f, 1, [ "RollerVerticalDown", "RollerHorizontalRight" ] ] #"Ice Palace - Bari University - Blue Bari 2" - - [ 0x0060, 0, [ "RollerVerticalUp", "RollerHorizontalLeft", "AntiFairyCircle", "BigSpike", "Bumper", "Beamos" ] ] #"Hyrule Castle - West - Blue Guard" + - [ 0x0060, 0, [ "RollerVerticalUp", "RollerHorizontalLeft", "AntiFairyCircle", "BigSpike", "Bumper", "Beamos", "SpikeBlock" ] ] #"Hyrule Castle - West - Blue Guard" - [ 0x0062, 0, [ "RollerVerticalUp", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Hyrule Castle - East - Blue Guard" - [ 0x0064, 2, [ "Bumper" , "Beamos" ] ] #"Thieves' Town - Attic Hall Left - Keese 2" - [ 0x0064, 3, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on pots From 2694efeb9f41846a7fde4f6860cfcf594c7c804c Mon Sep 17 00:00:00 2001 From: aerinon Date: Tue, 14 May 2024 14:08:33 -0600 Subject: [PATCH 26/41] fix: issues with major_only and too mnay major items chore: reformatted spoiler, moved crystal reqs to requirements --- BaseClasses.py | 51 ++++++++++++++++++++++++++--------------- Main.py | 3 ++- README.md | 8 +++++-- RELEASENOTES.md | 2 ++ source/item/FillUtil.py | 27 ++++++++++++++++++++++ 5 files changed, 69 insertions(+), 22 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index a78a7bc2..9cb3e2a4 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -2753,49 +2753,64 @@ class Spoiler(object): if self.metadata['goal'][player] in ['triforcehunt', 'trinity', 'ganonhunt']: 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('\n') + + # Item Settings 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"Flute Mode: {self.metadata['flute_mode'][player]}\n") outfile.write(f"Bow Mode: {self.metadata['bow_mode'][player]}\n") - 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('\n') + + # Item Pool Settings + outfile.write(f"Shopsanity: {yn(self.metadata['shopsanity'][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(f"Enemy Drop Shuffle: {self.metadata['dropshuffle'][player]}\n") + outfile.write(f"Take Any Caves: {self.metadata['take_any'][player]}\n") + outfile.write('\n') + + # Entrances outfile.write('Entrance Shuffle: %s\n' % self.metadata['shuffle'][player]) if self.metadata['shuffle'][player] != 'vanilla': outfile.write(f"Link's House Shuffled: {yn(self.metadata['shufflelinks'][player])}\n") outfile.write(f"Back of Tavern Shuffled: {yn(self.metadata['shuffletavern'][player])}\n") outfile.write(f"GT/Ganon Shuffled: {yn(self.metadata['shuffleganon'])}\n") outfile.write(f"Overworld Map: {self.metadata['overworld_map'][player]}\n") - outfile.write(f"Take Any Caves: {self.metadata['take_any'][player]}\n") - if self.metadata['goal'][player] != 'trinity': - outfile.write('Pyramid hole pre-opened: %s\n' % (self.metadata['open_pyramid'][player])) + outfile.write('Pyramid hole pre-opened: %s\n' % (self.metadata['open_pyramid'][player])) + outfile.write('\n') + + # Dungeons + 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(f"Small Key shuffle: {self.metadata['keyshuffle'][player]}\n") + outfile.write('Big Key shuffle: %s\n' % ('Yes' if self.metadata['bigkeyshuffle'][player] else 'No')) + outfile.write(f"Key Logic Algorithm:' {self.metadata['key_logic'][player]}\n") outfile.write('Door Shuffle: %s\n' % self.metadata['door_shuffle'][player]) if self.metadata['door_shuffle'][player] != 'vanilla': outfile.write(f"Intensity: {self.metadata['intensity'][player]}\n") outfile.write(f"Door Type Mode: {self.metadata['door_type_mode'][player]}\n") outfile.write(f"Trap Door Mode: {self.metadata['trap_door_mode'][player]}\n") - outfile.write(f"Key Logic Algorithm: {self.metadata['key_logic'][player]}\n") outfile.write(f"Decouple Doors: {yn(self.metadata['decoupledoors'][player])}\n") outfile.write(f"Spiral Stairs can self-loop: {yn(self.metadata['door_self_loops'][player])}\n") - outfile.write(f"Experimental: {yn(self.metadata['experimental'][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: {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(f"Small Key shuffle: {self.metadata['keyshuffle'][player]}\n") - outfile.write('Big Key shuffle: %s\n' % ('Yes' if self.metadata['bigkeyshuffle'][player] else 'No')) + outfile.write('\n') + + # Enemizer outfile.write('Boss shuffle: %s\n' % self.metadata['boss_shuffle'][player]) 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]) if self.metadata['enemy_shuffle'][player] != 'none': outfile.write(f"Enemy logic: {self.metadata['any_enemy_logic'][player]}\n") + outfile.write('\n') + + # Misc outfile.write(f"Hints: {yn(self.metadata['hints'][player])}\n") outfile.write('Race: %s\n' % ('Yes' if self.world.settings.world_rep['meta']['race'] else 'No')) @@ -2842,10 +2857,8 @@ class Spoiler(object): 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(str('Crystals Required for GT' + player_name + ':').ljust(line_width) + '%s\n' % (str(self.metadata['gt_crystals'][player]))) + outfile.write(str('Crystals Required for Ganon' + player_name + ':').ljust(line_width) + '%s\n' % (str(self.metadata['ganon_crystals'][player]))) if 'misc' in self.settings: outfile.write('\n\nBottle Refills:\n\n') diff --git a/Main.py b/Main.py index 93f5b048..983d4673 100644 --- a/Main.py +++ b/Main.py @@ -30,7 +30,7 @@ from ItemList import generate_itempool, difficulties, fill_prizes, customize_sho from UnderworldGlitchRules import create_hybridmajor_connections, create_hybridmajor_connectors from Utils import output_path, parse_player_names -from source.item.FillUtil import create_item_pool_config, massage_item_pool, district_item_pool_config +from source.item.FillUtil import create_item_pool_config, massage_item_pool, district_item_pool_config, verify_item_pool_config from source.overworld.EntranceShuffle2 import link_entrances_new from source.tools.BPS import create_bps_from_data from source.classes.CustomSettings import CustomSettings @@ -274,6 +274,7 @@ def main(args, seed=None, fish=None): for player in range(1, world.players + 1): generate_itempool(world, player) + verify_item_pool_config(world) logger.info(world.fish.translate("cli","cli","calc.access.rules")) for player in range(1, world.players + 1): diff --git a/README.md b/README.md index fd71ed19..a70372d5 100644 --- a/README.md +++ b/README.md @@ -449,7 +449,11 @@ This fill attempts to place all items in their vanilla locations when possible. This fill attempts to place major items in major locations. Major locations are where the major items are found in the vanilla game. This includes the spot next to Uncle in the Sewers, and the Boomerang chest in Hyrule Castle. -This location pool is expanded to where dungeon items are locations if those dungeon items are shuffled. The Capacity Fairy locations are included if Shopsanity is on. If retro is enabled in addition to shopsanity, then the Old Man Sword Cave and one location in each retro cave is included. Key drop locations can be included if small or big key shuffle is on. This gives a very good balance between overworld and underworld locations though the dungeons ones will be on bosses and in big chests generally. Seeds do become more linear but usually easier to figure out. +The location pool expands to where dungeon items are located if those dungeon items are shuffled. The Capacity Fairy locations are included if Shopsanity is on. If retro is enabled in addition to shopsanity, then the major locations will include the Old Man Sword Cave and one location in each retro cave. When the various enemy and pots keys are in the location pool, then those are included if small or big key shuffle is on. + +THe location pool will be expanded to include visible heart pieces locations if the number of major items exceeds the number of locations that are considered major. This mainly affects Trinity goal, Triforce Pieces hunts, Bomb Bag shuffle, and other settings that may not have perfect 1-to-1 correspondence with major locations and items. + +This algorithm generally gives a good balance between overworld and underworld locations. Seeds do become more linear but usually easier to figure out. #### Dungeon Restriction @@ -491,7 +495,7 @@ In multiworld, the districts chosen apply to all players. #### New Hints -Based on the district algorithm above (whether it is enabled or not,) new hints can appear about that district or dungeon. For each district and dungeon, it is evaluated whether it contains vital items and how many. If it has not any vital item, items then it moves onto useful items. Useful items are generally safeties or convenience items: shields, mails, half magic, bottles, medallions that aren't required, etc. If it contains none of those and is an overworld district, then it checks for a couple more things. First, if dungeons are shuffled, it looks to see if any are in the district, if so, one of those dungeons is picked for the hint. Then, if connectors are shuffled, it checks to see if you can get to unique region through a connector in that district. If none of the above apply, the district or dungeon is considered completely foolish. +Based on the district algorithm above (whether it is enabled or not), new hints can appear about that district or dungeon. For each district and dungeon, it is evaluated whether it contains vital items and how many. If it has not any vital item, items then it moves onto useful items. Useful items are generally safeties or convenience items: shields, mails, half magic, bottles, medallions that aren't required, etc. If it contains none of those and is an overworld district, then it checks for a couple more things. First, if dungeons are shuffled, it looks to see if any are in the district, if so, one of those dungeons is picked for the hint. Then, if connectors are shuffled, it checks to see if you can get to unique region through a connector in that district. If none of the above apply, the district or dungeon is considered completely foolish. ### Forbidden Boss Items diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 22048ed7..e1a5a605 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -148,6 +148,8 @@ These are now independent of retro mode and have three options: None, Random, an * Packaged build of unstable now available * Customizer: New PreferredLocationGroup for putting a set of items in a set of locations. See customizer docs. * Customizer: Fixed an issue with starting with `Ocarina` and flute_mode is active + * Spoiler: Some reformatting. Crystal req. for GT/Ganon moved to requirements section so randomized requirements don't show up in the meta section + * Algorithm: Major_Only. Supports up to 16 extra locations (the visible heart pieces) for when major item count exceeds major location count. Examples: Triforce Hunt, Trinity (Triforce on Ped), Bombbag shuffle * Fix: HC Big Key drop doesn't count on Basic Doors * Fix: Small Key for this dungeon in Hera Basement doesn't count twice for the key counter * Fix: All cross-dungeon modes with restrict boss items should require map/compass for the boss diff --git a/source/item/FillUtil.py b/source/item/FillUtil.py index 0e1db73c..db5a1a4f 100644 --- a/source/item/FillUtil.py +++ b/source/item/FillUtil.py @@ -263,6 +263,24 @@ def previously_reserved(location, world, player): return False +def verify_item_pool_config(world): + if world.algorithm == 'major_only': + major_pool = defaultdict(list) + for item in world.itempool: + if item.name in world.item_pool_config.item_pool[item.player]: + major_pool[item.player].append(item) + for player in major_pool: + available_locations = [world.get_location(l, player) for l in world.item_pool_config.location_groups[0].locations] + available_locations = [l for l in available_locations if l.item is None] + if len(available_locations) < len(major_pool[player]): + if len(major_pool[player]) - len(available_locations) <= len(mode_grouping['Heart Pieces Visible']): + logging.getLogger('').warning('Expanding location pool for extra major items') + world.item_pool_config.location_groups[1].locations = set(mode_grouping['Heart Pieces Visible']) + else: + raise Exception(f'Major only: there are only {len(available_locations)} locations' + f' for {len(major_pool[player])} major items for player {player}. Cannot generate.') + + def massage_item_pool(world): player_pool = defaultdict(list) for item in world.itempool: @@ -413,6 +431,9 @@ def filter_locations(item_to_place, locations, world, vanilla_skip=False, potion if item_to_place.name in config.item_pool[item_to_place.player]: restricted = config.location_groups[0].locations filtered = [l for l in locations if l.name in restricted] + if len(filtered) == 0 and len(config.location_groups[1].locations) > 0: + restricted = config.location_groups[1].locations + filtered = [l for l in locations if l.name in restricted] return filtered if world.algorithm == 'district': config = world.item_pool_config @@ -689,6 +710,12 @@ mode_grouping = { 'Graveyard Cave', 'Kakariko Well - Top', "Blind's Hideout - Top", 'Bonk Rock Cave', "Aginah's Cave", 'Chest Game', 'Digging Game', 'Mire Shed - Left', 'Mimic Cave' ], + 'Heart Pieces Visible': [ + 'Bumper Cave Ledge', 'Desert Ledge', 'Lake Hylia Island', 'Floating Island', # visible on OW + 'Maze Race', 'Pyramid', "Zora's Ledge", 'Sunken Treasure', 'Spectacle Rock', + 'Lumberjack Tree', 'Spectacle Rock Cave', 'Lost Woods Hideout', 'Checkerboard Cave', + 'Peg Cave', 'Cave 45', 'Graveyard Cave' + ], 'Big Keys': [ 'Eastern Palace - Big Key Chest', 'Ganons Tower - Big Key Chest', 'Desert Palace - Big Key Chest', 'Tower of Hera - Big Key Chest', 'Palace of Darkness - Big Key Chest', From 34b14ae157b8857977a119ae6ade08269078aec7 Mon Sep 17 00:00:00 2001 From: aerinon Date: Tue, 14 May 2024 14:19:41 -0600 Subject: [PATCH 27/41] build: disable mac b/c issues --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5d05b5e9..4f4e7557 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -111,7 +111,7 @@ jobs: strategy: matrix: # install/release on not bionic - os-name: [ ubuntu-latest, ubuntu-20.04, macOS-latest, windows-latest ] + os-name: [ ubuntu-latest, ubuntu-20.04, windows-latest ] python-version: [ 3.9 ] needs: [ install-build ] From 7bb3760ec69373c72a6be7491b40f555cff1e5dd Mon Sep 17 00:00:00 2001 From: aerinon Date: Tue, 14 May 2024 14:23:09 -0600 Subject: [PATCH 28/41] build: disable mac b/c issues #2 --- .github/workflows/ci.yml | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4f4e7557..685c3e8d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,7 +27,7 @@ jobs: # os & python versions strategy: matrix: - os-name: [ ubuntu-latest, ubuntu-20.04, macOS-latest, windows-latest ] + os-name: [ ubuntu-latest, ubuntu-20.04, windows-latest ] python-version: [ 3.9 ] # needs: [ install-test ] steps: @@ -242,11 +242,11 @@ jobs: name: archive-ubuntu-latest path: ${{ steps.parentDir.outputs.value }}/deploy/linux # download macos archive artifact - - name: Download MacOS Archive Artifact - uses: actions/download-artifact@v3 - with: - name: archive-macOS-latest - path: ${{ steps.parentDir.outputs.value }}/deploy/macos +# - name: Download MacOS Archive Artifact +# uses: actions/download-artifact@v3 +# with: +# name: archive-macOS-latest +# path: ${{ steps.parentDir.outputs.value }}/deploy/macos # download windows archive artifact - name: Download Windows Archive Artifact uses: actions/download-artifact@v3 @@ -292,17 +292,17 @@ jobs: asset_content_type: application/gzip if: contains(github.ref, 'master') || contains(github.ref, 'stable') || contains(github.ref, 'dev') || contains(github.ref, 'DoorRelease') # upload macos archive asset - - name: Upload MacOS Archive Asset - id: upload-macos-asset - uses: actions/upload-release-asset@v1.0.2 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ../deploy/macos/ALttPDoorRandomizer.tar.gz - asset_name: ALttPDoorRandomizer-${{ steps.debug_info.outputs.github_tag }}-osx.tar.gz - asset_content_type: application/gzip - if: contains(github.ref, 'master') || contains(github.ref, 'stable') || contains(github.ref, 'dev') || contains(github.ref, 'DoorRelease') +# - name: Upload MacOS Archive Asset +# id: upload-macos-asset +# uses: actions/upload-release-asset@v1.0.2 +# env: +# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} +# with: +# upload_url: ${{ steps.create_release.outputs.upload_url }} +# asset_path: ../deploy/macos/ALttPDoorRandomizer.tar.gz +# asset_name: ALttPDoorRandomizer-${{ steps.debug_info.outputs.github_tag }}-osx.tar.gz +# asset_content_type: application/gzip +# if: contains(github.ref, 'master') || contains(github.ref, 'stable') || contains(github.ref, 'dev') || contains(github.ref, 'DoorRelease') # upload windows archive asset - name: Upload Windows Archive Asset id: upload-windows-asset From 8f7ebc46b2880fc7b4b796e2303e1a8428255b00 Mon Sep 17 00:00:00 2001 From: Catobat <69204835+Catobat@users.noreply.github.com> Date: Mon, 20 May 2024 18:20:05 +0200 Subject: [PATCH 29/41] Fix edge cases with undefined_chance --- OverworldShuffle.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 5430e175..c0d17a1e 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -1049,9 +1049,13 @@ def shuffle_tiles(world, groups, result_list, do_grouped, forced_flips, player): # tile shuffle happens here removed = [] - if 0 < undefined_chance < 100: - for group in groups: - if group[0] in nonflipped_groups or (group[0] not in flipped_groups and random.randint(1, 100) > undefined_chance): + for group in groups: + if group[0] in nonflipped_groups: + removed.append(group) + else: + if group[0] in flipped_groups or undefined_chance >= 100: + continue + if undefined_chance == 0 or random.randint(1, 100) > undefined_chance: removed.append(group) # save shuffled tiles to list From 90817a14fd3c5d3ced00f0bd007d1cba7d888fba Mon Sep 17 00:00:00 2001 From: Catobat <69204835+Catobat@users.noreply.github.com> Date: Tue, 21 May 2024 18:09:31 +0200 Subject: [PATCH 30/41] Make Doors adjust to Santuary in the Dark World --- DoorShuffle.py | 2 +- Doors.py | 2 +- DungeonGenerator.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DoorShuffle.py b/DoorShuffle.py index 19483bd9..69991d13 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -3405,7 +3405,7 @@ def find_accessible_entrances(world, player, builder): visited_entrances = [] # Add Sanctuary as an additional entrance in open mode, since you can save and quit to there - if world.mode[player] == 'open' and world.get_region('Sanctuary', player).dungeon.name == builder.name and 'Sanctuary' not in entrances: + if not world.is_dark_chapel_start(player) and world.get_region('Sanctuary', player).dungeon.name == builder.name and 'Sanctuary' not in entrances: entrances.append('Sanctuary') visited_entrances.append('Sanctuary') regs.remove(world.get_region('Sanctuary', player)) diff --git a/Doors.py b/Doors.py index defdcbf0..c5519d13 100644 --- a/Doors.py +++ b/Doors.py @@ -1516,7 +1516,7 @@ def create_doors(world, player): # static portal flags world.get_door('Sanctuary S', player).dead_end(allowPassage=True) - if world.mode[player] == 'open' and world.shuffle[player] not in ['lean', 'swapped', 'crossed', 'insanity']: + if not world.is_dark_chapel_start(player) and world.shuffle[player] not in ['lean', 'swapped', 'crossed', 'insanity']: world.get_door('Sanctuary S', player).lw_restricted = True world.get_door('Eastern Hint Tile Blocked Path SE', player).passage = False world.get_door('TR Big Chest Entrance SE', player).passage = False diff --git a/DungeonGenerator.py b/DungeonGenerator.py index 5f34fbf2..1009d20a 100644 --- a/DungeonGenerator.py +++ b/DungeonGenerator.py @@ -1360,7 +1360,7 @@ def create_dungeon_builders(all_sectors, connections_tuple, world, player, dunge for name, builder in dungeon_map.items(): calc_allowance_and_dead_ends(builder, connections_tuple, world, player) - if world.mode[player] == 'open' and world.shuffle[player] not in ['lean', 'swapped', 'crossed', 'insanity']: + if not world.is_dark_chapel_start(player) and world.shuffle[player] not in ['lean', 'swapped', 'crossed', 'insanity']: sanc = find_sector('Sanctuary', candidate_sectors) if sanc: # only run if sanc if a candidate lw_builders = [] From 103e098a2e962db43d7a571eb710856cf4ba726a Mon Sep 17 00:00:00 2001 From: codemann8 Date: Thu, 23 May 2024 18:39:02 -0500 Subject: [PATCH 31/41] Initial Prize Shuffle Implementation commit c89c5d3798e2a777011e90d565d74af792330d9f commit 4159f2e7097fca648828a60d8f6878211d0ded9e commit a80e3a4301d69146ccfffe0f2f375adac381e165 commit d8ac588cb904152831f514d8276be4e39a43dcd0 commit 68eb75e3391631355b4f56f1dcb7e9dadadf1fdf commit ba241b47964eadfb40ad323f87b1117598dd91a6 commit aed2821c7165822f5fd5cc1ff3f58f2af095d915 commit bd1c5d8d35ae3cae5f27f236346fff057b7b8cd7 commit f034e31cc585a1648657fc2c4850ebc0c1d8bf78 Author: codemann8 --- BaseClasses.py | 108 +++++-- CLI.py | 3 +- DoorShuffle.py | 2 +- DungeonGenerator.py | 18 +- Dungeons.py | 51 +-- ER_hint_reference.txt | 2 +- EntranceShuffle.py | 42 +-- Fill.py | 88 ++++-- ItemList.py | 65 ++-- Items.py | 33 +- KeyDoorShuffle.py | 18 +- Main.py | 5 +- MultiClient.py | 18 ++ Regions.py | 88 ++++-- Rom.py | 293 +++++++++++------- data/base2current.bps | Bin 132319 -> 133286 bytes resources/app/cli/args.json | 7 + resources/app/cli/lang/en.json | 1 + resources/app/gui/lang/en.json | 4 + .../app/gui/randomize/dungeon/widgets.json | 12 + source/classes/CustomSettings.py | 2 + source/classes/constants.py | 1 + source/dungeon/DungeonStitcher.py | 10 +- source/item/FillUtil.py | 32 +- source/tools/MysteryUtils.py | 1 + 25 files changed, 573 insertions(+), 331 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index f0177fbd..5030b161 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -142,6 +142,7 @@ class World(object): set_player_attr('compassshuffle', False) set_player_attr('keyshuffle', 'none') set_player_attr('bigkeyshuffle', False) + set_player_attr('prizeshuffle', 'none') set_player_attr('restrict_boss_items', 'none') set_player_attr('bombbag', False) set_player_attr('flute_mode', False) @@ -473,7 +474,8 @@ class World(object): def push_precollected(self, item): item.world = self - if ((item.smallkey and self.keyshuffle[item.player] != 'none') + if ((item.prize and self.prizeshuffle[item.player] != 'none') + or (item.smallkey and self.keyshuffle[item.player] != 'none') or (item.bigkey and self.bigkeyshuffle[item.player])): item.advancement = True self.precollected_items.append(item) @@ -1607,7 +1609,8 @@ class Region(object): inside_dungeon_item = ((item.smallkey and self.world.keyshuffle[item.player] == 'none') or (item.bigkey and not self.world.bigkeyshuffle[item.player]) or (item.map and not self.world.mapshuffle[item.player]) - or (item.compass and not self.world.compassshuffle[item.player])) + or (item.compass and not self.world.compassshuffle[item.player]) + or (item.prize and self.world.prizeshuffle[item.player] == 'dungeon')) # not all small keys to escape must be in escape # sewer_hack = self.world.mode[item.player] == 'standard' and item.name == 'Small Key (Escape)' if inside_dungeon_item: @@ -1840,6 +1843,7 @@ class Dungeon(object): def __init__(self, name, regions, big_key, small_keys, dungeon_items, player, dungeon_id): self.name = name self.regions = regions + self.prize = None self.big_key = big_key self.small_keys = small_keys self.dungeon_items = dungeon_items @@ -1864,10 +1868,13 @@ class Dungeon(object): @property def all_items(self): - return self.dungeon_items + self.keys + return self.dungeon_items + self.keys + ([self.prize] if self.prize else []) def is_dungeon_item(self, item): - return item.player == self.player and item.name in [dungeon_item.name for dungeon_item in self.all_items] + if item.prize: + return item.player == self.player and self.prize is None and self.name not in ['Hyrule Castle', 'Agahnims Tower', 'Ganons Tower'] + else: + return item.player == self.player and item.name in [dungeon_item.name for dungeon_item in self.all_items] def count_dungeon_item(self): return len(self.dungeon_items) + 1 if self.big_key_required else 0 + self.key_number @@ -2621,7 +2628,7 @@ class Boss(object): class Location(object): - def __init__(self, player, name='', address=None, crystal=False, hint_text=None, parent=None, forced_item=None, + def __init__(self, player, name='', address=None, prize=False, hint_text=None, parent=None, forced_item=None, player_address=None, note=None): self.name = name self.parent_region = parent @@ -2635,7 +2642,7 @@ class Location(object): self.forced_item = None self.item = None self.event = False - self.crystal = crystal + self.prize = prize self.address = address self.player_address = player_address self.spot_type = 'Location' @@ -2643,14 +2650,14 @@ class Location(object): self.recursion_count = 0 self.staleness_count = 0 self.locked = False - self.real = not crystal + self.real = not prize self.always_allow = lambda item, state: False self.access_rule = lambda state: True self.verbose_rule = None self.item_rule = lambda item: True self.player = player self.skip = False - self.type = LocationType.Normal if not crystal else LocationType.Prize + self.type = LocationType.Normal if not prize else LocationType.Prize self.pot = None self.drop = None self.note = note @@ -2738,8 +2745,8 @@ class Item(object): self.player = player @property - def crystal(self): - return self.type == 'Crystal' + def prize(self): + return self.type == 'Prize' @property def smallkey(self): @@ -2767,11 +2774,33 @@ class Item(object): return item_dungeon def is_inside_dungeon_item(self, world): - return ((self.smallkey and world.keyshuffle[self.player] == 'none') + return ((self.prize and world.prizeshuffle[self.player] in ['none', 'dungeon']) + or (self.smallkey and world.keyshuffle[self.player] == 'none') 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 get_map_location(self): + if self.location: + if self.location.parent_region.type in [RegionType.LightWorld, RegionType.DarkWorld]: + return self.location + else: + def explore_region(region): + explored_regions.append(region.name) + for ent in region.entrances: + if ent.parent_region is not None: + if ent.parent_region.type in [RegionType.LightWorld, RegionType.DarkWorld]: + return ent + elif ent.parent_region.name not in explored_regions: + ret = explore_region(ent.parent_region) + if ret: + return ret + return None + explored_regions = list() + return explore_region(self.location.parent_region) + + return None + def __str__(self): return str(self.__unicode__()) @@ -2983,6 +3012,7 @@ class Spoiler(object): 'compassshuffle': self.world.compassshuffle, 'keyshuffle': self.world.keyshuffle, 'bigkeyshuffle': self.world.bigkeyshuffle, + 'prizeshuffle': self.world.prizeshuffle, 'boss_shuffle': self.world.boss_shuffle, 'enemy_shuffle': self.world.enemy_shuffle, 'enemy_health': self.world.enemy_health, @@ -3023,6 +3053,13 @@ class Spoiler(object): self.medallions[f'Misery Mire ({self.world.get_player_names(player)})'] = self.world.required_medallions[player][0] self.medallions[f'Turtle Rock ({self.world.get_player_names(player)})'] = self.world.required_medallions[player][1] + self.prizes = OrderedDict() + for player in range(1, self.world.players + 1): + player_name = '' if self.world.players == 1 else str(' (Player ' + str(player) + ')') + for dungeon in self.world.dungeons: + if dungeon.player == player and dungeon.prize: + self.prizes[dungeon.name + player_name] = dungeon.prize.name + self.bottles = OrderedDict() if self.world.players == 1: self.bottles['Waterfall Bottle'] = self.world.bottle_refills[1][0] @@ -3033,7 +3070,7 @@ class Spoiler(object): self.bottles[f'Pyramid Bottle ({self.world.get_player_names(player)})'] = self.world.bottle_refills[player][1] def include_item(item): - return 'all' in self.settings or ('items' in self.settings and not item.crystal) or ('prizes' in self.settings and item.crystal) + return 'all' in self.settings or ('items' in self.settings and not item.prize) or ('prizes' in self.settings and item.prize) self.locations = OrderedDict() listed_locations = set() @@ -3214,6 +3251,7 @@ class Spoiler(object): outfile.write('Compass Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['compassshuffle'][player])) outfile.write('Small Key Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['keyshuffle'][player]) outfile.write('Big Key Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['bigkeyshuffle'][player])) + outfile.write('Prize Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['prizeshuffle'][player]) outfile.write('Key Logic Algorithm:'.ljust(line_width) + '%s\n' % self.metadata['key_logic'][player]) outfile.write('\n') outfile.write('Door Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['door_shuffle'][player]) @@ -3282,7 +3320,10 @@ class Spoiler(object): 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\nPrizes:\n\n') + for dungeon, prize in self.prizes.items(): + outfile.write(str(dungeon + ':').ljust(line_width) + '%s\n' % prize) + if 'all' in self.settings or 'misc' in self.settings: outfile.write('\n\nBottle Refills:\n\n') for fairy, bottle in self.bottles.items(): @@ -3539,7 +3580,7 @@ dr_mode = {"basic": 1, "crossed": 2, "vanilla": 0, "partitioned": 3, 'paired': 4 er_mode = {"vanilla": 0, "simple": 1, "restricted": 2, "full": 3, "crossed": 4, "insanity": 5, 'lite': 8, 'lean': 9, "dungeonsfull": 7, "dungeonssimple": 6, "swapped": 10, "district": 11} -# byte 1: LLLW WSS? (logic, mode, sword) +# byte 1: LLLW WSSB (logic, mode, sword, bombbag) logic_mode = {"noglitches": 0, "minorglitches": 1, "nologic": 2, "owglitches": 3, "majorglitches": 4, "hybridglitches": 5} world_mode = {"open": 0, "standard": 1, "inverted": 2} sword_mode = {"random": 0, "assured": 1, "swordless": 2, "vanilla": 3} @@ -3550,12 +3591,12 @@ goal_mode = {'ganon': 0, 'pedestal': 1, 'dungeons': 2, 'triforcehunt': 3, 'cryst diff_mode = {"normal": 0, "hard": 1, "expert": 2} func_mode = {"normal": 0, "hard": 1, "expert": 2} -# byte 3: SDMM PIII (shop, decouple doors, mixed, palettes, intensity) +# byte 3: SDMM PIII (shop, decouple doors, mixed travel, 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) -# new byte 4: TDDD PPPP (tavern shuffle, drop, pottery) +# byte 4: TDDD PPPP (tavern shuffle, drop, pottery) # dropshuffle reserves 2 bits, pottery needs 4) drop_shuffle_mode = {'none': 0, 'keys': 1, 'underworld': 2} pottery_mode = {'none': 0, 'keys': 2, 'lottery': 3, 'dungeon': 4, 'cave': 5, 'cavekeys': 6, 'reduced': 7, @@ -3564,17 +3605,17 @@ pottery_mode = {'none': 0, 'keys': 2, 'lottery': 3, 'dungeon': 4, 'cave': 5, 'ca # byte 5: SCCC CTTX (self-loop doors, crystals gt, ctr2, experimental) counter_mode = {"default": 0, "off": 1, "on": 2, "pickup": 3} -# byte 6: ?CCC CPAA (crystals ganon, pyramid, access +# byte 6: LCCC CPAA (shuffle links, crystals ganon, pyramid, access access_mode = {"items": 0, "locations": 1, "none": 2} -# byte 7: B?MC DDEE (big, ?, maps, compass, door_type, enemies) +# byte 7: B?MC DDPP (big, ?, maps, compass, door_type, prize shuffle) door_type_mode = {'original': 0, 'big': 1, 'all': 2, 'chaos': 3} -enemy_mode = {"none": 0, "shuffled": 1, "chaos": 2, "random": 2, "legacy": 3} +prizeshuffle_mode = {'none': 0, 'dungeon': 1, 'wild': 3} -# byte 8: HHHD DPBS (enemy_health, enemy_dmg, potshuffle, bomb logic, shuffle links) -# potshuffle decprecated, now unused +# byte 8: HHHD DPEE (enemy_health, enemy_dmg, potshuffle, enemies) e_health = {"default": 0, "easy": 1, "normal": 2, "hard": 3, "expert": 4} e_dmg = {"default": 0, "shuffled": 1, "random": 2} +enemy_mode = {"none": 0, "shuffled": 1, "chaos": 2, "random": 2, "legacy": 3} # byte 9: RRAA ABBB (restrict boss mode, algorithm, boss shuffle) rb_mode = {"none": 0, "mapcompass": 1, "dungeon": 2} @@ -3593,9 +3634,9 @@ flutespot_mode = {"vanilla": 0, "balanced": 1, "random": 2} # byte 13: FBBB TTSS (flute_mode, bow_mode, take_any, small_key_mode) flute_mode = {'normal': 0, 'active': 1} -keyshuffle_mode = {'none': 0, 'off': 0, 'wild': 1, 'on': 1, 'universal': 2} # reserved 8 modes? +bow_mode = {'progressive': 0, 'silvers': 1, 'retro': 2, 'retro_silvers': 3} # reserved 8 modes? take_any_mode = {'none': 0, 'random': 1, 'fixed': 2} -bow_mode = {'progressive': 0, 'silvers': 1, 'retro': 2, 'retro_silvers': 3} +keyshuffle_mode = {'none': 0, 'off': 0, 'wild': 1, 'on': 1, 'universal': 2} # additions # byte 14: POOT TKKK (pseudoboots, overworld_map, trap_door_mode, key_logic_algo) @@ -3617,7 +3658,7 @@ class Settings(object): (dr_mode[w.doorShuffle[p]] << 5) | er_mode[w.shuffle[p]], (logic_mode[w.logic[p]] << 5) | (world_mode[w.mode[p]] << 3) - | (sword_mode[w.swords[p]] << 1), + | (sword_mode[w.swords[p]] << 1) | (0x1 if w.bombbag[p] else 0), (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), @@ -3633,15 +3674,15 @@ class Settings(object): | ((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), - ((8 if w.crystals_ganon_orig[p] == "random" else int(w.crystals_ganon_orig[p])) << 3) + (0x80 if w.shufflelinks[p] else 0) | ((8 if w.crystals_ganon_orig[p] == "random" else int(w.crystals_ganon_orig[p])) << 3) | (0x4 if w.is_pyramid_open(p) else 0) | access_mode[w.accessibility[p]], (0x80 if w.bigkeyshuffle[p] else 0) | (0x20 if w.mapshuffle[p] else 0) | (0x10 if w.compassshuffle[p] else 0) - | (door_type_mode[w.door_type_mode[p]] << 2) | (enemy_mode[w.enemy_shuffle[p]]), + | (door_type_mode[w.door_type_mode[p]] << 2) | prizeshuffle_mode[w.prizeshuffle[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), + (e_health[w.enemy_health[p]] << 5) | (e_dmg[w.enemy_damage[p]] << 3) + | (0x4 if w.potshuffle[p] else 0) | (enemy_mode[w.enemy_shuffle[p]]), (rb_mode[w.restrict_boss_items[p]] << 6) | (algo_mode[w.algorithm] << 3) | (boss_mode[w.boss_shuffle[p]]), @@ -3675,15 +3716,19 @@ class Settings(object): args.shuffle[p] = r(er_mode)[settings[0] & 0x1F] args.door_shuffle[p] = r(dr_mode)[(settings[0] & 0xE0) >> 5] + args.logic[p] = r(logic_mode)[(settings[1] & 0xE0) >> 5] args.mode[p] = r(world_mode)[(settings[1] & 0x18) >> 3] args.swords[p] = r(sword_mode)[(settings[1] & 0x6) >> 1] + args.bombbag[p] = True if settings[1] & 0x1 else False + 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[6] & 0x3] # args.retro[p] = True if settings[1] & 0x01 else False args.hints[p] = True if settings[2] & 0x01 else False + args.shopsanity[p] = True if settings[3] & 0x80 else False args.decoupledoors[p] = True if settings[3] & 0x40 else False args.mixed_travel[p] = r(mixed_travel_mode)[(settings[3] & 0x30) >> 4] @@ -3701,22 +3746,21 @@ class Settings(object): args.crystals_gt[p] = "random" if cgt == 8 else cgt args.experimental[p] = True if settings[5] & 0x1 else False + args.shufflelinks[p] = True if settings[6] & 0x80 else False cgan = (settings[6] & 0x78) >> 3 args.crystals_ganon[p] = "random" if cgan == 8 else cgan 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.door_type_mode[p] = r(door_type_mode)[(settings[7] & 0xc) >> 2] - args.shuffleenemies[p] = r(enemy_mode)[settings[7] & 0x3] + args.prizeshuffle[p] = r(prizeshuffle_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 + args.shuffleenemies[p] = r(enemy_mode)[settings[8] & 0x3] if len(settings) > 9: args.restrict_boss_items[p] = r(rb_mode)[(settings[9] & 0xC0) >> 6] diff --git a/CLI.py b/CLI.py index c9603654..c8763902 100644 --- a/CLI.py +++ b/CLI.py @@ -135,7 +135,7 @@ def parse_cli(argv, no_defaults=False): 'ow_terrain', 'ow_crossed', 'ow_keepsimilar', 'ow_mixed', 'ow_whirlpool', 'ow_fluteshuffle', 'flute_mode', 'bow_mode', 'take_any', 'boots_hint', 'shuffle', 'door_shuffle', 'intensity', 'crystals_ganon', 'crystals_gt', 'openpyramid', - 'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory', + 'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'prizeshuffle', 'startinventory', 'usestartinventory', 'bombbag', 'shuffleganon', 'overworld_map', 'restrict_boss_items', 'triforce_pool_min', 'triforce_pool_max', 'triforce_goal_min', 'triforce_goal_max', 'triforce_max_difference', 'triforce_min_difference', 'triforce_goal', 'triforce_pool', 'shufflelinks', 'shuffletavern', @@ -223,6 +223,7 @@ def parse_settings(): "compassshuffle": False, "keyshuffle": "none", "bigkeyshuffle": False, + "prizeshuffle": "none", "keysanity": False, "door_shuffle": "vanilla", "intensity": 3, diff --git a/DoorShuffle.py b/DoorShuffle.py index 19483bd9..aac435ef 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -1629,7 +1629,7 @@ def refine_hints(dungeon_builders): for name, builder in dungeon_builders.items(): 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': + if not location.event and '- Boss' not in location.name and not location.prize and location.name != 'Sanctuary': 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]}' diff --git a/DungeonGenerator.py b/DungeonGenerator.py index 5f34fbf2..39cc374a 100644 --- a/DungeonGenerator.py +++ b/DungeonGenerator.py @@ -11,7 +11,7 @@ from typing import List from BaseClasses import DoorType, Direction, CrystalBarrier, RegionType, Polarity, PolSlot, flooded_keys, Sector from BaseClasses import Hook, hook_from_door, Door -from Regions import dungeon_events, flooded_keys_reverse +from Regions import location_events, flooded_keys_reverse from Dungeons import split_region_starts from RoomData import DoorKind @@ -884,19 +884,19 @@ class ExplorationState(object): if key_checks and location not in self.found_locations: if location.forced_item and 'Small Key' in location.item.name: self.key_locations += 1 - if location.name not in dungeon_events and '- Prize' not in location.name and location.name not in ['Agahnim 1', 'Agahnim 2']: + if location.name not in location_events and not location.prize: self.ttl_locations += 1 if location not in self.found_locations: self.found_locations.append(location) if not bk_flag and (not location.forced_item or 'Big Key' in location.item.name): self.bk_found.add(location) - if location.name in dungeon_events and location.name not in self.events: + if location.name in location_events and location.name not in self.events: if self.flooded_key_check(location): self.perform_event(location.name, key_region) if location.name in flooded_keys_reverse.keys() and self.location_found( flooded_keys_reverse[location.name]): self.perform_event(flooded_keys_reverse[location.name], key_region) - if '- Prize' in location.name: + if location.prize: self.prize_received = True def flooded_key_check(self, location): @@ -1096,7 +1096,7 @@ def count_locations_exclude_big_chest(locations, world, player): def prize_or_event(loc): - return loc.name in dungeon_events or '- Prize' in loc.name or loc.name in ['Agahnim 1', 'Agahnim 2'] + return loc.name in location_events or loc.prize def reserved_location(loc, world, player): @@ -1557,13 +1557,13 @@ def define_sector_features(sectors): for sector in sectors: for region in sector.regions: for loc in region.locations: - if '- Prize' in loc.name or loc.name in ['Agahnim 1', 'Agahnim 2']: + if loc.prize or loc.name in ['Agahnim 1', 'Agahnim 2']: pass elif loc.forced_item and 'Small Key' in loc.item.name: sector.key_only_locations += 1 elif loc.forced_item and loc.forced_item.bigkey: sector.bk_provided = True - elif loc.name not in dungeon_events and not loc.forced_item: + elif loc.name not in location_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", @@ -1773,6 +1773,8 @@ def build_orig_location_set(dungeon_map): def requested_dungeon_items(world, player): num = 0 + if world.prizeshuffle[player] == 'dungeon': + num += 1 if not world.bigkeyshuffle[player]: num += 1 if not world.compassshuffle[player]: @@ -4055,7 +4057,7 @@ def calc_door_equation(door, sector, look_for_entrance, sewers_flag=None): crystal_barrier = CrystalBarrier.Either # todo: backtracking from double switch with orange on-- for loc in region.locations: - if loc.name in dungeon_events: + if loc.name in location_events: found_events.add(loc.name) for d in event_doors: if loc.name == d.req_event: diff --git a/Dungeons.py b/Dungeons.py index 7ffe518c..53818b7f 100644 --- a/Dungeons.py +++ b/Dungeons.py @@ -34,16 +34,16 @@ def create_dungeons(world, player): world.dungeons += [ES, EP, DP, ToH, AT, PoD, TT, SW, SP, IP, MM, TR, GT] -dungeon_music_addresses = {'Eastern Palace - Prize': [0x1559A], - 'Desert Palace - Prize': [0x1559B, 0x1559C, 0x1559D, 0x1559E], - 'Tower of Hera - Prize': [0x155C5, 0x1107A, 0x10B8C], - 'Palace of Darkness - Prize': [0x155B8], - 'Swamp Palace - Prize': [0x155B7], - 'Thieves\' Town - Prize': [0x155C6], - 'Skull Woods - Prize': [0x155BA, 0x155BB, 0x155BC, 0x155BD, 0x15608, 0x15609, 0x1560A, 0x1560B], - 'Ice Palace - Prize': [0x155BF], - 'Misery Mire - Prize': [0x155B9], - 'Turtle Rock - Prize': [0x155C7, 0x155A7, 0x155AA, 0x155AB]} +dungeon_music_addresses = {'Eastern Palace': [0x1559A], + 'Desert Palace': [0x1559B, 0x1559C, 0x1559D, 0x1559E], + 'Tower of Hera': [0x155C5, 0x1107A, 0x10B8C], + 'Palace of Darkness': [0x155B8], + 'Swamp Palace': [0x155B7], + 'Thieves Town': [0x155C6], + 'Skull Woods': [0x155BA, 0x155BB, 0x155BC, 0x155BD, 0x15608, 0x15609, 0x1560A, 0x1560B], + 'Ice Palace': [0x155BF], + 'Misery Mire': [0x155B9], + 'Turtle Rock': [0x155C7, 0x155A7, 0x155AA, 0x155AB]} hyrule_castle_regions = [ 'Hyrule Castle Lobby', 'Hyrule Castle West Lobby', 'Hyrule Castle East Lobby', 'Hyrule Castle East Hall', @@ -270,7 +270,7 @@ flexible_starts = { class DungeonInfo: - def __init__(self, free, keys, bk, map, compass, bk_drop, drops, prize, midx): + def __init__(self, free, keys, bk, map, compass, bk_drop, drops, prize, dungeon_idx, extra_map_idx): # todo reduce static maps ideas: prize, bk_name, sm_name, cmp_name, map_name): self.free_items = free self.key_num = keys @@ -281,23 +281,24 @@ class DungeonInfo: self.key_drops = drops self.prize = prize - self.map_index = midx + self.dungeon_index = dungeon_idx + self.extra_map_index = extra_map_idx dungeon_table = { - '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), + 'Hyrule Castle': DungeonInfo(6, 1, False, True, False, True, 3, None, 0x00, 0x00), + 'Eastern Palace': DungeonInfo(3, 0, True, True, True, False, 2, [0x1209D, 0x53F3A, 0x53F3B, 0x180052, 0x180070, 0x186FE2], 0x04, None), + 'Desert Palace': DungeonInfo(2, 1, True, True, True, False, 3, [0x1209E, 0x53F3C, 0x53F3D, 0x180053, 0x180072, 0x186FE3], 0x06, 0x12), + 'Tower of Hera': DungeonInfo(2, 1, True, True, True, False, 0, [0x120A5, 0x53F4A, 0x53F4B, 0x18005A, 0x180071, 0x186FEA], 0x14, None), + 'Agahnims Tower': DungeonInfo(0, 2, False, False, False, False, 2, None, 0x08, None), + 'Palace of Darkness': DungeonInfo(5, 6, True, True, True, False, 0, [0x120A1, 0x53F22, 0x53F43, 0x180056, 0x180073, 0x186FE6], 0x0C, None), + 'Swamp Palace': DungeonInfo(6, 1, True, True, True, False, 5, [0x120A0, 0x53F40, 0x53F41, 0x180055, 0x180079, 0x186FE5], 0x0A, None), + 'Skull Woods': DungeonInfo(2, 3, True, True, True, False, 2, [0x120A3, 0x53F46, 0x53F47, 0x180058, 0x180074, 0x186FE8], 0x10, 0x20), + 'Thieves Town': DungeonInfo(4, 1, True, True, True, False, 2, [0x120A6, 0x53F4C, 0x53F4D, 0x18005B, 0x180076, 0x186FEB], 0x16, None), + 'Ice Palace': DungeonInfo(3, 2, True, True, True, False, 4, [0x120A4, 0x53F48, 0x53F49, 0x180059, 0x180078, 0x186FE9], 0x12, None), + 'Misery Mire': DungeonInfo(2, 3, True, True, True, False, 3, [0x120A2, 0x53F44, 0x53F45, 0x180057, 0x180077, 0x186FE7], 0x0E, None), + 'Turtle Rock': DungeonInfo(5, 4, True, True, True, False, 2, [0x120A7, 0x53F4E, 0x53F4F, 0x18005C, 0x180075, 0x186FEC], 0x18, 0x3E), + 'Ganons Tower': DungeonInfo(20, 4, True, True, True, False, 4, None, 0x1A, None), } diff --git a/ER_hint_reference.txt b/ER_hint_reference.txt index c86ca335..943609ce 100644 --- a/ER_hint_reference.txt +++ b/ER_hint_reference.txt @@ -316,7 +316,7 @@ Library: near books Potion Shop: near potions Spike Cave: beyond spikes Mimic Cave: in a cave of mimicry -Chest Game: as a prize +Chest Game: as a game reward Chicken House: near poultry Aginah's Cave: with Aginah Ice Rod Cave: in a frozen cave diff --git a/EntranceShuffle.py b/EntranceShuffle.py index e49aa18e..56ff285f 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -2675,11 +2675,16 @@ ow_prize_table = {'Links House': (0x8b1, 0xb2d), '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)': (0x820, 0x730), 'Hyrule Castle Entrance (West)': (0x740, 0x5D0), - 'Hyrule Castle Entrance (East)': (0x8f0, 0x5D0), 'Inverted Pyramid Entrance': (0x6C0, 0x5D0), + 'Hyrule Castle Entrance (East)': (0x8f0, 0x5D0), 'Agahnims Tower': (0x820, 0x5D0), - '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), + 'Thieves Town': (0x1d0, 0x780), 'Skull Woods First Section Door': (0x2e0, 0x280), + 'Skull Woods Second Section Door (East)': (0x200, 0x240), + 'Skull Woods Second Section Door (West)': (0x0c0, 0x1c0), + 'Skull Woods Final Section': (0x082, 0x0b0), + 'Skull Woods First Section Hole (West)': (0x200, 0x2b0), + 'Skull Woods First Section Hole (East)': (0x340, 0x2e0), + 'Skull Woods First Section Hole (North)': (0x320, 0x1e0), + 'Skull Woods Second Section Hole': (0x0f0, 0x0b0), 'Ice Palace': (0xca0, 0xda0), 'Misery Mire': (0x100, 0xca0), 'Palace of Darkness': (0xf40, 0x620), 'Swamp Palace': (0x759, 0xED0), @@ -2687,16 +2692,23 @@ ow_prize_table = {'Links House': (0x8b1, 0xb2d), '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 Drop': (0x9D0, 0x680), 'Hyrule Castle Secret Entrance Stairs': (0x8D0, 0x700), + 'Kakariko Well Drop': (0x030, 0x680), 'Kakariko Well Cave': (0x060, 0x680), - 'Bat Cave Cave': (0x540, 0x8f0), + 'Bat Cave Drop': (0x520, 0x8f0), + 'Bat Cave Cave': (0x560, 0x940), 'Elder House (East)': (0x2b0, 0x6a0), 'Elder House (West)': (0x230, 0x6a0), + 'North Fairy Cave Drop': (0xa40, 0x500), 'North Fairy Cave': (0xa80, 0x440), + 'Lost Woods Hideout Drop': (0x290, 0x200), 'Lost Woods Hideout Stump': (0x240, 0x280), - 'Lumberjack Tree Cave': (0x4e0, 0x004), + 'Lumberjack Tree Tree': (0x4e0, 0x140), + 'Lumberjack Tree Cave': (0x560, 0x004), 'Two Brothers House (East)': (0x200, 0x0b60), 'Two Brothers House (West)': (0x180, 0x0b60), + 'Sanctuary Grave': (0x820, 0x4c0), 'Sanctuary': (0x720, 0x4a0), 'Old Man Cave (West)': (0x580, 0x2c0), 'Old Man Cave (East)': (0x620, 0x2c0), @@ -2721,17 +2733,13 @@ ow_prize_table = {'Links House': (0x8b1, 0xb2d), 'Hookshot Cave': (0xc80, 0x0c0), 'Hookshot Cave Back Entrance': (0xcf0, 0x004), 'Ganons Tower': (0x8D0, 0x080), + 'Pyramid Hole': (0x820, 0x680), + 'Inverted Pyramid Hole': (0x820, 0x680), '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, + 'Inverted Pyramid Entrance': (0x6C0, 0x5D0), '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), @@ -2743,11 +2751,8 @@ ow_prize_table = {'Links House': (0x8b1, 0xb2d), 'Sahasrahlas Hut': (0xcf0, 0x6c0), 'Lake Hylia Shop': (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), @@ -2756,8 +2761,6 @@ ow_prize_table = {'Links House': (0x8b1, 0xb2d), '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), @@ -2769,7 +2772,6 @@ ow_prize_table = {'Links House': (0x8b1, 0xb2d), '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), @@ -2798,6 +2800,6 @@ ow_prize_table = {'Links House': (0x8b1, 0xb2d), 'Mimic Cave': (0xc80, 0x180), 'Big Bomb Shop': (0x8b1, 0xb2d), 'Dark Lake Hylia Shop': (0xa40, 0xc40), - 'Lumberjack House': (0x4e0, 0x0d0), + 'Lumberjack House': (0x580, 0x100), 'Lake Hylia Fortune Teller': (0xa40, 0xc40), 'Kakariko Gamble Game': (0x2f0, 0xaf0)} diff --git a/Fill.py b/Fill.py index cc6141e8..5727e7c2 100644 --- a/Fill.py +++ b/Fill.py @@ -14,14 +14,19 @@ from source.item.FillUtil import filter_special_locations, valid_pot_items def get_dungeon_item_pool(world): - return [item for dungeon in world.dungeons for item in dungeon.all_items if item.location is None] + dungeon_items = [item for dungeon in world.dungeons for item in dungeon.all_items if item.location is None] + for player in range(1, world.players+1): + if world.prizeshuffle[player] != 'none': + dungeon_items.extend(ItemFactory(['Red Pendant', 'Blue Pendant', 'Green Pendant', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 7', 'Crystal 5', 'Crystal 6'], player)) + + return dungeon_items def promote_dungeon_items(world): world.itempool += get_dungeon_item_pool(world) for item in world.get_items(): - if item.smallkey or item.bigkey: + if item.smallkey or item.bigkey or item.prize: item.advancement = True elif item.map or item.compass: item.priority = True @@ -38,36 +43,62 @@ def fill_dungeons_restrictive(world, shuffled_locations): # 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] != 'none') + if ((item.prize and world.prizeshuffle[item.player] != 'none') + or (item.smallkey and world.keyshuffle[item.player] != 'none') 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.is_inside_dungeon_item(world)] - bigs, smalls, others = [], [], [] + bigs, smalls, prizes, others = [], [], [], [] for i in dungeon_items: - (bigs if i.bigkey else smalls if i.smallkey else others).append(i) + (bigs if i.bigkey else smalls if i.smallkey else prizes if i.prize else others).append(i) unplaced_smalls = list(smalls) for i in world.itempool: if i.smallkey and world.keyshuffle[i.player] != 'none': unplaced_smalls.append(i) - def fill(base_state, items, key_pool): - fill_restrictive(world, base_state, shuffled_locations, items, key_pool, True) + def fill(base_state, items, locations, key_pool=None): + fill_restrictive(world, base_state, locations, items, key_pool, True) all_state_base = world.get_all_state() big_state_base = all_state_base.copy() - for x in smalls + others: + for x in smalls + prizes + others: big_state_base.collect(x, True) - fill(big_state_base, bigs, unplaced_smalls) + fill(big_state_base, bigs, shuffled_locations, unplaced_smalls) random.shuffle(shuffled_locations) small_state_base = all_state_base.copy() - for x in others: + for x in prizes + others: small_state_base.collect(x, True) - fill(small_state_base, smalls, unplaced_smalls) + fill(small_state_base, smalls, shuffled_locations, unplaced_smalls) + + prizes_copy = prizes.copy() + for attempt in range(15): + try: + random.shuffle(prizes) + random.shuffle(shuffled_locations) + prize_state_base = all_state_base.copy() + for x in others: + prize_state_base.collect(x, True) + fill(prize_state_base, prizes, shuffled_locations) + except FillError as e: + logging.getLogger('').info("Failed to place dungeon prizes (%s). Will retry %s more times", e, 14 - attempt) + prizes = prizes_copy.copy() + for dungeon in world.dungeons: + if world.prizeshuffle[dungeon.player] == 'dungeon': + dungeon.prize = None + for prize in prizes: + if prize.location: + prize.location.item = None + prize.location = None + continue + break + else: + raise FillError(f'Unable to place dungeon prizes {", ".join(list(map(lambda d: d.hint_text, prize_locs)))}') + random.shuffle(shuffled_locations) - fill(all_state_base, others, None) + fill(all_state_base, others, shuffled_locations) def fill_restrictive(world, base_state, locations, itempool, key_pool=None, single_player_placement=False, @@ -141,7 +172,7 @@ def fill_restrictive(world, base_state, locations, itempool, key_pool=None, sing 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 + if item_to_place.smallkey or item_to_place.bigkey or item_to_place.prize: # a better test to see if a key can go there location.item = item_to_place location.event = True if item_to_place.smallkey: @@ -155,9 +186,9 @@ def verify_spot_to_fill(location, item_to_place, max_exp_state, single_player_pl test_state.sweep_for_events() if location.can_fill(test_state, item_to_place, perform_access_check): if valid_key_placement(item_to_place, location, key_pool, test_state, world): - if item_to_place.crystal or valid_dungeon_placement(item_to_place, location, world): + if item_to_place.prize or valid_dungeon_placement(item_to_place, location, world): return location - if item_to_place.smallkey or item_to_place.bigkey: + if item_to_place.smallkey or item_to_place.bigkey or item_to_place.prize: location.item = None location.event = False if item_to_place.smallkey: @@ -181,8 +212,8 @@ def valid_key_placement(item, location, key_pool, collection_state, world): key_logic = world.key_logic[item.player][dungeon.name] 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) + if key_logic.prize_location and dungeon.prize and dungeon.prize.location and dungeon.prize.location.player == item.player: + prize_loc = dungeon.prize.location cr_count = world.crystals_needed_for_gt[location.player] wild_keys = world.keyshuffle[item.player] != 'none' if wild_keys: @@ -193,7 +224,7 @@ def valid_key_placement(item, location, key_pool, collection_state, world): self_locking_keys = sum(1 for d, rule in key_logic.door_rules.items() if rule.allow_small and rule.small_location.item and rule.small_location.item.name == key_logic.small_key_name) return key_logic.check_placement(unplaced_keys, wild_keys, reached_keys, self_locking_keys, - location if item.bigkey else None, prize_loc, cr_count) + location if item.bigkey else None, prize_loc, cr_count) else: return not item.is_inside_dungeon_item(world) @@ -228,16 +259,19 @@ def track_outside_keys(item, location, world): def track_dungeon_items(item, location, world): - if location.parent_region.dungeon and not item.crystal: + if location.parent_region.dungeon and (not item.prize or world.prizeshuffle[item.player] == 'dungeon'): 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 + if item.prize: + location.parent_region.dungeon.prize = item def is_dungeon_item(item, world): - return ((item.smallkey and world.keyshuffle[item.player] == 'none') + return ((item.prize and world.prizeshuffle[item.player] == 'none') + or (item.smallkey and world.keyshuffle[item.player] == 'none') 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])) @@ -250,8 +284,8 @@ def recovery_placement(item_to_place, locations, world, state, base_state, itemp 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'] + if item_to_place.prize: + possible_swaps = [x for x in state.locations_checked if x.item.prize] return try_possible_swaps(possible_swaps, item_to_place, locations, world, base_state, itempool, key_pool, single_player_placement) else: @@ -310,11 +344,15 @@ def last_ditch_placement(item_to_place, locations, world, state, base_state, ite return 3 return 4 - if item_to_place.type == 'Crystal': - possible_swaps = [x for x in state.locations_checked if x.item.type == 'Crystal'] + # TODO: Verify correctness in using item player in multiworld situations + if item_to_place.prize and world.prizeshuffle[item_to_place.player] == 'none': + possible_swaps = [x for x in state.locations_checked if x.item.prize] else: + ignored_types = ['Event'] + if world.prizeshuffle[item_to_place.player] == 'none': + ignored_types.append('Prize') possible_swaps = [x for x in state.locations_checked - if x.item.type not in ['Event', 'Crystal'] and not x.forced_item and not x.locked] + if x.item.type not in ignored_types and not x.forced_item and not x.locked] 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) diff --git a/ItemList.py b/ItemList.py index 107b4b46..08563aba 100644 --- a/ItemList.py +++ b/ItemList.py @@ -5,7 +5,7 @@ import RaceRandom as random from BaseClasses import LocationType, 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, valid_pot_location +from Regions import location_events, shop_to_location_table, retro_shops, shop_table_by_location, valid_pot_location from Fill import FillError, fill_restrictive, get_dungeon_item_pool, track_dungeon_items, track_outside_keys from PotShuffle import vanilla_pots from Tables import bonk_prize_lookup @@ -240,41 +240,9 @@ def generate_itempool(world, player): old_man.forced_item = old_man.item old_man.skip = True - event_items = { - 'Agahnim 1': 'Beat Agahnim 1', - 'Agahnim 2': 'Beat Agahnim 2', - 'Eastern Palace - Boss Kill': 'Beat Boss', - 'Desert Palace - Boss Kill': 'Beat Boss', - 'Tower of Hera - Boss Kill': 'Beat Boss', - 'Palace of Darkness - Boss Kill': 'Beat Boss', - 'Swamp Palace - Boss Kill': 'Beat Boss', - 'Skull Woods - Boss Kill': 'Beat Boss', - 'Thieves\' Town - Boss Kill': 'Beat Boss', - 'Ice Palace - Boss Kill': 'Beat Boss', - 'Misery Mire - Boss Kill': 'Beat Boss', - 'Turtle Rock - Boss Kill': 'Beat Boss', - 'Lost Old Man': 'Escort Old Man', - 'Old Man Drop Off': 'Return Old Man', - 'Floodgate': 'Open Floodgate', - 'Big Bomb': 'Pick Up Big Bomb', - 'Pyramid Crack': 'Detonate Big Bomb', - 'Frog': 'Get Frog', - 'Missing Smith': 'Return Smith', - 'Dark Blacksmith Ruins': 'Pick Up Purple Chest', - 'Middle Aged Man': 'Deliver Purple Chest', - 'Trench 1 Switch': 'Trench 1 Filled', - 'Trench 2 Switch': 'Trench 2 Filled', - 'Swamp Drain': 'Drained Swamp', - 'Turtle Medallion Pad': 'Turtle Opened', - 'Attic Cracked Floor': 'Shining Light', - 'Suspicious Maiden': 'Maiden Rescued', - 'Revealing Light': 'Maiden Unmasked', - 'Ice Block Drop': 'Convenient Block', - 'Skull Star Tile': 'Hidden Pits' - } - - for loc, item in event_items.items(): - set_event_item(loc, item) + for loc, item in location_events.items(): + if item: + set_event_item(loc, item) if world.mode[player] == 'standard': set_event_item('Zelda Pickup', 'Zelda Herself') @@ -375,7 +343,8 @@ def generate_itempool(world, player): world.treasure_hunt_icon[player] = 'Triforce Piece' world.itempool.extend([item for item in get_dungeon_item_pool(world) if item.player == player - and ((item.smallkey and world.keyshuffle[player] != 'none') + and ((item.prize and world.prizeshuffle[player] == 'wild') + or (item.smallkey and world.keyshuffle[player] != 'none') or (item.bigkey and world.bigkeyshuffle[player]) or (item.map and world.mapshuffle[player]) or (item.compass and world.compassshuffle[player]))]) @@ -486,7 +455,7 @@ def generate_itempool(world, player): world.itempool = [beemizer(item) for item in world.itempool] # increase pool if not enough items - ttl_locations = sum(1 for x in world.get_unfilled_locations(player) if '- Prize' not in x.name) + ttl_locations = sum(1 for x in world.get_unfilled_locations(player) if world.prizeshuffle[player] != 'none' or not x.prize) pool_size = count_player_dungeon_item_pool(world, player) pool_size += sum(1 for x in world.itempool if x.player == player) @@ -725,9 +694,12 @@ def create_dynamic_bonkdrop_locations(world, player): def fill_prizes(world, attempts=15): + from Items import prize_item_table all_state = world.get_all_state(keys=True) for player in range(1, world.players + 1): - crystals = ItemFactory(['Red Pendant', 'Blue Pendant', 'Green Pendant', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 7', 'Crystal 5', 'Crystal 6'], player) + if world.prizeshuffle[player] != 'none': + continue + crystals = ItemFactory(list(prize_item_table.keys()), player) crystal_locations = [world.get_location('Turtle Rock - Prize', player), world.get_location('Eastern Palace - Prize', player), world.get_location('Desert Palace - Prize', player), world.get_location('Tower of Hera - Prize', player), world.get_location('Palace of Darkness - Prize', player), world.get_location('Thieves\' Town - Prize', player), world.get_location('Skull Woods - Prize', player), world.get_location('Swamp Palace - Prize', player), world.get_location('Ice Palace - Prize', player), world.get_location('Misery Mire - Prize', player)] @@ -742,6 +714,8 @@ def fill_prizes(world, attempts=15): random.shuffle(prizepool) random.shuffle(prize_locs) fill_restrictive(world, all_state, prize_locs, prizepool, single_player_placement=True) + for prize_loc in crystal_locations: + prize_loc.parent_region.dungeon.prize = prize_loc.item except FillError as e: logging.getLogger('').info("Failed to place dungeon prizes (%s). Will retry %s more times", e, attempts - attempt - 1) for location in empty_crystal_locations: @@ -1419,9 +1393,11 @@ def make_customizer_pool(world, player): target_amount = max(amount, len(dungeon.small_keys)) additional_amount = target_amount - len(dungeon.small_keys) dungeon.small_keys.extend([d_item] * additional_amount) - elif item_name.startswith('Big Key') or item_name.startswith('Map') or item_name.startswith('Compass'): + elif (item_name.startswith('Big Key') or item_name.startswith('Map') or item_name.startswith('Compass') + or item_name.startswith('Crystal') or item_name.endswith('Pendant')): d_item = ItemFactory(item_name, player) - if ((d_item.bigkey and not world.bigkeyshuffle[player]) + if ((d_item.prize and world.prizeshuffle[player] in ['none', 'dungeon']) + or (d_item.bigkey and not world.bigkeyshuffle[player]) or (d_item.compass and not world.compassshuffle[player]) or (d_item.map and not world.mapshuffle[player])): d_name = d_item.dungeon @@ -1566,11 +1542,11 @@ def set_default_triforce(goal, custom_goal, custom_total): def fill_specific_items(world): if world.customizer: + from Items import prize_item_table placements = world.customizer.get_placements() dungeon_pool = get_dungeon_item_pool(world) prize_pool = [] - prize_set = {'Red Pendant', 'Blue Pendant', 'Green Pendant', 'Crystal 1', 'Crystal 2', - 'Crystal 3', 'Crystal 4', 'Crystal 7', 'Crystal 5', 'Crystal 6'} + prize_set = set(prize_item_table.keys()) for p in range(1, world.players + 1): prize_pool.extend(prize_set) if placements: @@ -1672,7 +1648,8 @@ def get_item_and_event_flag(item, world, player, dungeon_pool, prize_set, prize_ def is_dungeon_item(item, world, player): - return ((item.startswith('Small Key') and world.keyshuffle[player] == 'none') + return (((item.startswith('Crystal') or item.endswith('Pendant')) and world.prizeshuffle[player] in ['none', 'dungeon']) + or (item.startswith('Small Key') and world.keyshuffle[player] == 'none') or (item.startswith('Big Key') and not world.bigkeyshuffle[player]) or (item.startswith('Compass') and not world.compassshuffle[player]) or (item.startswith('Map') and not world.mapshuffle[player])) diff --git a/Items.py b/Items.py index 8a4f9c14..d383b987 100644 --- a/Items.py +++ b/Items.py @@ -60,19 +60,19 @@ item_table = {'Bow': (True, False, None, 0x0B, 200, 'You have\nchosen the\narche '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, 0x37, 0x08], 999, None, None, None, None, None, None, None), - 'Blue Pendant': (True, False, 'Crystal', [0x02, 0x34, 0x60, 0x00, 0x69, 0x39, 0x09], 999, None, None, None, None, None, None, None), - 'Red Pendant': (True, False, 'Crystal', [0x01, 0x32, 0x60, 0x00, 0x69, 0x38, 0x0a], 999, None, None, None, None, None, None, None), + 'Green Pendant': (True, False, 'Prize', 0x37, 250, 'A pendant that\nsome old guy\nwill never see', 'and the green pendant', 'pendant kid', 'pendant for sale', 'fungus for pendant', 'pendant boy dabs again', 'a Prize'), + 'Blue Pendant': (True, False, 'Prize', 0x39, 150, 'A pendant that you\'ll never get', 'and the pendant', 'pendant kid', 'pendant for sale', 'fungus for pendant', 'pendant boy dabs again', 'a Prize'), + 'Red Pendant': (True, False, 'Prize', 0x38, 150, 'A pendant that you\'ll never get', 'and the pendant', 'pendant kid', 'pendant for sale', 'fungus for pendant', 'pendant boy dabs again', 'a Prize'), '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, 0x20, 0x01], 999, None, None, None, None, None, None, None), - 'Crystal 2': (True, False, 'Crystal', [0x10, 0x34, 0x64, 0x40, 0x79, 0x20, 0x02], 999, None, None, None, None, None, None, None), - 'Crystal 3': (True, False, 'Crystal', [0x40, 0x34, 0x64, 0x40, 0x6C, 0x20, 0x03], 999, None, None, None, None, None, None, None), - 'Crystal 4': (True, False, 'Crystal', [0x20, 0x34, 0x64, 0x40, 0x6D, 0x20, 0x04], 999, None, None, None, None, None, None, None), - 'Crystal 5': (True, False, 'Crystal', [0x04, 0x32, 0x64, 0x40, 0x6E, 0x20, 0x05], 999, None, None, None, None, None, None, None), - 'Crystal 6': (True, False, 'Crystal', [0x01, 0x32, 0x64, 0x40, 0x6F, 0x20, 0x06], 999, None, None, None, None, None, None, None), - 'Crystal 7': (True, False, 'Crystal', [0x08, 0x34, 0x64, 0x40, 0x7C, 0x20, 0x07], 999, None, None, None, None, None, None, None), + 'Crystal 1': (True, False, 'Prize', 0xB1, 200, 'A pretty nice,\nshiny crystal', 'and the crystal', 'crystal kid', 'crystal for sale', 'fungus for crystal', 'crystal boy sparkles again', 'a Prize'), + 'Crystal 2': (True, False, 'Prize', 0xB4, 200, 'A pretty nice,\nshiny crystal', 'and the crystal', 'crystal kid', 'crystal for sale', 'fungus for crystal', 'crystal boy sparkles again', 'a Prize'), + 'Crystal 3': (True, False, 'Prize', 0xB6, 200, 'A pretty nice,\nshiny crystal', 'and the crystal', 'crystal kid', 'crystal for sale', 'fungus for crystal', 'crystal boy sparkles again', 'a Prize'), + 'Crystal 4': (True, False, 'Prize', 0xB5, 200, 'A pretty nice,\nshiny crystal', 'and the crystal', 'crystal kid', 'crystal for sale', 'fungus for crystal', 'crystal boy sparkles again', 'a Prize'), + 'Crystal 5': (True, False, 'Prize', 0xB2, 250, 'A crystal that\nunlocks a bomb', 'and the red crystal', 'crystal kid', 'crystal for sale', 'fungus for crystal', 'crystal boy sparkles again', 'a Prize'), + 'Crystal 6': (True, False, 'Prize', 0xB0, 250, 'A crystal that\nunlocks a bomb', 'and the red crystal', 'crystal kid', 'crystal for sale', 'fungus for crystal', 'crystal boy sparkles again', 'a Prize'), + 'Crystal 7': (True, False, 'Prize', 0xB3, 200, 'A pretty nice,\nshiny crystal', 'and the crystal', 'crystal kid', 'crystal for sale', 'fungus for crystal', 'crystal boy sparkles again', 'a Prize'), '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'), @@ -203,3 +203,16 @@ item_table = {'Bow': (True, False, None, 0x0B, 200, 'You have\nchosen the\narche 'Farmable Bombs': (True, False, 'Event', 999, None, None, None, None, None, None, None, None), 'Farmable Rupees': (True, False, 'Event', 999, None, None, None, None, None, None, None, None), } + +prize_item_table = { + 'Green Pendant': [0x04, 0x38, 0x62, 0x00, 0x69, 0x08], + 'Blue Pendant': [0x02, 0x34, 0x60, 0x00, 0x69, 0x09], + 'Red Pendant': [0x01, 0x32, 0x60, 0x00, 0x69, 0x0a], + 'Crystal 1': [0x02, 0x34, 0x64, 0x40, 0x7F, 0x01], + 'Crystal 2': [0x10, 0x34, 0x64, 0x40, 0x79, 0x02], + 'Crystal 3': [0x40, 0x34, 0x64, 0x40, 0x6C, 0x03], + 'Crystal 4': [0x20, 0x34, 0x64, 0x40, 0x6D, 0x04], + 'Crystal 5': [0x04, 0x32, 0x64, 0x40, 0x6E, 0x05], + 'Crystal 6': [0x01, 0x32, 0x64, 0x40, 0x6F, 0x06], + 'Crystal 7': [0x08, 0x34, 0x64, 0x40, 0x7C, 0x07] +} diff --git a/KeyDoorShuffle.py b/KeyDoorShuffle.py index a5ca9cad..a451200e 100644 --- a/KeyDoorShuffle.py +++ b/KeyDoorShuffle.py @@ -3,7 +3,7 @@ import logging from collections import defaultdict, deque from BaseClasses import DoorType, dungeon_keys, KeyRuleType, RegionType -from Regions import dungeon_events +from Regions import location_events from Dungeons import dungeon_keys, dungeon_bigs, dungeon_table from DungeonGenerator import ExplorationState, get_special_big_key_doors, count_locations_exclude_big_chest, prize_or_event from DungeonGenerator import reserved_location, blind_boss_unavail @@ -1116,7 +1116,7 @@ def location_is_bk_locked(loc, key_logic): # 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'] +# return loc.name in location_events or '- Prize' in loc.name or loc.name in ['Agahnim 1', 'Agahnim 2'] # # # def reserved_location(loc, world, player): @@ -1504,7 +1504,7 @@ def validate_key_layout_sub_loop(key_layout, state, checked_states, flat_proposa bk_done = state.big_key_opened or num_bigs == 0 or (available_big_locations == 0 and not found_forced_bk) # prize door should not be opened if the boss is reachable - but not reached yet allow_for_prize_lock = (key_layout.prize_can_lock and - not any(x for x in state.found_locations if '- Prize' in x.name)) + not any(x for x in state.found_locations if x.prize)) prize_done = not key_layout.prize_relevant or state.prize_doors_opened or allow_for_prize_lock if smalls_done and bk_done and prize_done: return False @@ -1623,7 +1623,7 @@ def determine_prize_lock(key_layout, world, player): elif len(state.small_doors) > 0: open_a_door(state.small_doors[0].door, state, flat_proposal, world, player) expand_key_state(state, flat_proposal, world, player) - if any(x for x in state.found_locations if '- Prize' in x.name): + if any(x for x in state.found_locations if x.prize): key_layout.prize_can_lock = True @@ -1776,7 +1776,7 @@ def create_key_counter(state, key_layout, world, player): key_counter.key_only_locations[loc] = None elif loc.forced_item and loc.item.name == key_layout.key_logic.bk_name: key_counter.other_locations[loc] = None - elif loc.name not in dungeon_events: + elif loc.name not in location_events: key_counter.free_locations[loc] = None else: key_counter.other_locations[loc] = None @@ -1785,7 +1785,7 @@ def create_key_counter(state, key_layout, world, player): key_counter.big_key_opened = state.big_key_opened if len(state.prize_door_set) > 0 and state.prize_doors_opened: key_counter.prize_doors_opened = True - if any(x for x in key_counter.important_locations if '- Prize' in x.name): + if any(x for x in key_counter.important_locations if x.prize): key_counter.prize_received = True return key_counter @@ -1805,7 +1805,7 @@ def imp_locations_factory(world, player): def important_location(loc, world, player): - return '- Prize' in loc.name or loc.name in imp_locations_factory(world, player) or (loc.forced_big_key()) + return loc.prize or loc.name in imp_locations_factory(world, player) or (loc.forced_big_key()) def create_odd_key_counter(door, parent_counter, key_layout, world, player): @@ -2135,9 +2135,9 @@ def validate_key_placement(key_layout, world, player): found_keys = sum(1 for i in found_locations if i.item is not None and i.item.name == smallkey_name and i.item.player == player) + \ len(counter.key_only_locations) + keys_outside if key_layout.prize_relevant: - found_prize = any(x for x in counter.important_locations if '- Prize' in x.name) + found_prize = any(x for x in counter.important_locations if x.prize) if not found_prize and dungeon_table[key_layout.sector.name].prize: - prize_loc = world.get_location(dungeon_table[key_layout.sector.name].prize, player) + prize_loc = dungeon_table[key_layout.sector.name].prize.location if key_layout.prize_relevant == 'BigBomb': found_prize = prize_loc.item.name not in ['Crystal 5', 'Crystal 6'] elif key_layout.prize_relevant == 'GT': diff --git a/Main.py b/Main.py index 7c1b9aea..e18c114a 100644 --- a/Main.py +++ b/Main.py @@ -460,6 +460,7 @@ def init_world(args, fish): world.compassshuffle = args.compassshuffle.copy() world.keyshuffle = args.keyshuffle.copy() world.bigkeyshuffle = args.bigkeyshuffle.copy() + world.prizeshuffle = args.prizeshuffle.copy() world.bombbag = args.bombbag.copy() world.flute_mode = args.flute_mode.copy() world.bow_mode = args.bow_mode.copy() @@ -555,6 +556,7 @@ def copy_world(world): ret.compassshuffle = world.compassshuffle.copy() ret.keyshuffle = world.keyshuffle.copy() ret.bigkeyshuffle = world.bigkeyshuffle.copy() + ret.prizeshuffle = world.prizeshuffle.copy() ret.bombbag = world.bombbag.copy() ret.flute_mode = world.flute_mode.copy() ret.bow_mode = world.bow_mode.copy() @@ -751,6 +753,7 @@ def copy_world_premature(world, player): ret.compassshuffle = world.compassshuffle.copy() ret.keyshuffle = world.keyshuffle.copy() ret.bigkeyshuffle = world.bigkeyshuffle.copy() + ret.prizeshuffle = world.prizeshuffle.copy() ret.bombbag = world.bombbag.copy() ret.flute_mode = world.flute_mode.copy() ret.bow_mode = world.bow_mode.copy() @@ -889,7 +892,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) + new_loc = Location(location.player, location.name, location.address, location.prize, location.hint_text, new_reg) new_loc.type = location.type new_reg.locations.append(new_loc) diff --git a/MultiClient.py b/MultiClient.py index 3723599f..d28d401f 100644 --- a/MultiClient.py +++ b/MultiClient.py @@ -340,6 +340,16 @@ location_table_uw = {"Blind's Hideout - Top": (0x11d, 0x10), 'Ganons Tower - Mini Helmasaur Key Drop': (0x3d, 0x400), 'Ganons Tower - Pre-Moldorm Chest': (0x3d, 0x40), 'Ganons Tower - Validation Chest': (0x4d, 0x10)} +location_table_boss = {'Eastern Palace - Prize': 0x2000, + 'Desert Palace - Prize': 0x1000, + 'Tower of Hera - Prize': 0x0020, + 'Palace of Darkness - Prize': 0x0200, + 'Thieves Town - Prize': 0x0010, + 'Skull Woods - Prize': 0x0080, + 'Swamp Palace - Prize': 0x0400, + 'Ice Palace - Prize': 0x0040, + 'Misery Mire - Prize': 0x0100, + 'Turtle Rock - Prize': 0x0008} location_table_npc = {'Mushroom': 0x1000, 'King Zora': 0x2, 'Sahasrahla': 0x10, @@ -949,6 +959,14 @@ async def track_locations(ctx : Context, roomid, roomdata): if roomdata & mask != 0: new_check(location) + if not all([location in ctx.locations_checked for location in location_table_boss.keys()]): + boss_data = await snes_read(ctx, SAVEDATA_START + 0x472, 2) + if boss_data is not None: + boss_value = boss_data[0] | (boss_data[1] << 8) + for location, mask in location_table_boss.items(): + if boss_value & mask != 0 and location not in ctx.locations_checked: + new_check(location) + ow_begin = 0x82 ow_end = 0 ow_unchecked = {} diff --git a/Regions.py b/Regions.py index 6101d1ba..4b835c4a 100644 --- a/Regions.py +++ b/Regions.py @@ -1124,8 +1124,8 @@ def _create_region(player, name, type, hint='Hyrule', locations=None, exits=None ko_hint = key_drop_data[location][2] ret.locations.append(Location(player, location, None, False, ko_hint, ret, key_drop_data[location][3])) else: - address, player_address, crystal, hint_text = location_table[location] - ret.locations.append(Location(player, location, address, crystal, hint_text, ret, None, player_address)) + address, player_address, prize, hint_text = location_table[location] + ret.locations.append(Location(player, location, address, prize, hint_text, ret, None, player_address)) return ret def mark_light_dark_world_regions(world, player): @@ -1240,14 +1240,14 @@ def adjust_locations(world, player): # player address? it is in the shop table index += 1 setup_enemy_locations(world, player) + # disable forced prize locations + if world.prizeshuffle[player] != 'none': + for l in [name for name, data in location_table.items() if data[2]]: + location = world.get_location_unsafe(l, player) + if location: + location.prize = False # unreal events: - for l in ['Ganon', 'Agahnim 1', 'Agahnim 2', 'Frog', 'Missing Smith', 'Dark Blacksmith Ruins', 'Middle Aged Man', - 'Floodgate', 'Trench 1 Switch', 'Trench 2 Switch', 'Swamp Drain', 'Turtle Medallion Pad', - 'Attic Cracked Floor', 'Suspicious Maiden', 'Revealing Light', 'Big Bomb', 'Pyramid Crack', - 'Ice Block Drop', 'Lost Old Man', 'Old Man Drop Off', 'Zelda Pickup', 'Zelda Drop Off', 'Skull Star Tile', - 'Eastern Palace - Boss Kill', 'Desert Palace - Boss Kill', 'Tower of Hera - Boss Kill', - 'Palace of Darkness - Boss Kill', 'Swamp Palace - Boss Kill', 'Skull Woods - Boss Kill', - 'Thieves\' Town - Boss Kill', 'Ice Palace - Boss Kill', 'Misery Mire - Boss Kill', 'Turtle Rock - Boss Kill']: + for l in ['Ganon', 'Zelda Pickup', 'Zelda Drop Off'] + list(location_events): location = world.get_location_unsafe(l, player) if location: location.type = LocationType.Logical @@ -1397,18 +1397,42 @@ shop_table_by_location_id = {0x400000+cnt: x for cnt, x in enumerate(flat_normal shop_table_by_location_id = {**shop_table_by_location_id, **{0x400020+cnt: x for cnt, x in enumerate(flat_retro_shops)}} shop_table_by_location = {y: x for x, y in shop_table_by_location_id.items()} -dungeon_events = [ - 'Trench 1 Switch', - 'Trench 2 Switch', - 'Swamp Drain', - 'Attic Cracked Floor', - 'Suspicious Maiden', - 'Revealing Light', - 'Ice Block Drop', - 'Skull Star Tile', - 'Zelda Pickup', - 'Zelda Drop Off' -] + +location_events = { + 'Agahnim 1': 'Beat Agahnim 1', + 'Agahnim 2': 'Beat Agahnim 2', + 'Eastern Palace - Boss Kill': 'Beat Boss', + 'Desert Palace - Boss Kill': 'Beat Boss', + 'Tower of Hera - Boss Kill': 'Beat Boss', + 'Palace of Darkness - Boss Kill': 'Beat Boss', + 'Swamp Palace - Boss Kill': 'Beat Boss', + 'Skull Woods - Boss Kill': 'Beat Boss', + 'Thieves\' Town - Boss Kill': 'Beat Boss', + 'Ice Palace - Boss Kill': 'Beat Boss', + 'Misery Mire - Boss Kill': 'Beat Boss', + 'Turtle Rock - Boss Kill': 'Beat Boss', + 'Lost Old Man': 'Escort Old Man', + 'Old Man Drop Off': 'Return Old Man', + 'Floodgate': 'Open Floodgate', + 'Big Bomb': 'Pick Up Big Bomb', + 'Pyramid Crack': 'Detonate Big Bomb', + 'Frog': 'Get Frog', + 'Missing Smith': 'Return Smith', + 'Dark Blacksmith Ruins': 'Pick Up Purple Chest', + 'Middle Aged Man': 'Deliver Purple Chest', + 'Trench 1 Switch': 'Trench 1 Filled', + 'Trench 2 Switch': 'Trench 2 Filled', + 'Swamp Drain': 'Drained Swamp', + 'Turtle Medallion Pad': 'Turtle Opened', + 'Attic Cracked Floor': 'Shining Light', + 'Suspicious Maiden': 'Maiden Rescued', + 'Revealing Light': 'Maiden Unmasked', + 'Ice Block Drop': 'Convenient Block', + 'Skull Star Tile': 'Hidden Pits', + 'Zelda Pickup': None, + 'Zelda Drop Off': None +} + flooded_keys_reverse = { 'Swamp Palace - Trench 1 Pot Key': 'Trench 1 Switch', @@ -1524,7 +1548,7 @@ location_table = {'Mushroom': (0x180013, 0x186df8, False, 'in the woods'), 'Pyramid Fairy - Right': (0xe983, 0x186c17, False, 'near a fairy'), 'Brewery': (0xe9ec, 0x186c80, False, 'alone in a home'), 'C-Shaped House': (0xe9ef, 0x186c83, False, 'alone in a home'), - 'Chest Game': (0xeda8, 0x186e2b, False, 'as a prize'), + 'Chest Game': (0xeda8, 0x186e2b, False, 'as a game reward'), 'Bumper Cave Ledge': (0x180146, 0x186e15, False, 'on a ledge'), 'Mire Shed - Left': (0xea73, 0x186d07, False, 'near sparks'), 'Mire Shed - Right': (0xea76, 0x186d0a, False, 'near sparks'), @@ -1665,16 +1689,16 @@ location_table = {'Mushroom': (0x180013, 0x186df8, False, 'in the woods'), 'Skull Star Tile': (None, None, False, None), 'Zelda Pickup': (None, None, False, None), 'Zelda Drop Off': (None, None, False, None), - 'Eastern Palace - Prize': ([0x1209D, 0x53E76, 0x53E77, 0x180052, 0x180070, 0xC6FE, 0x186FE2], None, True, 'Eastern Palace'), - 'Desert Palace - Prize': ([0x1209E, 0x53E7A, 0x53E7B, 0x180053, 0x180072, 0xC6FF, 0x186FE3], None, True, 'Desert Palace'), - 'Tower of Hera - Prize': ([0x120A5, 0x53E78, 0x53E79, 0x18005A, 0x180071, 0xC706, 0x186FEA], None, True, 'Tower of Hera'), - 'Palace of Darkness - Prize': ([0x120A1, 0x53E7C, 0x53E7D, 0x180056, 0x180073, 0xC702, 0x186FE6], None, True, 'Palace of Darkness'), - 'Swamp Palace - Prize': ([0x120A0, 0x53E88, 0x53E89, 0x180055, 0x180079, 0xC701, 0x186FE5], None, True, 'Swamp Palace'), - 'Thieves\' Town - Prize': ([0x120A6, 0x53E82, 0x53E83, 0x18005B, 0x180076, 0xC707, 0x186FEB], None, True, 'Thieves Town'), - 'Skull Woods - Prize': ([0x120A3, 0x53E7E, 0x53E7F, 0x180058, 0x180074, 0xC704, 0x186FE8], None, True, 'Skull Woods'), - 'Ice Palace - Prize': ([0x120A4, 0x53E86, 0x53E87, 0x180059, 0x180078, 0xC705, 0x186FE9], None, True, 'Ice Palace'), - 'Misery Mire - Prize': ([0x120A2, 0x53E84, 0x53E85, 0x180057, 0x180077, 0xC703, 0x186FE7], None, True, 'Misery Mire'), - 'Turtle Rock - Prize': ([0x120A7, 0x53E80, 0x53E81, 0x18005C, 0x180075, 0xC708, 0x186FEC], None, True, 'Turtle Rock'), + 'Eastern Palace - Prize': (0xC6FE, 0x186E2C, True, 'with the Armos'), + 'Desert Palace - Prize': (0xC6FF, 0x186E2D, True, 'with Lanmolas'), + 'Tower of Hera - Prize': (0xC706, 0x186E2E, True, 'with Moldorm'), + 'Palace of Darkness - Prize': (0xC702, 0x186E2F, True, 'with Helmasaur King'), + 'Swamp Palace - Prize': (0xC701, 0x186E30, True, 'with Arrghus'), + 'Skull Woods - Prize': (0xC704, 0x186E31, True, 'with Mothula'), + 'Thieves\' Town - Prize': (0xC707, 0x186E32, True, 'with Blind'), + 'Ice Palace - Prize': (0xC705, 0x186E33, True, 'with Kholdstare'), + 'Misery Mire - Prize': (0xC703, 0x186E34, True, 'with Vitreous'), + 'Turtle Rock - Prize': (0xC708, 0x186E35, True, 'with Trinexx'), 'Kakariko Shop - Left': (None, None, False, 'for sale in Kakariko'), 'Kakariko Shop - Middle': (None, None, False, 'for sale in Kakariko'), 'Kakariko Shop - Right': (None, None, False, 'for sale in Kakariko'), diff --git a/Rom.py b/Rom.py index db416798..467c8522 100644 --- a/Rom.py +++ b/Rom.py @@ -29,7 +29,7 @@ from Text import KingsReturn_texts, Sanctuary_texts, Kakariko_texts, Blacksmiths from Text import LostWoods_texts, WishingWell_texts, DesertPalace_texts, MountainTower_texts, LinksHouse_texts from Text import Lumberjacks_texts, SickKid_texts, FluteBoy_texts, Zora_texts, MagicShop_texts, Sahasrahla_names from Utils import output_path, local_path, int16_as_bytes, int32_as_bytes, snes_to_pc -from Items import ItemFactory +from Items import ItemFactory, prize_item_table from EntranceShuffle import door_addresses, exit_ids, ow_prize_table from OverworldShuffle import default_flute_connections, flute_data from InitialSram import InitialSram @@ -43,7 +43,7 @@ from source.enemizer.Enemizer import write_enemy_shuffle_settings JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = 'ddc8fd3a8c7265b8748c0df4382f8f0e' +RANDOMIZERBASEHASH = 'd0d67e62722fc7c2192c1f3b1cd8284b' class JsonRom(object): @@ -462,32 +462,31 @@ def patch_rom(world, rom, player, team, is_mystery=False): if location.address is None or (type(location.address) is int and location.address >= 0x400000): continue - if not location.crystal: - if location.item is not None: - # Keys in their native dungeon should use the original item code for keys - itemid = handle_native_dungeon(location, itemid) - if world.remote_items[player]: - itemid = list(location_table.keys()).index(location.name) + 1 - assert itemid < 0x100 - rom.write_byte(location.player_address, 0xFF) - elif location.item.player != player: - if location.player_address is not None: - rom.write_byte(location.player_address, location.item.player) - else: - itemid = 0x5A - rom.write_byte(location.address, itemid) - else: + if location.item is not None: + # Keys in their native dungeon should use the original item code for keys + itemid = handle_native_dungeon(location, itemid) + if world.remote_items[player]: + itemid = list(location_table.keys()).index(location.name) + 1 + assert itemid < 0x100 + rom.write_byte(location.player_address, 0xFF) + elif location.item.player != player: + if location.player_address is not None: + rom.write_byte(location.player_address, location.item.player) + else: + itemid = 0x5A + rom.write_byte(location.address, itemid) + for dungeon in [d for d in world.dungeons if d.player == player]: + if dungeon.prize: # crystals - for address, value in zip(location.address, itemid): + for address, value in zip(dungeon_table[dungeon.name].prize, prize_item_table[dungeon.prize.name]): rom.write_byte(address, value) # patch music - music_addresses = dungeon_music_addresses[location.name] if world.mapshuffle[player]: music = random.choice([0x11, 0x16]) else: - music = 0x11 if 'Pendant' in location.item.name else 0x16 - for music_address in music_addresses: + music = 0x11 if 'Pendant' in dungeon.prize.name else 0x16 + for music_address in dungeon_music_addresses[dungeon.name]: rom.write_byte(music_address, music) if world.mapshuffle[player]: @@ -724,7 +723,8 @@ def patch_rom(world, rom, player, team, is_mystery=False): or (l.type == LocationType.Drop and not l.forced_item) or (l.type == LocationType.Normal and not l.forced_item) or (l.type == LocationType.Bonk and not l.forced_item) - or (l.type == LocationType.Shop and world.shopsanity[player]))] + or (l.type == LocationType.Shop and world.shopsanity[player]) + or (l.type == LocationType.Prize and not l.prize))] valid_loc_by_dungeon = valid_dungeon_locations(valid_locations) # fix hc big key problems (map and compass too) @@ -1077,7 +1077,11 @@ def patch_rom(world, rom, player, team, is_mystery=False): 0xFF, 0xFF, 0xFF, 0xFF, # end of table sentinel ]) - # item GFX changes + # item property changes + if world.prizeshuffle[player] != 'none': + # allows prizes to contribute to collection rate + write_int16s(rom, snes_to_pc(0x22C06E), [0x01]*3) # pendants + write_int16s(rom, snes_to_pc(0x22C160), [0x81]*7) # crystals if world.bombbag[player]: rom.write_byte(snes_to_pc(0x22C8A4), 0xE0) # use new bomb bag gfx rom.write_byte(snes_to_pc(0x22BD52), 0x02) @@ -1207,6 +1211,8 @@ def patch_rom(world, rom, player, team, is_mystery=False): rom.write_byte(0x1800A1, 0x01) # enable overworld screen transition draining for water level inside swamp rom.write_byte(0x180174, 0x01 if world.fix_fake_world[player] else 0x00) rom.write_byte(0x18017E, 0x01) # Fairy fountains only trade in bottles + # fix for allowing prize itemgets being able to update the HUD (buying prizes in shops and updating rupees) + rom.write_bytes(snes_to_pc(0x0799FB), [0x80, 0x11, 0xEA]) # Starting equipment if world.pseudoboots[player]: @@ -1262,94 +1268,152 @@ def patch_rom(world, rom, player, team, is_mystery=False): # Bitfield - enable text box to show with free roaming items # - # ---o bmcs + # --po bmcs + # p - enabled for non-prize crystals # o - enabled for outside dungeon items # b - enabled for inside big keys # m - enabled for inside maps # c - enabled for inside compasses # s - enabled for inside small keys - rom.write_byte(0x18016A, 0x10 | ((0x01 if world.keyshuffle[player] == 'wild' else 0x00) + rom.write_byte(0x18016A, 0x10 | ((0x20 if world.prizeshuffle[player] == 'wild' else 0x00) + | (0x01 if world.keyshuffle[player] == 'wild' else 0x00) | (0x02 if world.compassshuffle[player] else 0x00) | (0x04 if world.mapshuffle[player] else 0x00) | (0x08 if world.bigkeyshuffle[player] else 0x00))) # free roaming item text boxes rom.write_byte(0x18003B, 0x01 if world.mapshuffle[player] else 0x00) # maps showing crystals on overworld # compasses showing dungeon count - compass_mode = 0x00 + compass_mode = 0x80 if world.compassshuffle[player] else 0x00 if world.clock_mode != 'none' or world.dungeon_counters[player] == 'off': - compass_mode = 0x00 # Currently must be off if timer is on, because they use same HUD location - rom.write_byte(0x18003C, 0x00) + pass elif world.dungeon_counters[player] == 'on': - compass_mode = 0x02 # always on + compass_mode |= 0x02 # always on elif (world.compassshuffle[player] or world.doorShuffle[player] != 'vanilla' or world.dropshuffle[player] != 'none' or world.dungeon_counters[player] == 'pickup' or world.pottery[player] not in ['none', 'cave']): - compass_mode = 0x01 # show on pickup - if (world.shuffle[player] != 'vanilla' and world.overworld_map[player] != 'default') or world.owMixed[player]: - compass_mode |= 0x80 # turn on locating dungeons - if world.overworld_map[player] == 'compass': - compass_mode |= 0x20 # show icon if compass is collected, 0x00 for maps - if world.compassshuffle[player]: - compass_mode |= 0x40 # dungeon item that enables icon is wild - elif world.overworld_map[player] == 'map' or world.owMixed[player]: - if world.mapshuffle[player]: - compass_mode |= 0x40 # dungeon item that enables icon is wild - - if world.shuffle[player] != 'vanilla' and world.overworld_map[player] != 'default': - x_map_position_generic = [0x3c0, 0xbc0, 0x7c0, 0x1c0, 0x5c0, 0xdc0, 0x7c0, 0xbc0, 0x9c0, 0x3c0] - for idx, x_map in enumerate(x_map_position_generic): - rom.write_bytes(0x53df6+idx*2, int16_as_bytes(x_map)) - rom.write_bytes(0x53e16+idx*2, int16_as_bytes(0xFC0)) - elif world.overworld_map[player] == 'default': - # disable HC/AT/GT icons - if not world.owMixed[player]: - rom.write_bytes(0x53E8A, int16_as_bytes(0xFF00)) # GT - rom.write_bytes(0x53E8C, int16_as_bytes(0xFF00)) # AT - rom.write_bytes(0x53E8E, int16_as_bytes(0xFF00)) # HC - for dungeon, portal_list in dungeon_portals.items(): - ow_map_index = dungeon_table[dungeon].map_index - if world.shuffle[player] != 'vanilla' and world.overworld_map[player] == 'default': - vanilla_entrances = { 'Hyrule Castle': 'Hyrule Castle Entrance (South)', - 'Desert Palace': 'Desert Palace Entrance (North)', - 'Skull Woods': 'Skull Woods Final Section' - } - entrance_name = vanilla_entrances[dungeon] if dungeon in vanilla_entrances else dungeon - entrance = world.get_entrance(entrance_name, player) + compass_mode |= 0x01 # show on pickup + if world.overworld_map[player] == 'map': + compass_mode |= 0x10 # show icon if map is collected + elif world.overworld_map[player] == 'compass': + compass_mode |= 0x20 # show icon if compass is collected + if world.prizeshuffle[player] == 'wild': + compass_mode |= 0x40 # show icon if boss is defeated, hide if collected + rom.write_byte(0x18003C, compass_mode) + + def get_entrance_coords(ent): + if type(ent) is Location: + from OverworldShuffle import OWTileRegions + if ent.name == 'Hobo': + coords = (0xb80, 0xb80) + elif ent.name == 'Master Sword Pedestal': + coords = (0x06d, 0x070) else: - if world.shuffle[player] != 'vanilla': - if len(portal_list) == 1: - portal_idx = 0 - else: - if world.doorShuffle[player] not in ['vanilla', 'basic']: - # the random choice excludes sanctuary - portal_idx = next((i for i, elem in enumerate(portal_list) - if world.get_portal(elem, player).chosen), random.choice([1, 2, 3])) - else: - portal_idx = {'Hyrule Castle': 0, 'Desert Palace': 0, 'Skull Woods': 3, 'Turtle Rock': 3}[dungeon] + owid = OWTileRegions[ent.parent_region.name] + if owid == 0x81: + coords = (0x220, 0xf40) else: - if dungeon in ['Hyrule Castle', 'Agahnims Tower', 'Ganons Tower']: - portal_idx = -1 - elif len(portal_list) == 1: - portal_idx = 0 - else: - portal_idx = {'Desert Palace': 1, 'Skull Woods': 3, 'Turtle Rock': 0}[dungeon] - portal = world.get_portal(portal_list[0 if portal_idx == -1 else portal_idx], player) - entrance = portal.find_portal_entrance() - world_indicator = 0x01 if entrance.parent_region.type == RegionType.DarkWorld else 0x00 - coords = ow_prize_table[entrance.name] - # figure out compass entrances and what world (light/dark) - if world.overworld_map[player] != 'default' or world.owMixed[player]: - rom.write_bytes(0x53E36+ow_map_index*2, int16_as_bytes(coords[0])) - rom.write_bytes(0x53E56+ow_map_index*2, int16_as_bytes(coords[1])) - rom.write_byte(0x53EA6+ow_map_index, world_indicator) + owid = owid % 0x40 + coords = (0x200 * (owid % 0x08) + 0x100, 0x200 * int(owid / 0x08) + 0x100) + if owid in [0x00, 0x03, 0x05, 0x18, 0x1b, 0x1e, 0x30, 0x35]: + coords = (coords[0] + 0x100, coords[1] + 0x100) + else: + coords = ow_prize_table[ent.name] + coords = ((0x8000 if ent.parent_region.type == RegionType.DarkWorld else 0x0000) | coords[0], coords[1]) + return coords + if world.overworld_map[player] == 'default': + # disable HC/AT/GT icons + if not world.owMixed[player]: + write_int16(rom, snes_to_pc(0x0ABF52)+0x1A, 0x0000) # GT + write_int16(rom, snes_to_pc(0x0ABF52)+0x08, 0x0000) # AT + write_int16(rom, snes_to_pc(0x0ABF52)+0x00, 0x0000) # HC + for dungeon, portal_list in dungeon_portals.items(): + dungeon_index = dungeon_table[dungeon].dungeon_index + extra_map_index = dungeon_table[dungeon].extra_map_index + map_index = max(0, dungeon_index - 2) + + # write out dislocated coords + if map_index >= 0x02 and map_index < 0x18 and (world.overworld_map[player] != 'default' or world.prizeshuffle[player] == 'wild'): + owid_map = [0x1E, 0x30, 0xFF, 0x7B, 0x5E, 0x70, 0x40, 0x75, 0x03, 0x58, 0x47] + x_map_position_generic = [0x03c0, 0x0740, 0xff00, 0x03c0, 0x01c0, 0x0bc0, 0x05c0, 0x09c0, 0x0ac0, 0x07c0, 0x0dc0] + y_map_position_generic = [0xff00, 0xff00, 0xff00, 0x0fc0, 0x0fc0, 0x0fc0, 0x0fc0, 0x0fc0, 0xff00, 0x0fc0, 0x0fc0] + world_indicator = 0x0000 + idx = int((map_index-2)/2) + owid = owid_map[idx] + if owid != 0xFF: + if (owid < 0x40) == (world.is_tile_swapped(owid, player)): + world_indicator = 0x8000 + write_int16(rom, snes_to_pc(0x0ABE2E)+(map_index*6)+4, world_indicator | x_map_position_generic[idx]) + write_int16(rom, snes_to_pc(0x0ABE2E)+(map_index*6)+6, y_map_position_generic[idx]) + + # write out icon coord data + if world.prizeshuffle[player] == 'wild' and dungeon_table[dungeon].prize: + dungeon_obj = world.get_dungeon(dungeon, player) + entrance = dungeon_obj.prize.get_map_location() + coords = get_entrance_coords(entrance) + # prize location + write_int16s(rom, snes_to_pc(0x0ABE2E)+(map_index*6)+8, coords) + if world.shuffle[player] == 'vanilla' or world.overworld_map[player] == 'default': + # TODO: I think this is logically the same as some of the vanilla stuff below + vanilla_entrances = { 'Hyrule Castle': 'Hyrule Castle Entrance (South)', + 'Desert Palace': 'Desert Palace Entrance (North)', + 'Skull Woods': 'Skull Woods Final Section' } + entrance_name = vanilla_entrances[dungeon] if dungeon in vanilla_entrances else dungeon + if world.is_atgt_swapped(player): + swap_entrances = { 'Agahnims Tower': 'Ganons Tower', + 'Ganons Tower': 'Agahnims Tower' } + entrance_name = swap_entrances[dungeon] if dungeon in swap_entrances else entrance_name + entrance = world.get_entrance(entrance_name, player) + else: + if len(portal_list) == 1: + portal_idx = 0 + else: + vanilla_portal_idx = {'Hyrule Castle': 0, 'Desert Palace': 0, 'Skull Woods': 3, 'Turtle Rock': 3} + extra_map_offsets = {'Hyrule Castle': 0, 'Desert Palace': 0x12, 'Skull Woods': 0x20, 'Turtle Rock': 0x3E} + portal_idx = vanilla_portal_idx[dungeon] + offset = 0 + if (world.overworld_map[player] != 'default' and world.shuffle[player] not in ['vanilla', 'dungeonssimple'] + and (dungeon != 'Skull Woods' or world.shuffle[player] in ['district', 'insanity'])): + for i, elem in enumerate(portal_list): + if i != portal_idx and (elem != 'Sanctuary' or world.shuffle[player] in ['district', 'insanity']): + portal = world.get_portal(elem, player) + entrance = portal.find_portal_entrance() + coords = get_entrance_coords(entrance) + write_int16s(rom, snes_to_pc(0x8ABECA+extra_map_offsets[dungeon]+offset), coords) + offset += 4 + portal = world.get_portal(portal_list[portal_idx], player) + entrance = portal.find_portal_entrance() + coords = get_entrance_coords(entrance) + + # figure out compass entrances and what world (light/dark) + write_int16s(rom, snes_to_pc(0x0ABE2E)+(map_index*6), coords) + if world.prizeshuffle[player] != 'wild' and dungeon_table[dungeon].prize: + # prize location + write_int16s(rom, snes_to_pc(0x0ABE2E)+(map_index*6)+8, coords) + + # Map reveals + reveal_bytes = { + "Hyrule Castle": 0xC000, + "Eastern Palace": 0x2000, + "Desert Palace": 0x1000, + "Tower of Hera": 0x0020, + "Agahnims Tower": 0x800, + "Palace of Darkness": 0x0200, + "Thieves Town": 0x0010, + "Skull Woods": 0x0080, + "Swamp Palace": 0x0400, + "Ice Palace": 0x0040, + "Misery Mire": 0x0100, + "Turtle Rock": 0x0008, + "Ganons Tower": 0x0004 + } # in crossed doors - flip the compass exists flags if world.doorShuffle[player] not in ['vanilla', 'basic']: + compass_exists = 0x0000 for dungeon, portal_list in dungeon_portals.items(): - ow_map_index = dungeon_table[dungeon].map_index - exists_flag = any(x for x in world.get_dungeon(dungeon, player).dungeon_items if x.type == 'Compass') - rom.write_byte(0x53E96+ow_map_index, 0x1 if exists_flag else 0x0) - - rom.write_byte(0x18003C, compass_mode) + dungeon_index = dungeon_table[dungeon].dungeon_index + if any(x for x in world.get_dungeon(dungeon, player).dungeon_items if x.type == 'Compass'): + compass_exists |= reveal_bytes.get(dungeon, 0x0000) + write_int16(rom, snes_to_pc(0x0ABF6E), compass_exists) # Bitfield - enable free items to show up in menu # @@ -1360,34 +1424,17 @@ def patch_rom(world, rom, player, team, is_mystery=False): # b - Big Key # a - Small Key # - enable_menu_map_check = world.overworld_map[player] != 'default' and world.shuffle[player] != 'vanilla' + enable_menu_map_check = (world.overworld_map[player] != 'default' and world.shuffle[player] != 'vanilla') or world.prizeshuffle[player] == 'wild' rom.write_byte(0x180045, ((0x01 if world.keyshuffle[player] == 'wild' else 0x00) | (0x02 if world.bigkeyshuffle[player] else 0x00) | (0x04 if world.mapshuffle[player] or enable_menu_map_check else 0x00) | (0x08 if world.compassshuffle[player] else 0x00) # free roaming items in menu | (0x10 if world.logic[player] == 'nologic' else 0))) # boss icon - # Map reveals - reveal_bytes = { - "Eastern Palace": 0x2000, - "Desert Palace": 0x1000, - "Tower of Hera": 0x0020, - "Palace of Darkness": 0x0200, - "Thieves Town": 0x0010, - "Skull Woods": 0x0080, - "Swamp Palace": 0x0400, - "Ice Palace": 0x0040, - "Misery Mire'": 0x0100, - "Turtle Rock": 0x0008, - } - def get_reveal_bytes(itemName): - locations = world.find_items(itemName, player) - if len(locations) < 1: - return 0x0000 - location = locations[0] - if location.parent_region and location.parent_region.dungeon: - return reveal_bytes.get(location.parent_region.dungeon.name, 0x0000) + for dungeon in world.dungeons: + if dungeon.player == player and dungeon.prize and dungeon.prize.name == itemName: + return reveal_bytes.get(dungeon.name, 0x0000) return 0x0000 write_int16(rom, 0x18017A, get_reveal_bytes('Green Pendant') if world.mapshuffle[player] else 0x0000) # Sahasrahla reveal @@ -2160,6 +2207,8 @@ def write_strings(rom, world, player, team): items_to_hint.extend(SmallKeys) if world.bigkeyshuffle[player]: items_to_hint.extend(BigKeys) + if world.prizeshuffle[player] == 'wild': + items_to_hint.extend(Prizes) random.shuffle(items_to_hint) hint_count = 5 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'district', 'swapped'] else 8 hint_count += 2 if world.doorShuffle[player] not in ['vanilla', 'basic'] else 0 @@ -2273,10 +2322,14 @@ def write_strings(rom, world, player, team): crystal5 = world.find_items('Crystal 5', player)[0] crystal6 = world.find_items('Crystal 6', player)[0] - tt['bomb_shop'] = 'Big Bomb?\nMy supply is blocked until you clear %s and %s.' % (crystal5.hint_text, crystal6.hint_text) - greenpendant = world.find_items('Green Pendant', player)[0] - tt['sahasrahla_bring_courage'] = 'I lost my family heirloom in %s' % greenpendant.hint_text + if world.prizeshuffle[player] == 'none': + (crystal5, crystal6, greenpendant) = tuple([x.parent_region.dungeon.name for x in [crystal5, crystal6, greenpendant]]) + tt['bomb_shop'] = 'Big Bomb?\nMy supply is blocked until you clear %s and %s.' % (crystal5, crystal6) + tt['sahasrahla_bring_courage'] = 'I lost my family heirloom in %s' % greenpendant + else: + tt['bomb_shop'] = 'Big Bomb?\nThe crystals can be found %s and %s.' % (crystal5.hint_text, crystal6.hint_text) + tt['sahasrahla_bring_courage'] = 'My family heirloom can be found %s' % greenpendant.hint_text tt['sign_ganons_tower'] = ('You need %d crystal to enter.' if world.crystals_needed_for_gt[player] == 1 else 'You need %d crystals to enter.') % world.crystals_needed_for_gt[player] @@ -2966,6 +3019,18 @@ BigKeys = ['Big Key (Eastern Palace)', 'Big Key (Ganons Tower)' ] +Prizes = ['Green Pendant', + 'Blue Pendant', + 'Red Pendant', + 'Crystal 1', + 'Crystal 2', + 'Crystal 3', + 'Crystal 4', + 'Crystal 5', + 'Crystal 6', + 'Crystal 7' + ] + hash_alphabet = [ "Bow", "Boomerang", "Hookshot", "Bomb", "Mushroom", "Powder", "Rod", "Pendant", "Bombos", "Ether", "Quake", "Lamp", "Hammer", "Shovel", "Ocarina", "Bug Net", "Book", "Bottle", "Potion", "Cane", "Cape", "Mirror", "Boots", diff --git a/data/base2current.bps b/data/base2current.bps index f62d66ee9e21ea0082797493c7b19f7d6ccae920..789a90f922625ffead87cd48c9d766c0aa2843b7 100644 GIT binary patch delta 19452 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 delta 18286 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 ajs5`t0GCuYnV0QPf`4n1xFPL!!%(4 diff --git a/resources/app/cli/args.json b/resources/app/cli/args.json index fc102f1b..eef2cccc 100644 --- a/resources/app/cli/args.json +++ b/resources/app/cli/args.json @@ -387,6 +387,13 @@ "action": "store_true", "type": "bool" }, + "prizeshuffle": { + "choices": [ + "none", + "dungeon", + "wild" + ] + }, "keysanity": { "action": "store_true", "type": "bool", diff --git a/resources/app/cli/lang/en.json b/resources/app/cli/lang/en.json index e98c9228..80f479e8 100644 --- a/resources/app/cli/lang/en.json +++ b/resources/app/cli/lang/en.json @@ -350,6 +350,7 @@ "compassshuffle": [ "Compasses are no longer restricted to their dungeons, but can be anywhere. (default: %(default)s)" ], "keyshuffle": [ "Small Keys are no longer restricted to their dungeons, but can be anywhere. (default: %(default)s)" ], "bigkeyshuffle": [ "Big Keys are no longer restricted to their dungeons, but can be anywhere. (default: %(default)s)" ], + "prizeshuffle": [ "Prizes are no longer restricted to the bosses, but can be anywhere. (default: %(default)s)" ], "shopsanity": ["Shop contents are shuffle in the main item pool and other items can take their place. (default: %(default)s)"], "dropshuffle": [ "Controls how enemies drop items (default: %(default)s)", "None: Enemies drops prize packs or keys as normal", diff --git a/resources/app/gui/lang/en.json b/resources/app/gui/lang/en.json index 27bafd29..a9541a0b 100644 --- a/resources/app/gui/lang/en.json +++ b/resources/app/gui/lang/en.json @@ -59,6 +59,10 @@ "randomizer.dungeon.smallkeyshuffle.wild": "Randomized", "randomizer.dungeon.smallkeyshuffle.universal": "Universal", "randomizer.dungeon.bigkeyshuffle": "Big Keys", + "randomizer.dungeon.prizeshuffle": "Prizes", + "randomizer.dungeon.prizeshuffle.none": "On Boss", + "randomizer.dungeon.prizeshuffle.dungeon": "In Dungeon", + "randomizer.dungeon.prizeshuffle.wild": "Randomized", "randomizer.dungeon.decoupledoors": "Decouple Doors", "randomizer.dungeon.door_self_loops": "Allow Self-Looping Spiral Stairs", diff --git a/resources/app/gui/randomize/dungeon/widgets.json b/resources/app/gui/randomize/dungeon/widgets.json index b02dfa6f..268fee35 100644 --- a/resources/app/gui/randomize/dungeon/widgets.json +++ b/resources/app/gui/randomize/dungeon/widgets.json @@ -1,5 +1,17 @@ { "widgets": { + "prizeshuffle": { + "type": "selectbox", + "default": "default", + "options": [ + "none", + "dungeon", + "wild" + ], + "config": { + "padx": [20,0] + } + }, "key_logic_algorithm": { "type": "selectbox", "default": "default", diff --git a/source/classes/CustomSettings.py b/source/classes/CustomSettings.py index c67d760f..33fd8f87 100644 --- a/source/classes/CustomSettings.py +++ b/source/classes/CustomSettings.py @@ -138,6 +138,7 @@ class CustomSettings(object): args.experimental[p] = get_setting(settings['experimental'], args.experimental[p]) args.collection_rate[p] = get_setting(settings['collection_rate'], args.collection_rate[p]) args.openpyramid[p] = get_setting(settings['openpyramid'], args.openpyramid[p]) + args.prizeshuffle[p] = get_setting(settings['prizeshuffle'], args.prizeshuffle[p]) args.bigkeyshuffle[p] = get_setting(settings['bigkeyshuffle'], args.bigkeyshuffle[p]) args.keyshuffle[p] = get_setting(settings['keyshuffle'], args.keyshuffle[p]) args.mapshuffle[p] = get_setting(settings['mapshuffle'], args.mapshuffle[p]) @@ -320,6 +321,7 @@ class CustomSettings(object): settings_dict[p]['experimental'] = world.experimental[p] settings_dict[p]['collection_rate'] = world.collection_rate[p] settings_dict[p]['openpyramid'] = world.open_pyramid[p] + settings_dict[p]['prizeshuffle'] = world.prizeshuffle[p] settings_dict[p]['bigkeyshuffle'] = world.bigkeyshuffle[p] settings_dict[p]['keyshuffle'] = world.keyshuffle[p] settings_dict[p]['mapshuffle'] = world.mapshuffle[p] diff --git a/source/classes/constants.py b/source/classes/constants.py index 41d54fd8..4d170746 100644 --- a/source/classes/constants.py +++ b/source/classes/constants.py @@ -111,6 +111,7 @@ SETTINGSTOPROCESS = { "mapshuffle": "mapshuffle", "compassshuffle": "compassshuffle", "bigkeyshuffle": "bigkeyshuffle", + "prizeshuffle": "prizeshuffle", "key_logic_algorithm": "key_logic_algorithm", "dungeondoorshuffle": "door_shuffle", "dungeonintensity": "intensity", diff --git a/source/dungeon/DungeonStitcher.py b/source/dungeon/DungeonStitcher.py index 84139157..7f0efec5 100644 --- a/source/dungeon/DungeonStitcher.py +++ b/source/dungeon/DungeonStitcher.py @@ -5,7 +5,7 @@ import time from BaseClasses import CrystalBarrier, DoorType, Hook, RegionType, Sector from BaseClasses import hook_from_door, flooded_keys -from Regions import dungeon_events, flooded_keys_reverse +from Regions import location_events, flooded_keys_reverse def pre_validate(builder, entrance_region_names, split_dungeon, world, player): @@ -556,7 +556,7 @@ class ExplorationState(object): if key_checks and location not in self.found_locations: if location.forced_item and 'Small Key' in location.item.name: self.key_locations += 1 - if location.name not in dungeon_events and '- Prize' not in location.name and location.name not in ['Agahnim 1', 'Agahnim 2']: + if location.name not in location_events and not ('- Prize' in location.name and location.prize) and location.name not in ['Agahnim 1', 'Agahnim 2']: self.ttl_locations += 1 if location not in self.found_locations: self.found_locations.append(location) @@ -568,13 +568,13 @@ class ExplorationState(object): else: self.bk_found.add(location) self.re_add_big_key_doors() - if location.name in dungeon_events and location.name not in self.events: + if location.name in location_events and location.name not in self.events: if self.flooded_key_check(location): self.perform_event(location.name, key_region) if location.name in flooded_keys_reverse.keys() and self.location_found( flooded_keys_reverse[location.name]): self.perform_event(flooded_keys_reverse[location.name], key_region) - if '- Prize' in location.name: + if location.prize: self.prize_received = True def flooded_key_check(self, location): @@ -837,7 +837,7 @@ def count_locations_exclude_big_chest(locations, world, player): def prize_or_event(loc): - return loc.name in dungeon_events or '- Prize' in loc.name or loc.name in ['Agahnim 1', 'Agahnim 2'] + return loc.name in location_events or loc.prize def reserved_location(loc, world, player): diff --git a/source/item/FillUtil.py b/source/item/FillUtil.py index 1e8a8092..f49e7e0b 100644 --- a/source/item/FillUtil.py +++ b/source/item/FillUtil.py @@ -59,6 +59,8 @@ def create_item_pool_config(world): if info.prize: d_name = "Thieves' Town" if dungeon.startswith('Thieves') else dungeon config.reserved_locations[player].add(f'{d_name} - Boss') + if world.prizeshuffle[player] != 'none': + config.reserved_locations[player].add(f'{d_name} - Prize') for dungeon in world.dungeons: if world.restrict_boss_items[dungeon.player] != 'none': for item in dungeon.all_items: @@ -118,6 +120,9 @@ def create_item_pool_config(world): LocationGroup('bkgt').locs(mode_grouping['GT Trash'])] for loc_name in mode_grouping['Big Chests'] + mode_grouping['Heart Containers']: config.reserved_locations[player].add(loc_name) + if world.prizeshuffle[player] != 'none': + for loc_name in mode_grouping['Prizes']: + config.reserved_locations[player].add(loc_name) elif world.algorithm == 'major_only': config.location_groups = [ LocationGroup('MajorItems'), @@ -127,6 +132,8 @@ def create_item_pool_config(world): init_set = mode_grouping['Overworld Major'] + mode_grouping['Big Chests'] + mode_grouping['Heart Containers'] for player in range(1, world.players + 1): groups = LocationGroup('Major').locs(init_set) + if world.prizeshuffle[player] != 'none': + groups.locations.extend(mode_grouping['Prizes']) if world.bigkeyshuffle[player]: groups.locations.extend(mode_grouping['Big Keys']) if world.dropshuffle[player] != 'none': @@ -251,21 +258,33 @@ def location_prefilled(location, world, player): def previously_reserved(location, world, player): - if '- Boss' in location.name: + if '- Boss' in location.name or '- Prize' in location.name: if world.restrict_boss_items[player] == 'mapcompass' and (not world.compassshuffle[player] or not world.mapshuffle[player]): return True if world.restrict_boss_items[player] == 'dungeon' and (not world.compassshuffle[player] or not world.mapshuffle[player] or not world.bigkeyshuffle[player] - or world.keyshuffle[player] == 'none'): + or world.keyshuffle[player] == 'none' + or world.prizeshuffle[player] in ['none', 'dungeon']): return True return False def massage_item_pool(world): player_pool = defaultdict(list) + dungeon_pool = defaultdict(list) + for dungeon in world.dungeons: + if dungeon_table[dungeon.name].prize: + dungeon_pool[dungeon.player].append(dungeon) + for player in dungeon_pool: + dungeons = list(dungeon_pool[player]) + random.shuffle(dungeons) + dungeon_pool[player] = dungeons for item in world.itempool: + if item.prize: + dungeon = dungeon_pool[item.player].pop() + dungeon.prize = item player_pool[item.player].append(item) for dungeon in world.dungeons: for item in dungeon.all_items: @@ -273,7 +292,7 @@ def massage_item_pool(world): player_pool[item.player].append(item) player_locations = defaultdict(list) for player in player_pool: - player_locations[player] = [x for x in world.get_unfilled_locations(player) if '- Prize' not in x.name] + player_locations[player] = [x for x in world.get_unfilled_locations(player) if not x.prize] discrepancy = len(player_pool[player]) - len(player_locations[player]) if discrepancy: trash_options = [x for x in player_pool[player] if x.name in trash_items] @@ -342,6 +361,8 @@ def determine_major_items(world, player): major_item_set = set(major_items) if world.progressive == 'off': pass # now what? + if world.prizeshuffle[player] not in ['none', 'dungeon']: + major_item_set.update({x for x, y in item_table.items() if y[2] == 'Prize'}) if world.bigkeyshuffle[player]: major_item_set.update({x for x, y in item_table.items() if y[2] == 'BigKey'}) if world.keyshuffle[player] != 'none': @@ -687,6 +708,11 @@ mode_grouping = { 'Graveyard Cave', 'Kakariko Well - Top', "Blind's Hideout - Top", 'Bonk Rock Cave', "Aginah's Cave", 'Chest Game', 'Digging Game', 'Mire Shed - Left', 'Mimic Cave' ], + 'Prizes': [ + 'Eastern Palace - Prize', 'Desert Palace - Prize', 'Tower of Hera - Prize', + 'Palace of Darkness - Prize', 'Swamp Palace - Prize', 'Skull Woods - Prize', + "Thieves' Town - Prize", 'Ice Palace - Prize', 'Misery Mire - Prize', 'Turtle Rock - Prize', + ], 'Big Keys': [ 'Eastern Palace - Big Key Chest', 'Ganons Tower - Big Key Chest', 'Desert Palace - Big Key Chest', 'Tower of Hera - Big Key Chest', 'Palace of Darkness - Big Key Chest', diff --git a/source/tools/MysteryUtils.py b/source/tools/MysteryUtils.py index c37ea0a3..77f6dd2e 100644 --- a/source/tools/MysteryUtils.py +++ b/source/tools/MysteryUtils.py @@ -106,6 +106,7 @@ def roll_settings(weights): if 'u' in dungeon_items: ret.keyshuffle = 'universal' ret.bigkeyshuffle = get_choice_bool('bigkey_shuffle') if 'bigkey_shuffle' in weights else 'b' in dungeon_items + ret.prizeshuffle = get_choice('prize_shuffle') ret.accessibility = get_choice('accessibility') ret.restrict_boss_items = get_choice('restrict_boss_items') From 75187ec6089fd21a6cfc615710b5838d7a7dddac Mon Sep 17 00:00:00 2001 From: codemann8 Date: Thu, 23 May 2024 18:46:11 -0500 Subject: [PATCH 32/41] Minor TF text adjustments --- Text.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Text.py b/Text.py index 996f4483..e020fff7 100644 --- a/Text.py +++ b/Text.py @@ -96,9 +96,9 @@ Triforce_texts = [ "\n G G", " All your base\n are belong\n to us.", " You have ended\n the domination\n of Dr. Wily", - " Thanks for\n playing!!!", + " Thanks for\n playing!!!", "\n You Win!", - " Thank you!\n Your quest\n is over.", + " Thank you!\n Your quest\n is over.", " A winner\n is you!", "\n WINNER!!", "\n I'm sorry\n\nbut our princess is\n in another castle", From 143de53c2971854b7bd9415538080510c337fb0a Mon Sep 17 00:00:00 2001 From: codemann8 Date: Thu, 23 May 2024 18:48:33 -0500 Subject: [PATCH 33/41] Add print_template_yaml to Mystery --- Mystery.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Mystery.py b/Mystery.py index cec045e9..ca79541d 100644 --- a/Mystery.py +++ b/Mystery.py @@ -30,6 +30,7 @@ def main(): parser.add_argument('--teams', default=1, type=lambda value: max(int(value), 1)) parser.add_argument('--spoiler', default='none', choices=['none', 'settings', 'semi', 'full', 'debug']) parser.add_argument('--no_race', action='store_true') + parser.add_argument('--print_template_yaml', 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') @@ -67,6 +68,7 @@ def main(): erargs.spoiler = args.spoiler erargs.suppress_rom = args.suppress_rom erargs.suppress_meta = args.suppress_meta + erargs.print_template_yaml = args.print_template_yaml erargs.bps = args.bps erargs.race = not args.no_race erargs.outputname = seedname From edd4e8034db2d870debb10dcfe73441bf2f196b2 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Thu, 23 May 2024 18:50:35 -0500 Subject: [PATCH 34/41] Some cleanup --- Regions.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/Regions.py b/Regions.py index 4b835c4a..16f1a427 100644 --- a/Regions.py +++ b/Regions.py @@ -717,10 +717,8 @@ def create_dungeon_regions(world, player): create_dungeon_region(player, 'Thieves Compass Room', 'Thieves\' Town', ['Thieves\' Town - Compass Chest'], ['Thieves Compass Room NW Edge', 'Thieves Compass Room N Edge', 'Thieves Compass Room WS Edge', 'Thieves Compass Room W']), create_dungeon_region(player, 'Thieves Big Chest Nook', 'Thieves\' Town', ['Thieves\' Town - Big Key Chest'], ['Thieves Big Chest Nook ES Edge']), create_dungeon_region(player, 'Thieves Hallway', 'Thieves\' Town', ['Thieves\' Town - Hallway Pot Key'], ['Thieves Hallway SE', 'Thieves Hallway NE', 'Thieves Hallway WN', 'Thieves Hallway WS']), - #create_dungeon_region(player, 'Thieves Boss', 'Thieves\' Town', ['Thieves\' Town - Boss', 'Thieves\' Town - Prize'], ['Revealing Light', 'Thieves Boss SE']), create_dungeon_region(player, 'Thieves Boss', 'Thieves\' Town', ['Revealing Light'], ['Thieves Boss SE', 'Thieves Town Boss']), create_dungeon_region(player, 'Thieves Boss Spoils', 'Thieves\' Town', ['Thieves\' Town - Boss', 'Thieves\' Town - Prize', 'Thieves\' Town - Boss Kill']), - #create_dungeon_region(player, 'Thieves Revealing Light', 'Thieves\' Town', ['Revealing Light'], ['Thieves Boss Room']), create_dungeon_region(player, 'Thieves Pot Alcove Mid', 'Thieves\' Town', None, ['Thieves Pot Alcove Mid ES', 'Thieves Pot Alcove Mid WS']), create_dungeon_region(player, 'Thieves Pot Alcove Bottom', 'Thieves\' Town', None, ['Thieves Pot Alcove Bottom SW']), create_dungeon_region(player, 'Thieves Pot Alcove Top', 'Thieves\' Town', None, ['Thieves Pot Alcove Top NW']), From fa0cf87fdce9701730e9f028d98ae1d8bf887340 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Thu, 23 May 2024 18:51:56 -0500 Subject: [PATCH 35/41] SFX Instrument ban --- source/classes/SFX.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/classes/SFX.py b/source/classes/SFX.py index 1cc85f16..b98a76f8 100644 --- a/source/classes/SFX.py +++ b/source/classes/SFX.py @@ -473,8 +473,8 @@ sfx_instrument_changes = [ SFXInstrumentChange(0x02, 0x18, 0x10, [0x1A9707], ban=[0x00, 0x01, 0x06, 0x0C, 0x18]), SFXInstrumentChange(0x02, 0x19, 0x10, [0x1A971F], ban=[0x00, 0x01, 0x06, 0x0C, 0x18]), SFXInstrumentChange(0x02, 0x1A, 0x01, [0x1A96C7], type=Am, ban=[0x00, 0x06, 0x13], inc=[0x08, 0x0F, 0x10, 0x12, 0x15, 0x17]), - SFXInstrumentChange(0x02, 0x1B, 0x11, [0x1A96B8], type=Am, ban=[0x00, 0x06, 0x13], inc=[0x08, 0x0F, 0x10, 0x12, 0x15, 0x17]), - SFXInstrumentChange(0x02, 0x1C, 0x11, [0x1A96B2], type=Am, ban=[0x00, 0x06, 0x13], inc=[0x08, 0x0F, 0x10, 0x12, 0x15, 0x17]), + SFXInstrumentChange(0x02, 0x1B, 0x11, [0x1A96B8], type=Am, ban=[0x00, 0x01, 0x06, 0x13], inc=[0x08, 0x0F, 0x10, 0x12, 0x15, 0x17]), + SFXInstrumentChange(0x02, 0x1C, 0x11, [0x1A96B2], type=Am, ban=[0x00, 0x01, 0x06, 0x13], inc=[0x08, 0x0F, 0x10, 0x12, 0x15, 0x17]), SFXInstrumentChange(0x02, 0x1D, 0x16, [0x1A966C], ban=[0x09, 0x10, 0x11, 0x13, 0x15, 0x18]), SFXInstrumentChange(0x02, 0x1E, 0x01, [0x1A9928], type=Lg, ban=[0x06]), SFXInstrumentChange(0x02, 0x1F, 0x02, [0x1A969A], type=Me|Be|Hd, ban=[0x09]), From b55c3700c09baebf1a9cf96939241cce10b96466 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Thu, 23 May 2024 19:29:39 -0500 Subject: [PATCH 36/41] Owr actions update (#19) * Go for Broke * Let it fire * Add PipLine * Create the dir if it doesn't exist * Install Setuptools * Track Test Action's files * Fix Calling Job * Track Build Action files * Install Distutils, rename filenames * Fix Fail conditions * Make Build scripts smarter * Add file * Concat DLLs lists * Try to fail if Error DLLs * Try to make the fail smarter * Moar verbosity * Print the stuff first * Print outputs objects * See if this skips failure * Use py instead * Print error list * Don't ValueError * Try checking a different way * Try something else * Bleh, spell filename correctly * Update excluded_dlls.json * Ugh, gotta compare old to new somehow * Compare to old list * Condense build script * Moar verbosity * Update the global version * Update Excluded DLLs list * Actually use the bad DLLs list * Make a version number * Fix version number building * Fix version number building again * Fix Diagnostics * Try REST API stuff * Try REST API again * Moar REST * await * Get SHA * Try it all together * Del test workflow * Add Perms * Use a Token * Try this Token * Try different Token * Try different Token * Create App Version earlier * See this error again * Don't fail if App Version not made yet * Use New Secret * Print whole response * Documentation for Tagger * Update CI Instructions * Update CI * List References * Find latest tag Fix App Version getter * Fix commas * Check returned data * Update Build Script * Fix substring * Fix Git tag * Fix tag again * Visual indicators * Use encoding * Remove an indicator * Update CI * Update Project Name * PyInstaller Spec Template file * Update Build Script * Fix Tagger * Update CI * Download AppVersion during build * Test job can fail * Upload Logs instead of printing them * Change from Reusable Workflow to Action * Change ref to token * Compare to string * Use PAT * Use String literal * Remove Reusable Workflow * Update CI Scripts * Go for Broke * Let it fire * Add PipLine * Create the dir if it doesn't exist * Install Setuptools * Track Test Action's files * Fix Calling Job * Track Build Action files * Install Distutils, rename filenames * Fix Fail conditions * Make Build scripts smarter * Add file * Concat DLLs lists * Try to fail if Error DLLs * Try to make the fail smarter * Moar verbosity * Print the stuff first * Print outputs objects * See if this skips failure * Use py instead * Print error list * Don't ValueError * Try checking a different way * Try something else * Bleh, spell filename correctly * Update excluded_dlls.json * Ugh, gotta compare old to new somehow * Compare to old list * Condense build script * Moar verbosity * Update the global version * Update Excluded DLLs list * Actually use the bad DLLs list * Make a version number * Fix version number building * Fix version number building again * Fix Diagnostics * Try REST API stuff * Try REST API again * Moar REST * await * Get SHA * Try it all together * Del test workflow * Add Perms * Use a Token * Try this Token * Try different Token * Try different Token * Create App Version earlier * See this error again * Don't fail if App Version not made yet * Use New Secret * Print whole response * Documentation for Tagger * Update CI Instructions * Update CI * List References * Find latest tag Fix App Version getter * Fix commas * Check returned data * Update Build Script * Fix substring * Fix Git tag * Fix tag again * Visual indicators * Use encoding * Remove an indicator * Update CI * Update Project Name * PyInstaller Spec Template file * Update Build Script * Fix Tagger * Update CI * Download AppVersion during build * Test job can fail * Upload Logs instead of printing them * Change from Reusable Workflow to Action * Change ref to token * Compare to string * Use PAT * Use String literal * Remove Reusable Workflow * Update CI Scripts --------- Co-authored-by: Minnie A. Trethewey (Mike) --- .github/actions/appversion-prepare/action.yml | 51 ++ .github/actions/build/action.yml | 88 ++++ .github/actions/get-parent-dir/action.yml | 41 ++ .github/actions/install/action.yml | 49 ++ .github/actions/release-prepare/action.yml | 77 +++ .github/actions/tag-repo/action.yml | 76 +++ .github/actions/test/action.yml | 97 ++++ .github/workflows/ci.yml | 316 ------------- .github/workflows/release-complete.yml | 47 ++ .github/workflows/release-create.yml | 418 +++++++++++++++++ .gitignore | 7 +- DungeonRandomizer.py | 2 +- Text.py | 14 +- mystery_testsuite.yml | 4 +- resources/app/meta/manifests/app_version.txt | 0 resources/app/meta/manifests/binaries.json | 7 + .../app/meta/manifests/excluded_dlls.json | 34 ++ .../app/meta/manifests/pip_requirements.txt | 7 +- resources/ci/common/common.py | 51 +- resources/ci/common/get_get_pip.py | 6 +- resources/ci/common/get_pipline.py | 440 ++++++++++++++++++ resources/ci/common/get_upx.py | 5 +- resources/ci/common/list_actions.py | 168 +++++++ resources/ci/common/prepare_appversion.py | 12 +- resources/ci/common/prepare_binary.py | 48 +- resources/ci/common/prepare_release.py | 9 +- source/DungeonRandomizer.spec | 68 --- source/Gui.spec | 69 --- source/Template.spec | 98 ++++ source/classes/appversion.py | 17 + source/classes/diags.py | 27 +- source/gui/randomize/generation.py | 2 +- source/meta/build-dr.py | 27 -- source/meta/build-gui.py | 27 -- source/meta/build.py | 155 ++++++ source/meta/check_errordlls.py | 10 + source/meta/run_diags.py | 10 + test/MysteryTestSuite.py | 46 +- test/NewTestSuite.py | 34 +- 39 files changed, 2076 insertions(+), 588 deletions(-) create mode 100644 .github/actions/appversion-prepare/action.yml create mode 100644 .github/actions/build/action.yml create mode 100644 .github/actions/get-parent-dir/action.yml create mode 100644 .github/actions/install/action.yml create mode 100644 .github/actions/release-prepare/action.yml create mode 100644 .github/actions/tag-repo/action.yml create mode 100644 .github/actions/test/action.yml delete mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/release-complete.yml create mode 100644 .github/workflows/release-create.yml create mode 100644 resources/app/meta/manifests/app_version.txt create mode 100644 resources/app/meta/manifests/binaries.json create mode 100644 resources/app/meta/manifests/excluded_dlls.json create mode 100644 resources/ci/common/get_pipline.py create mode 100644 resources/ci/common/list_actions.py delete mode 100644 source/DungeonRandomizer.spec delete mode 100644 source/Gui.spec create mode 100644 source/Template.spec create mode 100644 source/classes/appversion.py delete mode 100644 source/meta/build-dr.py delete mode 100644 source/meta/build-gui.py create mode 100644 source/meta/build.py create mode 100644 source/meta/check_errordlls.py create mode 100644 source/meta/run_diags.py diff --git a/.github/actions/appversion-prepare/action.yml b/.github/actions/appversion-prepare/action.yml new file mode 100644 index 00000000..a8abdfde --- /dev/null +++ b/.github/actions/appversion-prepare/action.yml @@ -0,0 +1,51 @@ +name: Prepare AppVersion +description: Prepare AppVersion document for later use + +runs: + using: "composite" + steps: + # checkout commit + - name: Checkout commit + shell: bash + run: | + echo "Checkout commit" + - name: Checkout commit + uses: actions/checkout@v4.1.4 + + # Set Run Number + - name: Set Run Number + shell: bash + run: | + echo "Set Run Number" + - name: Set Run Number + id: set_run_number + shell: bash + run: | + GITHUB_RUN_NUMBER="${{ github.run_number }}a${{ github.run_attempt }}" + echo "github_run_number=$GITHUB_RUN_NUMBER" >> $GITHUB_OUTPUT + + # Prepare AppVersion + #TODO: source/classes/appversion.py writes the tag format + - name: 💬Prepare AppVersion + shell: bash + run: | + echo "💬Prepare AppVersion" + - name: Prepare AppVersion + shell: bash + env: + OS_NAME: ${{ inputs.os-name }} + GITHUB_RUN_NUMBER: ${{ steps.set_run_number.outputs.github_run_number }} + run: | + python -m source.classes.appversion + python ./resources/ci/common/prepare_appversion.py + + # upload appversion artifact for later step + - name: 🔼Upload AppVersion Artifact + shell: bash + run: | + echo "🔼Upload AppVersion Artifact" + - name: 🔼Upload AppVersion Artifact + uses: actions/upload-artifact@v4.3.3 + with: + name: appversion + path: ./resources/app/meta/manifests/app_version.txt diff --git a/.github/actions/build/action.yml b/.github/actions/build/action.yml new file mode 100644 index 00000000..728e0c12 --- /dev/null +++ b/.github/actions/build/action.yml @@ -0,0 +1,88 @@ +name: Build +description: Build app +inputs: + calling-job: + required: true + description: Job that's calling this one + os-name: + required: true + description: OS to run on + python-version: + required: true + description: Python version to install + +runs: + using: "composite" + steps: + # checkout commit + - name: Checkout commit + shell: bash + run: | + echo "Checkout commit" + - name: Checkout commit + uses: actions/checkout@v4.1.4 + + # get parent dir + - name: Get Parent Directory + id: parentDir + uses: ./.github/actions/get-parent-dir + + # try to get UPX + - name: Get UPX + shell: bash + run: | + echo "Get UPX" + - name: Get UPX + shell: bash + env: + OS_NAME: ${{ inputs.os-name }} + UPX_VERSION: "4.2.3" + run: | + python ./resources/ci/common/get_upx.py + + # run build.py + - name: 💬Build Binaries + shell: bash + run: | + echo "💬Build Binaries" + - name: Build Binaries + shell: bash + run: | + pip install pyinstaller + python -m source.meta.build + + # upload problem children + # - name: 🔼Upload Problem Children Artifact + # shell: bash + # run: | + # echo "🔼Upload Problem Children Artifact" + # - name: 🔼Upload Problem Children Artifact + # uses: actions/upload-artifact@v4.3.3 + # with: + # name: problemchildren-${{ inputs.os-name }}-py${{ inputs.python-version }} + # path: ./resources/app/meta/manifests/excluded_dlls.json + # if-no-files-found: ignore # 'warn' or 'ignore' are also available, defaults to `warn` + + # prepare binary artifact for later step + - name: 💬Prepare Binary Artifact + shell: bash + run: | + echo "💬Prepare Binary Artifact" + - name: Prepare Binary Artifact + shell: bash + env: + OS_NAME: ${{ inputs.os-name }} + run: | + python ./resources/ci/common/prepare_binary.py + + # upload binary artifact for later step + - name: 🔼Upload Binary Artifact + shell: bash + run: | + echo "🔼Upload Binary Artifact" + - name: 🔼Upload Binary Artifact + uses: actions/upload-artifact@v4.3.3 + with: + name: binary-${{ inputs.os-name }}-py${{ inputs.python-version }} + path: ${{ steps.parentDir.outputs.parentDir }}/artifact + if-no-files-found: error # 'warn' or 'ignore' are also available, defaults to `warn` diff --git a/.github/actions/get-parent-dir/action.yml b/.github/actions/get-parent-dir/action.yml new file mode 100644 index 00000000..0194f3f1 --- /dev/null +++ b/.github/actions/get-parent-dir/action.yml @@ -0,0 +1,41 @@ +name: 📁Get Parent Directory +description: Get Parent Directory + +outputs: + parentDirNotWin: + description: "Parent Directory (!Windows)" + value: ${{ steps.parentDirNotWin.outputs.value }} + parentDir: + description: "Parent Directory (Windows)" + value: ${{ steps.parentDir.outputs.value }} + +######### +# actions +######### +# mad9000/actions-find-and-replace-string@5 + +runs: + using: "composite" + steps: + # get parent directory + - name: Get Repo Name + uses: mad9000/actions-find-and-replace-string@5 + id: repoName + with: + source: ${{ github.repository }} + find: "${{ github.repository_owner }}/" + replace: "" + - name: 📁Get Parent Directory Path (!Windows) + uses: mad9000/actions-find-and-replace-string@5 + id: parentDirNotWin + with: + source: ${{ github.workspace }} + find: "${{ steps.repoName.outputs.value }}/${{ steps.repoName.outputs.value }}" + replace: ${{ steps.repoName.outputs.value }} + - name: 📁Get Parent Directory Path (Windows) + uses: mad9000/actions-find-and-replace-string@5 + id: parentDir + with: + source: ${{ steps.parentDirNotWin.outputs.value }} + find: '${{ steps.repoName.outputs.value }}\${{ steps.repoName.outputs.value }}' + replace: ${{ steps.repoName.outputs.value }} diff --git a/.github/actions/install/action.yml b/.github/actions/install/action.yml new file mode 100644 index 00000000..7a062c2d --- /dev/null +++ b/.github/actions/install/action.yml @@ -0,0 +1,49 @@ +name: 💿Install +description: Install app +inputs: + calling-job: + required: true + description: Job that's calling this one + os-name: + required: true + description: OS to run on + python-version: + required: true + description: Python version to install + +######### +# actions +######### +# actions/checkout@v4.1.4 +# actions/setup-python@v5.1.0 +# actions/upload-artifact@v4.3.3 + +runs: + using: "composite" + steps: + # install python + - name: 💿Install Python + uses: actions/setup-python@v5.1.0 + with: + python-version: ${{ inputs.python-version }} + # install modules via pip + - name: 💿Install Modules + shell: bash + env: + OS_NAME: ${{ inputs.os-name }} + run: | + echo "Install Modules" + python ./resources/ci/common/get_pipline.py + # print pipline + - name: PipLine + shell: bash + run: | + echo "PipLine" + cat ./resources/user/meta/manifests/pipline.txt + # upload pipline + - name: 🔼Upload PipLine + uses: actions/upload-artifact@v4.3.3 + with: + name: pipline-${{ inputs.calling-job }}-${{ inputs.os-name }}-py${{ inputs.python-version }} + path: ./resources/user/meta/manifests + if: contains(inputs.calling-job, 'test') diff --git a/.github/actions/release-prepare/action.yml b/.github/actions/release-prepare/action.yml new file mode 100644 index 00000000..05bf9a65 --- /dev/null +++ b/.github/actions/release-prepare/action.yml @@ -0,0 +1,77 @@ +name: 📀->📦Prepare Release +description: Prepare Release for Deployment +inputs: + os-name: + required: true + description: OS to run on + python-version: + required: true + description: Python version to install + +######### +# actions +######### +# Artheau/SpriteSomething/get-parent-dir +# actions/checkout@v4.1.4 +# actions/download-artifact@v4.1.7 + +runs: + using: "composite" + steps: + # checkout commit + - name: ✔️Checkout commit + shell: bash + run: | + echo "✔️Checkout commit" + - name: ✔️Checkout commit + uses: actions/checkout@v4.1.4 + + # get parent dir + - name: 📁Get Parent Directory + shell: bash + run: | + echo "📁Get Parent Directory" + - name: 📁Get Parent Directory + id: parentDir + uses: ./.github/actions/get-parent-dir + + # download binary artifact + - name: 🔽Download Binary Artifact + shell: bash + run: | + echo "🔽Download Binary Artifact" + - name: 🔽Download Binary Artifact + uses: actions/download-artifact@v4.1.7 + with: + name: binary-${{ inputs.os-name }}-py${{ inputs.python-version }} + path: ./ + + # download appversion artifact + - name: 🔽Download AppVersion Artifact + uses: actions/download-artifact@v4.1.7 + with: + name: appversion + path: ${{ steps.parentDir.outputs.parentDir }}/build + + # Prepare Release + - name: 💬Prepare Release + shell: bash + run: | + echo "💬Prepare Release" + - name: Prepare Release + shell: bash + env: + OS_NAME: ${{ inputs.os-name }} + run: | + python ./resources/ci/common/prepare_release.py + + # upload archive artifact for later step + - name: 🔼Upload Archive Artifact + shell: bash + run: | + echo "🔼Upload Archive Artifact" + - name: 🔼Upload Archive Artifact + uses: actions/upload-artifact@v4.3.3 + with: + name: archive-${{ inputs.os-name }}-py${{ inputs.python-version }} + path: ${{ steps.parentDir.outputs.parentDir }}/deploy diff --git a/.github/actions/tag-repo/action.yml b/.github/actions/tag-repo/action.yml new file mode 100644 index 00000000..f4c2db50 --- /dev/null +++ b/.github/actions/tag-repo/action.yml @@ -0,0 +1,76 @@ +name: 🏷️Tag Repository +description: Tag a repository + +inputs: + repository: + description: "Repository Owner/Name; octocat/Hello-World" + required: true + ref-name: + description: "Reference name; branch, tag, etc" + required: true + github-tag: + description: "Reference to tag with" + required: true + debug: + description: "Debug Mode, won't set tag" + required: false + default: "false" + +runs: + using: "composite" + steps: + - name: 🏷️Tag Repository + uses: actions/github-script@v7.0.1 + with: + github-token: ${{ env.FINE_PAT }} + script: | + const debug = ${{ inputs.debug }} == "true" || ${{ inputs.debug }} == true; + const repository = '${{ inputs.repository }}'; + const owner = repository.substring(0,repository.indexOf('/')); + const repo = repository.substring(repository.indexOf('/')+1); + const ref = '${{ inputs.ref-name }}'; + // get git tag + const gitTag = '${{ inputs.github-tag }}'; + console.log('Repo Data: ', `${owner}/${repo}@${ref}`) + console.log('Git tag: ', gitTag) + if(gitTag == '') { + let msg = 'Result: 🔴No Git Tag sent, aborting!'; + console.log(msg) + core.setFailed(msg) + return + } + // get latest commit + const latestCommit = await github.rest.git.getRef({ + owner: owner, + repo: repo, + ref: ref + }) + // get latest refs + const latestRefs = await github.rest.git.listMatchingRefs({ + owner: owner, + repo: repo + }) + let latestTag = ''; // bucket for latest tag + // get last tag in data + for(let thisRef of latestRefs.data) { + if(thisRef['ref'].indexOf('tags') > -1) { + let refParts = thisRef['ref'].split('/'); + latestTag = refParts[-1]; + } + } + console.log('Latest tag:', latestTag) + if(latestTag != gitTag) { + if(debug) { + console.log(`DEBUG: 🔵Creating '${gitTag}' tag`) + } else { + console.log(`Result: 🟢Creating '${gitTag}' tag`) + github.rest.git.createRef({ + owner: owner, + repo: repo, + ref: `refs/tags/${gitTag}`, + sha: latestCommit.data.object.sha + }) + } + } else { + console.log('Result: 🟡Not creating release tag') + } diff --git a/.github/actions/test/action.yml b/.github/actions/test/action.yml new file mode 100644 index 00000000..95db06db --- /dev/null +++ b/.github/actions/test/action.yml @@ -0,0 +1,97 @@ +name: ⏱️Test +description: Test app +inputs: + os-name: + required: true + description: OS to run on + python-version: + required: true + description: Python version to install + +######### +# actions +######### +# actions/checkout@v4.1.4 +# actions/download-artifact@v4.1.7 +# actions/upload-artifact@v4.3.3 +# coactions/setup-xvfb@v1.0.1 + +runs: + using: "composite" + steps: + # download pipline + - name: 🔽Download PipLine + shell: bash + run: | + echo "🔽Download PipLine" + - name: 🔽Download PipLine + uses: actions/download-artifact@v4.1.7 + with: + name: pipline-test-${{ inputs.os-name }}-py${{ inputs.python-version }} + path: ./resources/user/meta/manifests + + # run tests + - name: 🖥️Test Base + shell: bash + run: | + echo "🖥️Test Base" + - name: 🖥️Test Base + shell: bash + run: | + mkdir -p ./failures + echo "" > ./failures/errors.txt + python -m pip install tqdm + python ./test/NewTestSuite.py + # - name: 🖥️Test Mystery + # shell: bash + # run: | + # echo "🖥️Test Mystery" + # if: contains(inputs.os-name, 'macos') + # - name: 🖥️Test Mystery + # shell: bash + # run: | + # python ./test/MysteryTestSuite.py + # if: contains(inputs.os-name, 'macos') + + # upload logs + - name: 🔼Upload Logs + shell: bash + run: | + echo "🔼Upload Logs" + - name: 🔼Upload Logs + uses: actions/upload-artifact@v4.3.3 + with: + name: logs-${{ inputs.os-name }}-py${{ inputs.python-version }} + path: ./logs + if-no-files-found: ignore + + # print failures + - name: 💬Print Failures + if: failure() + shell: bash + run: | + echo "💬Print Failures" + - name: Print Failures + if: failure() + shell: bash + run: | + ERR_STRING="$(cat ./failures/errors.txt)" + ERR_STRING="${ERR_STRING//'%'/'%25'}" + ERR_STRING="${ERR_STRING//$'\n'/' | '}" + ERR_STRING="${ERR_STRING//$'\r'/' | '}" + ERR_STRING="${ERR_STRING//$'\n'/'%0A'}" + ERR_STRING="${ERR_STRING//$'\r'/'%0D'}" + echo "::error ::$ERR_STRING" + + # upload failures + - name: 🔼Upload Failures + if: failure() + shell: bash + run: | + echo "🔼Upload Failures" + - name: 🔼Upload Failures + if: failure() + uses: actions/upload-artifact@v4.3.3 + with: + name: failures-${{ inputs.os-name }}-py${{ inputs.python-version }} + path: ./failures diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index ab6c8163..00000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,316 +0,0 @@ -# workflow name -name: Build - -# fire on -on: - push: - branches: - - DoorDev - pull_request: - branches: - - DoorDev - -# stuff to do -jobs: - # Install & Build - # Set up environment - # Build - # Run build-gui.py - # Run build-dr.py - install-build: - name: Install/Build - # cycle through os list - runs-on: ${{ matrix.os-name }} - - # VM settings - # os & python versions - strategy: - matrix: - os-name: [ ubuntu-latest, ubuntu-20.04, macOS-latest, windows-latest ] - python-version: [ 3.9 ] -# needs: [ install-test ] - steps: - # checkout commit - - name: Checkout commit - uses: actions/checkout@v3 - # install python - - name: Install python - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - architecture: "x64" - - run: | - python --version - # install dependencies via pip - - name: Install dependencies via pip - env: - OS_NAME: ${{ matrix.os-name }} - run: | - python ./resources/ci/common/install.py - pip install pyinstaller - # get parent directory - - name: Get Repo Name - uses: mad9000/actions-find-and-replace-string@3 - id: repoName - with: - source: ${{ github.repository }} - find: '${{ github.repository_owner }}/' - replace: '' - - name: Get Parent Directory Path (!Windows) - uses: mad9000/actions-find-and-replace-string@3 - id: parentDirNotWin - with: - source: ${{ github.workspace }} - find: '${{ steps.repoName.outputs.value }}/${{ steps.repoName.outputs.value }}' - replace: ${{ steps.repoName.outputs.value }} - - name: Get Parent Directory Path (Windows) - uses: mad9000/actions-find-and-replace-string@3 - id: parentDir - with: - source: ${{ steps.parentDirNotWin.outputs.value }} - find: '${{ steps.repoName.outputs.value }}\${{ steps.repoName.outputs.value }}' - replace: ${{ steps.repoName.outputs.value }} - # try to get UPX - - name: Get UPX - env: - OS_NAME: ${{ matrix.os-name }} - run: | - python ./resources/ci/common/get_upx.py - # run build-gui.py - - name: Build GUI - run: | - python ./source/meta/build-gui.py - # run build-dr.py - - name: Build DungeonRandomizer - run: | - python ./source/meta/build-dr.py - # prepare binary artifacts for later step - - name: Prepare Binary Artifacts - env: - OS_NAME: ${{ matrix.os-name }} - run: | - python ./resources/ci/common/prepare_binary.py - # upload binary artifacts for later step - - name: Upload Binary Artifacts - uses: actions/upload-artifact@v3 - with: - name: binaries-${{ matrix.os-name }} - path: ${{ steps.parentDir.outputs.value }}/artifact - - # Install & Preparing Release - # Set up environment - # Local Prepare Release action - install-prepare-release: - name: Install/Prepare Release - # cycle through os list - runs-on: ${{ matrix.os-name }} - - # VM settings - # os & python versions - strategy: - matrix: - # install/release on not bionic - os-name: [ ubuntu-latest, ubuntu-20.04, macOS-latest, windows-latest ] - python-version: [ 3.9 ] - - needs: [ install-build ] - steps: - # checkout commit - - name: Checkout commit - uses: actions/checkout@v3 - # install python - - name: Install Python - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - architecture: "x64" - - run: | - python --version - # install dependencies via pip - - name: Install Dependencies via pip - env: - OS_NAME: ${{ matrix.os-name }} - run: | - python ./resources/ci/common/install.py - # get parent directory - - name: Get Repo Name - uses: mad9000/actions-find-and-replace-string@3 - id: repoName - with: - source: ${{ github.repository }} - find: '${{ github.repository_owner }}/' - replace: '' - - name: Get Parent Directory Path (!Windows) - uses: mad9000/actions-find-and-replace-string@3 - id: parentDirNotWin - with: - source: ${{ github.workspace }} - find: '${{ steps.repoName.outputs.value }}/${{ steps.repoName.outputs.value }}' - replace: ${{ steps.repoName.outputs.value }} - - name: Get Parent Directory Path (Windows) - uses: mad9000/actions-find-and-replace-string@3 - id: parentDir - with: - source: ${{ steps.parentDirNotWin.outputs.value }} - find: '${{ steps.repoName.outputs.value }}\${{ steps.repoName.outputs.value }}' - replace: ${{ steps.repoName.outputs.value }} - # download binary artifact - - name: Download Binary Artifact - uses: actions/download-artifact@v3 - with: - name: binaries-${{ matrix.os-name }} - path: ./ - # Prepare AppVersion & Release - - name: Prepare AppVersion & Release - env: - OS_NAME: ${{ matrix.os-name }} - run: | - python ./build-app_version.py - python ./resources/ci/common/prepare_appversion.py - python ./resources/ci/common/prepare_release.py - # upload appversion artifact for later step - - name: Upload AppVersion Artifact - uses: actions/upload-artifact@v3 - with: - name: appversion-${{ matrix.os-name }} - path: ./resources/app/meta/manifests/app_version.txt - # upload archive artifact for later step - - name: Upload Archive Artifact - uses: actions/upload-artifact@v3 - with: - name: archive-${{ matrix.os-name }} - path: ${{ steps.parentDir.outputs.value }}/deploy - - # Deploy to GitHub Releases - # Release Name: ALttPDoorRandomizer v${GITHUB_TAG} - # Release Body: Inline content of RELEASENOTES.md - # Release Body: Fallback to URL to RELEASENOTES.md - # Release Files: ${{ steps.parentDir.outputs.value }}/deploy - deploy-release: - name: Deploy GHReleases - runs-on: ${{ matrix.os-name }} - - # VM settings - # os & python versions - strategy: - matrix: - # release only on focal - os-name: [ ubuntu-latest ] - python-version: [ 3.9 ] - - needs: [ install-prepare-release ] - steps: - # checkout commit - - name: Checkout commit - uses: actions/checkout@v3 - # get parent directory - - name: Get Repo Name - uses: mad9000/actions-find-and-replace-string@3 - id: repoName - with: - source: ${{ github.repository }} - find: '${{ github.repository_owner }}/' - replace: '' - - name: Get Parent Directory Path (!Windows) - uses: mad9000/actions-find-and-replace-string@3 - id: parentDirNotWin - with: - source: ${{ github.workspace }} - find: '${{ steps.repoName.outputs.value }}/${{ steps.repoName.outputs.value }}' - replace: ${{ steps.repoName.outputs.value }} - - name: Get Parent Directory Path (Windows) - uses: mad9000/actions-find-and-replace-string@3 - id: parentDir - with: - source: ${{ steps.parentDirNotWin.outputs.value }} - find: '${{ steps.repoName.outputs.value }}\${{ steps.repoName.outputs.value }}' - replace: ${{ steps.repoName.outputs.value }} - - name: Install Dependencies via pip - run: | - python -m pip install pytz requests - # download appversion artifact - - name: Download AppVersion Artifact - uses: actions/download-artifact@v3 - with: - name: appversion-${{ matrix.os-name }} - path: ${{ steps.parentDir.outputs.value }}/build - # download ubuntu archive artifact - - name: Download Ubuntu Archive Artifact - uses: actions/download-artifact@v3 - with: - name: archive-ubuntu-latest - path: ${{ steps.parentDir.outputs.value }}/deploy/linux - # download macos archive artifact - - name: Download MacOS Archive Artifact - uses: actions/download-artifact@v3 - with: - name: archive-macOS-latest - path: ${{ steps.parentDir.outputs.value }}/deploy/macos - # download windows archive artifact - - name: Download Windows Archive Artifact - uses: actions/download-artifact@v3 - with: - name: archive-windows-latest - path: ${{ steps.parentDir.outputs.value }}/deploy/windows - # debug info - - name: Debug Info - id: debug_info -# shell: bash -# git tag ${GITHUB_TAG} -# git push origin ${GITHUB_TAG} - run: | - GITHUB_TAG="$(head -n 1 ../build/app_version.txt)" - echo "::set-output name=github_tag::$GITHUB_TAG" - GITHUB_TAG="v${GITHUB_TAG}" - RELEASE_NAME="ALttPDoorRandomizer ${GITHUB_TAG}" - echo "Release Name: ${RELEASE_NAME}" - echo "Git Tag: ${GITHUB_TAG}" - # create a pre/release - - name: Create a Pre/Release - id: create_release - uses: actions/create-release@v1.1.4 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: v${{ steps.debug_info.outputs.github_tag }} - release_name: ALttPDoorRandomizer v${{ steps.debug_info.outputs.github_tag }} - body_path: RELEASENOTES.md - draft: true - prerelease: true - if: contains(github.ref, 'master') || contains(github.ref, 'stable') || contains(github.ref, 'dev') || contains(github.ref, 'DoorRelease') - # upload linux archive asset - - name: Upload Linux Archive Asset - id: upload-linux-asset - uses: actions/upload-release-asset@v1.0.2 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ../deploy/linux/ALttPDoorRandomizer.tar.gz - asset_name: ALttPDoorRandomizer-${{ steps.debug_info.outputs.github_tag }}-linux-focal.tar.gz - asset_content_type: application/gzip - if: contains(github.ref, 'master') || contains(github.ref, 'stable') || contains(github.ref, 'dev') || contains(github.ref, 'DoorRelease') - # upload macos archive asset - - name: Upload MacOS Archive Asset - id: upload-macos-asset - uses: actions/upload-release-asset@v1.0.2 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ../deploy/macos/ALttPDoorRandomizer.tar.gz - asset_name: ALttPDoorRandomizer-${{ steps.debug_info.outputs.github_tag }}-osx.tar.gz - asset_content_type: application/gzip - if: contains(github.ref, 'master') || contains(github.ref, 'stable') || contains(github.ref, 'dev') || contains(github.ref, 'DoorRelease') - # upload windows archive asset - - name: Upload Windows Archive Asset - id: upload-windows-asset - uses: actions/upload-release-asset@v1.0.2 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ../deploy/windows/ALttPDoorRandomizer.zip - asset_name: ALttPDoorRandomizer-${{ steps.debug_info.outputs.github_tag }}-windows.zip - asset_content_type: application/zip - if: contains(github.ref, 'master') || contains(github.ref, 'stable') || contains(github.ref, 'dev') || contains(github.ref, 'DoorRelease') diff --git a/.github/workflows/release-complete.yml b/.github/workflows/release-complete.yml new file mode 100644 index 00000000..a193e0ec --- /dev/null +++ b/.github/workflows/release-complete.yml @@ -0,0 +1,47 @@ +# workflow name +name: 🏷️Tag Repositories + +# Fine-grained personal access token +# https://github.com/settings/tokens?type=beta +# token needs perms: +# actions: read/write +# commit statuses: read/write +# contents: read/write +# workflows: read/write +# copy token +# Actions secrets and variables +# github.com///settings/secrets/actions +# repository secret +# name a new secret "ALTTPER_TAGGER" +# value set to copied token + +# fire on +on: + release: + types: + - released + +jobs: + # Tag Baserom + tag-baserom: + name: 🖳Tag Baserom + runs-on: ${{ matrix.os-name }} + strategy: + matrix: + os-name: [ + # ubuntu-latest + "ubuntu-22.04" + ] + + steps: + # call checkout + - name: ✔️Checkout commit + uses: actions/checkout@v4.1.4 + - name: 🏷️Tag Repository + uses: ./.github/actions/tag-repo + env: + FINE_PAT: ${{ secrets.ALTTPER_TAGGER }} + with: + repository: ${{ github.repository_owner }}/z3randomizer + ref-name: heads/OWMain + github-tag: ${{ github.event.release.tag_name }} diff --git a/.github/workflows/release-create.yml b/.github/workflows/release-create.yml new file mode 100644 index 00000000..981f1f75 --- /dev/null +++ b/.github/workflows/release-create.yml @@ -0,0 +1,418 @@ +# workflow name +name: ⏱️Test/🔨Build/🚀Deploy + +# fire on +on: [ + push, + pull_request +] + +# on: +# push: +# branches: +# - DoorDevUnstable +# - DoorDev +# - OverworldShuffleDev +# - OverworldShuffle +# pull_request: +# branches: +# - DoorDevUnstable +# - DoorDev +# - OverworldShuffleDev +# - OverworldShuffle + +# stuff to do +jobs: + # Diagnostics + diags: + # diagnostics + # call checkout + # call install python + # print python version + # call install + # call analyze github actions + # install extra python modules + # run diagnostics + name: 🧮 + runs-on: ${{ matrix.os-name }} + continue-on-error: True + + strategy: + matrix: + #TODO: OS List to run on + os-name: [ + # ubuntu-latest, # ubuntu-22.04 + ubuntu-22.04, + ubuntu-20.04, + macos-latest, # macos-12 + windows-latest # windows-2022 + ] + #TODO: Python Version to run on + python-version: [ "3.12" ] + steps: + # call checkout + - name: ✔️Checkout commit + uses: actions/checkout@v4.1.4 + # call install python + - name: 💿Install Python + uses: actions/setup-python@v5.1.0 + with: + python-version: ${{ matrix.python-version }} + # print python version + - name: 🐍Python Version + shell: bash + run: | + python --version + # call install + - name: 💿Call Install + uses: ./.github/actions/install + with: + calling-job: diags + os-name: ${{ matrix.os-name }} + python-version: ${{ matrix.python-version }} + # call analyze github actions + - name: ⚙️Analyze used GitHub Actions + shell: bash + run: | + python ./resources/ci/common/list_actions.py + # install extra python modules + - name: 💿Install extra Python Modules + shell: bash + run: | + python -m pip install setuptools + # run diagnostics + - name: 🧮Print Diagnostics + shell: bash + run: | + python -m source.meta.run_diags + + # Test + install-test: + # test + # call checkout + # call install + # run tests + name: 💿/⏱️ + runs-on: ${{ matrix.os-name }} + continue-on-error: False + + strategy: + matrix: + #TODO: OS List to run on + os-name: [ + # ubuntu-latest, # ubuntu-22.04 + ubuntu-22.04, + ubuntu-20.04, + macos-latest, # macos-12 + windows-latest # windows-2022 + ] + #TODO: Python Version to run on + python-version: [ "3.12" ] + steps: + # call checkout + - name: ✔️Checkout commit + uses: actions/checkout@v4.1.4 + # call install + - name: 💿Call Install + uses: ./.github/actions/install + with: + calling-job: test + os-name: ${{ matrix.os-name }} + python-version: ${{ matrix.python-version }} + # call test + - name: ⏱️Call Test + uses: ./.github/actions/test + with: + os-name: ${{ matrix.os-name }} + python-version: ${{ matrix.python-version }} + + # Prepare AppVersion + appversion-prepare: + # prepare appversion + # call checkout + # call install + # call appversion-prepare + name: 💬 + runs-on: ${{ matrix.os-name }} + needs: [install-test] + continue-on-error: False + + strategy: + matrix: + #TODO: OS List to run on + os-name: [ + # ubuntu-latest, # ubuntu-22.04 + ubuntu-22.04, + ] + #TODO: Python Version to run on + python-version: [ "3.12" ] + steps: + # call checkout + - name: ✔️Checkout commit + uses: actions/checkout@v4.1.4 + # call install + - name: 💿Call Install + uses: ./.github/actions/install + with: + calling-job: appversion-prepare + os-name: ${{ matrix.os-name }} + python-version: ${{ matrix.python-version }} + # call appversion-prepare + - name: 💬Call Prepare AppVersion + uses: ./.github/actions/appversion-prepare + + # Build + install-build: + # build + # call checkout + # call install + # call build + name: 💿/🔨 + runs-on: ${{ matrix.os-name }} + needs: [appversion-prepare] + continue-on-error: False + + strategy: + matrix: + #TODO: OS List to run on + os-name: [ + # ubuntu-latest, # ubuntu-22.04 + ubuntu-22.04, + ubuntu-20.04, + macos-latest, # macos-12 + windows-latest # windows-2022 + ] + #TODO: Python Version to run on + python-version: [ "3.12" ] + steps: + # call checkout + - name: ✔️Checkout commit + uses: actions/checkout@v4.1.4 + # call install + - name: 💿Call Install + uses: ./.github/actions/install + with: + calling-job: build + os-name: ${{ matrix.os-name }} + python-version: ${{ matrix.python-version }} + # call build + - name: 🔨Call Build + uses: ./.github/actions/build + with: + calling-job: build + os-name: ${{ matrix.os-name }} + python-version: ${{ matrix.python-version }} + + # Prepare Release + release-prepare: + # prepare release + # call checkout + # install extra python modules + # call prepare release + name: 💿/📀->📦 + runs-on: ${{ matrix.os-name }} + needs: [install-build] + continue-on-error: False + + strategy: + matrix: + #TODO: OS List to run on + os-name: [ + # ubuntu-latest, # ubuntu-22.04 + ubuntu-22.04, + ubuntu-20.04, + macos-latest, # macos-12 + windows-latest # windows-2022 + ] + python-version: [ "3.12" ] + steps: + # call checkout + - name: ✔️Checkout commit + uses: actions/checkout@v4.1.4 + # install extra python modules + - name: 💿Install extra Python Modules + shell: bash + run: | + python -m pip install setuptools + # call prepare release + - name: 📀->📦Prepare Release + uses: ./.github/actions/release-prepare + with: + os-name: ${{ matrix.os-name }} + python-version: ${{ matrix.python-version }} + + # Deploy Release + # Needs to be top-level for SECRET to work easily + release-deploy: + name: 📀->🚀 + runs-on: ${{ matrix.os-name }} + needs: [release-prepare] + + strategy: + matrix: + #TODO: OS List to run on + os-name: [ + # ubuntu-latest, # ubuntu-22.04 + ubuntu-22.04, + ] + #TODO: Python Version to run on + python-version: [ "3.12" ] + + steps: + # checkout commit + - name: ✔️Checkout commit + uses: actions/checkout@v4.1.4 + + # install extra python modules + - name: 💿Install extra Python Modules + shell: bash + run: | + python -m pip install pytz requests + + # get parent dir + - name: 📁Get Parent Directory + id: parentDir + uses: ./.github/actions/get-parent-dir + + # download appversion artifact + - name: 🔽Download AppVersion Artifact + uses: actions/download-artifact@v4.1.7 + with: + name: appversion + path: ${{ steps.parentDir.outputs.parentDir }}/build + + # download ubuntu archive artifact + - name: 🔽Download Ubuntu Archive Artifact + uses: actions/download-artifact@v4.1.7 + with: + # should run on latest explicit ubuntu version + name: archive-ubuntu-22.04-py${{ matrix.python-version }} + path: ${{ steps.parentDir.outputs.parentDir }}/deploy/linux + + # download macos archive artifact + - name: 🔽Download MacOS Archive Artifact + uses: actions/download-artifact@v4.1.7 + with: + name: archive-macos-latest-py${{ matrix.python-version }} + path: ${{ steps.parentDir.outputs.parentDir }}/deploy/macos + + # download windows archive artifact + - name: 🔽Download Windows Archive Artifact + uses: actions/download-artifact@v4.1.7 + with: + name: archive-windows-latest-py${{ matrix.python-version }} + path: ${{ steps.parentDir.outputs.parentDir }}/deploy/windows + + # determine linux archive asset + - name: ❔Identify Linux Archive Asset + id: identify-linux-asset + shell: bash + run: | + ASSET_LINUX="$(ls ${{ steps.parentDir.outputs.parentDir }}/deploy/linux)" + echo "asset_linux=$ASSET_LINUX" >> $GITHUB_OUTPUT + + # determine macos archive asset + - name: ❔Identify MacOS Archive Asset + id: identify-macos-asset + shell: bash + run: | + ASSET_MACOS="$(ls ${{ steps.parentDir.outputs.parentDir }}/deploy/macos)" + echo "asset_macos=$ASSET_MACOS" >> $GITHUB_OUTPUT + + # determine windows archive asset + - name: ❔Identify Windows Archive Asset + id: identify-windows-asset + shell: bash + run: | + ASSET_WIN="$(ls ${{ steps.parentDir.outputs.parentDir }}/deploy/windows)" + echo "asset_windows=$ASSET_WIN" >> $GITHUB_OUTPUT + + # archive listing + # - name: Archive Listing + # shell: bash + # run: | + # ls -R ${{ steps.parentDir.outputs.parentDir }}/deploy/ + + # debug info + #TODO: Project Name + - name: 📝Debug Info + id: debug_info + run: | + PROJECT_NAME="ALttPOverworldRandomizer" + echo "project_name=$PROJECT_NAME" >> $GITHUB_OUTPUT + + GITHUB_TAG="$(head -n 1 ../build/app_version.txt)" + echo "github_tag=$GITHUB_TAG" >> $GITHUB_OUTPUT + + RELEASE_NAME="${PROJECT_NAME} ${GITHUB_TAG}" + echo "release_name=$RELEASE_NAME" >> $GITHUB_OUTPUT + + ASSET_PREFIX="${PROJECT_NAME}-${GITHUB_TAG}" + echo "asset_prefix=$ASSET_PREFIX" >> $GITHUB_OUTPUT + + echo "Project Name: ${PROJECT_NAME}" + echo "Release Name: ${RELEASE_NAME}" + echo "Asset Prefix: ${ASSET_PREFIX}" + echo "Git Tag: ${GITHUB_TAG}" + echo "Linux Asset: ${{ steps.identify-linux-asset.outputs.asset_linux }}" + echo "MacOS Asset: ${{ steps.identify-macos-asset.outputs.asset_macos }}" + echo "Windows Asset: ${{ steps.identify-windows-asset.outputs.asset_windows }}" + + # create a release (MASTER) + #TODO: Make sure we updated RELEASENOTES.md + #TODO: Make sure we're firing on the proper branches + # if: contains(github.ref, 'master') # branch or tag name + # if: contains(github.event.head_commit.message, 'Version bump') # commit message + - name: 📀->🚀Create a Release (MASTER) + id: create_release + uses: actions/create-release@v1.1.4 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ steps.debug_info.outputs.github_tag }} + release_name: ${{ steps.debug_info.outputs.release_name }} + body_path: RELEASENOTES.md + # draft: true + if: contains(github.ref, 'master') + + # upload linux archive asset (MASTER) + #TODO: Make sure we're firing on the proper branches + - name: 🔼Upload Linux Archive Asset (MASTER) + id: upload-linux-asset + uses: actions/upload-release-asset@v1.0.2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ${{ steps.parentDir.outputs.parentDir }}/deploy/linux/${{ steps.identify-linux-asset.outputs.asset_linux }} + asset_name: ${{ steps.debug_info.outputs.asset_prefix }}-linux-focal.tar.gz + asset_content_type: application/gzip + if: contains(github.ref, 'master') + + # upload macos archive asset (MASTER) + #TODO: Make sure we're firing on the proper branches + - name: 🔼Upload MacOS Archive Asset (MASTER) + id: upload-macos-asset + uses: actions/upload-release-asset@v1.0.2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ${{ steps.parentDir.outputs.parentDir }}/deploy/macos/${{ steps.identify-macos-asset.outputs.asset_macos }} + asset_name: ${{ steps.debug_info.outputs.asset_prefix }}-osx.tar.gz + asset_content_type: application/gzip + if: contains(github.ref, 'master') + + # upload windows archive asset (MASTER) + #TODO: Make sure we're firing on the proper branches + - name: 🔼Upload Windows Archive Asset (MASTER) + id: upload-windows-asset + uses: actions/upload-release-asset@v1.0.2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ${{ steps.parentDir.outputs.parentDir }}/deploy/windows/${{ steps.identify-windows-asset.outputs.asset_windows }} + asset_name: ${{ steps.debug_info.outputs.asset_prefix }}-windows.zip + asset_content_type: application/zip + if: contains(github.ref, 'master') diff --git a/.gitignore b/.gitignore index a2d08623..0adf6d06 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,7 @@ *.bst *.wixobj *.bat -build +/build bundle/components.wxs dist README.html @@ -40,7 +40,10 @@ resources/user/* get-pip.py venv -test +/test test_games/ data/sprites/official/selan.1.zspr *.zspr + +*errors.txt +*success.txt diff --git a/DungeonRandomizer.py b/DungeonRandomizer.py index cf0f73bc..94b83faf 100755 --- a/DungeonRandomizer.py +++ b/DungeonRandomizer.py @@ -23,7 +23,7 @@ def start(): # print diagnostics # usage: py DungeonRandomizer.py --diags if args.diags: - diags = diagnostics.output(__version__) + diags = diagnostics.output() print("\n".join(diags)) sys.exit(0) diff --git a/Text.py b/Text.py index e020fff7..b2753011 100644 --- a/Text.py +++ b/Text.py @@ -2,6 +2,8 @@ from collections import OrderedDict import logging import re +import warnings +warnings.filterwarnings("ignore", category=SyntaxWarning) text_addresses = {'Pedestal': (0x180300, 256), 'Triforce': (0x180400, 256), @@ -106,18 +108,18 @@ Triforce_texts = [ " Whelp…\n that just\n happened", " Oh hey…\n it's you", "\n Wheeeeee!!", - " Time for\n another one?", + " Time for\n another one?", " And\n\n scene", "\n GOT EM!!", "\n THE VALUUUE!!!", " Cool seed,\n\n right?", "\n We did it!", " Spam those\n emotes in\n wilds chat", - "\n O M G", + "\n O M G", " Hello. Will you\n you be my friend?", " Beetorp\n was\n here!", " The Wind Fish\n will wake soon.\n Hoot!", - " Meow Meow Meow\n Meow Meow Meow\n Oh My God!", + " Meow Meow Meow\n Meow Meow Meow\n Oh my god!", " Ahhhhhhhhh\n Ya ya yaaaah\n Ya ya yaaah", " .done\n\n .comment lol", " You get to\n drink from\n the firehose", @@ -645,7 +647,7 @@ class MultiByteCoreTextMapper(object): linespace = wrap line = lines.pop(0) - match = re.search('^\{[A-Z0-9_:]+\}$', line) + match = re.search(r'^\{[A-Z0-9_:]+\}$', line) if match: if line == '{PAGEBREAK}': if lineindex % 3 != 0: @@ -664,13 +666,13 @@ class MultiByteCoreTextMapper(object): while words: word = words.pop(0) - match = re.search('^(\{[A-Z0-9_:]+\}).*', word) + match = re.search(r'^(\{[A-Z0-9_:]+\}).*', word) if match: start_command = match.group(1) outbuf.extend(cls.special_commands[start_command]) word = word.replace(start_command, '') - match = re.search('(\{[A-Z0-9_:]+\})\.?$', word) + match = re.search(r'(\{[A-Z0-9_:]+\})\.?$', word) if match: end_command = match.group(1) word = word.replace(end_command, '') diff --git a/mystery_testsuite.yml b/mystery_testsuite.yml index 38393165..08110a27 100644 --- a/mystery_testsuite.yml +++ b/mystery_testsuite.yml @@ -98,8 +98,8 @@ compass_shuffle: on: 1 off: 1 smallkey_shuffle: - on: 1 - off: 1 + wild: 1 + none: 1 bigkey_shuffle: on: 1 off: 1 diff --git a/resources/app/meta/manifests/app_version.txt b/resources/app/meta/manifests/app_version.txt new file mode 100644 index 00000000..e69de29b diff --git a/resources/app/meta/manifests/binaries.json b/resources/app/meta/manifests/binaries.json new file mode 100644 index 00000000..620df72f --- /dev/null +++ b/resources/app/meta/manifests/binaries.json @@ -0,0 +1,7 @@ +[ + "DungeonRandomizer", + "Gui", + "MultiClient", + "MultiServer", + "Mystery" +] diff --git a/resources/app/meta/manifests/excluded_dlls.json b/resources/app/meta/manifests/excluded_dlls.json new file mode 100644 index 00000000..825d5999 --- /dev/null +++ b/resources/app/meta/manifests/excluded_dlls.json @@ -0,0 +1,34 @@ +[ + "conio", + "console", + "convert", + "datetime", + "debug", + "environment", + "errorhandling", + "file", + "filesystem", + "handle", + "heap", + "interlocked", + "libraryloader", + "locale", + "localization", + "math", + "memory", + "namedpipe", + "process", + "processenvironment", + "processthreads", + "profile", + "rtlsupport", + "runtime", + "stdio", + "string", + "synch", + "sysinfo", + "time", + "timezone", + "util", + "utility" +] diff --git a/resources/app/meta/manifests/pip_requirements.txt b/resources/app/meta/manifests/pip_requirements.txt index faa3c48f..26bede61 100644 --- a/resources/app/meta/manifests/pip_requirements.txt +++ b/resources/app/meta/manifests/pip_requirements.txt @@ -1,7 +1,8 @@ aenum +aioconsole +colorama +distro fast-enum python-bps-continued -colorama -aioconsole +pyyaml websockets -pyyaml \ No newline at end of file diff --git a/resources/ci/common/common.py b/resources/ci/common/common.py index b89a266b..5329c768 100644 --- a/resources/ci/common/common.py +++ b/resources/ci/common/common.py @@ -1,6 +1,11 @@ import os # for env vars import stat # file statistics import sys # default system info +try: + import distro +except ModuleNotFoundError as e: + pass + from my_path import get_py_path global UBUNTU_VERSIONS @@ -8,15 +13,20 @@ global DEFAULT_EVENT global DEFAULT_REPO_SLUG global FILENAME_CHECKS global FILESIZE_CHECK -UBUNTU_VERSIONS = { - "latest": "focal", - "20.04": "focal", - "18.04": "bionic", - "16.04": "xenial" -} +# GitHub Hosted Runners +# https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#standard-github-hosted-runners-for-public-repositories +# ubuntu: 22.04, 20.04 +# windows: 2022, 2019 +# macos: 14, 13, 12, 11 DEFAULT_EVENT = "event" DEFAULT_REPO_SLUG = "miketrethewey/ALttPDoorRandomizer" -FILENAME_CHECKS = [ "Gui", "DungeonRandomizer" ] +FILENAME_CHECKS = [ + "DungeonRandomizer", + "Gui", + "MultiClient", + "MultiServer", + "Mystery" +] FILESIZE_CHECK = (6 * 1024 * 1024) # 6MB # take number of bytes and convert to string with units measure @@ -38,12 +48,19 @@ def prepare_env(): global DEFAULT_REPO_SLUG env = {} - # get app version + # get app version APP_VERSION = "" - APP_VERSION_FILE = os.path.join(".","resources","app","meta","manifests","app_version.txt") - if os.path.isfile(APP_VERSION_FILE): - with open(APP_VERSION_FILE,"r") as f: - APP_VERSION = f.readlines()[0].strip() + APP_VERSION_FILES = [ + os.path.join(".","resources","app","meta","manifests","app_version.txt"), + os.path.join("..","build","app_version.txt") + ] + for app_version_file in APP_VERSION_FILES: + if os.path.isfile(app_version_file): + with open(app_version_file,"r") as f: + lines = f.readlines() + if len(lines) > 0: + APP_VERSION = lines[0].strip() + # ci data env["CI_SYSTEM"] = os.getenv("CI_SYSTEM","") # py data @@ -96,9 +113,11 @@ def prepare_env(): OS_VERSION = OS_NAME[OS_NAME.find('-')+1:] OS_NAME = OS_NAME[:OS_NAME.find('-')] if OS_NAME == "linux" or OS_NAME == "ubuntu": - if OS_VERSION in UBUNTU_VERSIONS: - OS_VERSION = UBUNTU_VERSIONS[OS_VERSION] - OS_DIST = OS_VERSION + try: + if distro.codename() != "": + OS_DIST = distro.codename() + except NameError as e: + pass if OS_VERSION == "" and not OS_DIST == "" and not OS_DIST == "notset": OS_VERSION = OS_DIST @@ -111,7 +130,7 @@ def prepare_env(): # if the app version didn't have the build number, add it # set to . if env["BUILD_NUMBER"] not in GITHUB_TAG: - GITHUB_TAG += '.' + env["BUILD_NUMBER"] + GITHUB_TAG += ".r" + env["BUILD_NUMBER"] env["GITHUB_TAG"] = GITHUB_TAG env["OS_NAME"] = OS_NAME diff --git a/resources/ci/common/get_get_pip.py b/resources/ci/common/get_get_pip.py index a0e127ba..1de2898b 100644 --- a/resources/ci/common/get_get_pip.py +++ b/resources/ci/common/get_get_pip.py @@ -10,7 +10,7 @@ def get_get_pip(PY_VERSION): try: import pip except ImportError: - print("Getting pip getter!") + print("🟡Getting pip getter!") #make the request! url = "https://bootstrap.pypa.io/get-pip.py" context = ssl._create_unverified_context() @@ -40,7 +40,7 @@ def get_get_pip(PY_VERSION): if float(PY_VERSION) > 0: PYTHON_EXECUTABLE = "py" - print("Getting pip!") + print("🟡Getting pip!") args = [ env["PYTHON_EXE_PATH"] + PYTHON_EXECUTABLE, '-' + str(PY_VERSION), @@ -58,6 +58,6 @@ if __name__ == "__main__": try: import pip - print("pip is installed") + print("🟢pip is installed") except ImportError: get_get_pip(PY_VERSION) diff --git a/resources/ci/common/get_pipline.py b/resources/ci/common/get_pipline.py new file mode 100644 index 00000000..edc5714a --- /dev/null +++ b/resources/ci/common/get_pipline.py @@ -0,0 +1,440 @@ +# import modules +import common # app common functions + +import json # json manipulation +import os # for os data, filesystem manipulation +import subprocess # for running shell commands +import sys # for system commands +import traceback # for errors + +# get env +env = common.prepare_env() # get environment variables + +# width for labels +WIDTH = 70 + +# bucket for cli args +args = [] + +# pip exe path +PIPEXE = "" + +# py exe path +# py version +# py minor version +PYTHON_EXECUTABLE = os.path.splitext(sys.executable.split(os.path.sep).pop())[0] # get command to run python +PYTHON_VERSION = sys.version.split(" ")[0] +PYTHON_MINOR_VERSION = '.'.join(PYTHON_VERSION.split(".")[:2]) + +# pip string version +# pip float version +PIP_VERSION = "" +PIP_FLOAT_VERSION = 0 + +# success +SUCCESS = False +# bucket for versions +VERSIONS = {} + +# process module output +# read output from installing +# print relevant info +# print unknown stuff +def process_module_output(lines): + for line in lines: + # if there's an error, print it and bail + if "status 'error'" in line.strip(): + print( + "🔴[%s] %s" + % + ( + "_", + line.strip() + ) + ) + return + # sys.exit(1) + # if it's already satisfied or building a wheel, print version data + elif "already satisfied" in line or \ + "Building wheel" in line or \ + "Created wheel" in line: + + modulename = print_module_line(line) + + if "=" not in modulename and VERSIONS[modulename]["installed"] != VERSIONS[modulename]["latest"]: + # install modules from list + ret = subprocess.run( + [ + *args, + "-m", + PIPEXE, + "install", + "--upgrade", + f"{modulename}" + ], + capture_output=True, + text=True + ) + # if there's output + if ret.stdout.strip(): + process_module_output(ret.stdout.strip().split("\n")) + + # ignore lines about certain things + elif "Attempting uninstall" in line or \ + "Collecting" in line or \ + "Downloading" in line or \ + "eta 0:00:00" in line or \ + "Found existing" in line or \ + "Installing collected" in line or \ + "Preparing metadata" in line or \ + "Successfully built" in line or \ + "Successfully installed" in line or \ + "Successfully uninstalled" in line or \ + "Stored in" in line or \ + "Uninstalling " in line or \ + "Using cached" in line: + pass + # else, I don't know what it is, print it + else: + print(line.strip()) + print("") + +# print module line +# name, installed version, latest version +def print_module_line(line): + global VERSIONS + # is it already installed? + satisfied = line.strip().split(" in ") + # get the installed version + sver = ((len(satisfied) > 1) and satisfied[1].split("(").pop().replace(")", "")) or "" + + # if we're making a wheel + if "Created wheel" in line: + line = line.strip().split(':') + satisfied = [line[0]] + sver = line[1].split('-')[1] + + # get module name + modulename = satisfied[0].replace("Requirement already satisfied: ", "") + # save info for later use + VERSIONS[modulename] = { + "installed": sver, + "latest": (sver and get_module_version(satisfied[0].split(" ")[-1])).strip() or "" + } + + # print what we found + print( + ( + "[%s] %s\t%s\t%s" + % + ( + "Building wheel" in line and '.' or "X", + satisfied[0].ljust(len("Requirement already satisfied: ") + len("python-bps-continued")), + VERSIONS[modulename]["installed"], + VERSIONS[modulename]["latest"] + ) + ) + ) + # return the name of this module + return modulename + +# get module version +# get installed version +def get_module_version(module): + # pip index versions [module] // >= 21.2 + # pip install [module]== // >= 21.1 + # pip install --use-deprecated=legacy-resolver [module]== // >= 20.3 + # pip install [module]== // >= 9.0 + # pip install [module]==blork // < 9.0 + global args + global PIPEXE + global PIP_FLOAT_VERSION + ret = "" + ver = "" + + # based on version of pip, get the installation status of a module + if float(PIP_FLOAT_VERSION) >= 21.2: + ret = subprocess.run( + [ + *args, + "-m", + PIPEXE, + "index", + "versions", + module + ], + capture_output=True, + text=True + ) + lines = ret.stdout.strip().split("\n") + lines = lines[2::] + vers = (list(map(lambda x: x.split(' ')[-1], lines))) + if len(vers) > 1: + ver = vers[1] + elif float(PIP_FLOAT_VERSION) >= 21.1: + ret = subprocess.run( + [ + *args, + "-m", + PIPEXE, + "install", + f"{module}==" + ], + capture_output=True, + text=True + ) + elif float(PIP_FLOAT_VERSION) >= 20.3: + ret = subprocess.run( + [ + *args, + "-m", + PIPEXE, + "install", + "--use-deprecated=legacy-resolver", + f"{module}==" + ], + capture_output=True, + text=True + ) + elif float(PIP_FLOAT_VERSION) >= 9.0: + ret = subprocess.run( + [ + *args, + "-m", + PIPEXE, + "install", + f"{module}==" + ], + capture_output=True, + text=True + ) + elif float(PIP_FLOAT_VERSION) < 9.0: + ret = subprocess.run( + [ + *args, + "-m", + PIPEXE, + "install", + f"{module}==blork" + ], + capture_output=True, + ext=True + ) + + # if ver == "" and ret.stderr.strip(): + # ver = (ret.stderr.strip().split("\n")[0].split(",")[-1].replace(')', '')).strip() + + # return what we found + return ver + +# get python info +def python_info(): + global args + global PYTHON_VERSION + + # get python debug info + ret = subprocess.run([*args, "--version"], capture_output=True, text=True) + if ret.stdout.strip(): + PYTHON_VERSION = ret.stdout.strip().split(" ")[1] + PY_STRING = ( + "%s\t%s\t%s" + % + ( + ((isinstance(args[0], list) and " ".join( + args[0])) or args[0]).strip(), + PYTHON_VERSION, + sys.platform + ) + ) + print(PY_STRING) + print('.' * WIDTH) + +# get pip info +def pip_info(): + global args + global PIPEXE + global PIPEXE + global VERSIONS + + # get pip debug info + ret = subprocess.run( + [ + *args, + "-m", + PIPEXE, + "--version" + ], + capture_output=True, + text=True + ) + if ret.stdout.strip(): + if " from " in ret.stdout.strip(): + PIP_VERSION = ret.stdout.strip().split(" from ")[0].split(" ")[1] + if PIP_VERSION: + b, f, a = PIP_VERSION.partition('.') + global PIP_FLOAT_VERSION + PIP_FLOAT_VERSION = b+f+a.replace('.', '') + PIP_LATEST = get_module_version("pip") + + VERSIONS["py"] = { + "version": PYTHON_VERSION, + "platform": sys.platform + } + VERSIONS["pip"] = { + "version": [ + PIP_VERSION, + PIP_FLOAT_VERSION + ], + "latest": PIP_LATEST + } + + PIP_STRING = ( + "%s\t%s\t%s\t%s\t%s\t%s" + % + ( + ((isinstance(args[0], list) and " ".join( + args[0])) or args[0]).strip(), + PYTHON_VERSION, + sys.platform, + PIPEXE, + PIP_VERSION, + PIP_LATEST + ) + ) + print(PIP_STRING) + print('.' * WIDTH) + +# upgrade pip +def pip_upgrade(): + global args + global PIPEXE + + # upgrade pip + ret = subprocess.run( + [ + *args, + "-m", + PIPEXE, + "install", + "--upgrade", "pip" + ], + capture_output=True, + text=True + ) + # get output + if ret.stdout.strip(): + # if it's not already satisfied, update it + if "already satisfied" not in ret.stdout.strip(): + print(ret.stdout.strip()) + pip_info() + +# install modules +def install_modules(): + global args + global PIPEXE + global SUCCESS + + # install modules from list + ret = subprocess.run( + [ + *args, + "-m", + PIPEXE, + "install", + "-r", + os.path.join( + ".", + "resources", + "app", + "meta", + "manifests", + "pip_requirements.txt" + ) + ], + capture_output=True, + text=True + ) + + # if there's output + if ret.stdout.strip(): + process_module_output(ret.stdout.strip().split("\n")) + manifests_path = os.path.join(".", "resources", "user", "meta", "manifests") + if not os.path.isdir(manifests_path): + os.makedirs(manifests_path) + + with open(os.path.join(manifests_path, "settings.json"), "w+") as settings: + settings.write( + json.dumps( + { + "py": args, + "pip": PIPEXE, + "pipline": " ".join(args) + " -m " + PIPEXE, + "versions": VERSIONS + }, + indent=2 + ) + ) + with open(os.path.join(manifests_path, "pipline.txt"), "w+") as settings: + settings.write(" ".join(args) + " -m " + PIPEXE) + SUCCESS = True + + +def main(): + global args + global PIPEXE + global SUCCESS + # print python debug info + heading = ( + "%s-%s-%s" + % + ( + PYTHON_EXECUTABLE, + PYTHON_VERSION, + sys.platform + ) + ) + print(heading) + print('=' * WIDTH) + + # figure out pip executable + PIPEXE = "pip" if "windows" in env["OS_NAME"] else "pip3" + PIPEXE = "pip" if "osx" in env["OS_NAME"] and "actions" in env["CI_SYSTEM"] else PIPEXE + + PIP_VERSION = "" # holder for pip's version + + SUCCESS = False + # foreach py executable + for PYEXE in ["py", "python3", "python"]: + if SUCCESS: + continue + + args = [] + # if it's the py launcher, specify the version + if PYEXE == "py": + PYEXE = [PYEXE, "-" + PYTHON_MINOR_VERSION] + # if it ain't windows, skip it + if "windows" not in env["OS_NAME"]: + continue + + # build executable command + if isinstance(PYEXE, list): + args = [*PYEXE] + else: + args = [PYEXE] + + try: + python_info() + + # foreach pip executable + for PIPEXE in ["pip3", "pip"]: + pip_info() + pip_upgrade() + install_modules() + + # if something else went fucky, print it + except Exception as e: + traceback.print_exc() + + +if __name__ == "__main__": + main() diff --git a/resources/ci/common/get_upx.py b/resources/ci/common/get_upx.py index 73c6b6f3..16982aaf 100644 --- a/resources/ci/common/get_upx.py +++ b/resources/ci/common/get_upx.py @@ -21,10 +21,11 @@ if not os.path.isdir(os.path.join(".","upx")): UPX_FILE = UPX_SLUG + ".tar.xz" UPX_URL = "https://github.com/upx/upx/releases/download/v" + UPX_VERSION + '/' + UPX_FILE + # if it's not macos if "osx" not in env["OS_NAME"]: - print("Getting UPX: " + UPX_FILE) + # download UPX with open(os.path.join(".",UPX_FILE),"wb") as upx: UPX_REQ = urllib.request.Request( UPX_URL, @@ -34,8 +35,10 @@ if not os.path.isdir(os.path.join(".","upx")): UPX_DATA = UPX_REQ.read() upx.write(UPX_DATA) + # extract UPX unpack_archive(UPX_FILE,os.path.join(".")) + # move UPX os.rename(os.path.join(".",UPX_SLUG),os.path.join(".","upx")) os.remove(os.path.join(".",UPX_FILE)) diff --git a/resources/ci/common/list_actions.py b/resources/ci/common/list_actions.py new file mode 100644 index 00000000..4aab7812 --- /dev/null +++ b/resources/ci/common/list_actions.py @@ -0,0 +1,168 @@ +# pylint: disable=invalid-name +''' +List GitHub Actions versions used and latest versions +''' +import json +import os +import ssl +import urllib.request +import yaml +from json.decoder import JSONDecodeError + +allACTIONS = {} +listACTIONS = [] + +VER_WIDTH = 10 +NAME_WIDTH = 40 +LINE_WIDTH = 1 + NAME_WIDTH + 5 + VER_WIDTH + 5 + VER_WIDTH + 1 + +def process_walk(key, node): + ''' + Process walking through the array + ''' + global allACTIONS + global listACTIONS + if key == "uses": + action = node.split('@') + version = "" + if '@' in node: + version = action[1] + action = action[0] + if action not in allACTIONS: + allACTIONS[action] = { + "versions": [], + "latest": "" + } + allACTIONS[action]["versions"].append(version) + allACTIONS[action]["versions"] = list( + set( + allACTIONS[action]["versions"] + ) + ) + listACTIONS.append(node) + + +def walk(key, node): + ''' + How to walk through the array + ''' + if isinstance(node, dict): + return {k: walk(k, v) for k, v in node.items()} + elif isinstance(node, list): + return [walk(key, x) for x in node] + else: + return process_walk(key, node) + + +for r, d, f in os.walk(os.path.join(".", ".github")): + if "actions" in r or "workflows" in r: + for filename in f: + # if it's not a YAML or it's turned off, skip it + if (".yml" not in filename and ".yaml" not in filename) or (".off" in filename): + continue + listACTIONS = [] + # print filename + filename_line = "-" * (len(os.path.join(r, filename)) + 2) + print( + " " + + filename_line + + " " + ) + print("| " + os.path.join(r, filename) + " |") + # read the file + with(open(os.path.join(r, filename), "r", encoding="utf-8")) as yamlFile: + print( + "|" + + filename_line + + "-" + + ("-" * (LINE_WIDTH - len(filename_line) + 1)) + + " " + ) + yml = yaml.safe_load(yamlFile) + walk("uses", yml) + dictACTIONS = {} + for k in sorted(list(set(listACTIONS))): + action = k.split('@')[0] + version = k.split('@')[1] if '@' in k else "" + latest = "" + # if it's not a location action, get the latest version number + if "./." not in action: + apiURL = f"https://api.github.com/repos/{action}/releases/latest" + if True: + apiReq = None + try: + apiReq = urllib.request.urlopen( + apiURL, + context=ssl._create_unverified_context() + ) + except urllib.error.HTTPError as e: + if e.code != 403: + print(e.code, apiURL) + if apiReq: + apiRes = {} + try: + apiRes = json.loads( + apiReq.read().decode("utf-8")) + except JSONDecodeError as e: + raise ValueError("🔴API Request failed: " + apiURL) + if apiRes: + latest = apiRes["tag_name"] if "tag_name" in apiRes else "" + if latest != "": + allACTIONS[action]["latest"] = latest + dictACTIONS[action] = version + # print action name and version info + for action, version in dictACTIONS.items(): + print( + "| " + \ + f"{action.ljust(NAME_WIDTH)}" + \ + "\t" + \ + f"{(version or 'N/A').ljust(VER_WIDTH)}" + \ + "\t" + \ + f"{(allACTIONS[action]['latest'] or 'N/A').ljust(VER_WIDTH)}" + \ + " |" + ) + print( + " " + + ("-" * (LINE_WIDTH + 2)) + + " " + ) + print("") + +# print outdated versions summary +first = True +outdated = False +for action, actionData in allACTIONS.items(): + if len(actionData["versions"]) > 0: + if actionData["latest"] != "" and actionData["versions"][0] != actionData["latest"]: + outdated = True + if first: + first = False + filename_line = "-" * (len("| Outdated |")) + print( + " " + + filename_line + + " " + ) + print("| 🔴Outdated |") + print( + "|" + + filename_line + + "-" + + ("-" * (LINE_WIDTH - len(filename_line) + 1)) + + " " + ) + print( + "| " + \ + f"{action.ljust(40)}" + \ + "\t" + \ + f"{(','.join(actionData['versions']) or 'N/A').ljust(10)}" + \ + "\t" + \ + f"{actionData['latest'].ljust(10)}" + \ + " |" + ) +if outdated: + print( + " " + + ("-" * (LINE_WIDTH + 2)) + + " " + ) diff --git a/resources/ci/common/prepare_appversion.py b/resources/ci/common/prepare_appversion.py index bd26318e..0f413298 100644 --- a/resources/ci/common/prepare_appversion.py +++ b/resources/ci/common/prepare_appversion.py @@ -5,12 +5,12 @@ from shutil import copy # file manipulation env = common.prepare_env() # set tag to app_version.txt -if not env["GITHUB_TAG"] == "": - with open(os.path.join(".","resources","app","meta","manifests","app_version.txt"),"w+") as f: - _ = f.read() - f.seek(0) - f.write(env["GITHUB_TAG"]) - f.truncate() +# if not env["GITHUB_TAG"] == "": +# with open(os.path.join(".","resources","app","meta","manifests","app_version.txt"),"w+") as f: +# _ = f.read() +# f.seek(0) +# f.write(env["GITHUB_TAG"]) +# f.truncate() if not os.path.isdir(os.path.join("..","build")): os.mkdir(os.path.join("..","build")) diff --git a/resources/ci/common/prepare_binary.py b/resources/ci/common/prepare_binary.py index ff9b7c99..4d9ac5e4 100644 --- a/resources/ci/common/prepare_binary.py +++ b/resources/ci/common/prepare_binary.py @@ -1,42 +1,48 @@ -import distutils.dir_util # for copying trees +""" +Locate and prepare binary builds +""" +# import distutils.dir_util # for copying trees import os # for env vars -import stat # for file stats -import subprocess # do stuff at the shell level +# import stat # for file stats +# import subprocess # do stuff at the shell level import common -from shutil import copy, make_archive, move, rmtree # file manipulation +from shutil import move # file manipulation env = common.prepare_env() # make dir to put the binary in if not os.path.isdir(os.path.join("..","artifact")): - os.mkdir(os.path.join("..","artifact")) + os.mkdir(os.path.join("..","artifact")) BUILD_FILENAME = "" # list executables BUILD_FILENAME = common.find_binary('.') if BUILD_FILENAME == "": - BUILD_FILENAME = common.find_binary(os.path.join("..","artifact")) + BUILD_FILENAME = common.find_binary(os.path.join("..","artifact")) if isinstance(BUILD_FILENAME,str): - BUILD_FILENAME = list(BUILD_FILENAME) + BUILD_FILENAME = list(BUILD_FILENAME) BUILD_FILENAMES = BUILD_FILENAME +print("OS Name: " + env["OS_NAME"]) +print("OS Version: " + env["OS_VERSION"]) +print("OS Distribution: " + env["OS_DIST"]) +print("") for BUILD_FILENAME in BUILD_FILENAMES: - DEST_FILENAME = common.prepare_filename(BUILD_FILENAME) + DEST_FILENAME = common.prepare_filename(BUILD_FILENAME) - print("OS Name: " + env["OS_NAME"]) - print("OS Version: " + env["OS_VERSION"]) - print("Build Filename: " + BUILD_FILENAME) - print("Dest Filename: " + DEST_FILENAME) - if not BUILD_FILENAME == "": - print("Build Filesize: " + common.file_size(BUILD_FILENAME)) - else: - exit(1) + print("Build Filename: " + BUILD_FILENAME) + print("Dest Filename: " + DEST_FILENAME) + if not BUILD_FILENAME == "": + print("Build Filesize: " + common.file_size(BUILD_FILENAME)) + else: + exit(1) - if not BUILD_FILENAME == "": - move( - os.path.join(".",BUILD_FILENAME), - os.path.join("..","artifact",BUILD_FILENAME) - ) + if not BUILD_FILENAME == "": + move( + os.path.join(".",BUILD_FILENAME), + os.path.join("..","artifact",BUILD_FILENAME) + ) + print("") diff --git a/resources/ci/common/prepare_release.py b/resources/ci/common/prepare_release.py index c3869f2d..8bab9de4 100644 --- a/resources/ci/common/prepare_release.py +++ b/resources/ci/common/prepare_release.py @@ -101,7 +101,8 @@ if len(BUILD_FILENAMES) > 0: # .zip if windows # .tar.gz otherwise if len(BUILD_FILENAMES) > 1: - ZIP_FILENAME = os.path.join("..","deploy",env["REPO_NAME"]) + # ZIP_FILENAME = os.path.join("..","deploy",env["REPO_NAME"]) + ZIP_FILENAME = os.path.join("..","deploy","ALttPOverworldRandomizer") else: ZIP_FILENAME = os.path.join("..","deploy",os.path.splitext(BUILD_FILENAME)[0]) if env["OS_NAME"] == "windows": @@ -124,15 +125,15 @@ for BUILD_FILENAME in BUILD_FILENAMES: print("Build Filename: " + BUILD_FILENAME) print("Build Filesize: " + common.file_size(BUILD_FILENAME)) else: - print("No Build to prepare: " + BUILD_FILENAME) + print("🟡No Build to prepare: " + BUILD_FILENAME) if not ZIP_FILENAME == "": print("Zip Filename: " + ZIP_FILENAME) print("Zip Filesize: " + common.file_size(ZIP_FILENAME)) else: - print("No Zip to prepare: " + ZIP_FILENAME) + print("🟡No Zip to prepare: " + ZIP_FILENAME) -print("Git tag: " + env["GITHUB_TAG"]) +print("App Version: " + env["GITHUB_TAG"]) if (len(BUILD_FILENAMES) == 0) or (ZIP_FILENAME == ""): exit(1) diff --git a/source/DungeonRandomizer.spec b/source/DungeonRandomizer.spec deleted file mode 100644 index beb2ecfc..00000000 --- a/source/DungeonRandomizer.spec +++ /dev/null @@ -1,68 +0,0 @@ -# -*- mode: python -*- - -import sys - -block_cipher = None -console = True # <--- change this to True to enable command prompt when the app runs - -if sys.platform.find("mac") or sys.platform.find("osx"): - console = False - -BINARY_SLUG = "DungeonRandomizer" - -def recurse_for_py_files(names_so_far): - returnvalue = [] - for name in os.listdir(os.path.join(*names_so_far)): - if name != "__pycache__": - subdir_name = os.path.join(*names_so_far, name) - if os.path.isdir(subdir_name): - new_name_list = names_so_far + [name] - for filename in os.listdir(os.path.join(*new_name_list)): - base_file,file_extension = os.path.splitext(filename) - if file_extension == ".py": - new_name = ".".join(new_name_list+[base_file]) - if not new_name in returnvalue: - returnvalue.append(new_name) - returnvalue.extend(recurse_for_py_files(new_name_list)) - returnvalue.append("PIL._tkinter_finder") #Linux needs this - return returnvalue - -hiddenimports = [] -binaries = [] - -a = Analysis([f"../{BINARY_SLUG}.py"], - pathex=[], - binaries=binaries, - datas=[('../data/', 'data/')], - hiddenimports=hiddenimports, - hookspath=[], - runtime_hooks=[], - excludes=[], - win_no_prefer_redirects=False, - win_private_assemblies=False, - cipher=block_cipher, - noarchive=False) - -# https://stackoverflow.com/questions/17034434/how-to-remove-exclude-modules-and-files-from-pyinstaller -excluded_binaries = [ - 'VCRUNTIME140.dll', - 'ucrtbase.dll', - 'msvcp140.dll', - 'mfc140u.dll'] -a.binaries = TOC([x for x in a.binaries if x[0] not in excluded_binaries]) - -pyz = PYZ(a.pure, a.zipped_data, - cipher=block_cipher) -exe = EXE(pyz, - a.scripts, - a.binaries, - a.zipfiles, - a.datas, - [], - name=BINARY_SLUG, - debug=False, - bootloader_ignore_signals=False, - strip=False, - upx=True, - runtime_tmpdir=None, - console=console ) diff --git a/source/Gui.spec b/source/Gui.spec deleted file mode 100644 index 8b140a75..00000000 --- a/source/Gui.spec +++ /dev/null @@ -1,69 +0,0 @@ -# -*- mode: python -*- - -import sys - -block_cipher = None -console = True # <--- change this to True to enable command prompt when the app runs - -if sys.platform.find("mac") or sys.platform.find("osx"): - console = False - -BINARY_SLUG = "Gui" - -def recurse_for_py_files(names_so_far): - returnvalue = [] - for name in os.listdir(os.path.join(*names_so_far)): - if name != "__pycache__": - subdir_name = os.path.join(*names_so_far, name) - if os.path.isdir(subdir_name): - new_name_list = names_so_far + [name] - for filename in os.listdir(os.path.join(*new_name_list)): - base_file,file_extension = os.path.splitext(filename) - if file_extension == ".py": - new_name = ".".join(new_name_list+[base_file]) - if not new_name in returnvalue: - returnvalue.append(new_name) - returnvalue.extend(recurse_for_py_files(new_name_list)) - returnvalue.append("PIL._tkinter_finder") #Linux needs this - return returnvalue - -hiddenimports = [] -binaries = [] - -a = Analysis([f"../{BINARY_SLUG}.py"], - pathex=[], - binaries=binaries, - datas=[('../data/', 'data/')], - hiddenimports=hiddenimports, - hookspath=[], - runtime_hooks=[], - excludes=[], - win_no_prefer_redirects=False, - win_private_assemblies=False, - cipher=block_cipher, - noarchive=False) - -# https://stackoverflow.com/questions/17034434/how-to-remove-exclude-modules-and-files-from-pyinstaller -excluded_binaries = [ - 'VCRUNTIME140.dll', - 'ucrtbase.dll', - 'msvcp140.dll', - 'mfc140u.dll'] -a.binaries = TOC([x for x in a.binaries if x[0] not in excluded_binaries]) - -pyz = PYZ(a.pure, a.zipped_data, - cipher=block_cipher) -exe = EXE(pyz, - a.scripts, - a.binaries, - a.zipfiles, - a.datas, - [], - name=BINARY_SLUG, - debug=False, - bootloader_ignore_signals=False, - icon='../data/ER.ico', - strip=False, - upx=True, - runtime_tmpdir=None, - console=console ) diff --git a/source/Template.spec b/source/Template.spec new file mode 100644 index 00000000..87fb999a --- /dev/null +++ b/source/Template.spec @@ -0,0 +1,98 @@ +# -*- mode: python -*- + +import json +import os +import sys +from json.decoder import JSONDecodeError +from PyInstaller.utils.hooks import collect_submodules + +block_cipher = None +console = False # <--- change this to True to enable command prompt when the app runs + +if sys.platform.find("mac") or sys.platform.find("osx"): + console = True + +BINARY_SLUG = "" + + +def recurse_for_py_files(names_so_far): + # get py files + returnvalue = [] + for name in os.listdir(os.path.join(*names_so_far)): + # ignore __pycache__ + if name != "__pycache__": + subdir_name = os.path.join(*names_so_far, name) + if os.path.isdir(subdir_name): + new_name_list = names_so_far + [name] + for filename in os.listdir(os.path.join(*new_name_list)): + base_file, file_extension = os.path.splitext(filename) + # if it's a .py + if file_extension == ".py": + new_name = ".".join(new_name_list+[base_file]) + if not new_name in returnvalue: + returnvalue.append(new_name) + returnvalue.extend(recurse_for_py_files(new_name_list)) + return returnvalue + + +hiddenimports = recurse_for_py_files(["source"]) +for hidden in (collect_submodules("pkg_resources")): + hiddenimports.append(hidden) + +a = Analysis( + [f"../{BINARY_SLUG}.py"], + pathex=[], + binaries=[], + datas=[('../data/', 'data/')], + hiddenimports=hiddenimports, + hookspath=[], + runtime_hooks=[], + excludes=[], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher, + noarchive=False +) + +# https://stackoverflow.com/questions/17034434/how-to-remove-exclude-modules-and-files-from-pyinstaller +excluded_binaries = [ + 'mfc140u.dll', + 'msvcp140.dll', + 'ucrtbase.dll', + 'VCRUNTIME140.dll' +] + +# win is temperamental +with open(os.path.join(".","resources","app","meta","manifests","excluded_dlls.json")) as dllsManifest: + dlls = [] + try: + dlls = json.load(dllsManifest) + except JSONDecodeError as e: + raise ValueError("Windows DLLs manifest malformed!") + for dll in dlls: + for submod in ["core", "crt"]: + for ver in ["1-1-0", "1-1-1", "1-2-0", "2-1-0"]: + excluded_binaries.append(f"api-ms-win-{submod}-{dll}-l{ver}.dll") + +a.binaries = TOC([x for x in a.binaries if x[0] not in excluded_binaries]) + +pyz = PYZ( + a.pure, + a.zipped_data, + cipher=block_cipher +) +exe = EXE( + pyz, + a.scripts, + a.binaries, + a.zipfiles, + a.datas, + [], + name=BINARY_SLUG, + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + runtime_tmpdir=None, + console=console +) diff --git a/source/classes/appversion.py b/source/classes/appversion.py new file mode 100644 index 00000000..3d677d1e --- /dev/null +++ b/source/classes/appversion.py @@ -0,0 +1,17 @@ +import os + +from OverworldShuffle import __version__ +OWR_VERSION = __version__ + +def write_appversion(): + APP_VERSION = OWR_VERSION + if "-" in APP_VERSION: + APP_VERSION = APP_VERSION[:APP_VERSION.find("-")] + APP_VERSION_FILE = os.path.join(".","resources","app","meta","manifests","app_version.txt") + with open(APP_VERSION_FILE,"w") as f: + f.seek(0) + f.truncate() + f.write(APP_VERSION) + +if __name__ == "__main__": + write_appversion() diff --git a/source/classes/diags.py b/source/classes/diags.py index 3e2c4121..e26c4e43 100644 --- a/source/classes/diags.py +++ b/source/classes/diags.py @@ -1,16 +1,28 @@ import platform, sys, os, subprocess -import pkg_resources -from datetime import datetime +try: + import pkg_resources +except ModuleNotFoundError as e: + pass +import datetime + +from Main import __version__ +DR_VERSION = __version__ + +from OverworldShuffle import __version__ +OWR_VERSION = __version__ + +PROJECT_NAME = "ALttP Overworld Randomizer" def diagpad(str): - return str.ljust(len("ALttP Door Randomizer Version") + 5,'.') + return str.ljust(len(f"{PROJECT_NAME} Version") + 5,'.') -def output(APP_VERSION): +def output(): lines = [ - "ALttP Door Randomizer Diagnostics", + f"{PROJECT_NAME} Diagnostics", "=================================", - diagpad("UTC Time") + str(datetime.utcnow())[:19], - diagpad("ALttP Door Randomizer Version") + APP_VERSION, + diagpad("UTC Time") + str(datetime.datetime.now(datetime.UTC))[:19], + diagpad("ALttP Door Randomizer Version") + DR_VERSION, + diagpad(f"{PROJECT_NAME} Version") + OWR_VERSION, diagpad("Python Version") + platform.python_version() ] lines.append(diagpad("OS Version") + "%s %s" % (platform.system(), platform.release())) @@ -35,6 +47,7 @@ def output(APP_VERSION): pkg = pkg.split("==") lines.append(diagpad(pkg[0]) + pkg[1]) ''' + installed_packages = [] installed_packages = [str(d) for d in pkg_resources.working_set] #this doesn't work from the .exe either, but it doesn't crash the program installed_packages.sort() for pkg in installed_packages: diff --git a/source/gui/randomize/generation.py b/source/gui/randomize/generation.py index c66eceb6..868402e4 100644 --- a/source/gui/randomize/generation.py +++ b/source/gui/randomize/generation.py @@ -154,7 +154,7 @@ def generation_page(parent,settings): diag.geometry(str(dims["window"]["width"]) + 'x' + str(dims["window"]["height"])) text = Text(diag, width=dims["textarea.characters"]["width"], height=dims["textarea.characters"]["height"]) text.pack() - text.insert(INSERT,"\n".join(diagnostics.output(__version__))) + text.insert(INSERT,"\n".join(diagnostics.output())) # dialog button self.widgets[widget].pieces["button"] = Button(self.widgets[widget].pieces["frame"], text='Run Diagnostics', command=partial(diags)) diff --git a/source/meta/build-dr.py b/source/meta/build-dr.py deleted file mode 100644 index a83c9d56..00000000 --- a/source/meta/build-dr.py +++ /dev/null @@ -1,27 +0,0 @@ -import subprocess -import os -import shutil -import sys - -# Spec file -SPEC_FILE = os.path.join(".", "source", "DungeonRandomizer.spec") - -# Destination is current dir -DEST_DIRECTORY = '.' - -# Check for UPX -if os.path.isdir("upx"): - upx_string = "--upx-dir=upx" -else: - upx_string = "" - -if os.path.isdir("build") and not sys.platform.find("mac") and not sys.platform.find("osx"): - shutil.rmtree("build") - -# Run pyinstaller for DungeonRandomizer -subprocess.run(" ".join([f"pyinstaller {SPEC_FILE} ", - upx_string, - "-y ", - f"--distpath {DEST_DIRECTORY} ", - ]), - shell=True) diff --git a/source/meta/build-gui.py b/source/meta/build-gui.py deleted file mode 100644 index 4986df67..00000000 --- a/source/meta/build-gui.py +++ /dev/null @@ -1,27 +0,0 @@ -import subprocess -import os -import shutil -import sys - -# Spec file -SPEC_FILE = os.path.join(".", "source", "Gui.spec") - -# Destination is current dir -DEST_DIRECTORY = '.' - -# Check for UPX -if os.path.isdir("upx"): - upx_string = "--upx-dir=upx" -else: - upx_string = "" - -if os.path.isdir("build") and not sys.platform.find("mac") and not sys.platform.find("osx"): - shutil.rmtree("build") - -# Run pyinstaller for Gui -subprocess.run(" ".join([f"pyinstaller {SPEC_FILE} ", - upx_string, - "-y ", - f"--distpath {DEST_DIRECTORY} ", - ]), - shell=True) diff --git a/source/meta/build.py b/source/meta/build.py new file mode 100644 index 00000000..4cc29436 --- /dev/null +++ b/source/meta/build.py @@ -0,0 +1,155 @@ +''' +Build Entrypoints +''' +import json +import platform +import os # for checking for dirs +import re +from json.decoder import JSONDecodeError +from subprocess import Popen, PIPE, STDOUT, CalledProcessError + +DEST_DIRECTORY = "." + +# UPX greatly reduces the filesize. You can get this utility from https://upx.github.io/ +# just place it in a subdirectory named "upx" and this script will find it +UPX_DIR = "upx" +if os.path.isdir(os.path.join(".", UPX_DIR)): + upx_string = f"--upx-dir={UPX_DIR}" +else: + upx_string = "" +GO = True +DIFF_DLLS = False + +# set a global var for Actions to try to read +def set_output(name, value): + with open(os.environ['GITHUB_OUTPUT'], 'a') as fh: + print(f'{name}={value}', file=fh) + +# build the thing +def run_build(slug): + global GO + global DIFF_DLLS + + print(f"Building '{slug}' via Python {platform.python_version()}") + + # get template, mod to do the thing + specTemplateFile = open(os.path.join(".","source","Template.spec")) + specTemplate = specTemplateFile.read() + specTemplateFile.close() + with(open(os.path.join(".","source",f"{slug}.spec"), "w")) as specFile: + print(f"Writing '{slug}' PyInstaller spec file") + thisTemplate = specTemplate.replace("", slug) + specFile.write(thisTemplate) + + PYINST_EXECUTABLE = "pyinstaller" + args = [ + os.path.join("source", f"{slug}.spec").replace(os.sep, os.sep * 2), + upx_string, + "-y", + f"--distpath={DEST_DIRECTORY}" + ] + errs = [] + strs = [] + print("PyInstaller args: %s" % " ".join(args)) + cmd = [ + PYINST_EXECUTABLE, + *args + ] + + ret = { + "stdout": [], + "stderr": [] + } + + with Popen(cmd, stdout=PIPE, stderr=STDOUT, bufsize=1, universal_newlines=True) as p: + for line in p.stdout: + ret["stdout"].append(line) + print(line, end='') + # if p.stderr: + # for line in p.stderr: + # ret["stderr"].append(line) + # print(line, end='') + # if p.returncode != 0: + # raise CalledProcessError(p.returncode, p.args) + + # check stdout & stderr + for key in ["stdout","stderr"]: + if len(ret[key]) > 0: + for line in ret[key]: + # UPX can't compress this file + if "NotCompressibleException" in line.strip(): + print(line) + errs.append(line.strip()) + # print UPX messages + if "UPX" in line: + print(line) + # try to get DLL filename + elif "NotCompressibleException" in line.strip(): + matches = re.search(r'api-ms-win-(?:[^-]*)-([^-]*)', line.strip()) + if matches: + strAdd = matches.group(1) + strs.append(strAdd) + errs.append(line.strip()) + # print collected errors + if len(errs) > 0: + print("=" * 10) + print("| ERRORS |") + print("=" * 10) + print("\n".join(errs)) + else: + GO = False + + # if we identified DLLs to ignore + if len(strs) > 0: + # read DLLs manifest that we've already got saved + with open(os.path.join(".","resources","app","meta","manifests","excluded_dlls.json"), "w+", encoding="utf-8") as dllsManifest: + oldDLLs = [] + try: + oldDLLs = json.load(dllsManifest) + except JSONDecodeError as e: + oldDLLs = [] + # raise ValueError("Windows DLLs manifest malformed!") + + # bucket for new list + newDLLs = sorted(list(set(oldDLLs))) + + # items to add + addDLLs = sorted(list(set(strs))) + + # add items + newDLLs += addDLLs + newDLLs = sorted(list(set(newDLLs))) + + # if the lists differ, we've gotta update the included list + diffDLLs = newDLLs != oldDLLs + + if diffDLLs: + DIFF_DLLS = True + dllsManifest.seek(0) + dllsManifest.truncate() + dllsManifest.write(json.dumps(sorted(newDLLs), indent=2)) + + print(f"Old DLLs: {json.dumps(sorted(oldDLLs))}") + print(f"Add DLLs: {json.dumps(sorted(addDLLs))}") + print(f"New DLLs: {json.dumps(sorted(newDLLs))}") + print(f"Diff DLLs: {DIFF_DLLS}") + print("") + +def go_build(slug): + slug = slug or "" + if slug != "": + GO = True + while GO: + run_build(slug) + GO = False + +if __name__ == "__main__": + binary_slugs = [] + #TODO: Make sure we've got the proper binaries that we need + with open(os.path.join(".","resources","app","meta","manifests","binaries.json")) as binariesFile: + binary_slugs = json.load(binariesFile) + for file_slug in binary_slugs: + go_build(file_slug) + if DIFF_DLLS: + print("🔴Had to update Error DLLs list!") + exit(1) diff --git a/source/meta/check_errordlls.py b/source/meta/check_errordlls.py new file mode 100644 index 00000000..b9a56590 --- /dev/null +++ b/source/meta/check_errordlls.py @@ -0,0 +1,10 @@ +import json +import os + +error_dlls_path = os.path.join(".","resources","app","meta","manifests","excluded_dlls.json") +if os.path.isfile(error_dlls_path): + with open(error_dlls_path, "r") as error_dlls_file: + error_dlls_json = json.load(error_dlls_file) + if len(error_dlls_json) > 0 and error_dlls_json[0].strip() != "": + print(error_dlls_json) + # exit(1) diff --git a/source/meta/run_diags.py b/source/meta/run_diags.py new file mode 100644 index 00000000..0424674a --- /dev/null +++ b/source/meta/run_diags.py @@ -0,0 +1,10 @@ +from source.classes import diags as diags + +global VERBOSE +VERBOSE = True + +if __name__ == "__main__": + if VERBOSE: + print("DIAGNOSTICS") + print('.' * 70) + print("\n".join(diags.output())) diff --git a/test/MysteryTestSuite.py b/test/MysteryTestSuite.py index ea155dd8..e14baa61 100644 --- a/test/MysteryTestSuite.py +++ b/test/MysteryTestSuite.py @@ -1,3 +1,4 @@ +import os import subprocess import sys import multiprocessing @@ -8,6 +9,16 @@ from collections import OrderedDict cpu_threads = multiprocessing.cpu_count() py_version = f"{sys.version_info.major}.{sys.version_info.minor}" +PYLINE = "python" +PIPLINE_PATH = os.path.join(".","resources","user","meta","manifests","pipline.txt") +if os.path.isfile(PIPLINE_PATH): + with open(PIPLINE_PATH) as pipline_file: + PYLINE = pipline_file.read().replace("-m pip","").strip() + +results = { + "errors": [], + "success": [] +} def main(args=None): successes = [] @@ -25,7 +36,7 @@ def main(args=None): def test(testname: str, command: str): tests[testname] = [command] - basecommand = f"python3.8 Mystery.py --suppress_rom --suppress_meta" + basecommand = f"{PYLINE} Mystery.py --suppress_rom --suppress_meta" def gen_seed(): taskcommand = basecommand + " " + command @@ -98,6 +109,10 @@ if __name__ == "__main__": cpu_threads = args.cpu_threads + LOGPATH = os.path.join(".","logs") + if not os.path.isdir(LOGPATH): + os.makedirs(LOGPATH) + for dr in [['mystery', args.count if args.count else 1, 1]]: for tense in range(1, dr[2] + 1): @@ -112,13 +127,36 @@ if __name__ == "__main__": print() if errors: - with open(f"{dr[0]}{(f'-{tense}' if dr[0] in ['basic', 'crossed'] else '')}-errors.txt", 'w') as stream: + errors_filename = f"{dr[0]}" + if dr[0] in ["basic","crossed"]: + errors_filename += f"-{tense}" + errors_filename += "-errors.txt" + with open( + os.path.join( + LOGPATH, + errors_filename + ), + 'w' + ) as stream: for error in errors: stream.write(error[0] + "\n") stream.write(error[1] + "\n") stream.write(error[2] + "\n\n") + error[2] = error[2].split("\n") + results["errors"].append(error) - with open("success.txt", "w") as stream: + with open(os.path.join(LOGPATH, "mystery-success.txt"), "w") as stream: stream.write(str.join("\n", successes)) + results["success"] = successes - input("Press enter to continue") + num_errors = len(results["errors"]) + num_success = len(results["success"]) + num_total = num_errors + num_success + + print(f"Errors: {num_errors}/{num_total}") + print(f"Success: {num_success}/{num_total}") + # print(results) + + if (num_errors/num_total) > (num_success/num_total): + # exit(1) + pass diff --git a/test/NewTestSuite.py b/test/NewTestSuite.py index 8e7b9e1c..b389795d 100644 --- a/test/NewTestSuite.py +++ b/test/NewTestSuite.py @@ -10,6 +10,16 @@ from collections import OrderedDict cpu_threads = multiprocessing.cpu_count() py_version = f"{sys.version_info.major}.{sys.version_info.minor}" +PYLINE = "python" +PIPLINE_PATH = os.path.join(".","resources","user","meta","manifests","pipline.txt") +if os.path.isfile(PIPLINE_PATH): + with open(PIPLINE_PATH) as pipline_file: + PYLINE = pipline_file.read().replace("-m pip","").strip() + +results = { + "errors": [], + "success": [] +} def main(args=None): successes = [] @@ -28,7 +38,7 @@ def main(args=None): def test(test_name: str, command: str, test_file: str): tests[test_name] = [command] - base_command = f"python3 DungeonRandomizer.py --suppress_rom --suppress_spoiler" + base_command = f"{PYLINE} DungeonRandomizer.py --suppress_rom --jsonout --spoiler none" def gen_seed(): task_command = base_command + " " + command @@ -102,7 +112,7 @@ if __name__ == "__main__": test_suites = {} # not sure if it supports subdirectories properly yet - for root, dirnames, filenames in os.walk('test/suite'): + for root, dirnames, filenames in os.walk(os.path.join("test","suite")): test_suites[root] = fnmatch.filter(filenames, '*.yaml') args = argparse.Namespace() @@ -113,14 +123,30 @@ if __name__ == "__main__": successes += s print() + LOGPATH = os.path.join(".","logs") + if not os.path.isdir(LOGPATH): + os.makedirs(LOGPATH) + if errors: - with open(f"new-test-suite-errors.txt", 'w') as stream: + with open(os.path.join(LOGPATH, "new-test-suite-errors.txt"), 'w') as stream: for error in errors: stream.write(error[0] + "\n") stream.write(error[1] + "\n") stream.write(error[2] + "\n\n") + error[2] = error[2].split("\n") + results["errors"].append(error) with open("new-test-suite-success.txt", "w") as stream: stream.write(str.join("\n", successes)) + results["success"] = successes - input("Press enter to continue") + num_errors = len(results["errors"]) + num_success = len(results["success"]) + num_total = num_errors + num_success + + print(f"Errors: {num_errors}/{num_total}") + print(f"Success: {num_success}/{num_total}") + # print(results) + + if (num_errors/num_total) > (num_success/num_total): + exit(1) From e7819de9c8e23351a7b435d5e435391dbede6a54 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Thu, 23 May 2024 19:56:39 -0500 Subject: [PATCH 37/41] Further config of GH Actions for OWR --- .github/workflows/release-create.yml | 12 ++++-------- Text.py | 6 +++--- build-app_version.py | 4 ++-- resources/ci/common/list_actions.py | 4 ++-- 4 files changed, 11 insertions(+), 15 deletions(-) diff --git a/.github/workflows/release-create.yml b/.github/workflows/release-create.yml index 981f1f75..5942ff1b 100644 --- a/.github/workflows/release-create.yml +++ b/.github/workflows/release-create.yml @@ -359,10 +359,6 @@ jobs: echo "Windows Asset: ${{ steps.identify-windows-asset.outputs.asset_windows }}" # create a release (MASTER) - #TODO: Make sure we updated RELEASENOTES.md - #TODO: Make sure we're firing on the proper branches - # if: contains(github.ref, 'master') # branch or tag name - # if: contains(github.event.head_commit.message, 'Version bump') # commit message - name: 📀->🚀Create a Release (MASTER) id: create_release uses: actions/create-release@v1.1.4 @@ -373,7 +369,7 @@ jobs: release_name: ${{ steps.debug_info.outputs.release_name }} body_path: RELEASENOTES.md # draft: true - if: contains(github.ref, 'master') + if: contains(github.ref, 'OverworldShuffle') && contains(github.event.head_commit.message, 'Version bump') # branch/tag name and commit message # upload linux archive asset (MASTER) #TODO: Make sure we're firing on the proper branches @@ -387,7 +383,7 @@ jobs: asset_path: ${{ steps.parentDir.outputs.parentDir }}/deploy/linux/${{ steps.identify-linux-asset.outputs.asset_linux }} asset_name: ${{ steps.debug_info.outputs.asset_prefix }}-linux-focal.tar.gz asset_content_type: application/gzip - if: contains(github.ref, 'master') + if: contains(github.ref, 'OverworldShuffle') && contains(github.event.head_commit.message, 'Version bump') # branch/tag name and commit message # upload macos archive asset (MASTER) #TODO: Make sure we're firing on the proper branches @@ -401,7 +397,7 @@ jobs: asset_path: ${{ steps.parentDir.outputs.parentDir }}/deploy/macos/${{ steps.identify-macos-asset.outputs.asset_macos }} asset_name: ${{ steps.debug_info.outputs.asset_prefix }}-osx.tar.gz asset_content_type: application/gzip - if: contains(github.ref, 'master') + if: contains(github.ref, 'OverworldShuffle') && contains(github.event.head_commit.message, 'Version bump') # branch/tag name and commit message # upload windows archive asset (MASTER) #TODO: Make sure we're firing on the proper branches @@ -415,4 +411,4 @@ jobs: asset_path: ${{ steps.parentDir.outputs.parentDir }}/deploy/windows/${{ steps.identify-windows-asset.outputs.asset_windows }} asset_name: ${{ steps.debug_info.outputs.asset_prefix }}-windows.zip asset_content_type: application/zip - if: contains(github.ref, 'master') + if: contains(github.ref, 'OverworldShuffle') && contains(github.event.head_commit.message, 'Version bump') # branch/tag name and commit message diff --git a/Text.py b/Text.py index b2753011..08756f8a 100644 --- a/Text.py +++ b/Text.py @@ -108,18 +108,18 @@ Triforce_texts = [ " Whelp…\n that just\n happened", " Oh hey…\n it's you", "\n Wheeeeee!!", - " Time for\n another one?", + " Time for\n another one?", " And\n\n scene", "\n GOT EM!!", "\n THE VALUUUE!!!", " Cool seed,\n\n right?", "\n We did it!", " Spam those\n emotes in\n wilds chat", - "\n O M G", + "\n O M G", " Hello. Will you\n you be my friend?", " Beetorp\n was\n here!", " The Wind Fish\n will wake soon.\n Hoot!", - " Meow Meow Meow\n Meow Meow Meow\n Oh my god!", + " Meow Meow Meow\n Meow Meow Meow\n Oh My God!", " Ahhhhhhhhh\n Ya ya yaaaah\n Ya ya yaaah", " .done\n\n .comment lol", " You get to\n drink from\n the firehose", diff --git a/build-app_version.py b/build-app_version.py index 6d63f3ca..69332cba 100644 --- a/build-app_version.py +++ b/build-app_version.py @@ -1,5 +1,5 @@ -from Main import __version__ as DRVersion +from OverworldShuffle import __version__ as OWVersion import os with(open(os.path.join("resources","app","meta","manifests","app_version.txt"),"w+")) as f: - f.write(DRVersion) + f.write(OWVersion) diff --git a/resources/ci/common/list_actions.py b/resources/ci/common/list_actions.py index 4aab7812..ec4caf5f 100644 --- a/resources/ci/common/list_actions.py +++ b/resources/ci/common/list_actions.py @@ -104,7 +104,7 @@ for r, d, f in os.walk(os.path.join(".", ".github")): apiRes = json.loads( apiReq.read().decode("utf-8")) except JSONDecodeError as e: - raise ValueError("🔴API Request failed: " + apiURL) + raise ValueError("API Request failed: " + apiURL) if apiRes: latest = apiRes["tag_name"] if "tag_name" in apiRes else "" if latest != "": @@ -143,7 +143,7 @@ for action, actionData in allACTIONS.items(): filename_line + " " ) - print("| 🔴Outdated |") + print("| Outdated |") print( "|" + filename_line + From e15ac6b9a4f7a621c5627e82f61c3ec15189c37c Mon Sep 17 00:00:00 2001 From: codemann8 Date: Fri, 24 May 2024 09:41:54 -0500 Subject: [PATCH 38/41] Fixed moved door_addresses --- source/overworld/EntranceData.py | 282 +++++++++++++++---------------- 1 file changed, 141 insertions(+), 141 deletions(-) diff --git a/source/overworld/EntranceData.py b/source/overworld/EntranceData.py index 4e6636e8..a9fe0b2a 100644 --- a/source/overworld/EntranceData.py +++ b/source/overworld/EntranceData.py @@ -4,148 +4,148 @@ # | ([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) -door_addresses = {'Links House': (0x00, (0x0104, 0x2c, 0x0506, 0x0a9a, 0x0832, 0x0ae8, 0x08b8, 0x0b07, 0x08bf, 0x06, 0xfe, 0x0816, 0x0000)), - 'Desert Palace Entrance (South)': (0x08, (0x0084, 0x30, 0x0314, 0x0c56, 0x00a6, 0x0ca8, 0x0128, 0x0cc3, 0x0133, 0x0a, 0xfa, 0x0000, 0x0000)), - 'Desert Palace Entrance (West)': (0x0A, (0x0083, 0x30, 0x0280, 0x0c46, 0x0003, 0x0c98, 0x0088, 0x0cb3, 0x0090, 0x0a, 0xfd, 0x0000, 0x0000)), - 'Desert Palace Entrance (North)': (0x0B, (0x0063, 0x30, 0x0016, 0x0c00, 0x00a2, 0x0c28, 0x0128, 0x0c6d, 0x012f, 0x00, 0x0e, 0x0000, 0x0000)), - 'Desert Palace Entrance (East)': (0x09, (0x0085, 0x30, 0x02a8, 0x0c4a, 0x0142, 0x0c98, 0x01c8, 0x0cb7, 0x01cf, 0x06, 0xfe, 0x0000, 0x0000)), - 'Eastern Palace': (0x07, (0x00c9, 0x1e, 0x005a, 0x0600, 0x0ed6, 0x0618, 0x0f50, 0x066d, 0x0f5b, 0x00, 0xfa, 0x0000, 0x0000)), - 'Tower of Hera': (0x32, (0x0077, 0x03, 0x0050, 0x0014, 0x087c, 0x0068, 0x08f0, 0x0083, 0x08fb, 0x0a, 0xf4, 0x0000, 0x0000)), - 'Hyrule Castle Entrance (South)': (0x03, (0x0061, 0x1b, 0x0530, 0x0692, 0x0784, 0x06cc, 0x07f8, 0x06ff, 0x0803, 0x0e, 0xfa, 0x0000, 0x87be)), - 'Hyrule Castle Entrance (West)': (0x02, (0x0060, 0x1b, 0x0016, 0x0600, 0x06ae, 0x0604, 0x0728, 0x066d, 0x0733, 0x00, 0x02, 0x0000, 0x8124)), - 'Hyrule Castle Entrance (East)': (0x04, (0x0062, 0x1b, 0x004a, 0x0600, 0x0856, 0x0604, 0x08c8, 0x066d, 0x08d3, 0x00, 0xfa, 0x0000, 0x8158)), - 'Inverted Pyramid Entrance': (0x35, (0x0010, 0x1b, 0x0418, 0x0679, 0x06b4, 0x06c6, 0x0728, 0x06e6, 0x0733, 0x07, 0xf9, 0x0000, 0x0000)), - 'Agahnims Tower': (0x23, (0x00e0, 0x1b, 0x0032, 0x0600, 0x0784, 0x0634, 0x07f8, 0x066d, 0x0803, 0x00, 0x0a, 0x0000, 0x82be)), - 'Thieves Town': (0x33, (0x00db, 0x58, 0x0b2e, 0x075a, 0x0176, 0x07a8, 0x01f8, 0x07c7, 0x0203, 0x06, 0xfa, 0x0000, 0x0000)), - 'Skull Woods First Section Door': (0x29, (0x0058, 0x40, 0x0f4c, 0x01f6, 0x0262, 0x0248, 0x02e8, 0x0263, 0x02ef, 0x0a, 0xfe, 0x0000, 0x0000)), - 'Skull Woods Second Section Door (East)': (0x28, (0x0057, 0x40, 0x0eb8, 0x01e6, 0x01c2, 0x0238, 0x0248, 0x0253, 0x024f, 0x0a, 0xfe, 0x0000, 0x0000)), - 'Skull Woods Second Section Door (West)': (0x27, (0x0056, 0x40, 0x0c8e, 0x01a6, 0x0062, 0x01f8, 0x00e8, 0x0213, 0x00ef, 0x0a, 0x0e, 0x0000, 0x0000)), - 'Skull Woods Final Section': (0x2A, (0x0059, 0x40, 0x0282, 0x0066, 0x0016, 0x00b8, 0x0098, 0x00d3, 0x00a3, 0x0a, 0xfa, 0x0000, 0x0000)), - 'Ice Palace': (0x2C, (0x000e, 0x75, 0x0bc6, 0x0d6a, 0x0c3e, 0x0db8, 0x0cb8, 0x0dd7, 0x0cc3, 0x06, 0xf2, 0x0000, 0x0000)), - 'Misery Mire': (0x26, (0x0098, 0x70, 0x0414, 0x0c79, 0x00a6, 0x0cc7, 0x0128, 0x0ce6, 0x0133, 0x07, 0xfa, 0x0000, 0x0000)), - 'Palace of Darkness': (0x25, (0x004a, 0x5e, 0x005a, 0x0600, 0x0ed6, 0x0628, 0x0f50, 0x066d, 0x0f5b, 0x00, 0xfa, 0x0000, 0x0000)), - 'Swamp Palace': (0x24, (0x0028, 0x7b, 0x049e, 0x0e8c, 0x06f2, 0x0ed8, 0x0778, 0x0ef9, 0x077f, 0x04, 0xfe, 0x0000, 0x0000)), - 'Turtle Rock': (0x34, (0x00d6, 0x47, 0x0712, 0x00da, 0x0e96, 0x0128, 0x0f08, 0x0147, 0x0f13, 0x06, 0xfa, 0x0000, 0x0000)), - 'Dark Death Mountain Ledge (West)': (0x14, (0x0023, 0x45, 0x07ca, 0x0103, 0x0c46, 0x0157, 0x0cb8, 0x0172, 0x0cc3, 0x0b, 0x0a, 0x0000, 0x0000)), - 'Dark Death Mountain Ledge (East)': (0x18, (0x0024, 0x45, 0x07e0, 0x0103, 0x0d00, 0x0157, 0x0d78, 0x0172, 0x0d7d, 0x0b, 0x00, 0x0000, 0x0000)), - 'Turtle Rock Isolated Ledge Entrance': (0x17, (0x00d5, 0x45, 0x0ad4, 0x0164, 0x0ca6, 0x01b8, 0x0d18, 0x01d3, 0x0d23, 0x0a, 0xfa, 0x0000, 0x0000)), - 'Hyrule Castle Secret Entrance Stairs': (0x31, (0x0055, 0x1b, 0x044a, 0x067a, 0x0854, 0x06c8, 0x08c8, 0x06e7, 0x08d3, 0x06, 0xfa, 0x0000, 0x0000)), - 'Kakariko Well Cave': (0x38, (0x002f, 0x18, 0x0386, 0x0665, 0x0032, 0x06b7, 0x00b8, 0x06d2, 0x00bf, 0x0b, 0xfe, 0x0000, 0x0000)), - 'Bat Cave Cave': (0x10, (0x00e3, 0x22, 0x0412, 0x087a, 0x048e, 0x08c8, 0x0508, 0x08e7, 0x0513, 0x06, 0x02, 0x0000, 0x0000)), - 'Elder House (East)': (0x0D, (0x00f3, 0x18, 0x02c4, 0x064a, 0x0222, 0x0698, 0x02a8, 0x06b7, 0x02af, 0x06, 0xfe, 0x05d4, 0x0000)), - 'Elder House (West)': (0x0C, (0x00f2, 0x18, 0x02bc, 0x064c, 0x01e2, 0x0698, 0x0268, 0x06b9, 0x026f, 0x04, 0xfe, 0x05cc, 0x0000)), - 'North Fairy Cave': (0x37, (0x0008, 0x15, 0x0088, 0x0400, 0x0a36, 0x0448, 0x0aa8, 0x046f, 0x0ab3, 0x00, 0x0a, 0x0000, 0x0000)), - 'Lost Woods Hideout Stump': (0x2B, (0x00e1, 0x00, 0x0f4e, 0x01f6, 0x0262, 0x0248, 0x02e8, 0x0263, 0x02ef, 0x0a, 0x0e, 0x0000, 0x0000)), - 'Lumberjack Tree Cave': (0x11, (0x00e2, 0x02, 0x0118, 0x0015, 0x04c6, 0x0067, 0x0548, 0x0082, 0x0553, 0x0b, 0xfa, 0x0000, 0x0000)), - 'Two Brothers House (East)': (0x0F, (0x00f5, 0x29, 0x0880, 0x0b07, 0x0200, 0x0b58, 0x0238, 0x0b74, 0x028d, 0x09, 0x00, 0x0b86, 0x0000)), - 'Two Brothers House (West)': (0x0E, (0x00f4, 0x28, 0x08a0, 0x0b06, 0x0100, 0x0b58, 0x01b8, 0x0b73, 0x018d, 0x0a, 0x00, 0x0bb6, 0x0000)), - 'Sanctuary': (0x01, (0x0012, 0x13, 0x001c, 0x0400, 0x06de, 0x0414, 0x0758, 0x046d, 0x0763, 0x00, 0x02, 0x0000, 0x01aa)), - 'Old Man Cave (West)': (0x05, (0x00f0, 0x0a, 0x03a0, 0x0264, 0x0500, 0x02b8, 0x05a8, 0x02d3, 0x058d, 0x0a, 0x00, 0x0000, 0x0000)), - 'Old Man Cave (East)': (0x06, (0x00f1, 0x03, 0x1402, 0x0294, 0x0604, 0x02e8, 0x0678, 0x0303, 0x0683, 0x0a, 0xfc, 0x0000, 0x0000)), - 'Old Man House (Bottom)': (0x2F, (0x00e4, 0x03, 0x181a, 0x031e, 0x06b4, 0x03a7, 0x0728, 0x038d, 0x0733, 0x00, 0x0c, 0x0000, 0x0000)), - 'Old Man House (Top)': (0x30, (0x00e5, 0x03, 0x10c6, 0x0224, 0x0814, 0x0278, 0x0888, 0x0293, 0x0893, 0x0a, 0x0c, 0x0000, 0x0000)), - 'Death Mountain Return Cave (East)': (0x2E, (0x00e7, 0x03, 0x0d82, 0x01c4, 0x0600, 0x0218, 0x0648, 0x0233, 0x067f, 0x0a, 0x00, 0x0000, 0x0000)), - 'Death Mountain Return Cave (West)': (0x2D, (0x00e6, 0x0a, 0x00a0, 0x0205, 0x0500, 0x0257, 0x05b8, 0x0272, 0x058d, 0x0b, 0x00, 0x0000, 0x0000)), - 'Spectacle Rock Cave Peak': (0x22, (0x00ea, 0x03, 0x092c, 0x0133, 0x0754, 0x0187, 0x07c8, 0x01a2, 0x07d3, 0x0b, 0xfc, 0x0000, 0x0000)), - 'Spectacle Rock Cave': (0x21, (0x00fa, 0x03, 0x0eac, 0x01e3, 0x0754, 0x0237, 0x07c8, 0x0252, 0x07d3, 0x0b, 0xfc, 0x0000, 0x0000)), - 'Spectacle Rock Cave (Bottom)': (0x20, (0x00f9, 0x03, 0x0d9c, 0x01c3, 0x06d4, 0x0217, 0x0748, 0x0232, 0x0753, 0x0b, 0xfc, 0x0000, 0x0000)), - 'Paradox Cave (Bottom)': (0x1D, (0x00ff, 0x05, 0x0ee0, 0x01e3, 0x0d00, 0x0237, 0x0da8, 0x0252, 0x0d7d, 0x0b, 0x00, 0x0000, 0x0000)), - 'Paradox Cave (Middle)': (0x1E, (0x00ef, 0x05, 0x17e0, 0x0304, 0x0d00, 0x0358, 0x0dc8, 0x0373, 0x0d7d, 0x0a, 0x00, 0x0000, 0x0000)), - 'Paradox Cave (Top)': (0x1F, (0x00df, 0x05, 0x0460, 0x0093, 0x0d00, 0x00e7, 0x0db8, 0x0102, 0x0d7d, 0x0b, 0x00, 0x0000, 0x0000)), - 'Fairy Ascension Cave (Bottom)': (0x19, (0x00fd, 0x05, 0x0dd4, 0x01c4, 0x0ca6, 0x0218, 0x0d18, 0x0233, 0x0d23, 0x0a, 0xfa, 0x0000, 0x0000)), - 'Fairy Ascension Cave (Top)': (0x1A, (0x00ed, 0x05, 0x0ad4, 0x0163, 0x0ca6, 0x01b7, 0x0d18, 0x01d2, 0x0d23, 0x0b, 0xfa, 0x0000, 0x0000)), - 'Spiral Cave': (0x1C, (0x00ee, 0x05, 0x07c8, 0x0108, 0x0c46, 0x0158, 0x0cb8, 0x0177, 0x0cc3, 0x06, 0xfa, 0x0000, 0x0000)), - 'Spiral Cave (Bottom)': (0x1B, (0x00fe, 0x05, 0x0cca, 0x01a3, 0x0c56, 0x01f7, 0x0cc8, 0x0212, 0x0cd3, 0x0b, 0xfa, 0x0000, 0x0000)), - 'Bumper Cave (Bottom)': (0x15, (0x00fb, 0x4a, 0x03a0, 0x0263, 0x0500, 0x02b7, 0x05a8, 0x02d2, 0x058d, 0x0b, 0x00, 0x0000, 0x0000)), - 'Bumper Cave (Top)': (0x16, (0x00eb, 0x4a, 0x00a0, 0x020a, 0x0500, 0x0258, 0x05b8, 0x0277, 0x058d, 0x06, 0x00, 0x0000, 0x0000)), - 'Superbunny Cave (Top)': (0x13, (0x00e8, 0x45, 0x0460, 0x0093, 0x0d00, 0x00e7, 0x0db8, 0x0102, 0x0d7d, 0x0b, 0x00, 0x0000, 0x0000)), - 'Superbunny Cave (Bottom)': (0x12, (0x00f8, 0x45, 0x0ee0, 0x01e4, 0x0d00, 0x0238, 0x0d78, 0x0253, 0x0d7d, 0x0a, 0x00, 0x0000, 0x0000)), - 'Hookshot Cave': (0x39, (0x003c, 0x45, 0x04da, 0x00a3, 0x0cd6, 0x0107, 0x0d48, 0x0112, 0x0d53, 0x0b, 0xfa, 0x0000, 0x0000)), - 'Hookshot Cave Back Entrance': (0x3A, (0x002c, 0x45, 0x004c, 0x0000, 0x0c56, 0x0038, 0x0cc8, 0x006f, 0x0cd3, 0x00, 0x0a, 0x0000, 0x0000)), - 'Ganons Tower': (0x36, (0x000c, 0x43, 0x0052, 0x0000, 0x0884, 0x0028, 0x08f8, 0x006f, 0x0903, 0x00, 0xfc, 0x0000, 0x0000)), - 'Pyramid Entrance': (0x35, (0x0010, 0x5b, 0x0b0e, 0x075a, 0x0674, 0x07a8, 0x06e8, 0x07c7, 0x06f3, 0x06, 0xfa, 0x0000, 0x0000)), - 'Skull Woods First Section Hole (West)': ([0xDB84D, 0xDB84E], None), - 'Skull Woods First Section Hole (East)': ([0xDB84F, 0xDB850], None), +door_addresses = {'Links House': (0x00, (0x0104, 0x2c, 0x0506, 0x0a9a, 0x0832, 0x0ae8, 0x08b8, 0x0b07, 0x08bf, 0x06, 0xfe, 0x0816, 0x0000), 0x00), + 'Desert Palace Entrance (South)': (0x08, (0x0084, 0x30, 0x0314, 0x0c56, 0x00a6, 0x0ca8, 0x0128, 0x0cc3, 0x0133, 0x0a, 0xfa, 0x0000, 0x0000), 0x00), + 'Desert Palace Entrance (West)': (0x0A, (0x0083, 0x30, 0x0280, 0x0c46, 0x0003, 0x0c98, 0x0088, 0x0cb3, 0x0090, 0x0a, 0xfd, 0x0000, 0x0000), 0x00), + 'Desert Palace Entrance (North)': (0x0B, (0x0063, 0x30, 0x0016, 0x0c00, 0x00a2, 0x0c28, 0x0128, 0x0c6d, 0x012f, 0x00, 0x0e, 0x0000, 0x0000), 0x00), + 'Desert Palace Entrance (East)': (0x09, (0x0085, 0x30, 0x02a8, 0x0c4a, 0x0142, 0x0c98, 0x01c8, 0x0cb7, 0x01cf, 0x06, 0xfe, 0x0000, 0x0000), 0x00), + 'Eastern Palace': (0x07, (0x00c9, 0x1e, 0x005a, 0x0600, 0x0ed6, 0x0618, 0x0f50, 0x066d, 0x0f5b, 0x00, 0xfa, 0x0000, 0x0000), 0x00), + 'Tower of Hera': (0x32, (0x0077, 0x03, 0x0050, 0x0014, 0x087c, 0x0068, 0x08f0, 0x0083, 0x08fb, 0x0a, 0xf4, 0x0000, 0x0000), 0x00), + 'Hyrule Castle Entrance (South)': (0x03, (0x0061, 0x1b, 0x0530, 0x0692, 0x0784, 0x06cc, 0x07f8, 0x06ff, 0x0803, 0x0e, 0xfa, 0x0000, 0x87be), 0x00), + 'Hyrule Castle Entrance (West)': (0x02, (0x0060, 0x1b, 0x0016, 0x0600, 0x06ae, 0x0604, 0x0728, 0x066d, 0x0733, 0x00, 0x02, 0x0000, 0x8124), 0x00), + 'Hyrule Castle Entrance (East)': (0x04, (0x0062, 0x1b, 0x004a, 0x0600, 0x0856, 0x0604, 0x08c8, 0x066d, 0x08d3, 0x00, 0xfa, 0x0000, 0x8158), 0x00), + 'Inverted Pyramid Entrance': (0x35, (0x0010, 0x1b, 0x000e, 0x0600, 0x0676, 0x0604, 0x06e8, 0x066d, 0x06f3, 0x00, 0x0a, 0x0000, 0x811c), 0x00), + 'Agahnims Tower': (0x23, (0x00e0, 0x1b, 0x0032, 0x0600, 0x0784, 0x0634, 0x07f8, 0x066d, 0x0803, 0x00, 0x0a, 0x0000, 0x82be), 0x40), + 'Thieves Town': (0x33, (0x00db, 0x58, 0x0b2e, 0x075a, 0x0176, 0x07a8, 0x01f8, 0x07c7, 0x0203, 0x06, 0xfa, 0x0000, 0x0000), 0x20), + 'Skull Woods First Section Door': (0x29, (0x0058, 0x40, 0x0f4c, 0x01f6, 0x0262, 0x0248, 0x02e8, 0x0263, 0x02ef, 0x0a, 0xfe, 0x0000, 0x0000), 0x00), + 'Skull Woods Second Section Door (East)': (0x28, (0x0057, 0x40, 0x0eb8, 0x01e6, 0x01c2, 0x0238, 0x0248, 0x0253, 0x024f, 0x0a, 0xfe, 0x0000, 0x0000), 0x00), + 'Skull Woods Second Section Door (West)': (0x27, (0x0056, 0x40, 0x0c8e, 0x01a6, 0x0062, 0x01f8, 0x00e8, 0x0213, 0x00ef, 0x0a, 0x0e, 0x0000, 0x0000), 0x00), + 'Skull Woods Final Section': (0x2A, (0x0059, 0x40, 0x0282, 0x0066, 0x0016, 0x00b8, 0x0098, 0x00d3, 0x00a3, 0x0a, 0xfa, 0x0000, 0x0000), 0x20), + 'Ice Palace': (0x2C, (0x000e, 0x75, 0x0bc6, 0x0d6a, 0x0c3e, 0x0db8, 0x0cb8, 0x0dd7, 0x0cc3, 0x06, 0xf2, 0x0000, 0x0000), 0x00), + 'Misery Mire': (0x26, (0x0098, 0x70, 0x0414, 0x0c79, 0x00a6, 0x0cc7, 0x0128, 0x0ce6, 0x0133, 0x07, 0xfa, 0x0000, 0x0000), 0x20), + 'Palace of Darkness': (0x25, (0x004a, 0x5e, 0x005a, 0x0600, 0x0ed6, 0x0628, 0x0f50, 0x066d, 0x0f5b, 0x00, 0xfa, 0x0000, 0x0000), 0x20), + 'Swamp Palace': (0x24, (0x0028, 0x7b, 0x049e, 0x0e8c, 0x06f2, 0x0ed8, 0x0778, 0x0ef9, 0x077f, 0x04, 0xfe, 0x0000, 0x0000), 0x00), + 'Turtle Rock': (0x34, (0x00d6, 0x47, 0x0712, 0x00da, 0x0e96, 0x0128, 0x0f08, 0x0147, 0x0f13, 0x06, 0xfa, 0x0000, 0x0000), 0x20), + 'Dark Death Mountain Ledge (West)': (0x14, (0x0023, 0x45, 0x07ca, 0x0103, 0x0c46, 0x0157, 0x0cb8, 0x0172, 0x0cc3, 0x0b, 0x0a, 0x0000, 0x0000), 0x00), + 'Dark Death Mountain Ledge (East)': (0x18, (0x0024, 0x45, 0x07e0, 0x0103, 0x0d00, 0x0157, 0x0d78, 0x0172, 0x0d7d, 0x0b, 0x00, 0x0000, 0x0000), 0x00), + 'Turtle Rock Isolated Ledge Entrance': (0x17, (0x00d5, 0x45, 0x0ad4, 0x0164, 0x0ca6, 0x01b8, 0x0d18, 0x01d3, 0x0d23, 0x0a, 0xfa, 0x0000, 0x0000), 0x00), + 'Hyrule Castle Secret Entrance Stairs': (0x31, (0x0055, 0x1b, 0x044a, 0x067a, 0x0854, 0x06c8, 0x08c8, 0x06e7, 0x08d3, 0x06, 0xfa, 0x0000, 0x0000), 0x00), + 'Kakariko Well Cave': (0x38, (0x002f, 0x18, 0x0386, 0x0665, 0x0032, 0x06b7, 0x00b8, 0x06d2, 0x00bf, 0x0b, 0xfe, 0x0000, 0x0000), 0x00), + 'Bat Cave Cave': (0x10, (0x00e3, 0x22, 0x0412, 0x087a, 0x048e, 0x08c8, 0x0508, 0x08e7, 0x0513, 0x06, 0x02, 0x0000, 0x0000), 0x00), + 'Elder House (East)': (0x0D, (0x00f3, 0x18, 0x02c4, 0x064a, 0x0222, 0x0698, 0x02a8, 0x06b7, 0x02af, 0x06, 0xfe, 0x05d4, 0x0000), 0x00), + 'Elder House (West)': (0x0C, (0x00f2, 0x18, 0x02bc, 0x064c, 0x01e2, 0x0698, 0x0268, 0x06b9, 0x026f, 0x04, 0xfe, 0x05cc, 0x0000), 0x00), + 'North Fairy Cave': (0x37, (0x0008, 0x15, 0x0088, 0x0400, 0x0a36, 0x0448, 0x0aa8, 0x046f, 0x0ab3, 0x00, 0x0a, 0x0000, 0x0000), 0x00), + 'Lost Woods Hideout Stump': (0x2B, (0x00e1, 0x00, 0x0f4e, 0x01f6, 0x0262, 0x0248, 0x02e8, 0x0263, 0x02ef, 0x0a, 0x0e, 0x0000, 0x0000), 0x00), + 'Lumberjack Tree Cave': (0x11, (0x00e2, 0x02, 0x0118, 0x0015, 0x04c6, 0x0067, 0x0548, 0x0082, 0x0553, 0x0b, 0xfa, 0x0000, 0x0000), 0x00), + 'Two Brothers House (East)': (0x0F, (0x00f5, 0x29, 0x0880, 0x0b07, 0x0200, 0x0b58, 0x0238, 0x0b74, 0x028d, 0x09, 0x00, 0x0b86, 0x0000), 0x00), + 'Two Brothers House (West)': (0x0E, (0x00f4, 0x28, 0x08a0, 0x0b06, 0x0100, 0x0b58, 0x01b8, 0x0b73, 0x018d, 0x0a, 0x00, 0x0bb6, 0x0000), 0x00), + 'Sanctuary': (0x01, (0x0012, 0x13, 0x001c, 0x0400, 0x06de, 0x0414, 0x0758, 0x046d, 0x0763, 0x00, 0x02, 0x0000, 0x01aa), 0x00), + 'Old Man Cave (West)': (0x05, (0x00f0, 0x0a, 0x03a0, 0x0264, 0x0500, 0x02b8, 0x05a8, 0x02d3, 0x058d, 0x0a, 0x00, 0x0000, 0x0000), 0x00), + 'Old Man Cave (East)': (0x06, (0x00f1, 0x03, 0x1402, 0x0294, 0x0604, 0x02e8, 0x0678, 0x0303, 0x0683, 0x0a, 0xfc, 0x0000, 0x0000), 0x00), + 'Old Man House (Bottom)': (0x2F, (0x00e4, 0x03, 0x181a, 0x031e, 0x06b4, 0x03a7, 0x0728, 0x038d, 0x0733, 0x00, 0x0c, 0x0000, 0x0000), 0x00), + 'Old Man House (Top)': (0x30, (0x00e5, 0x03, 0x10c6, 0x0224, 0x0814, 0x0278, 0x0888, 0x0293, 0x0893, 0x0a, 0x0c, 0x0000, 0x0000), 0x00), + 'Death Mountain Return Cave (East)': (0x2E, (0x00e7, 0x03, 0x0d82, 0x01c4, 0x0600, 0x0218, 0x0648, 0x0233, 0x067f, 0x0a, 0x00, 0x0000, 0x0000), 0x00), + 'Death Mountain Return Cave (West)': (0x2D, (0x00e6, 0x0a, 0x00a0, 0x0205, 0x0500, 0x0257, 0x05b8, 0x0272, 0x058d, 0x0b, 0x00, 0x0000, 0x0000), 0x00), + 'Spectacle Rock Cave Peak': (0x22, (0x00ea, 0x03, 0x092c, 0x0133, 0x0754, 0x0187, 0x07c8, 0x01a2, 0x07d3, 0x0b, 0xfc, 0x0000, 0x0000), 0x00), + 'Spectacle Rock Cave': (0x21, (0x00fa, 0x03, 0x0eac, 0x01e3, 0x0754, 0x0237, 0x07c8, 0x0252, 0x07d3, 0x0b, 0xfc, 0x0000, 0x0000), 0x00), + 'Spectacle Rock Cave (Bottom)': (0x20, (0x00f9, 0x03, 0x0d9c, 0x01c3, 0x06d4, 0x0217, 0x0748, 0x0232, 0x0753, 0x0b, 0xfc, 0x0000, 0x0000), 0x00), + 'Paradox Cave (Bottom)': (0x1D, (0x00ff, 0x05, 0x0ee0, 0x01e3, 0x0d00, 0x0237, 0x0da8, 0x0252, 0x0d7d, 0x0b, 0x00, 0x0000, 0x0000), 0x00), + 'Paradox Cave (Middle)': (0x1E, (0x00ef, 0x05, 0x17e0, 0x0304, 0x0d00, 0x0358, 0x0dc8, 0x0373, 0x0d7d, 0x0a, 0x00, 0x0000, 0x0000), 0x00), + 'Paradox Cave (Top)': (0x1F, (0x00df, 0x05, 0x0460, 0x0093, 0x0d00, 0x00e7, 0x0db8, 0x0102, 0x0d7d, 0x0b, 0x00, 0x0000, 0x0000), 0x00), + 'Fairy Ascension Cave (Bottom)': (0x19, (0x00fd, 0x05, 0x0dd4, 0x01c4, 0x0ca6, 0x0218, 0x0d18, 0x0233, 0x0d23, 0x0a, 0xfa, 0x0000, 0x0000), 0x00), + 'Fairy Ascension Cave (Top)': (0x1A, (0x00ed, 0x05, 0x0ad4, 0x0163, 0x0ca6, 0x01b7, 0x0d18, 0x01d2, 0x0d23, 0x0b, 0xfa, 0x0000, 0x0000), 0x00), + 'Spiral Cave': (0x1C, (0x00ee, 0x05, 0x07c8, 0x0108, 0x0c46, 0x0158, 0x0cb8, 0x0177, 0x0cc3, 0x06, 0xfa, 0x0000, 0x0000), 0x00), + 'Spiral Cave (Bottom)': (0x1B, (0x00fe, 0x05, 0x0cca, 0x01a3, 0x0c56, 0x01f7, 0x0cc8, 0x0212, 0x0cd3, 0x0b, 0xfa, 0x0000, 0x0000), 0x00), + 'Bumper Cave (Bottom)': (0x15, (0x00fb, 0x4a, 0x03a0, 0x0263, 0x0500, 0x02b7, 0x05a8, 0x02d2, 0x058d, 0x0b, 0x00, 0x0000, 0x0000), 0x00), + 'Bumper Cave (Top)': (0x16, (0x00eb, 0x4a, 0x00a0, 0x020a, 0x0500, 0x0258, 0x05b8, 0x0277, 0x058d, 0x06, 0x00, 0x0000, 0x0000), 0x00), + 'Superbunny Cave (Top)': (0x13, (0x00e8, 0x45, 0x0460, 0x0093, 0x0d00, 0x00e7, 0x0db8, 0x0102, 0x0d7d, 0x0b, 0x00, 0x0000, 0x0000), 0x00), + 'Superbunny Cave (Bottom)': (0x12, (0x00f8, 0x45, 0x0ee0, 0x01e4, 0x0d00, 0x0238, 0x0d78, 0x0253, 0x0d7d, 0x0a, 0x00, 0x0000, 0x0000), 0x00), + 'Hookshot Cave': (0x39, (0x003c, 0x45, 0x04da, 0x00a3, 0x0cd6, 0x0107, 0x0d48, 0x0112, 0x0d53, 0x0b, 0xfa, 0x0000, 0x0000), 0x20), + 'Hookshot Cave Back Entrance': (0x3A, (0x002c, 0x45, 0x004c, 0x0000, 0x0c56, 0x0038, 0x0cc8, 0x006f, 0x0cd3, 0x00, 0x0a, 0x0000, 0x0000), 0x00), + 'Ganons Tower': (0x36, (0x000c, 0x43, 0x0052, 0x0000, 0x0884, 0x0028, 0x08f8, 0x006f, 0x0903, 0x00, 0xfc, 0x0000, 0x0000), 0x20), + 'Pyramid Entrance': (0x35, (0x0010, 0x5b, 0x0b0e, 0x075a, 0x0674, 0x07a8, 0x06e8, 0x07c7, 0x06f3, 0x06, 0xfa, 0x0000, 0x0000), 0x00), + 'Skull Woods First Section Hole (West)': ([0xDB84D, 0xDB84E], None), + 'Skull Woods First Section Hole (East)': ([0xDB84F, 0xDB850], None), 'Skull Woods First Section Hole (North)': ([0xDB84C], None), - 'Skull Woods Second Section Hole': ([0xDB851, 0xDB852], None), - 'Pyramid Hole': ([0xDB854, 0xDB855, 0xDB856], None), - 'Inverted Pyramid Hole': ([0xDB854, 0xDB855, 0xDB856, 0x180340], None), - 'Waterfall of Wishing': (0x5B, (0x0114, 0x0f, 0x0080, 0x0200, 0x0e00, 0x0207, 0x0e60, 0x026f, 0x0e7d, 0x00, 0x00, 0x0000, 0x0000)), - 'Dam': (0x4D, (0x010b, 0x3b, 0x04a0, 0x0e8a, 0x06fa, 0x0ed8, 0x0778, 0x0ef7, 0x077f, 0x06, 0xfa, 0x0000, 0x0000)), - 'Blinds Hideout': (0x60, (0x0119, 0x18, 0x02b2, 0x064a, 0x0186, 0x0697, 0x0208, 0x06b7, 0x0213, 0x06, 0xfa, 0x0000, 0x0000)), - 'Hyrule Castle Secret Entrance Drop': ([0xDB858], None), - 'Bonk Fairy (Light)': (0x76, (0x0126, 0x2b, 0x00a0, 0x0a0a, 0x0700, 0x0a67, 0x0788, 0x0a77, 0x0785, 0x06, 0xfa, 0x0000, 0x0000)), - 'Lake Hylia Fairy': (0x5D, (0x0115, 0x2e, 0x0016, 0x0a00, 0x0cb6, 0x0a37, 0x0d28, 0x0a6d, 0x0d33, 0x00, 0x00, 0x0000, 0x0000)), - 'Light Hype Fairy': (0x6B, (0x0115, 0x34, 0x00a0, 0x0c04, 0x0900, 0x0c58, 0x0988, 0x0c73, 0x0985, 0x0a, 0xf6, 0x0000, 0x0000)), - 'Desert Fairy': (0x71, (0x0115, 0x3a, 0x0000, 0x0e00, 0x0400, 0x0e26, 0x0468, 0x0e6d, 0x0485, 0x00, 0x00, 0x0000, 0x0000)), - 'Kings Grave': (0x5A, (0x0113, 0x14, 0x0320, 0x0456, 0x0900, 0x04a6, 0x0998, 0x04c3, 0x097d, 0x0a, 0xf6, 0x0000, 0x0000)), - 'Tavern North': (0x42, (0x0103, 0x18, 0x1440, 0x08a7, 0x0206, 0x091b, 0x0288, 0x0914, 0x0293, 0xf7, 0x09, 0xFFFF, 0x0000)), - 'Chicken House': (0x4A, (0x0108, 0x18, 0x1120, 0x0837, 0x0106, 0x0888, 0x0188, 0x08a4, 0x0193, 0x07, 0xf9, 0x1530, 0x0000)), - 'Aginahs Cave': (0x70, (0x010a, 0x30, 0x0656, 0x0cc6, 0x02aa, 0x0d18, 0x0328, 0x0d33, 0x032f, 0x08, 0xf8, 0x0000, 0x0000)), - 'Sahasrahlas Hut': (0x44, (0x0105, 0x1e, 0x0610, 0x06d4, 0x0c76, 0x0727, 0x0cf0, 0x0743, 0x0cfb, 0x0a, 0xf6, 0x0000, 0x0000)), - 'Lake Hylia Shop': (0x57, (0x0112, 0x35, 0x0022, 0x0c00, 0x0b1a, 0x0c26, 0x0b98, 0x0c6d, 0x0b9f, 0x00, 0x00, 0x0000, 0x0000)), - 'Capacity Upgrade': (0x5C, (0x0115, 0x35, 0x0a46, 0x0d36, 0x0c2a, 0x0d88, 0x0ca8, 0x0da3, 0x0caf, 0x0a, 0xf6, 0x0000, 0x0000)), - 'Kakariko Well Drop': ([0xDB85C, 0xDB85D], None), - 'Blacksmiths Hut': (0x63, (0x0121, 0x22, 0x010c, 0x081a, 0x0466, 0x0868, 0x04d8, 0x0887, 0x04e3, 0x06, 0xfa, 0x041A, 0x0000)), - 'Bat Cave Drop': ([0xDB859, 0xDB85A], None), - 'Sick Kids House': (0x3F, (0x0102, 0x18, 0x10be, 0x0826, 0x01f6, 0x0877, 0x0278, 0x0893, 0x0283, 0x08, 0xf8, 0x14CE, 0x0000)), - 'North Fairy Cave Drop': ([0xDB857], None), - 'Lost Woods Gamble': (0x3B, (0x0100, 0x00, 0x004e, 0x0000, 0x0272, 0x0008, 0x02f0, 0x006f, 0x02f7, 0x00, 0x00, 0x0000, 0x0000)), - 'Fortune Teller (Light)': (0x64, (0x0122, 0x11, 0x060e, 0x04b4, 0x027d, 0x0508, 0x02f8, 0x0523, 0x0302, 0x0a, 0xf6, 0x0000, 0x0000)), - 'Snitch Lady (East)': (0x3D, (0x0101, 0x18, 0x0ad8, 0x074a, 0x02c6, 0x0798, 0x0348, 0x07b7, 0x0353, 0x06, 0xfa, 0x0DE8, 0x0000)), - 'Snitch Lady (West)': (0x3E, (0x0101, 0x18, 0x0788, 0x0706, 0x0046, 0x0758, 0x00c8, 0x0773, 0x00d3, 0x08, 0xf8, 0x0B98, 0x0000)), - 'Bush Covered House': (0x43, (0x0103, 0x18, 0x1156, 0x081a, 0x02b6, 0x0868, 0x0338, 0x0887, 0x0343, 0x06, 0xfa, 0x1466, 0x0000)), - 'Tavern (Front)': (0x41, (0x0103, 0x18, 0x1842, 0x0916, 0x0206, 0x0967, 0x0288, 0x0983, 0x0293, 0x08, 0xf8, 0x1C50, 0x0000)), - 'Light World Bomb Hut': (0x49, (0x0107, 0x18, 0x1800, 0x0916, 0x0000, 0x0967, 0x0068, 0x0983, 0x008d, 0x08, 0xf8, 0x9C0C, 0x0000)), - 'Kakariko Shop': (0x45, (0x011f, 0x18, 0x16a8, 0x08e7, 0x0136, 0x0937, 0x01b8, 0x0954, 0x01c3, 0x07, 0xf9, 0x1AB6, 0x0000)), - 'Lost Woods Hideout Drop': ([0xDB853], None), - 'Lumberjack Tree Tree': ([0xDB85B], None), - 'Cave 45': (0x50, (0x011b, 0x32, 0x0680, 0x0cc9, 0x0400, 0x0d16, 0x0438, 0x0d36, 0x0485, 0x07, 0xf9, 0x0000, 0x0000)), - 'Graveyard Cave': (0x51, (0x011b, 0x14, 0x0016, 0x0400, 0x08a2, 0x0446, 0x0918, 0x046d, 0x091f, 0x00, 0x00, 0x0000, 0x0000)), - 'Checkerboard Cave': (0x7D, (0x0126, 0x30, 0x00c8, 0x0c0a, 0x024a, 0x0c67, 0x02c8, 0x0c77, 0x02cf, 0x06, 0xfa, 0x0000, 0x0000)), - 'Mini Moldorm Cave': (0x7C, (0x0123, 0x35, 0x1480, 0x0e96, 0x0a00, 0x0ee8, 0x0a68, 0x0f03, 0x0a85, 0x08, 0xf8, 0x0000, 0x0000)), - 'Long Fairy Cave': (0x54, (0x011e, 0x2f, 0x06a0, 0x0aca, 0x0f00, 0x0b18, 0x0fa8, 0x0b37, 0x0f85, 0x06, 0xfa, 0x0000, 0x0000)), - 'Good Bee Cave': (0x6A, (0x0120, 0x37, 0x0084, 0x0c00, 0x0e26, 0x0c36, 0x0e98, 0x0c6f, 0x0ea3, 0x00, 0x00, 0x0000, 0x0000)), - '20 Rupee Cave': (0x7A, (0x0125, 0x37, 0x0200, 0x0c23, 0x0e00, 0x0c86, 0x0e68, 0x0c92, 0x0e7d, 0x0d, 0xf3, 0x0000, 0x0000)), - '50 Rupee Cave': (0x78, (0x0124, 0x3a, 0x0790, 0x0eea, 0x047a, 0x0f47, 0x04f8, 0x0f57, 0x04ff, 0x06, 0xfa, 0x0000, 0x0000)), - 'Ice Rod Cave': (0x7F, (0x0120, 0x37, 0x0080, 0x0c00, 0x0e00, 0x0c37, 0x0e48, 0x0c6f, 0x0e7d, 0x00, 0x00, 0x0000, 0x0000)), - 'Bonk Rock Cave': (0x79, (0x0124, 0x13, 0x0280, 0x044a, 0x0600, 0x04a7, 0x0638, 0x04b7, 0x067d, 0x06, 0xfa, 0x0000, 0x0000)), - 'Library': (0x48, (0x0107, 0x29, 0x0100, 0x0a14, 0x0200, 0x0a67, 0x0278, 0x0a83, 0x0285, 0x0a, 0xf6, 0x040E, 0x0000)), - 'Potion Shop': (0x4B, (0x0109, 0x16, 0x070a, 0x04e6, 0x0c56, 0x0538, 0x0cc8, 0x0553, 0x0cd3, 0x08, 0xf8, 0x0A98, 0x0000)), - 'Sanctuary Grave': ([0xDB85E], None), - 'Hookshot Fairy': (0x4F, (0x010c, 0x05, 0x0ee0, 0x01e3, 0x0d00, 0x0236, 0x0d78, 0x0252, 0x0d7d, 0x0b, 0xf5, 0x0000, 0x0000)), - 'Pyramid Fairy': (0x62, (0x0116, 0x5b, 0x0b1e, 0x0754, 0x06fa, 0x07a7, 0x0778, 0x07c3, 0x077f, 0x0a, 0xf6, 0x0000, 0x0000)), - 'East Dark World Hint': (0x68, (0x010e, 0x6f, 0x06a0, 0x0aca, 0x0f00, 0x0b18, 0x0fa8, 0x0b37, 0x0f85, 0x06, 0xfa, 0x0000, 0x0000)), - 'Palace of Darkness Hint': (0x67, (0x011a, 0x5e, 0x0c24, 0x0794, 0x0d12, 0x07e8, 0x0d90, 0x0803, 0x0d97, 0x0a, 0xf6, 0x0000, 0x0000)), - 'Dark Lake Hylia Fairy': (0x6C, (0x0115, 0x6e, 0x0016, 0x0a00, 0x0cb6, 0x0a36, 0x0d28, 0x0a6d, 0x0d33, 0x00, 0x00, 0x0000, 0x0000)), - 'Dark Lake Hylia Ledge Fairy': (0x80, (0x0115, 0x77, 0x0080, 0x0c00, 0x0e00, 0x0c37, 0x0e48, 0x0c6f, 0x0e7d, 0x00, 0x00, 0x0000, 0x0000)), - 'Dark Lake Hylia Ledge Spike Cave': (0x7B, (0x0125, 0x77, 0x0200, 0x0c27, 0x0e00, 0x0c86, 0x0e68, 0x0c96, 0x0e7d, 0x09, 0xf7, 0x0000, 0x0000)), - 'Dark Lake Hylia Ledge Hint': (0x69, (0x010e, 0x77, 0x0084, 0x0c00, 0x0e26, 0x0c36, 0x0e98, 0x0c6f, 0x0ea3, 0x00, 0x00, 0x0000, 0x0000)), - 'Hype Cave': (0x3C, (0x011e, 0x74, 0x00a0, 0x0c0a, 0x0900, 0x0c58, 0x0988, 0x0c77, 0x097d, 0x06, 0xfa, 0x0000, 0x0000)), - 'Bonk Fairy (Dark)': (0x77, (0x0126, 0x6b, 0x00a0, 0x0a05, 0x0700, 0x0a66, 0x0788, 0x0a72, 0x0785, 0x0b, 0xf5, 0x0000, 0x0000)), - 'Brewery': (0x47, (0x0106, 0x58, 0x16a8, 0x08e4, 0x013e, 0x0938, 0x01b8, 0x0953, 0x01c3, 0x0a, 0xf6, 0x1AB6, 0x0000)), - 'C-Shaped House': (0x53, (0x011c, 0x58, 0x09d8, 0x0744, 0x02ce, 0x0797, 0x0348, 0x07b3, 0x0353, 0x0a, 0xf6, 0x0DE8, 0x0000)), - 'Chest Game': (0x46, (0x0106, 0x58, 0x078a, 0x0705, 0x004e, 0x0758, 0x00c8, 0x0774, 0x00d3, 0x09, 0xf7, 0x0B98, 0x0000)), - 'Hammer Peg Cave': (0x7E, (0x0127, 0x62, 0x0894, 0x091e, 0x0492, 0x09a6, 0x0508, 0x098b, 0x050f, 0x00, 0x00, 0x0000, 0x0000)), - 'Red Shield Shop': (0x74, (0x0110, 0x5a, 0x079a, 0x06e8, 0x04d6, 0x0738, 0x0548, 0x0755, 0x0553, 0x08, 0xf8, 0x0AA8, 0x0000)), - 'Dark Sanctuary Hint': (0x59, (0x0112, 0x53, 0x001e, 0x0400, 0x06e2, 0x0446, 0x0758, 0x046d, 0x075f, 0x00, 0x00, 0x0000, 0x0000)), - 'Fortune Teller (Dark)': (0x65, (0x0122, 0x51, 0x0610, 0x04b4, 0x027e, 0x0507, 0x02f8, 0x0523, 0x0303, 0x0a, 0xf6, 0x091E, 0x0000)), - 'Dark World Shop': (0x5F, (0x010f, 0x58, 0x1058, 0x0814, 0x02be, 0x0868, 0x0338, 0x0883, 0x0343, 0x0a, 0xf6, 0x0000, 0x0000)), - 'Dark Lumberjack Shop': (0x56, (0x010f, 0x42, 0x041c, 0x0074, 0x04e2, 0x00c7, 0x0558, 0x00e3, 0x055f, 0x0a, 0xf6, 0x0000, 0x0000)), - 'Dark Potion Shop': (0x6E, (0x010f, 0x56, 0x080e, 0x04f4, 0x0c66, 0x0548, 0x0cd8, 0x0563, 0x0ce3, 0x0a, 0xf6, 0x0000, 0x0000)), - 'Archery Game': (0x58, (0x0111, 0x69, 0x069e, 0x0ac4, 0x02ea, 0x0b18, 0x0368, 0x0b33, 0x036f, 0x0a, 0xf6, 0x09AC, 0x0000)), - 'Mire Shed': (0x5E, (0x010d, 0x70, 0x0384, 0x0c69, 0x001e, 0x0cb6, 0x0098, 0x0cd6, 0x00a3, 0x07, 0xf9, 0x0000, 0x0000)), - 'Mire Hint': (0x61, (0x0114, 0x70, 0x0654, 0x0cc5, 0x02aa, 0x0d16, 0x0328, 0x0d32, 0x032f, 0x09, 0xf7, 0x0000, 0x0000)), - 'Mire Fairy': (0x55, (0x0115, 0x70, 0x03a8, 0x0c6a, 0x013a, 0x0cb7, 0x01b8, 0x0cd7, 0x01bf, 0x06, 0xfa, 0x0000, 0x0000)), - 'Spike Cave': (0x40, (0x0117, 0x43, 0x0ed4, 0x01e4, 0x08aa, 0x0236, 0x0928, 0x0253, 0x092f, 0x0a, 0xf6, 0x0000, 0x0000)), - 'Dark Death Mountain Shop': (0x6D, (0x0112, 0x45, 0x0ee0, 0x01e3, 0x0d00, 0x0236, 0x0da8, 0x0252, 0x0d7d, 0x0b, 0xf5, 0x0000, 0x0000)), - 'Dark Death Mountain Fairy': (0x6F, (0x0115, 0x43, 0x1400, 0x0294, 0x0600, 0x02e8, 0x0678, 0x0303, 0x0685, 0x0a, 0xf6, 0x0000, 0x0000)), - 'Mimic Cave': (0x4E, (0x010c, 0x05, 0x07e0, 0x0103, 0x0d00, 0x0156, 0x0d78, 0x0172, 0x0d7d, 0x0b, 0xf5, 0x0000, 0x0000)), - 'Big Bomb Shop': (0x52, (0x011c, 0x6c, 0x0506, 0x0a9a, 0x0832, 0x0ae7, 0x08b8, 0x0b07, 0x08bf, 0x06, 0xfa, 0x0816, 0x0000)), - 'Dark Lake Hylia Shop': (0x73, (0x010f, 0x75, 0x0380, 0x0c6a, 0x0a00, 0x0cb8, 0x0a58, 0x0cd7, 0x0a85, 0x06, 0xfa, 0x0000, 0x0000)), - 'Lumberjack House': (0x75, (0x011f, 0x02, 0x049c, 0x0088, 0x04e6, 0x00d8, 0x0558, 0x00f7, 0x0563, 0x08, 0xf8, 0x07AA, 0x0000)), - 'Lake Hylia Fortune Teller': (0x72, (0x0122, 0x35, 0x0380, 0x0c6a, 0x0a00, 0x0cb8, 0x0a58, 0x0cd7, 0x0a85, 0x06, 0xfa, 0x0000, 0x0000)), - 'Kakariko Gamble Game': (0x66, (0x0118, 0x29, 0x069e, 0x0ac4, 0x02ea, 0x0b18, 0x0368, 0x0b33, 0x036f, 0x0a, 0xf6, 0x09AC, 0x0000))} + 'Skull Woods Second Section Hole': ([0xDB851, 0xDB852], None), + 'Pyramid Hole': ([0xDB854, 0xDB855, 0xDB856], None), + 'Inverted Pyramid Hole': ([0xDB854, 0xDB855, 0xDB856, 0x180340], None), + 'Waterfall of Wishing': (0x5B, (0x0114, 0x0f, 0x0080, 0x0200, 0x0e00, 0x0207, 0x0e60, 0x026f, 0x0e7d, 0x00, 0x00, 0x0000, 0x0000), 0x00), + 'Dam': (0x4D, (0x010b, 0x3b, 0x04a0, 0x0e8a, 0x06fa, 0x0ed8, 0x0778, 0x0ef7, 0x077f, 0x06, 0xfa, 0x0000, 0x0000), 0x00), + 'Blinds Hideout': (0x60, (0x0119, 0x18, 0x02b2, 0x064a, 0x0186, 0x0697, 0x0208, 0x06b7, 0x0213, 0x06, 0xfa, 0x0000, 0x0000), 0x00), + 'Hyrule Castle Secret Entrance Drop': ([0xDB858], None), + 'Bonk Fairy (Light)': (0x76, (0x0126, 0x2b, 0x00a0, 0x0a0a, 0x0700, 0x0a67, 0x0788, 0x0a77, 0x0785, 0x06, 0xfa, 0x0000, 0x0000), 0x20), + 'Lake Hylia Fairy': (0x5D, (0x0115, 0x2e, 0x0016, 0x0a00, 0x0cb6, 0x0a37, 0x0d28, 0x0a6d, 0x0d33, 0x00, 0x00, 0x0000, 0x0000), 0x00), + 'Light Hype Fairy': (0x6B, (0x0115, 0x34, 0x00a0, 0x0c04, 0x0900, 0x0c58, 0x0988, 0x0c73, 0x0985, 0x0a, 0xf6, 0x0000, 0x0000), 0x02), + 'Desert Fairy': (0x71, (0x0115, 0x3a, 0x0000, 0x0e00, 0x0400, 0x0e26, 0x0468, 0x0e6d, 0x0485, 0x00, 0x00, 0x0000, 0x0000), 0x00), + 'Kings Grave': (0x5A, (0x0113, 0x14, 0x0320, 0x0456, 0x0900, 0x04a6, 0x0998, 0x04c3, 0x097d, 0x0a, 0xf6, 0x0000, 0x0000), 0x20), + 'Tavern North': (0x42, (0x0103, 0x18, 0x1440, 0x08a7, 0x0206, 0x091b, 0x0288, 0x0914, 0x0293, 0xf7, 0x09, 0xFFFF, 0x0000), 0x00), + 'Chicken House': (0x4A, (0x0108, 0x18, 0x1120, 0x0837, 0x0106, 0x0888, 0x0188, 0x08a4, 0x0193, 0x07, 0xf9, 0x1530, 0x0000), 0x00), + 'Aginahs Cave': (0x70, (0x010a, 0x30, 0x0656, 0x0cc6, 0x02aa, 0x0d18, 0x0328, 0x0d33, 0x032f, 0x08, 0xf8, 0x0000, 0x0000), 0x00), + 'Sahasrahlas Hut': (0x44, (0x0105, 0x1e, 0x0610, 0x06d4, 0x0c76, 0x0727, 0x0cf0, 0x0743, 0x0cfb, 0x0a, 0xf6, 0x0000, 0x0000), 0x00), + 'Lake Hylia Shop': (0x57, (0x0112, 0x35, 0x0022, 0x0c00, 0x0b1a, 0x0c26, 0x0b98, 0x0c6d, 0x0b9f, 0x00, 0x00, 0x0000, 0x0000), 0x00), + 'Capacity Upgrade': (0x5C, (0x0115, 0x35, 0x0a46, 0x0d36, 0x0c2a, 0x0d88, 0x0ca8, 0x0da3, 0x0caf, 0x0a, 0xf6, 0x0000, 0x0000), 0x00), + 'Kakariko Well Drop': ([0xDB85C, 0xDB85D], None), + 'Blacksmiths Hut': (0x63, (0x0121, 0x22, 0x010c, 0x081a, 0x0466, 0x0868, 0x04d8, 0x0887, 0x04e3, 0x06, 0xfa, 0x041A, 0x0000), 0x00), + 'Bat Cave Drop': ([0xDB859, 0xDB85A], None), + 'Sick Kids House': (0x3F, (0x0102, 0x18, 0x10be, 0x0826, 0x01f6, 0x0877, 0x0278, 0x0893, 0x0283, 0x08, 0xf8, 0x14CE, 0x0000), 0x00), + 'North Fairy Cave Drop': ([0xDB857], None), + 'Lost Woods Gamble': (0x3B, (0x0100, 0x00, 0x004e, 0x0000, 0x0272, 0x0008, 0x02f0, 0x006f, 0x02f7, 0x00, 0x00, 0x0000, 0x0000), 0x00), + 'Fortune Teller (Light)': (0x64, (0x0122, 0x11, 0x060e, 0x04b4, 0x027d, 0x0508, 0x02f8, 0x0523, 0x0302, 0x0a, 0xf6, 0x0000, 0x0000), 0x00), + 'Snitch Lady (East)': (0x3D, (0x0101, 0x18, 0x0ad8, 0x074a, 0x02c6, 0x0798, 0x0348, 0x07b7, 0x0353, 0x06, 0xfa, 0x0DE8, 0x0000), 0x00), + 'Snitch Lady (West)': (0x3E, (0x0101, 0x18, 0x0788, 0x0706, 0x0046, 0x0758, 0x00c8, 0x0773, 0x00d3, 0x08, 0xf8, 0x0B98, 0x0000), 0x00), + 'Bush Covered House': (0x43, (0x0103, 0x18, 0x1156, 0x081a, 0x02b6, 0x0868, 0x0338, 0x0887, 0x0343, 0x06, 0xfa, 0x1466, 0x0000), 0x00), + 'Tavern (Front)': (0x41, (0x0103, 0x18, 0x1842, 0x0916, 0x0206, 0x0967, 0x0288, 0x0983, 0x0293, 0x08, 0xf8, 0x1C50, 0x0000), 0x00), + 'Light World Bomb Hut': (0x49, (0x0107, 0x18, 0x1800, 0x0916, 0x0000, 0x0967, 0x0068, 0x0983, 0x008d, 0x08, 0xf8, 0x9C0C, 0x0000), 0x02), + 'Kakariko Shop': (0x45, (0x011f, 0x18, 0x16a8, 0x08e7, 0x0136, 0x0937, 0x01b8, 0x0954, 0x01c3, 0x07, 0xf9, 0x1AB6, 0x0000), 0x00), + 'Lost Woods Hideout Drop': ([0xDB853], None), + 'Lumberjack Tree Tree': ([0xDB85B], None), + 'Cave 45': (0x50, (0x011b, 0x32, 0x0680, 0x0cc9, 0x0400, 0x0d16, 0x0438, 0x0d36, 0x0485, 0x07, 0xf9, 0x0000, 0x0000), 0x00), + 'Graveyard Cave': (0x51, (0x011b, 0x14, 0x0016, 0x0400, 0x08a2, 0x0446, 0x0918, 0x046d, 0x091f, 0x00, 0x00, 0x0000, 0x0000), 0x00), + 'Checkerboard Cave': (0x7D, (0x0126, 0x30, 0x00c8, 0x0c0a, 0x024a, 0x0c67, 0x02c8, 0x0c77, 0x02cf, 0x06, 0xfa, 0x0000, 0x0000), 0x20), + 'Mini Moldorm Cave': (0x7C, (0x0123, 0x35, 0x1480, 0x0e96, 0x0a00, 0x0ee8, 0x0a68, 0x0f03, 0x0a85, 0x08, 0xf8, 0x0000, 0x0000), 0x02), + 'Long Fairy Cave': (0x54, (0x011e, 0x2f, 0x06a0, 0x0aca, 0x0f00, 0x0b18, 0x0fa8, 0x0b37, 0x0f85, 0x06, 0xfa, 0x0000, 0x0000), 0x00), + 'Good Bee Cave': (0x6A, (0x0120, 0x37, 0x0084, 0x0c00, 0x0e26, 0x0c36, 0x0e98, 0x0c6f, 0x0ea3, 0x00, 0x00, 0x0000, 0x0000), 0x00), + '20 Rupee Cave': (0x7A, (0x0125, 0x37, 0x0200, 0x0c23, 0x0e00, 0x0c86, 0x0e68, 0x0c92, 0x0e7d, 0x0d, 0xf3, 0x0000, 0x0000), 0x20), + '50 Rupee Cave': (0x78, (0x0124, 0x3a, 0x0790, 0x0eea, 0x047a, 0x0f47, 0x04f8, 0x0f57, 0x04ff, 0x06, 0xfa, 0x0000, 0x0000), 0x20), + 'Ice Rod Cave': (0x7F, (0x0120, 0x37, 0x0080, 0x0c00, 0x0e00, 0x0c37, 0x0e48, 0x0c6f, 0x0e7d, 0x00, 0x00, 0x0000, 0x0000), 0x02), + 'Bonk Rock Cave': (0x79, (0x0124, 0x13, 0x0280, 0x044a, 0x0600, 0x04a7, 0x0638, 0x04b7, 0x067d, 0x06, 0xfa, 0x0000, 0x0000), 0x20), + 'Library': (0x48, (0x0107, 0x29, 0x0100, 0x0a14, 0x0200, 0x0a67, 0x0278, 0x0a83, 0x0285, 0x0a, 0xf6, 0x040E, 0x0000), 0x00), + 'Potion Shop': (0x4B, (0x0109, 0x16, 0x070a, 0x04e6, 0x0c56, 0x0538, 0x0cc8, 0x0553, 0x0cd3, 0x08, 0xf8, 0x0A98, 0x0000), 0x00), + 'Sanctuary Grave': ([0xDB85E], None), + 'Hookshot Fairy': (0x4F, (0x010c, 0x05, 0x0ee0, 0x01e3, 0x0d00, 0x0236, 0x0d78, 0x0252, 0x0d7d, 0x0b, 0xf5, 0x0000, 0x0000), 0x00), + 'Pyramid Fairy': (0x62, (0x0116, 0x5b, 0x0b1e, 0x0754, 0x06fa, 0x07a7, 0x0778, 0x07c3, 0x077f, 0x0a, 0xf6, 0x0000, 0x0000), 0x02), + 'East Dark World Hint': (0x68, (0x010e, 0x6f, 0x06a0, 0x0aca, 0x0f00, 0x0b18, 0x0fa8, 0x0b37, 0x0f85, 0x06, 0xfa, 0x0000, 0x0000), 0x00), + 'Palace of Darkness Hint': (0x67, (0x011a, 0x5e, 0x0c24, 0x0794, 0x0d12, 0x07e8, 0x0d90, 0x0803, 0x0d97, 0x0a, 0xf6, 0x0000, 0x0000), 0x00), + 'Dark Lake Hylia Fairy': (0x6C, (0x0115, 0x6e, 0x0016, 0x0a00, 0x0cb6, 0x0a36, 0x0d28, 0x0a6d, 0x0d33, 0x00, 0x00, 0x0000, 0x0000), 0x00), + 'Dark Lake Hylia Ledge Fairy': (0x80, (0x0115, 0x77, 0x0080, 0x0c00, 0x0e00, 0x0c37, 0x0e48, 0x0c6f, 0x0e7d, 0x00, 0x00, 0x0000, 0x0000), 0x02), + 'Dark Lake Hylia Ledge Spike Cave': (0x7B, (0x0125, 0x77, 0x0200, 0x0c27, 0x0e00, 0x0c86, 0x0e68, 0x0c96, 0x0e7d, 0x09, 0xf7, 0x0000, 0x0000), 0x20), + 'Dark Lake Hylia Ledge Hint': (0x69, (0x010e, 0x77, 0x0084, 0x0c00, 0x0e26, 0x0c36, 0x0e98, 0x0c6f, 0x0ea3, 0x00, 0x00, 0x0000, 0x0000), 0x00), + 'Hype Cave': (0x3C, (0x011e, 0x74, 0x00a0, 0x0c0a, 0x0900, 0x0c58, 0x0988, 0x0c77, 0x097d, 0x06, 0xfa, 0x0000, 0x0000), 0x02), + 'Bonk Fairy (Dark)': (0x77, (0x0126, 0x6b, 0x00a0, 0x0a05, 0x0700, 0x0a66, 0x0788, 0x0a72, 0x0785, 0x0b, 0xf5, 0x0000, 0x0000), 0x20), + 'Brewery': (0x47, (0x0106, 0x58, 0x16a8, 0x08e4, 0x013e, 0x0938, 0x01b8, 0x0953, 0x01c3, 0x0a, 0xf6, 0x1AB6, 0x0000), 0x02), + 'C-Shaped House': (0x53, (0x011c, 0x58, 0x09d8, 0x0744, 0x02ce, 0x0797, 0x0348, 0x07b3, 0x0353, 0x0a, 0xf6, 0x0DE8, 0x0000), 0x00), + 'Chest Game': (0x46, (0x0106, 0x58, 0x078a, 0x0705, 0x004e, 0x0758, 0x00c8, 0x0774, 0x00d3, 0x09, 0xf7, 0x0B98, 0x0000), 0x00), + 'Hammer Peg Cave': (0x7E, (0x0127, 0x62, 0x0894, 0x091e, 0x0492, 0x09a6, 0x0508, 0x098b, 0x050f, 0x00, 0x00, 0x0000, 0x0000), 0x20), + 'Red Shield Shop': (0x74, (0x0110, 0x5a, 0x079a, 0x06e8, 0x04d6, 0x0738, 0x0548, 0x0755, 0x0553, 0x08, 0xf8, 0x0AA8, 0x0000), 0x00), + 'Dark Sanctuary Hint': (0x59, (0x0112, 0x53, 0x001e, 0x0400, 0x06e2, 0x0446, 0x0758, 0x046d, 0x075f, 0x00, 0x00, 0x0000, 0x0000), 0x00), + 'Fortune Teller (Dark)': (0x65, (0x0122, 0x51, 0x0610, 0x04b4, 0x027e, 0x0507, 0x02f8, 0x0523, 0x0303, 0x0a, 0xf6, 0x091E, 0x0000), 0x00), + 'Dark World Shop': (0x5F, (0x010f, 0x58, 0x1058, 0x0814, 0x02be, 0x0868, 0x0338, 0x0883, 0x0343, 0x0a, 0xf6, 0x0000, 0x0000), 0x00), + 'Dark Lumberjack Shop': (0x56, (0x010f, 0x42, 0x041c, 0x0074, 0x04e2, 0x00c7, 0x0558, 0x00e3, 0x055f, 0x0a, 0xf6, 0x0000, 0x0000), 0x00), + 'Dark Potion Shop': (0x6E, (0x010f, 0x56, 0x080e, 0x04f4, 0x0c66, 0x0548, 0x0cd8, 0x0563, 0x0ce3, 0x0a, 0xf6, 0x0000, 0x0000), 0x00), + 'Archery Game': (0x58, (0x0111, 0x69, 0x069e, 0x0ac4, 0x02ea, 0x0b18, 0x0368, 0x0b33, 0x036f, 0x0a, 0xf6, 0x09AC, 0x0000), 0x00), + 'Mire Shed': (0x5E, (0x010d, 0x70, 0x0384, 0x0c69, 0x001e, 0x0cb6, 0x0098, 0x0cd6, 0x00a3, 0x07, 0xf9, 0x0000, 0x0000), 0x00), + 'Mire Hint': (0x61, (0x0114, 0x70, 0x0654, 0x0cc5, 0x02aa, 0x0d16, 0x0328, 0x0d32, 0x032f, 0x09, 0xf7, 0x0000, 0x0000), 0x00), + 'Mire Fairy': (0x55, (0x0115, 0x70, 0x03a8, 0x0c6a, 0x013a, 0x0cb7, 0x01b8, 0x0cd7, 0x01bf, 0x06, 0xfa, 0x0000, 0x0000), 0x00), + 'Spike Cave': (0x40, (0x0117, 0x43, 0x0ed4, 0x01e4, 0x08aa, 0x0236, 0x0928, 0x0253, 0x092f, 0x0a, 0xf6, 0x0000, 0x0000), 0x00), + 'Dark Death Mountain Shop': (0x6D, (0x0112, 0x45, 0x0ee0, 0x01e3, 0x0d00, 0x0236, 0x0da8, 0x0252, 0x0d7d, 0x0b, 0xf5, 0x0000, 0x0000), 0x00), + 'Dark Death Mountain Fairy': (0x6F, (0x0115, 0x43, 0x1400, 0x0294, 0x0600, 0x02e8, 0x0678, 0x0303, 0x0685, 0x0a, 0xf6, 0x0000, 0x0000), 0x00), + 'Mimic Cave': (0x4E, (0x010c, 0x05, 0x07e0, 0x0103, 0x0d00, 0x0156, 0x0d78, 0x0172, 0x0d7d, 0x0b, 0xf5, 0x0000, 0x0000), 0x00), + 'Big Bomb Shop': (0x52, (0x011c, 0x6c, 0x0506, 0x0a9a, 0x0832, 0x0ae7, 0x08b8, 0x0b07, 0x08bf, 0x06, 0xfa, 0x0816, 0x0000), 0x00), + 'Dark Lake Hylia Shop': (0x73, (0x010f, 0x75, 0x0380, 0x0c6a, 0x0a00, 0x0cb8, 0x0a58, 0x0cd7, 0x0a85, 0x06, 0xfa, 0x0000, 0x0000), 0x00), + 'Lumberjack House': (0x75, (0x011f, 0x02, 0x049c, 0x0088, 0x04e6, 0x00d8, 0x0558, 0x00f7, 0x0563, 0x08, 0xf8, 0x07AA, 0x0000), 0x00), + 'Lake Hylia Fortune Teller': (0x72, (0x0122, 0x35, 0x0380, 0x0c6a, 0x0a00, 0x0cb8, 0x0a58, 0x0cd7, 0x0a85, 0x06, 0xfa, 0x0000, 0x0000), 0x00), + 'Kakariko Gamble Game': (0x66, (0x0118, 0x29, 0x069e, 0x0ac4, 0x02ea, 0x0b18, 0x0368, 0x0b33, 0x036f, 0x0a, 0xf6, 0x09AC, 0x0000), 0x00)} ow_prize_table = {'Links House': (0x8b1, 0xb2d), 'Desert Palace Entrance (South)': (0x108, 0xd70), 'Desert Palace Entrance (West)': (0x031, 0xca0), From 39dc0abb533a482d46f9324807a8747b49f81853 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Fri, 24 May 2024 15:26:12 -0500 Subject: [PATCH 39/41] Banning bunny traversal thru DM Return Cave and Paradox Cave Top --- Regions.py | 3 ++- Rules.py | 3 ++- source/overworld/EntranceShuffle2.py | 2 ++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Regions.py b/Regions.py index 16f1a427..5d01b959 100644 --- a/Regions.py +++ b/Regions.py @@ -276,7 +276,8 @@ def create_regions(world, player): 'Paradox Cave Lower - Right', 'Paradox Cave Lower - Far Right', '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, 'Paradox Cave', 'a connector', None, ['Paradox Cave Exit (Middle)', 'Paradox Cave Climb', 'Paradox Cave Drop']), + create_cave_region(player, 'Paradox Cave (Top)', 'a connector', None, ['Paradox Cave Exit (Top)', 'Paradox Cave Descent']), create_cave_region(player, 'Paradox Shop', 'a common shop', ['Paradox Shop - Left', 'Paradox Shop - Middle', 'Paradox Shop - Right']), create_cave_region(player, 'Waterfall of Wishing', 'a cave with two chests', ['Waterfall Fairy - Left', 'Waterfall Fairy - Right']), create_cave_region(player, 'Fortune Teller (Light)', 'a fortune teller'), diff --git a/Rules.py b/Rules.py index 38879136..1ad5607e 100644 --- a/Rules.py +++ b/Rules.py @@ -1681,7 +1681,8 @@ def set_bunny_rules(world, player, inverted): all_single_exit_dungeons = ['Eastern Palace', 'Tower of Hera', 'Castle Tower', 'Palace of Darkness', 'Swamp Palace', 'Thieves Town', 'Ice Palace', 'Misery Mire', 'Ganons Tower'] hmg_single_exit_dungeons = [d for d in all_single_exit_dungeons if d not in ['Tower of Hera', 'Misery Mire', 'Thieves Town']] bunny_impassable_caves = ['Bumper Cave (top)', 'Bumper Cave (bottom)', 'Two Brothers House', - 'Hookshot Cave (Middle)', 'Pyramid', 'Spiral Cave (Top)', 'Fairy Ascension Cave (Drop)'] + 'Hookshot Cave (Middle)', 'Pyramid', 'Spiral Cave (Top)', 'Fairy Ascension Cave (Drop)', + 'Death Mountain Return Cave (right)', 'Paradox Cave (Top)'] bunny_accessible_locations = ['Link\'s Uncle', 'Sahasrahla', 'Sick Kid', 'Lost Woods Hideout', 'Lumberjack Tree', 'Checkerboard Cave', 'Potion Shop', 'Spectacle Rock Cave', 'Pyramid', 'Old Man', 'Hype Cave - Generous Guy', 'Peg Cave', 'Bumper Cave Ledge', 'Dark Blacksmith Ruins', diff --git a/source/overworld/EntranceShuffle2.py b/source/overworld/EntranceShuffle2.py index 45948c0a..704754aa 100644 --- a/source/overworld/EntranceShuffle2.py +++ b/source/overworld/EntranceShuffle2.py @@ -2627,6 +2627,8 @@ mandatory_connections = [('Lost Woods Hideout (top to bottom)', 'Lost Woods Hide ('Paradox Cave Push Block', 'Paradox Cave Front'), ('Paradox Cave Chest Area NE', 'Paradox Cave Bomb Area'), ('Paradox Cave Bomb Jump', 'Paradox Cave'), + ('Paradox Cave Climb', 'Paradox Cave (Top)'), + ('Paradox Cave Descent', 'Paradox Cave'), ('Paradox Cave Drop', 'Paradox Cave Chest Area'), ('Paradox Shop', 'Paradox Shop'), ('Fairy Ascension Cave Climb', 'Fairy Ascension Cave (Top)'), From 747f5fae98e37402a8e05834d822e27cba9ff97f Mon Sep 17 00:00:00 2001 From: codemann8 Date: Fri, 24 May 2024 16:19:37 -0500 Subject: [PATCH 40/41] Some further fixes to Prize Shuffle --- DoorShuffle.py | 2 ++ Dungeons.py | 2 +- Fill.py | 2 +- Rom.py | 10 ++++------ data/base2current.bps | Bin 133282 -> 133325 bytes 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/DoorShuffle.py b/DoorShuffle.py index 43f50458..074e71d4 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -2696,6 +2696,8 @@ def calc_used_dungeon_items(builder, world, player): base += 1 if not world.mapshuffle[player] and (builder.name != 'Agahnims Tower' or not basic_flag): base += 1 + if world.prizeshuffle[player] == 'dungeon' and builder.name not in ['Hyrule Castle', 'Agahnims Tower', 'Ganons Tower']: + base += 1 return base diff --git a/Dungeons.py b/Dungeons.py index 53818b7f..8d72760b 100644 --- a/Dungeons.py +++ b/Dungeons.py @@ -291,7 +291,7 @@ dungeon_table = { 'Desert Palace': DungeonInfo(2, 1, True, True, True, False, 3, [0x1209E, 0x53F3C, 0x53F3D, 0x180053, 0x180072, 0x186FE3], 0x06, 0x12), 'Tower of Hera': DungeonInfo(2, 1, True, True, True, False, 0, [0x120A5, 0x53F4A, 0x53F4B, 0x18005A, 0x180071, 0x186FEA], 0x14, None), 'Agahnims Tower': DungeonInfo(0, 2, False, False, False, False, 2, None, 0x08, None), - 'Palace of Darkness': DungeonInfo(5, 6, True, True, True, False, 0, [0x120A1, 0x53F22, 0x53F43, 0x180056, 0x180073, 0x186FE6], 0x0C, None), + 'Palace of Darkness': DungeonInfo(5, 6, True, True, True, False, 0, [0x120A1, 0x53F42, 0x53F43, 0x180056, 0x180073, 0x186FE6], 0x0C, None), 'Swamp Palace': DungeonInfo(6, 1, True, True, True, False, 5, [0x120A0, 0x53F40, 0x53F41, 0x180055, 0x180079, 0x186FE5], 0x0A, None), 'Skull Woods': DungeonInfo(2, 3, True, True, True, False, 2, [0x120A3, 0x53F46, 0x53F47, 0x180058, 0x180074, 0x186FE8], 0x10, 0x20), 'Thieves Town': DungeonInfo(4, 1, True, True, True, False, 2, [0x120A6, 0x53F4C, 0x53F4D, 0x18005B, 0x180076, 0x186FEB], 0x16, None), diff --git a/Fill.py b/Fill.py index b3591897..8a917070 100644 --- a/Fill.py +++ b/Fill.py @@ -270,7 +270,7 @@ def track_dungeon_items(item, location, world): def is_dungeon_item(item, world): - return ((item.prize and world.prizeshuffle[item.player] == 'none') + return ((item.prize and world.prizeshuffle[item.player] in ['none', 'dungeon']) or (item.smallkey and world.keyshuffle[item.player] == 'none') or (item.bigkey and not world.bigkeyshuffle[item.player]) or (item.compass and not world.compassshuffle[item.player]) diff --git a/Rom.py b/Rom.py index 1bc5d759..e07e3371 100644 --- a/Rom.py +++ b/Rom.py @@ -43,7 +43,7 @@ from source.enemizer.Enemizer import write_enemy_shuffle_settings JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '361d9f67bfd927c7993426cacd778e8f' +RANDOMIZERBASEHASH = '096e6adebfa630e827c662f12f79b4cd' class JsonRom(object): @@ -477,7 +477,7 @@ def patch_rom(world, rom, player, team, is_mystery=False): rom.write_byte(location.address, itemid) for dungeon in [d for d in world.dungeons if d.player == player]: if dungeon.prize: - # crystals + # prizes for address, value in zip(dungeon_table[dungeon.name].prize, prize_item_table[dungeon.prize.name]): rom.write_byte(address, value) @@ -1211,8 +1211,6 @@ def patch_rom(world, rom, player, team, is_mystery=False): rom.write_byte(0x1800A1, 0x01) # enable overworld screen transition draining for water level inside swamp rom.write_byte(0x180174, 0x01 if world.fix_fake_world[player] else 0x00) rom.write_byte(0x18017E, 0x01) # Fairy fountains only trade in bottles - # fix for allowing prize itemgets being able to update the HUD (buying prizes in shops and updating rupees) - rom.write_bytes(snes_to_pc(0x0799FB), [0x80, 0x11, 0xEA]) # Starting equipment if world.pseudoboots[player]: @@ -1437,8 +1435,8 @@ def patch_rom(world, rom, player, team, is_mystery=False): return reveal_bytes.get(dungeon.name, 0x0000) return 0x0000 - write_int16(rom, 0x18017A, get_reveal_bytes('Green Pendant') if world.mapshuffle[player] else 0x0000) # Sahasrahla reveal - write_int16(rom, 0x18017C, get_reveal_bytes('Crystal 5')|get_reveal_bytes('Crystal 6') if world.mapshuffle[player] else 0x0000) # Bomb Shop Reveal + write_int16(rom, 0x18017A, get_reveal_bytes('Green Pendant')) # Sahasrahla reveal + write_int16(rom, 0x18017C, get_reveal_bytes('Crystal 5')|get_reveal_bytes('Crystal 6')) # Bomb Shop Reveal rom.write_byte(0x180172, 0x01 if world.keyshuffle[player] == 'universal' else 0x00) # universal keys rom.write_byte(0x180175, 0x01 if world.bow_mode[player].startswith('retro') else 0x00) # rupee bow diff --git a/data/base2current.bps b/data/base2current.bps index 81de70aecdc914462bf4fc86d821290277bd9c6f..0aa27aaf7877197ced96152fb0be2d7e1c21b802 100644 GIT binary patch delta 549 zcmV+=0^0qekqFI^2(Tan1SCz{va=)uZG|tEr-e0>U7s5#R^#C&Cb-0F$3MA~z+%5TXVE zzdOT$B0c~v zlZWSsnJ`QMd;n$uX4|s^h(U##Oq3Ny1ON={hM5=uEC4J3 zJd*-|&-IVQ?}dRlqB&KFSBNc!cQk+jH-j#LeszhnmfiOP3S`0w4k#x3@L~0%QaTiOk7VYn|}<6gbNK delta 486 zcmVZG|tEr-e0>U7s5#R^#B%+g|I3hYCq71(^!-}ZC zGm{2_ficU1_2M9_YoL{Y5qGCfPICRd@KRs0wa#MU@ZZ*pASL2kuebw5fQFmalM;AO%|?g8hX4Qo From db45e98e86ac442ad219c8cd8572d94de7837dc0 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sat, 25 May 2024 03:19:06 -0500 Subject: [PATCH 41/41] Version bump 0.5.0.0 --- OverworldShuffle.py | 2 +- README.md | 32 +++++++++++++++++++++++++++----- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 57745d4a..20b29ca5 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -8,7 +8,7 @@ from OWEdges import OWTileRegions, OWEdgeGroups, OWEdgeGroupsTerrain, OWExitType from OverworldGlitchRules import create_owg_connections from Utils import bidict -version_number = '0.4.0.2' +version_number = '0.5.0.0' # branch indicator is intentionally different across branches version_branch = '-u' diff --git a/README.md b/README.md index afdd4201..12ac61e5 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ For a first (and second) seed... *and I say "second" because I feel like both of - `Bonk Drops` - Recommend to NOT turn this on, especially if you don't have starting boots, but you could enable this in future seeds if you've become more familiar with OWR, as you'll be visiting these screens anyways, you might as well grab the items on the way :) ## "What tracker should I use?" -I personally use 2 trackers together, at least if Layout Shuffle is enabled. Currently, DunkaTracker is the ONLY tracker that is useful for tracking Layout Shuffle, can highly recommend. However, I personally don't like the main portion of the tracker and ignore the main window completely. For tracking everything else, I use `CodeTracker`, a tracker pack that is installable within `EmoTracker`, this handles ALL OWR modes EXCEPT Layout Shuffle (and generally Crossed OW, but missing that doesn't make much of a difference). I am unaware of ANY trackers outside of these 2 that handle OWR modes, so my advice would be to familiarize yourself with both trackers and pick and choose the parts you find most useful for yourself. +I personally use 2 trackers together, at least if Layout Shuffle is enabled. Currently, DunkaTracker is the ONLY tracker that is useful for tracking Layout Shuffle, highly recommended. However, I personally don't like the main portion of the tracker and ignore the main window completely. For tracking everything else, I use `CodeTracker`, a tracker pack that is installable within `EmoTracker`, this handles ALL OWR modes EXCEPT Layout Shuffle (and generally Crossed OW, but missing that doesn't make much of a difference). I am unaware of ANY trackers outside of these 2 that handle OWR modes, so my advice would be to familiarize yourself with both trackers and pick and choose the parts you find most useful for yourself. # Terminology @@ -105,6 +105,10 @@ Similar edges on the larger OW tiles are a bit harder to intuitively determine. Similar edges are used when `Keep Similar Edges Together` is enabled and have meaning when used with `Layout Shuffle` (with and without `Free Terrain`) and `Crossed` settings. +# Overworld Map Changes + +The overworld map check screen has been completely overhauled in this OWR fork. In other LTTP forks, there are numbered icons to indicate the crystal number of the crystal and those are shown alongside crystal prizes. OWR has changed this so the numbers instead correspond to dungeon numbers. In the LW, you have the 3 pendant dungeons, these are indicated by a blue 1, 2, and 3. In the DW, you have the 7 crystal dungeon, these are indicated by red numbers (1-7). In addition, there may be some mode combinations where HC, AT, and GT may be visible via icon, indicated by a white H, a white A, and a skull. An example of what you can expect to see on a map check can be found [here](https://cdn.discordapp.com/attachments/783989090017738753/1243839950663847936/newmapcheck.gif?ex=6652efb9&is=66519e39&hm=8be1d514458c09d6881a0d6ae0e539adf1f6227374a7d61a8f3a1831e18f0395&). + # Inverted Changes The version of Inverted included in OWR varies quite a bit compared to the Door Rando and VT (main rando) forks. This is often referred to as Inverted 2.0, as this is a plan to shift it to a newer/updated concept, intended to either enhance/improve the Inverted experience or to restore some behaviors more closer to vanilla. Some of these changes are likely going to be added to all rando forks/branches and subject to change, but this will be based on user feedback. @@ -122,7 +126,7 @@ The version of Inverted included in OWR varies quite a bit compared to the Door - Ice Palace has been re-sealed to vanilla, portal moved to outer edge of moat (makes IP mirror locked) - Glitched modes will now use vanilla terrain except where necessary -Note: These changes do impact the logic. If you use `CodeTracker`, these Inverted 2.0 logic rules are automatically detected if using autotracker, indicated by a 2 in the corner of the World State mode icon. This can also be manually applied if you right-click the World State mode icon. +Note: These changes do impact the logic. If you use `CodeTracker`, these Inverted 2.0 logic rules are automatically detected if using autotracker, indicated by a 2 in the corner of the World State mode icon. This can also be manually applied if you right-click the World State mode icon. # Settings @@ -189,7 +193,7 @@ Being that this uses concepts from Inverted, it will be important to review the During gameplay: - When on the OW, there will be an L or D in the upper left corner, indicating which world you are currently in. Mirroring still works the same, you must be in the DW to mirror to the LW. - - When doing a map check (pressing X while on the OW), the tiles shown will reflect the flipped tiles. This means that dungeon prizes will show the prizes for the dungeons that are now part of that world, beware of Desert/Mire and Eastern/PoD. Here is an image showing the difference of appearance when tiles are flipped on the [map check](https://cdn.discordapp.com/attachments/783989090017738753/970646558049714196/lttp-lw-mapcheck.gif?ex=66015e8d&is=65eee98d&hm=b4d97c52d6aed593f0e6ec54924696ba969ce11109ce5ba1291b50a8a3e2dac8&) screen. + - When doing a map check (pressing X while on the OW), the tiles shown will reflect the flipped tiles. This means that dungeon prizes will show the prizes for the dungeons that are now part of that world. Here is an image showing the difference of appearance when tiles are flipped on the [map check](https://cdn.discordapp.com/attachments/783989090017738753/970646558049714196/lttp-lw-mapcheck.gif?ex=665272cd&is=6651214d&hm=6962fe0b16a7919a91066e96fea29d28fbecd404e6c0dc344146f17553425296&) screen. Note: Tiles are put into Tile Groups (see `Terminology`) that must be shuffled together when certain settings are enabled. For instance, if ER is disabled, then any tiles that have a connector cave that leads to a different tile, then those tiles must flip together. @@ -248,7 +252,7 @@ This adds 42 new item locations to the game. These bonk locations are limited to - Some screens are coded to change the "alternate tree color", some of them are strange (just how the vanilla game does it) - Rocks and statues are unable to be made to have a different color -Here is a map that shows all the [Bonk Locations](https://cdn.discordapp.com/attachments/1105770688649895968/1105770806769877072/bonkdrops.png?ex=6603d650&is=65f16150&hm=3576367abd636ba7723ef30e87a4bc407c5e1eb9a8be325e90b1e22c04c58401&). FYI, the numbers indicate how many bonk items there. The stars with a green square are all Bonk Locations that are unlocked after you kill Aga 1. +Here is a map that shows all the [Bonk Locations](https://cdn.discordapp.com/attachments/1105770688649895968/1105770806769877072/bonkdrops.png?ex=66524790&is=6650f610&hm=7c16f009d514256d3fdd02667f9876fae4178ec2989e5f1e0cfd32c7207e144e&). FYI, the numbers indicate how many bonk items there. The stars with a green square are all Bonk Locations that are unlocked after you kill Aga 1. As far as map trackers, Bonk Locations are supported on `CodeTracker` when the Bonk Drops option is enabled. @@ -264,6 +268,24 @@ As far as map trackers, Bonk Locations are supported on `CodeTracker` when the B - 1 8x Bomb Pack - 1 Good Bee +## Prize Shuffle + +A new option has been added to shuffle the 10 dungeon prizes in ways that they haven't been shuffled before. This means that dungeon prizes can be found in other item locations, such as chests or free-standing item locations. This also means that bosses are able to drop a 2nd item in place of the shuffled prize. + +### On Boss + +This is the normal prize behavior that has been a part of rando up until now. The dungeon prizes are shuffled amongst themselves in their own pool and aren't considered part of the main collection total. + +### In Dungeon + +This option shuffles the prize into a location somewhere within the dungeon that it is assigned to. + +### Randomized + +This option freely shuffles the prizes throughout the world. While the dungeon prizes can end up anywhere, they still are assigned to a specific dungeon. When you defeat the boss of a certain dungeon, checking the map on the overworld will reveal the location WHERE you can find the prize, an example shown [here](https://cdn.discordapp.com/attachments/783989090017738753/1243840288867487754/prizemap-all.gif?ex=6652f00a&is=66519e8a&hm=a49ef2c84d9862349e331ae87c99c3ce34ae5cd0690073521423d7e61c5429dc&). Finding the map will still reveal WHAT the prize is. If you defeated a boss but haven't collected the map for that dungeon, the prize will be indicated by a red X, example shown [here](https://cdn.discordapp.com/attachments/783989090017738753/1243840289278263316/prizemap-boss.gif?ex=6652f00a&is=66519e8a&hm=f1d75388b2ca039f5c35f244109ff659b34235d6ce2f76516ad46f978ec49f91&). If you collected a map but haven't defeated the boss yet, the icon indicator on the map will be shown on the top edge (for LW dungeons) or the bottom edge (for DW dungeons), but it will show you WHAT the prize is for that dungeon, an example of that is shown [here](https://cdn.discordapp.com/attachments/783989090017738753/1243840289718669372/prizemap-map.gif?ex=6652f00a&is=66519e8a&hm=47fa004f493c63842bec3a54b7703d95d9a6a05067fa3fb539d48de9c0cb1698&). + +- It is important to note that the overworld map check has changed: the numbered icons that are displayed are NO LONGER indicating the crystal number like they have in the past. They are now indicating the dungeon that it belongs to; a blue 1-3 indicates the 3 LW dungeons (EP, DP, and ToH) and a red 1-7 indicate the 7 DW dungeons + ## New Goal Options (--goal) ### Trinity @@ -327,7 +349,7 @@ Here is a [Swapped ER Reference Sheet](http://zelda.codemann8.com/images/shared/ This is an entrance shuffle that only shuffles entrances within their respective `Districts` (See Below). Also, dropdowns and the entrance that a dropdown normally exits are NOT kept together; this also means that the Skull Woods entrances are no longer guaranteed to lead to Skull Woods. Also, since there is no district that can span multiple worlds, this is NOT a cross-world entrance mode. -Districts are a concept originally conceived by Aerinon in the Door Randomizer, where parts of the OW are split into areas and given a name. Here is a [District Map](https://cdn.discordapp.com/attachments/783989090017738753/1194615705027477534/districts.png?ex=66040e12&is=65f19912&hm=9ef382f004f7013e018f0b04d0bc98727f87b8b5da6499f34dbcf6e14bb0ac90&) showing how they are split up. +Districts are a concept originally conceived by Aerinon in the Door Randomizer, where parts of the OW are split into areas and given a name. Here is a [District Map](https://cdn.discordapp.com/attachments/783989090017738753/1194615705027477534/districts.png?ex=66527f52&is=66512dd2&hm=6f29d8df27eea6a3489b3bb74381e02fb9706158c2f00bcef652f156a53c1268&) showing how they are split up. # Command Line Options